From d3d125aa9acfd6ee9e48804cc24f4a50eda70535 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 14 Dec 2022 20:51:55 +0000 Subject: [PATCH 0001/2055] Bug #1999571: Similar books: Grouped search problems. Fixed by adding a new API method db.cache,split_if_is_multiple_composite(). This method converts the value to a list if the composite is "tags-like". The intention is that plugins etc can use the method if needed. --- src/calibre/db/cache.py | 7 +++++++ src/calibre/gui2/actions/similar_books.py | 1 + 2 files changed, 8 insertions(+) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index f797790dba..80b925e770 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2349,6 +2349,13 @@ class Cache: return f.get_books_for_val(item_id_or_composite_value, self._get_proxy_metadata, self._all_book_ids()) return self._books_for_field(f.name, int(item_id_or_composite_value)) + @read_api + def split_if_is_multiple_composite(self, f, v): + fm = self.field_metadata.get(f, None) + if fm and fm['datatype'] == 'composite' and fm['is_multiple']: + return [v.strip() for v in v.split(',') if v.strip()] + return v + @read_api def data_for_find_identical_books(self): ''' Return data that can be used to implement diff --git a/src/calibre/gui2/actions/similar_books.py b/src/calibre/gui2/actions/similar_books.py index e9015aa29b..cc248ba20e 100644 --- a/src/calibre/gui2/actions/similar_books.py +++ b/src/calibre/gui2/actions/similar_books.py @@ -66,6 +66,7 @@ class SimilarBooksAction(InterfaceAction): v = mi.get(f, None) if not v: continue + v = db.new_api.split_if_is_multiple_composite(f, v) if isinstance(v, list): val.update(v) else: From c38a220aba96738fae6063a49232ff4064a3fd20 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Dec 2022 08:05:35 +0530 Subject: [PATCH 0002/2055] Kobo driver: Bump the max supported firmware version I have no way to test this, but since several people have reported the existing driver continues to work with this firmware version... Fixes #1999685 [Firmware not supported - Kobo Aura H2O Edition 2](https://bugs.launchpad.net/calibre/+bug/1999685) --- src/calibre/devices/kobo/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index f601f99cc0..6cf6eb7465 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -1399,7 +1399,7 @@ class KOBOTOUCH(KOBO): # Starting with firmware version 3.19.x, the last number appears to be is a # build number. A number will be recorded here but it can be safely ignored # when testing the firmware version. - max_supported_fwversion = (4, 34, 20097) + max_supported_fwversion = (4, 35, 20400) # The following document firmware versions where new function or devices were added. # Not all are used, but this feels a good place to record it. min_fwversion_shelves = (2, 0, 0) From 91ce1e3cd1763a84bf08636fd00bea2f3b4053b6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Dec 2022 12:31:44 +0530 Subject: [PATCH 0003/2055] Update Indian Express --- recipes/indian_express.recipe | 65 +++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/recipes/indian_express.recipe b/recipes/indian_express.recipe index e9e3620c6b..60fc14c3f8 100644 --- a/recipes/indian_express.recipe +++ b/recipes/indian_express.recipe @@ -1,5 +1,6 @@ from calibre.web.feeds.news import BasicNewsRecipe, classes - +from datetime import date, datetime, timedelta +from calibre.utils.date import parse_date class IndianExpress(BasicNewsRecipe): title = u'Indian Express' @@ -13,7 +14,7 @@ class IndianExpress(BasicNewsRecipe): use_embedded_content = False remove_attributes = ['style', 'height', 'width'] ignore_duplicate_articles = {'url'} - + extra_css = ''' #storycenterbyline {font-size:small;} #img-cap {font-size:small;} @@ -22,7 +23,7 @@ class IndianExpress(BasicNewsRecipe): #sub-d{color:#202020; font-style:italic;} .ie-authorbox{font-size:small;} ''' - + resolve_internal_links = True remove_empty_feeds = True @@ -40,25 +41,31 @@ class IndianExpress(BasicNewsRecipe): ' custom-share o-story-paper-quite ie-network-commenting audio-player-tts-sec' ) ] - + def parse_index(self): + section_list = [ ('Front Page', 'https://indianexpress.com/print/front-page/'), ('India', 'https://indianexpress.com/section/india/'), - # ('Express Network', 'https://indianexpress.com/print/express-network/'), + #('Express Network', 'https://indianexpress.com/print/express-network/'), ('Delhi Confidential', 'https://indianexpress.com/section/delhi-confidential/'), ('Opinion', 'http://indianexpress.com/section/opinion/'), ('UPSC-CSE Key', 'https://indianexpress.com/section/upsc-current-affairs/'), + ('Explained', 'https://indianexpress.com/section/explained/'), ('Business', 'https://indianexpress.com/section/business/'), - ('Political Pulse', 'https://indianexpress.com/section/political-pulse/'), + #('Political Pulse', 'https://indianexpress.com/section/political-pulse/'), ('Sunday Eye', 'https://indianexpress.com/section/express-sunday-eye/'), - # ('Education', 'https://indianexpress.com/section/education/'), - # ('Gadgets', 'https://indianexpress.com/section/technology/gadgets/'), - # ('Tech Review', 'https://indianexpress.com/section/technology/tech-reviews/'), + ('World', 'https://indianexpress.com/section/world/'), + #('Education', 'https://indianexpress.com/section/education/'), + #('Gadgets', 'https://indianexpress.com/section/technology/gadgets/'), + ('Tech Review', 'https://indianexpress.com/section/technology/tech-reviews/'), + #('Techhook', 'https://indianexpress.com/section/technology/techook/'), + #('Laptops', 'https://indianexpress.com/section/technology/laptops/'), + #('Mobiles & Tabs', 'https://indianexpress.com/section/technology/mobile-tabs/'), ('Science', 'https://indianexpress.com/section/technology/science/'), ('Movie Review', 'https://indianexpress.com/section/entertainment/movie-review/'), ] - + feeds = [] # For each section title, fetch the article urls @@ -67,30 +74,40 @@ class IndianExpress(BasicNewsRecipe): section_url = section[1] self.log(section_title, section_url) soup = self.index_to_soup(section_url) - articles = self.articles_from_soup(soup) + if '/world/' in section_url or '/explained/' in section_url: + articles = self.articles_from_page(soup) + else: + articles = self.articles_from_soup(soup) if articles: feeds.append((section_title, articles)) return feeds - + + def articles_from_page(self, soup): + ans = [] + for div in soup.findAll(attrs={'class':['northeast-topbox', 'explained-section-grid']}): + for a in div.findAll('a', href=True): + if not a.find('img') and not '/section/' in a['href']: + url = a['href'] + title = self.tag_to_string(a) + self.log('\t', title, '\n\t\t', url) + ans.append({'title': title, 'url': url, 'description': ''}) + return ans + def articles_from_soup(self, soup): ans = [] div = soup.find('div', attrs={'class':['nation', 'o-opin']}) for art in div.findAll(attrs={'class':['articles', 'o-opin-article']}): for a in art.findAll('a', href=True): - if not a.find('img'): + if not a.find('img') and not '/profile/' in a['href']: url = a['href'] title = self.tag_to_string(a) desc = '' if p:= art.find('p'): desc = self.tag_to_string(p) if da := art.find('div', attrs={'class':['date', 'o-opin-date']}): - from datetime import datetime, timedelta - from calibre.utils.date import parse_date - d = parse_date(self.tag_to_string(da)).replace(tzinfo=None) + date = parse_date(self.tag_to_string(da)).replace(tzinfo=None) today = datetime.now() - if (today - d) > timedelta(self.oldest_article): - url = '' - if not url or not title: + if (today - date) > timedelta(self.oldest_article): continue self.log('\t', title, '\n\t', desc, '\n\t\t', url) ans.append({'title': title, 'url': url, 'description': desc}) @@ -104,8 +121,7 @@ class IndianExpress(BasicNewsRecipe): return citem['content'] def preprocess_html(self, soup): - h2 = soup.find('h2') - if h2: + if h2 := soup.find('h2'): h2.name = 'p' h2['id'] = 'sub-d' for span in soup.findAll( @@ -119,4 +135,9 @@ class IndianExpress(BasicNewsRecipe): if lazy is not None: lazy.extract() noscript.name = 'div' - return soup + if span := soup.find('span', content=True, attrs={'itemprop':'dateModified'}): + date = parse_date(span['content']).replace(tzinfo=None) + today = datetime.now() + if (today - date) > timedelta(self.oldest_article): + self.abort_article('Skipping old article') + return soup \ No newline at end of file From 32acf0c7a5b98a35de7d3f739ad67a9120fed50b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Dec 2022 13:45:39 +0530 Subject: [PATCH 0004/2055] Fix test failures in srv module not being reported fully --- src/calibre/srv/tests/base.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/calibre/srv/tests/base.py b/src/calibre/srv/tests/base.py index 7e9f907cf6..3da695acaa 100644 --- a/src/calibre/srv/tests/base.py +++ b/src/calibre/srv/tests/base.py @@ -34,13 +34,14 @@ class BaseTest(SimpleTest): for i in range(max_retries + 1): failures_before = len(result.failures) errors_before = len(result.errors) - super().run(result=result) + ret = super().run(result=result) if len(result.failures) == failures_before and len(result.errors) == errors_before: - return + return ret print(f'Retrying test {self._testMethodName} after failure/error') - q = result.failures if len(result.failures) > failures_before else result.errors - q.pop(-1) - time.sleep(1) + if i < max_retries: + q = result.failures if len(result.failures) > failures_before else result.errors + q.pop(-1) + time.sleep(1) class LibraryBaseTest(BaseTest): From 3537db5a8c58324491fd2d3cc7aba1576de3d58d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Dec 2022 17:02:26 +0530 Subject: [PATCH 0005/2055] Content server: When using user accounts on the homepage show recently read books from any device not just the current device. Fixes #1998557 [[feature request] Content-server viewer suggesting of book being read on other devices](https://bugs.launchpad.net/calibre/+bug/1998557) --- src/calibre/srv/books.py | 10 +++- src/calibre/srv/code.py | 4 ++ src/calibre/srv/last_read.py | 87 ++++++++++++++++++++++++++++++++ src/calibre/srv/tests/content.py | 15 ++++++ src/pyj/book_list/home.pyj | 58 +++++++++++++++------ src/pyj/session.pyj | 1 + 6 files changed, 158 insertions(+), 17 deletions(-) create mode 100644 src/calibre/srv/last_read.py diff --git a/src/calibre/srv/books.py b/src/calibre/srv/books.py index a83059731a..515d296b7c 100644 --- a/src/calibre/srv/books.py +++ b/src/calibre/srv/books.py @@ -13,7 +13,9 @@ from threading import Lock, RLock from calibre.constants import cache_dir, iswindows from calibre.customize.ui import plugin_for_input_format +from calibre.ebooks.metadata import authors_to_string from calibre.srv.errors import BookNotFound, HTTPNotFound +from calibre.srv.last_read import last_read_cache from calibre.srv.metadata import book_as_json from calibre.srv.render_book import RENDER_VERSION from calibre.srv.routes import endpoint, json @@ -220,8 +222,14 @@ def set_last_read_position(ctx, rd, library_id, book_id, fmt): device, cfi, pos_frac = data['device'], data['cfi'], data['pos_frac'] except Exception: raise HTTPNotFound('Invalid data') + cfi = cfi or None db.set_last_read_position( - book_id, fmt, user=user, device=device, cfi=cfi or None, pos_frac=pos_frac) + book_id, fmt, user=user, device=device, cfi=cfi, pos_frac=pos_frac) + if user: + with db.safe_read_lock: + tt = db._field_for('title', book_id) + tt += ' ' + _('by') + ' ' + authors_to_string(db._field_for('authors', book_id)) + last_read_cache().add_last_read_position(library_id, book_id, fmt, user, cfi, pos_frac, tt) rd.outheaders['Content-type'] = 'text/plain' return b'' diff --git a/src/calibre/srv/code.py b/src/calibre/srv/code.py index 0a93b525db..c706c97bd6 100644 --- a/src/calibre/srv/code.py +++ b/src/calibre/srv/code.py @@ -18,6 +18,7 @@ from calibre.srv.ajax import search_result from calibre.srv.errors import ( BookNotFound, HTTPBadRequest, HTTPForbidden, HTTPNotFound, HTTPRedirect, ) +from calibre.srv.last_read import last_read_cache from calibre.srv.metadata import ( book_as_json, categories_as_json, categories_settings, icon_map, ) @@ -167,6 +168,9 @@ def basic_interface_data(ctx, rd): 'lang_code_for_user_manual': lang_code_for_user_manual(), } ans['library_map'], ans['default_library_id'] = ctx.library_info(rd) + if ans['username']: + lrc = last_read_cache() + ans['recently_read_by_user'] = lrc.get_recently_read(ans['username']) return ans diff --git a/src/calibre/srv/last_read.py b/src/calibre/srv/last_read.py new file mode 100644 index 0000000000..df04e7a4e4 --- /dev/null +++ b/src/calibre/srv/last_read.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2022, Kovid Goyal + +import apsw +import os +from contextlib import suppress +from threading import Lock +from time import time_ns + +from calibre.constants import cache_dir + +creation_sql = ''' +CREATE TABLE IF NOT EXISTS last_read_positions ( id INTEGER PRIMARY KEY, + library_id TEXT NOT NULL, + book INTEGER NOT NULL, + format TEXT NOT NULL COLLATE NOCASE, + user TEXT NOT NULL, + cfi TEXT NOT NULL, + epoch INTEGER NOT NULL, + pos_frac REAL NOT NULL DEFAULT 0, + tooltip TEXT NOT NULL, + UNIQUE(user, library_id, book, format) +); +CREATE INDEX IF NOT EXISTS users_id ON last_read_positions (user); +''' + +lock = Lock() + + +class LastReadCache: + + def __init__(self, path='', limit=5): + self.limit = limit + self.conn = apsw.Connection(path or os.path.join(cache_dir(), 'srv-last-read.sqlite')) + self.execute(creation_sql) + + def get(self, *args, **kw): + ans = self.conn.cursor().execute(*args) + if kw.get('all', True): + return ans.fetchall() + with suppress(StopIteration, IndexError): + return next(ans)[0] + + def execute(self, sql, bindings=None): + cursor = self.conn.cursor() + return cursor.execute(sql, bindings) + + def add_last_read_position(self, library_id, book_id, fmt, user, cfi, pos_frac, tooltip): + with lock, self.conn: + if not cfi: + self.execute( + 'DELETE FROM last_read_positions WHERE library_id=? AND book=? AND format=? AND user=?', + (library_id, book_id, fmt, user)) + else: + epoch = time_ns() + self.execute( + 'INSERT OR REPLACE INTO last_read_positions(library_id,book,format,user,cfi,epoch,pos_frac,tooltip) VALUES (?,?,?,?,?,?,?,?)', + (library_id, book_id, fmt, user, cfi, epoch, pos_frac, tooltip)) + items = tuple(self.get('SELECT epoch FROM last_read_positions WHERE user=? ORDER BY epoch DESC', (user,), all=True)) + if len(items) > self.limit: + limit_epoch = items[self.limit][0] + self.execute('DELETE FROM last_read_positions WHERE user=? AND epoch <= ?', (user, limit_epoch)) + return epoch + + def get_recently_read(self, user): + with lock: + ans = [] + for library_id, book, fmt, cfi, epoch, pos_frac, tooltip in self.execute( + 'SELECT library_id,book,format,cfi,epoch,pos_frac,tooltip FROM last_read_positions WHERE user=? ORDER BY epoch DESC', (user,) + ): + ans.append({ + 'library_id': library_id, 'book_id': book, 'format': fmt, + 'cfi': cfi, 'epoch':epoch, 'pos_frac':pos_frac, 'tooltip': tooltip, + }) + return ans + + +path_cache = {} + + +def last_read_cache(path=''): + with lock: + ans = path_cache.get(path) + if ans is None: + ans = path_cache[path] = LastReadCache(path) + return ans diff --git a/src/calibre/srv/tests/content.py b/src/calibre/srv/tests/content.py index ba14345c57..ac5aa1a3a0 100644 --- a/src/calibre/srv/tests/content.py +++ b/src/calibre/srv/tests/content.py @@ -262,3 +262,18 @@ class ContentTest(LibraryBaseTest): text = 'a' * (127 * 1024) t('

{0}

{0}'.format(text), [{"n":"p","x":text}, {'n':'p','x':text}]) # }}} + + def test_last_read_cache(self): # {{{ + from calibre.srv.last_read import last_read_cache, path_cache + path_cache.clear() + lrc = last_read_cache(':memory:') + epoch = lrc.add_last_read_position('lib', 1, 'FMT', 'user', 'epubcfi(/)', 0.1, 'tt') + expected = {'library_id': 'lib', 'book_id': 1, 'format': 'FMT', 'cfi': 'epubcfi(/)', 'epoch': epoch, 'pos_frac': 0.1, 'tooltip': 'tt'} + self.ae(lrc.get_recently_read('user'), [expected]) + epoch = lrc.add_last_read_position('lib', 1, 'FMT', 'user', 'epubcfi(/)', 0.2, 'tt') + expected['epoch'], expected['pos_frac'] = epoch, 0.2 + self.ae(lrc.get_recently_read('user'), [expected]) + for book_id in range(2, 7): + lrc.add_last_read_position('lib', book_id, 'FMT', 'user', 'epubcfi(/)', 0.1, 'tt') + self.ae(len(lrc.get_recently_read('user')), lrc.limit) + # }}} diff --git a/src/pyj/book_list/home.pyj b/src/pyj/book_list/home.pyj index b0cf8d0592..f8ca51f27d 100644 --- a/src/pyj/book_list/home.pyj +++ b/src/pyj/book_list/home.pyj @@ -3,16 +3,18 @@ from __python__ import bound_methods, hash_literals from elementmaker import E -from gettext import gettext as _ -from ajax import ajax_send +from ajax import absolute_path, ajax_send from book_list.cover_grid import BORDER_RADIUS from book_list.globals import get_db -from book_list.library_data import last_virtual_library_for, sync_library_books, all_libraries -from book_list.router import open_book, update_window_title +from book_list.library_data import ( + all_libraries, last_virtual_library_for, sync_library_books +) +from book_list.router import open_book, open_book_url, update_window_title from book_list.top_bar import add_button, create_top_bar from book_list.ui import set_default_panel_handler, show_panel from dom import add_extra_css, build_rule, clear, ensure_id, set_css, unique_id +from gettext import gettext as _ from modals import create_custom_dialog from session import get_device_uuid, get_interface_data from utils import conditional_timeout, safe_set_inner_html, username_key @@ -98,17 +100,44 @@ def start_sync(to_sync): sync_library_books(lid, libraries[lid], sync_data_received) -def show_recent_stage2(books): - container = document.getElementById(this) - if not container or not books.length: - return +def prepare_recent_container(container): container.style.display = 'block' container.style.borderBottom = 'solid 1px currentColor' container.style.paddingBottom = '1em' container.appendChild(E.h2(_( 'Continue reading…'))) container.appendChild(E.div(style='display:flex')) - images = container.lastChild + cover_container = container.lastChild + container.appendChild(E.div(style='margin: 1rem 1rem', + create_button( + _('Browse all downloaded books…'), + action=def(): + show_panel('local_books') + ))) + return cover_container + + +def show_recent_for_user(container_id, interface_data): + container = document.getElementById(container_id) + images = prepare_recent_container(container) + for item in interface_data.recently_read_by_user[:3]: + q = {'library_id': item.library_id} + if item.cfi: + q.bookpos = item.cfi + url_to_read = open_book_url(item.book_id, item.format, q) + images.appendChild(E.div(style='margin: 0 1em', + E.a( + title=item.tooltip, + href=url_to_read, + E.img(alt=item.tooltip, src=absolute_path(f'get/cover/{item.book_id}/{item.library_id}')) + ))) + + +def show_recent_stage2(books): + container = document.getElementById(this) + if not container or not books.length: + return + images = prepare_recent_container(container) db = get_db() to_sync = v'[]' username = get_interface_data().username @@ -129,12 +158,6 @@ def show_recent_stage2(books): if book.cover_name: db.get_file(book, book.cover_name, show_cover.bind(img_id)) start_sync(to_sync) - container.appendChild(E.div(style='margin: 1rem 1rem', - create_button( - _('Browse all downloaded books…'), - action=def(): - show_panel('local_books') - ))) def show_recent(): @@ -242,7 +265,10 @@ def init(container_id): recent = E.div(style='display:none', class_='recently-read') recent_container_id = ensure_id(recent) container.appendChild(recent) - conditional_timeout(recent_container_id, 5, show_recent) + if interface_data.username and interface_data.recently_read_by_user and interface_data.recently_read_by_user.length > 0: + show_recent_for_user(recent_container_id, interface_data) + else: + conditional_timeout(recent_container_id, 5, show_recent) # Choose library container.appendChild(E.h2(_('Choose the calibre library to browse…'))) diff --git a/src/pyj/session.pyj b/src/pyj/session.pyj index 0c1d9cd29e..f53f8d99d5 100644 --- a/src/pyj/session.pyj +++ b/src/pyj/session.pyj @@ -243,6 +243,7 @@ default_interface_data = { 'custom_list_template': None, 'num_per_page': 50, 'lang_code_for_user_manual': '', + 'recently_read_by_user': v'[]', } def get_interface_data(): From d47fa7a62984712daaf09587d9819b72e50e5240 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Dec 2022 17:27:26 +0530 Subject: [PATCH 0006/2055] pep8 --- recipes/indian_express.recipe | 37 ++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/recipes/indian_express.recipe b/recipes/indian_express.recipe index 60fc14c3f8..efbd434694 100644 --- a/recipes/indian_express.recipe +++ b/recipes/indian_express.recipe @@ -1,7 +1,8 @@ from calibre.web.feeds.news import BasicNewsRecipe, classes -from datetime import date, datetime, timedelta +from datetime import datetime, timedelta from calibre.utils.date import parse_date + class IndianExpress(BasicNewsRecipe): title = u'Indian Express' language = 'en_IN' @@ -14,7 +15,7 @@ class IndianExpress(BasicNewsRecipe): use_embedded_content = False remove_attributes = ['style', 'height', 'width'] ignore_duplicate_articles = {'url'} - + extra_css = ''' #storycenterbyline {font-size:small;} #img-cap {font-size:small;} @@ -23,7 +24,7 @@ class IndianExpress(BasicNewsRecipe): #sub-d{color:#202020; font-style:italic;} .ie-authorbox{font-size:small;} ''' - + resolve_internal_links = True remove_empty_feeds = True @@ -41,31 +42,31 @@ class IndianExpress(BasicNewsRecipe): ' custom-share o-story-paper-quite ie-network-commenting audio-player-tts-sec' ) ] - + def parse_index(self): - + section_list = [ ('Front Page', 'https://indianexpress.com/print/front-page/'), ('India', 'https://indianexpress.com/section/india/'), - #('Express Network', 'https://indianexpress.com/print/express-network/'), + # ('Express Network', 'https://indianexpress.com/print/express-network/'), ('Delhi Confidential', 'https://indianexpress.com/section/delhi-confidential/'), ('Opinion', 'http://indianexpress.com/section/opinion/'), ('UPSC-CSE Key', 'https://indianexpress.com/section/upsc-current-affairs/'), ('Explained', 'https://indianexpress.com/section/explained/'), ('Business', 'https://indianexpress.com/section/business/'), - #('Political Pulse', 'https://indianexpress.com/section/political-pulse/'), + # ('Political Pulse', 'https://indianexpress.com/section/political-pulse/'), ('Sunday Eye', 'https://indianexpress.com/section/express-sunday-eye/'), ('World', 'https://indianexpress.com/section/world/'), - #('Education', 'https://indianexpress.com/section/education/'), - #('Gadgets', 'https://indianexpress.com/section/technology/gadgets/'), + # ('Education', 'https://indianexpress.com/section/education/'), + # ('Gadgets', 'https://indianexpress.com/section/technology/gadgets/'), ('Tech Review', 'https://indianexpress.com/section/technology/tech-reviews/'), - #('Techhook', 'https://indianexpress.com/section/technology/techook/'), - #('Laptops', 'https://indianexpress.com/section/technology/laptops/'), - #('Mobiles & Tabs', 'https://indianexpress.com/section/technology/mobile-tabs/'), + # ('Techhook', 'https://indianexpress.com/section/technology/techook/'), + # ('Laptops', 'https://indianexpress.com/section/technology/laptops/'), + # ('Mobiles & Tabs', 'https://indianexpress.com/section/technology/mobile-tabs/'), ('Science', 'https://indianexpress.com/section/technology/science/'), ('Movie Review', 'https://indianexpress.com/section/entertainment/movie-review/'), ] - + feeds = [] # For each section title, fetch the article urls @@ -81,24 +82,24 @@ class IndianExpress(BasicNewsRecipe): if articles: feeds.append((section_title, articles)) return feeds - + def articles_from_page(self, soup): ans = [] for div in soup.findAll(attrs={'class':['northeast-topbox', 'explained-section-grid']}): for a in div.findAll('a', href=True): - if not a.find('img') and not '/section/' in a['href']: + if not a.find('img') and '/section/' not in a['href']: url = a['href'] title = self.tag_to_string(a) self.log('\t', title, '\n\t\t', url) ans.append({'title': title, 'url': url, 'description': ''}) return ans - + def articles_from_soup(self, soup): ans = [] div = soup.find('div', attrs={'class':['nation', 'o-opin']}) for art in div.findAll(attrs={'class':['articles', 'o-opin-article']}): for a in art.findAll('a', href=True): - if not a.find('img') and not '/profile/' in a['href']: + if not a.find('img') and '/profile/' not in a['href']: url = a['href'] title = self.tag_to_string(a) desc = '' @@ -140,4 +141,4 @@ class IndianExpress(BasicNewsRecipe): today = datetime.now() if (today - date) > timedelta(self.oldest_article): self.abort_article('Skipping old article') - return soup \ No newline at end of file + return soup From 1704e1346012fe5e7cc53098cf82e5abbfdb3934 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Dec 2022 19:13:10 +0530 Subject: [PATCH 0007/2055] Fix unclosed resource warning on some test failures --- src/calibre/srv/tests/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/srv/tests/base.py b/src/calibre/srv/tests/base.py index 3da695acaa..dd3efcf1b8 100644 --- a/src/calibre/srv/tests/base.py +++ b/src/calibre/srv/tests/base.py @@ -80,7 +80,8 @@ class LibraryBaseTest(BaseTest): db.init() db.set_cover({1:I('lt.png', data=True), 2:I('polish.png', data=True)}) db.add_format(1, 'FMT1', BytesIO(b'book1fmt1'), run_hooks=False) - db.add_format(1, 'EPUB', open(P('quick_start/eng.epub'), 'rb'), run_hooks=False) + with open(P('quick_start/eng.epub'), 'rb') as src: + db.add_format(1, 'EPUB', src, run_hooks=False) db.add_format(1, 'FMT2', BytesIO(b'book1fmt2'), run_hooks=False) db.add_format(2, 'FMT1', BytesIO(b'book2fmt1'), run_hooks=False) db.backend.conn.close() From 4fa7648de4c9656fc6f6d2d4c5b095c8188a8777 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Dec 2022 19:47:49 +0530 Subject: [PATCH 0008/2055] Fix test failing on Windows Use an AUTOINCREMENT id rather than epoch which is not predictable since system clocks can change. --- src/calibre/srv/last_read.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/calibre/srv/last_read.py b/src/calibre/srv/last_read.py index df04e7a4e4..44230b6c6f 100644 --- a/src/calibre/srv/last_read.py +++ b/src/calibre/srv/last_read.py @@ -11,7 +11,7 @@ from time import time_ns from calibre.constants import cache_dir creation_sql = ''' -CREATE TABLE IF NOT EXISTS last_read_positions ( id INTEGER PRIMARY KEY, +CREATE TABLE IF NOT EXISTS last_read_positions ( id INTEGER PRIMARY KEY AUTOINCREMENT, library_id TEXT NOT NULL, book INTEGER NOT NULL, format TEXT NOT NULL COLLATE NOCASE, @@ -57,10 +57,10 @@ class LastReadCache: self.execute( 'INSERT OR REPLACE INTO last_read_positions(library_id,book,format,user,cfi,epoch,pos_frac,tooltip) VALUES (?,?,?,?,?,?,?,?)', (library_id, book_id, fmt, user, cfi, epoch, pos_frac, tooltip)) - items = tuple(self.get('SELECT epoch FROM last_read_positions WHERE user=? ORDER BY epoch DESC', (user,), all=True)) + items = tuple(self.get('SELECT id FROM last_read_positions WHERE user=? ORDER BY epoch DESC', (user,), all=True)) if len(items) > self.limit: - limit_epoch = items[self.limit][0] - self.execute('DELETE FROM last_read_positions WHERE user=? AND epoch <= ?', (user, limit_epoch)) + limit_id = items[self.limit][0] + self.execute('DELETE FROM last_read_positions WHERE user=? AND id <= ?', (user, limit_id)) return epoch def get_recently_read(self, user): From eda40df3a9ce91dac8f74d66bf0fd50afe6924cd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Dec 2022 19:58:41 +0530 Subject: [PATCH 0009/2055] Use the id for ordering as well --- src/calibre/srv/last_read.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/srv/last_read.py b/src/calibre/srv/last_read.py index 44230b6c6f..fc30af9527 100644 --- a/src/calibre/srv/last_read.py +++ b/src/calibre/srv/last_read.py @@ -57,7 +57,7 @@ class LastReadCache: self.execute( 'INSERT OR REPLACE INTO last_read_positions(library_id,book,format,user,cfi,epoch,pos_frac,tooltip) VALUES (?,?,?,?,?,?,?,?)', (library_id, book_id, fmt, user, cfi, epoch, pos_frac, tooltip)) - items = tuple(self.get('SELECT id FROM last_read_positions WHERE user=? ORDER BY epoch DESC', (user,), all=True)) + items = tuple(self.get('SELECT id FROM last_read_positions WHERE user=? ORDER BY id DESC', (user,), all=True)) if len(items) > self.limit: limit_id = items[self.limit][0] self.execute('DELETE FROM last_read_positions WHERE user=? AND id <= ?', (user, limit_id)) From 2e15b0549b928b47df0fd17098e4c8058753be7f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Dec 2022 20:08:40 +0530 Subject: [PATCH 0010/2055] ... --- src/calibre/srv/last_read.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/calibre/srv/last_read.py b/src/calibre/srv/last_read.py index fc30af9527..6d7597deb1 100644 --- a/src/calibre/srv/last_read.py +++ b/src/calibre/srv/last_read.py @@ -59,8 +59,7 @@ class LastReadCache: (library_id, book_id, fmt, user, cfi, epoch, pos_frac, tooltip)) items = tuple(self.get('SELECT id FROM last_read_positions WHERE user=? ORDER BY id DESC', (user,), all=True)) if len(items) > self.limit: - limit_id = items[self.limit][0] - self.execute('DELETE FROM last_read_positions WHERE user=? AND id <= ?', (user, limit_id)) + self.execute('DELETE FROM last_read_positions WHERE user=? AND id <= ?', (user, items[self.limit][0])) return epoch def get_recently_read(self, user): From dd666a8778faea05223a8e00e8da6018b1d95968 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 16 Dec 2022 07:58:14 +0530 Subject: [PATCH 0011/2055] Make showing recently read for user a bit more robust --- src/calibre/srv/code.py | 3 +-- src/pyj/book_list/home.pyj | 24 ++++++++++++++++++++---- src/pyj/book_list/main.pyj | 3 +++ src/pyj/session.pyj | 1 - 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/calibre/srv/code.py b/src/calibre/srv/code.py index c706c97bd6..2b98282c64 100644 --- a/src/calibre/srv/code.py +++ b/src/calibre/srv/code.py @@ -169,8 +169,7 @@ def basic_interface_data(ctx, rd): } ans['library_map'], ans['default_library_id'] = ctx.library_info(rd) if ans['username']: - lrc = last_read_cache() - ans['recently_read_by_user'] = lrc.get_recently_read(ans['username']) + ans['recently_read_by_user'] = tuple(x for x in last_read_cache().get_recently_read(ans['username']) if x['library_id'] in ans['library_map']) return ans diff --git a/src/pyj/book_list/home.pyj b/src/pyj/book_list/home.pyj index f8ca51f27d..c9e0f19ecc 100644 --- a/src/pyj/book_list/home.pyj +++ b/src/pyj/book_list/home.pyj @@ -33,6 +33,13 @@ add_extra_css(def(): return ans ) +recently_read_by_user = {'updated': False} + + +def update_recently_read_by_user(items): + recently_read_by_user.items = items + recently_read_by_user.updated = True + def show_cover(blob, name, mt, book): img = document.getElementById(this) @@ -117,10 +124,10 @@ def prepare_recent_container(container): return cover_container -def show_recent_for_user(container_id, interface_data): +def show_recent_for_user(container_id): container = document.getElementById(container_id) images = prepare_recent_container(container) - for item in interface_data.recently_read_by_user[:3]: + for item in recently_read_by_user.items[:3]: q = {'library_id': item.library_id} if item.cfi: q.bookpos = item.cfi @@ -160,6 +167,15 @@ def show_recent_stage2(books): start_sync(to_sync) +def show_recent_for_user_if_fetched(): + container = this + if not recently_read_by_user.updated: + return conditional_timeout(container.id, 5, show_recent_for_user_if_fetched) + if recently_read_by_user.items and recently_read_by_user.items.length > 0: + return show_recent_for_user(container.id) + return show_recent.call(container) + + def show_recent(): container = this db = get_db() @@ -265,8 +281,8 @@ def init(container_id): recent = E.div(style='display:none', class_='recently-read') recent_container_id = ensure_id(recent) container.appendChild(recent) - if interface_data.username and interface_data.recently_read_by_user and interface_data.recently_read_by_user.length > 0: - show_recent_for_user(recent_container_id, interface_data) + if interface_data.username: + conditional_timeout(recent_container_id, 5, show_recent_for_user_if_fetched) else: conditional_timeout(recent_container_id, 5, show_recent) diff --git a/src/pyj/book_list/main.pyj b/src/pyj/book_list/main.pyj index f336b49518..e5d144f5b3 100644 --- a/src/pyj/book_list/main.pyj +++ b/src/pyj/book_list/main.pyj @@ -13,6 +13,7 @@ from popups import install_event_filters from utils import safe_set_inner_html from book_list.constants import book_list_container_id, read_book_container_id, INIT_ENDPOINT +from book_list.home import update_recently_read_by_user from book_list.library_data import fetch_init_data, update_library_data, url_books_query from book_list.theme import get_color, get_font_family, css_for_variables from book_list.router import update_window_title, set_default_mode_handler, apply_url, set_mode_handler, on_pop_state @@ -83,6 +84,7 @@ def init_ui(): def install_data_and_init_ui(raw_data): data = JSON.parse(raw_data) update_interface_data(data) + update_recently_read_by_user(data.recently_read_by_user) update_library_data(data) interface_data = get_interface_data() sd = UserSessionData(interface_data.username, interface_data.user_session_data) @@ -132,6 +134,7 @@ def do_update_interface_data(): if end_type is 'load': data = JSON.parse(xhr.responseText) update_interface_data(data) + update_recently_read_by_user(data.recently_read_by_user) if data.translations?: get_translations(data.translations) install(data.translations) diff --git a/src/pyj/session.pyj b/src/pyj/session.pyj index f53f8d99d5..0c1d9cd29e 100644 --- a/src/pyj/session.pyj +++ b/src/pyj/session.pyj @@ -243,7 +243,6 @@ default_interface_data = { 'custom_list_template': None, 'num_per_page': 50, 'lang_code_for_user_manual': '', - 'recently_read_by_user': v'[]', } def get_interface_data(): From c3ab07100996c7426453e6f570d9b9e7a173a302 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 16 Dec 2022 07:59:36 +0530 Subject: [PATCH 0012/2055] version 6.10.0 --- Changelog.txt | 45 ++++++++++++++++++++++++++++++++++++++++ src/calibre/constants.py | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/Changelog.txt b/Changelog.txt index 737a06b259..4e77546b7e 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -23,6 +23,51 @@ # - title by author # }}} +{{{ 6.10.0 2022-12-16 + +:: new features + +- [major] Content server: Add support for searching the full text of books. Simply click the FTS link on the search page to start a full text search. + +- [1998557] Content server: When using user accounts, the homepage now shows recently read books from any device not just the current device + +- [1999685] Kobo driver: Bump the max supported firmware version + +- [1998780] Conversion: New Output profile for the Kindle Scribe + +- [1998705] Check library: Allow ignoring folder names as well as files names + +:: bug fixes + +- [1999349] Edit book: Fix various formatting operations not inserting the tags in the correct place in the presence of non-BMP characters + +- Edit book: Use instead of for strikethrough + +- [1998899] Edit book: Fix export saved search to search panel not preserving the wrap checkbox state + +- [1998767] Content server: Redirect the index page to always have trailing slash when using URL prefixes + +- Book list: Workaround for change in Qt 6 behavior where clicking on an already selected row does not deselect other rows + +- [1998165] Windows: Fix a regression in calibre 6 causing Open With to not extract icons from EXE files + +:: improved recipes +- Indian Express +- Financial Times +- TIME Magazine +- Hindu Business Line Print Edition +- Arts and Letters Daily +- Frontline +- Sportstar +- New Yorker + +:: new recipes +- Fokus by Henrik Holm +- Press Information Bureau by unkn0wn +- Himal Southasian by unkn0wn +- Indian Express Print Edition by unkn0wn +}}} + {{{ 6.9.0 2022-11-25 :: new features diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 94591da48f..8f39aa076b 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -5,7 +5,7 @@ from functools import lru_cache import sys, locale, codecs, os, collections, collections.abc __appname__ = 'calibre' -numeric_version = (6, 9, 0) +numeric_version = (6, 10, 0) __version__ = '.'.join(map(str, numeric_version)) git_version = None __author__ = "Kovid Goyal " From 1d866e0b6bcda83652bf2f754bee225869d085ff Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 16 Dec 2022 10:47:06 +0530 Subject: [PATCH 0013/2055] Windows: Forgot to convert QImage to QPixmap when converting HICON --- src/calibre/utils/open_with/windows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/open_with/windows.py b/src/calibre/utils/open_with/windows.py index b5de257c64..2a59c93890 100644 --- a/src/calibre/utils/open_with/windows.py +++ b/src/calibre/utils/open_with/windows.py @@ -16,7 +16,7 @@ ICON_SIZE = 256 def hicon_to_pixmap(hicon): - return progress_indicator.image_from_hicon(int(hicon)) + return QPixmap.fromImage(progress_indicator.image_from_hicon(int(hicon))) def pixmap_to_data(pixmap): From 6e5a4246408576fd0ddf6bc406bb06e3ac5bf89e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 17 Dec 2022 07:57:56 +0530 Subject: [PATCH 0014/2055] Fix a regression in the previous release that caused spurious error message when doing some out of band searches. Fixes #1999936 [IndexError: tuple index out of range](https://bugs.launchpad.net/calibre/+bug/1999936) --- src/calibre/gui2/library/models.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 543eb8e40a..8069af295e 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -620,6 +620,13 @@ class BooksModel(QAbstractTableModel): # {{{ def current_changed(self, current, previous, emit_signal=True): if current.isValid(): idx = current.row() + try: + self.db.id(idx) + except Exception: + # can happen if an out of band search is done causing the index + # to no longer be valid since this function is now called after + # an event loop tick. + return try: data = self.get_book_display_info(idx) except Exception: From c24f59151ca08645224ba7fbccd22c24d7a95760 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 17 Dec 2022 15:01:10 +0530 Subject: [PATCH 0015/2055] Edit book: File browser: Allow using keyboard shortcuts to re-order the spine --- manual/edit.rst | 13 ++++--- src/calibre/gui2/tweak_book/file_list.py | 45 +++++++++++++++++++++++- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/manual/edit.rst b/manual/edit.rst index a15be4b97d..ff3a8c669f 100644 --- a/manual/edit.rst +++ b/manual/edit.rst @@ -133,11 +133,14 @@ Changing text file order ^^^^^^^^^^^^^^^^^^^^^^^^^^ You can re-arrange the order in which text (HTML) files are opened when reading -the book by simply dragging and dropping them in the Files browser. For the -technically inclined, this is called re-ordering the book spine. Note that you -have to drop the items *between* other items, not on top of them, this can be a -little fiddly until you get used to it. Dropping on top of another file will -cause the files to be merged. +the book by simply dragging and dropping them in the Files browser or clicking +on the file to move and then pressing the :kbd:`Ctrl+Shift` modifiers with the +:kbd:`Up`, :kbd:`Down`, :kbd:`Home` or :kbd:`End` keys. For the technically +inclined, this is called re-ordering the book spine. + +Note that you have to drop the items *between* other items, not on top of them, +this can be a little fiddly until you get used to it. Dropping on top of +another file will cause the files to be merged. Marking the cover ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/src/calibre/gui2/tweak_book/file_list.py b/src/calibre/gui2/tweak_book/file_list.py index 57e2b52b23..38efccf934 100644 --- a/src/calibre/gui2/tweak_book/file_list.py +++ b/src/calibre/gui2/tweak_book/file_list.py @@ -374,6 +374,9 @@ class FileList(QTreeWidget, OpenWithHandler): return new_names = new_names[:insertion_point] + names + new_names[insertion_point:] order = [[name, linear_map[name]] for name in new_names] + self.request_reorder(order) + + def request_reorder(self, order): # Ensure that all non-linear items are at the end, by making any non-linear # items not at the end, linear for i, (name, linear) in tuple(enumerate(order)): @@ -797,9 +800,20 @@ class FileList(QTreeWidget, OpenWithHandler): self.mark_requested.emit(name, 'nav') def keyPressEvent(self, ev): - if ev.key() in (Qt.Key.Key_Delete, Qt.Key.Key_Backspace): + k = ev.key() + mods = ev.modifiers() & ( + Qt.KeyboardModifier.ShiftModifier | Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.MetaModifier) + if k in (Qt.Key.Key_Delete, Qt.Key.Key_Backspace): ev.accept() self.request_delete() + elif mods == (Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier): + m = self.categories['text'].childCount() + amt = {Qt.Key.Key_Up: -1, Qt.Key.Key_Down: 1, Qt.Key.Key_Home: -m, Qt.Key.Key_End: m}.get(k, None) + if amt is not None: + ev.accept() + self.move_selected_text_items(amt) + else: + return QTreeWidget.keyPressEvent(self, ev) else: return QTreeWidget.keyPressEvent(self, ev) @@ -861,6 +875,35 @@ class FileList(QTreeWidget, OpenWithHandler): ans.discard('') return ans + def move_selected_text_items(self, amt: int) -> bool: + parent = self.categories['text'] + children = tuple(parent.child(i) for i in range(parent.childCount())) + selected_names = tuple(c.data(0, NAME_ROLE) for c in children if c.isSelected()) + if not selected_names or amt == 0: + return False + current_order = tuple(c.data(0, NAME_ROLE) for c in children) + linear_map = {c.data(0, NAME_ROLE):c.data(0, LINEAR_ROLE) for c in children} + order_map = {name: i for i, name in enumerate(current_order)} + new_order = list(current_order) + changed = False + items = reversed(selected_names) if amt > 0 else selected_names + if amt < 0: + items = selected_names + delta = max(amt, -order_map[selected_names[0]]) + else: + items = reversed(selected_names) + delta = min(amt, len(children) - 1 - order_map[selected_names[-1]]) + for name in items: + i = order_map[name] + new_i = min(max(0, i + delta), len(current_order) - 1) + if new_i != i: + changed = True + del new_order[i] + new_order.insert(new_i, name) + if changed: + self.request_reorder([[n, linear_map[n]] for n in new_order]) + return changed + def copy_selected_files(self): self.initiate_file_copy.emit(self.selected_names) From 80ba2d01986e5f429c07e9755e0f2630ac24039e Mon Sep 17 00:00:00 2001 From: Michael Salaverry Date: Sat, 17 Dec 2022 15:36:45 +0200 Subject: [PATCH 0016/2055] feat: added globes english feed fix: selectors for existing recipes --- recipes/calcalist.recipe | 65 ++++++++++---------- recipes/en_globes_co_il.recipe | 34 +++++++++++ recipes/globes_co_il.recipe | 61 ++++++++----------- recipes/walla.recipe | 50 ++++++++-------- recipes/ynet.recipe | 106 ++++++++++++++++++--------------- 5 files changed, 176 insertions(+), 140 deletions(-) create mode 100644 recipes/en_globes_co_il.recipe diff --git a/recipes/calcalist.recipe b/recipes/calcalist.recipe index 271633fc73..4f888b0bd9 100644 --- a/recipes/calcalist.recipe +++ b/recipes/calcalist.recipe @@ -3,8 +3,8 @@ import re class AdvancedUserRecipe1283848012(BasicNewsRecipe): - description = 'This is a recipe of Calcalist.co.il. The recipe downloads the article page to not hurt the sites advertising income.' - cover_url = 'http://ftp5.bizportal.co.il/web/giflib/news/calcalist.JPG' + description = 'This is a recipe of Calcalist.co.il' + cover_url = 'https://images1.calcalist.co.il//picserver3/wcm_upload_dev/2022/09/15/Hk9OzwlWi/calcalistlogn.png' title = u'Calcalist' language = 'he' __author__ = 'marbs' @@ -16,39 +16,40 @@ class AdvancedUserRecipe1283848012(BasicNewsRecipe): max_articles_per_feed = 100 remove_attributes = ['width'] simultaneous_downloads = 5 - keep_only_tags = dict(name='div', attrs={'id': 'articleContainer'}) + keep_only_tags = [ + dict(name='h1', attrs={'class': 'mainTitle'}), + dict(name='h2', attrs={'class': 'subTitle'}), + dict(name='div', attrs={'class': 'ArticleBodyComponent'}), + ] remove_tags = [dict(name='p', attrs={'text': [' ']})] max_articles_per_feed = 100 preprocess_regexps = [ (re.compile(r'

 

', re.DOTALL | re.IGNORECASE), lambda match: '') ] - feeds = [(u'\u05d3\u05e3 \u05d4\u05d1\u05d9\u05ea', u'http://www.calcalist.co.il/integration/StoryRss8.xml'), - (u'24/7', u'http://www.calcalist.co.il/integration/StoryRss3674.xml'), - (u'\u05d1\u05d0\u05d6\u05d6', - u'http://www.calcalist.co.il/integration/StoryRss3674.xml'), - (u'\u05de\u05d1\u05d6\u05e7\u05d9\u05dd', - u'http://www.calcalist.co.il/integration/StoryRss184.xml'), - (u'\u05d4\u05e9\u05d5\u05e7', - u'http://www.calcalist.co.il/integration/StoryRss2.xml'), - (u'\u05d1\u05d0\u05e8\u05e5', - u'http://www.calcalist.co.il/integration/StoryRss14.xml'), - (u'\u05d4\u05db\u05e1\u05e3', - u'http://www.calcalist.co.il/integration/StoryRss9.xml'), - (u'\u05e0\u05d3\u05dc"\u05df', - u'http://www.calcalist.co.il/integration/StoryRss7.xml'), - (u'\u05e2\u05d5\u05dc\u05dd', - u'http://www.calcalist.co.il/integration/StoryRss13.xml'), - (u'\u05e4\u05e8\u05e1\u05d5\u05dd \u05d5\u05e9\u05d9\u05d5\u05d5\u05e7', - u'http://www.calcalist.co.il/integration/StoryRss5.xml'), - (u'\u05e4\u05e0\u05d0\u05d9', - u'http://www.calcalist.co.il/integration/StoryRss3.xml'), - (u'\u05d8\u05db\u05e0\u05d5\u05dc\u05d5\u05d2\u05d9', - u'http://www.calcalist.co.il/integration/StoryRss4.xml'), - (u'\u05e2\u05e1\u05e7\u05d9 \u05e1\u05e4\u05d5\u05e8\u05d8', u'http://www.calcalist.co.il/integration/StoryRss18.xml')] - - def print_version(self, url): - split1 = url.split("-") - print_url = 'http://www.calcalist.co.il/Ext/Comp/ArticleLayout/CdaArticlePrintPreview/1,2506,L-' + \ - split1[1] - return print_url + feeds = [ + (u" דף הבית", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-8,00.xml"), + (u" 24/7", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3674,00.xml"), + (u" באזז", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3673,00.xml"), + (u" משפט", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3772,00.xml"), + (u" רכב", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3783,00.xml"), + (u" אחריות וסביבה", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3781,00.xml"), + (u" דעות", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3791,00.xml"), + (u" תיירות ותעופה", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3784,00.xml"), + (u" קריירה", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3782,00.xml"), + (u" אחד העם", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3768,00.xml"), + (u" המלצות ואזהרות", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3771,00.xml"), + (u" הייטק והון סיכון", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3928,00.xml"), + (u" חדשות טכנולוגיה", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3778,00.xml"), + (u" תקשורת", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-4471,00.xml"), + (u" אינטרנט", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3773,00.xml"), + (u" מכשירים וגאדג'טים", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3777,00.xml"), + (u" המדריך", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3880,00.xml"), + (u" אפליקציות", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3998,00.xml"), + (u" Play", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3792,00.xml"), + (u" הכסף", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-9,00.xml"), + (u" עולם", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-13,00.xml"), + (u" פרסום ושיווק", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-5,00.xml"), + (u" פנאי", u"http://www.calcalist.co.il/GeneralRSS/0,16335,L-3,00.xml"), + (u" עסקי ספורט", u"http://WallaNewsw.calcalist.co.il/GeneralRSS/0,16335,L-18,00.xml") + ] diff --git a/recipes/en_globes_co_il.recipe b/recipes/en_globes_co_il.recipe new file mode 100644 index 0000000000..6f08d64af1 --- /dev/null +++ b/recipes/en_globes_co_il.recipe @@ -0,0 +1,34 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class En_Globes_Recipe(BasicNewsRecipe): + description = 'This is en.globes.co.il.' + cover_url = 'https://www.globes.co.il/images/GlobesEN-144x40.gif' + title = u'Globes in English' + language = 'en' + __author__ = 'barakplasma' + extra_css = 'img {max-width:100%;}' + simultaneous_downloads = 5 + remove_javascript = True + keep_only_tags = [ + dict(name='h1', attrs={'id': 'F_Title'}), + dict(name='h2', attrs={'id': 'coteret_SubCoteret'}), + dict(name='p', attrs={'id': None}), + ] + max_articles_per_feed = 100 + + feeds = [ + (u"Main Headlines", u"https://www.globes.co.il/WebService/Rss/RssFeeder.asmx/FeederNode?iID=942"), + (u"Israeli stocks on Wall Street", u"https://www.globes.co.il/WebService/Rss/RssFeeder.asmx/FeederKeyword?iID=1392"), + (u"All news", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=1725"), + (u"Macro economics", u"https://www.globes.co.il/WebService/Rss/RssFeeder.asmx/FeederKeyword?iID=1389"), + (u"Aerospace and defense", u"https://www.globes.co.il/WebService/Rss/RssFeeder.asmx/FeederKeyword?iID=1380"), + (u"Real estate", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederKeyword?iID=1385"), + (u"Energy and water", u"https://www.globes.co.il/WebService/Rss/RssFeeder.asmx/FeederKeyword?iID=1382"), + (u"Start-ups and venture capital", u"https://www.globes.co.il/WebService/Rss/RssFeeder.asmx/FeederKeyword?iID=1397"), + (u"Financial services", u"https://www.globes.co.il/WebService/Rss/RssFeeder.asmx/FeederKeyword?iID=1383"), + (u"Tel Aviv markets", u"https://www.globes.co.il/WebService/Rss/RssFeeder.asmx/FeederKeyword?iID=1404"), + (u"Healthcare", u"https://www.globes.co.il/WebService/Rss/RssFeeder.asmx/FeederKeyword?iID=1377"), + (u"Telecommunications", u"https://www.globes.co.il/WebService/Rss/RssFeeder.asmx/FeederKeyword?iID=1386"), + (u"Information technology", u"https://www.globes.co.il/WebService/Rss/RssFeeder.asmx/FeederKeyword?iID=1376"), + (u"Transport and infrastructure", u"https://www.globes.co.il/WebService/Rss/RssFeeder.asmx/FeederKeyword?iID=1388"), + ] \ No newline at end of file diff --git a/recipes/globes_co_il.recipe b/recipes/globes_co_il.recipe index 48b7db4fed..39da2e32f8 100644 --- a/recipes/globes_co_il.recipe +++ b/recipes/globes_co_il.recipe @@ -1,49 +1,40 @@ from calibre.web.feeds.news import BasicNewsRecipe -import re - class AdvancedUserRecipe1283848012(BasicNewsRecipe): description = 'This is Globes.co.il.' - cover_url = 'http://www.the7eye.org.il/SiteCollectionImages/BAKTANA/arye_avnery_010709_377.jpg' + cover_url = 'https://images.globes.co.il/globes/logo-138-35-2.svg?ver=1' title = u'Globes' language = 'he' - __author__ = 'marbs' + __author__ = 'marbs & barakplasma' extra_css = 'img {max-width:100%;} body{direction: rtl;max-width:100%;}title{direction: rtl; } article_description{direction: rtl; }, a.article{direction: rtl;max-width:100%;} calibre_feed_description{direction: rtl; }' # noqa simultaneous_downloads = 5 remove_javascript = True + keep_only_tags = [ + dict(name='h1', attrs={'id': 'F_Title'}), + dict(name='h2', attrs={'id': 'coteret_SubCoteretText'}), + dict(name='div', attrs={'class': 'articleInner'}), + ] timefmt = '[%a, %d %b, %Y]' oldest_article = 1 max_articles_per_feed = 100 remove_attributes = ['width', 'style'] - feeds = [(u'שוק ההון', u'http://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=585'), - (u'נדל"ן', u'http://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=607'), - (u'וול סטריט ושווקי העולם', - u'http://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=1225'), - (u'ניתוח טכני', u'http://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=1294'), - (u'היי טק', u'http://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=594'), - (u'נתח שוק וצרכנות', - u'http://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=821'), - (u'דין וחשבון', u'http://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=829'), - (u'רכב', u'http://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=3220'), - (u'דעות', u'http://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=845'), - (u'קניון המניות - טור שבועי', - u'http://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=3175'), - (u'סביבה', u'http://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=3221')] - - def print_version(self, url): - split1 = url.split("=") - print_url = 'http://www.globes.co.il/serve/globes/printwindow.asp?did=' + \ - split1[1] - return print_url - - def preprocess_html(self, soup): - soup.find('tr', attrs={'bgcolor': 'black'} - ).findPrevious('tr').extract() - soup.find('tr', attrs={'bgcolor': 'black'}).extract() - return soup - - def fixChars(self, string): - # Replace lsquo (\x91) - fixed = re.sub("■", "■", string) - return fixed + feeds = [ + (u"עידכוני RSS ", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=3038"), + (u"כל הכתבות", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=2"), + (u"שוק ההון", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=585"), + (u"בארץ", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=9917"), + (u"גלובלי ושוקי עולם", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=1225"), + (u"גלובסטק", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=594"), + (u"דין וחשבון", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=829"), + (u"דעות", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=845"), + (u"וידאו", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=2007"), + (u"ליידי גלובס", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=3314"), + (u"מגזין G", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=3312"), + (u"nadlan", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=607"), + (u"נתח שוק וצרכנות", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=821"), + (u"מטבעות דיגיטליים", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=9758"), + (u"קריירה", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iid=3266"), + (u"תיירות", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iid=9010"), + (u"רכב", u"https://www.globes.co.il/webservice/rss/rssfeeder.asmx/FeederNode?iID=3220") + ] diff --git a/recipes/walla.recipe b/recipes/walla.recipe index 27522a9dd2..6392302f06 100644 --- a/recipes/walla.recipe +++ b/recipes/walla.recipe @@ -5,34 +5,36 @@ from calibre.web.feeds.news import BasicNewsRecipe class AdvancedUserRecipe1283848012(BasicNewsRecipe): description = 'The WallaNews.' - cover_url = 'http://ftp5.bizportal.co.il/web/giflib/news/rsPhoto/sz_5/rsz_220_220_logo_walla.gif' + cover_url = 'https://upload.wikimedia.org/wikipedia/he/d/d5/Walla_logo.svg' title = u'Walla' language = 'he' - __author__ = 'marbs' - extra_css = 'img {max-width:100%;} body{direction: rtl;},title{direction: rtl; } ,article_description{direction: rtl; }, a.article{direction: rtl; } ,calibre_feed_description{direction: rtl; }' # noqa + __author__ = 'marbs & barakplasma' + extra_css = 'body{direction: rtl;},title{direction: rtl; } ,article_description{direction: rtl; }, a.article{direction: rtl; } ,calibre_feed_description{direction: rtl; }' simultaneous_downloads = 5 - timefmt = '[%a, %d %b, %Y]' + timefmt = '[%d/%m/%y %H:%M]' oldest_article = 1 max_articles_per_feed = 100 - keep_only_tags = dict(name='div', attrs={'class': 'wp-0-b w3'}) - remove_tags = [dict(name='div', attrs={'class': 'tagsContainer'})] + keep_only_tags = [ + dict(name='h1', attrs={'class': 'title'}), + dict(name='p', attrs={'class': 'subtitle'}), + dict(name='p', attrs={'class': 'article_speakable'}), + ] max_articles_per_feed = 100 - feeds = [(u'חדשות', u'http://rss.walla.co.il/?w=/1/0/1/@rss'), - (u'עסקים', u'http://rss.walla.co.il/?w=/2/3/1/@rss'), - (u'תרבות', u'http://rss.walla.co.il/?w=/4/249/1/@rss'), - (u'בריאות', u'http://rss.walla.co.il/?w=/5/18/1/@rss'), - (u'TECH', u'http://rss.walla.co.il/?w=/6/4/1/@rss'), - (u'אסטרולוגיה', u'http://rss.walla.co.il/?w=/8/3307/1/@rss'), - (u'בעלי חיים', u'http://rss.walla.co.il/?w=/59/5703/1/@rss'), - (u'רכב', u'http://rss.walla.co.il/?w=/31/4700/1/@rss'), - (u'סלבס', u'http://rss.walla.co.il/?w=/22/3600/1/@rss'), - (u'אוכל', u'http://rss.walla.co.il/?w=/9/903/1/@rss'), - (u'אופנה', u'http://rss.walla.co.il/?w=/24/2120/1/@rss'), - (u'ברנזה', u'http://rss.walla.co.il/?w=/27/3900/1/@rss'), - (u'ZONE', u'http://rss.walla.co.il/?w=/18/500/1/@rss'), - (u'ספורט', u'http://rss.walla.co.il/?w=/3/7/1/@rss')] - - def print_version(self, url): - print_url = url + '/@@/item/printer' - return print_url + feeds = [ + (u'חדשות', u'https://rss.walla.co.il/feed/1?type=main'), + (u'ספורט', u'https://rss.walla.co.il/feed/3?type=main'), + (u'כסף', u'https://rss.walla.co.il/feed/2?type=main'), + (u'רכב', u'https://rss.walla.co.il/feed/31?type=main'), + (u'תרבות', u'https://rss.walla.co.il/feed/4?type=main'), + (u'סלבס', u'https://rss.walla.co.il/feed/22?type=main'), + (u'אופנה', u'https://rss.walla.co.il/feed/24?type=main'), + (u'אוכל', u'https://rss.walla.co.il/feed/9?type=main'), + (u'בריאות', u'https://rss.walla.co.il/feed/139?type=main'), + (u'תיירות', u'https://rss.walla.co.il/feed/14?type=main'), + (u'TECH', u'https://rss.walla.co.il/feed/6?type=main'), + (u'כיף', u'https://rss.walla.co.il/feed/13?type=main'), + (u'בית ועיצוב', u'https://rss.walla.co.il/feed/35?type=main'), + (u'יהדות', u'https://rss.walla.co.il/feed/138?type=main'), + (u'מקומי', u'https://rss.walla.co.il/feed/430?type=main') + ] diff --git a/recipes/ynet.recipe b/recipes/ynet.recipe index 6604c16751..160d3940d7 100644 --- a/recipes/ynet.recipe +++ b/recipes/ynet.recipe @@ -5,15 +5,20 @@ import mechanize class AdvancedUserRecipe1283848012(BasicNewsRecipe): - description = 'This is a recipe of Ynet.co.il. The recipe opens the article page and clicks on an advertisement to not hurt the sites advertising income.' - cover_url = 'http://www.bneiakiva.net/uploads/images/ynet%282%29.jpg' + description = 'This is a recipe of Ynet.co.il.' + cover_url = 'http://www.ynet.co.il/images/CENTRAL_logo.gif' title = u'Ynet' - __author__ = 'marbs' + __author__ = 'marbs & barakplasma' language = 'he' extra_css = 'img {max-width:100%;direction: rtl;} #article{direction: rtl;} div{direction: rtl;} title{direction: rtl; } article_description{direction: rtl; } a.article{direction: rtl; } calibre_feed_description{direction: rtl; } body{direction: ltr;}' # noqa remove_attributes = ['width'] simultaneous_downloads = 5 - keep_only_tags = dict(name='div', attrs={'id': 'articleContainer'}) + keep_only_tags = [ + dict(name='h1', attrs={'class': 'mainTitle'}), + dict(name='h2', attrs={'class': 'subTitle'}), + dict(name='div', attrs={'class': 'text_editor_paragraph rtl'}), + dict(name='div', attrs={'class': 'ArticleBodyComponent'}), + ] remove_javascript = True timefmt = '[%a, %d %b, %Y]' oldest_article = 1 @@ -28,48 +33,51 @@ class AdvancedUserRecipe1283848012(BasicNewsRecipe): soup.body['dir'] = 'rtl' return soup - feeds = [(u'\u05d7\u05d3\u05e9\u05d5\u05ea', - u'http://www.ynet.co.il/Integration/StoryRss2.xml'), - (u'\u05db\u05dc\u05db\u05dc\u05d4', - u'http://www.ynet.co.il/Integration/StoryRss6.xml'), - (u'\u05e6\u05e8\u05db\u05e0\u05d5\u05ea', - u'http://www.ynet.co.il/Integration/StoryRss437.xml'), - (u'\u05e1\u05e4\u05d5\u05e8\u05d8', - u'http://www.ynet.co.il/Integration/StoryRss3.xml'), - (u'\u05ea\u05e8\u05d1\u05d5\u05ea', - u'http://www.ynet.co.il/Integration/StoryRss538.xml'), - (u'\u05de\u05e2\u05d5\u05e8\u05d1\u05d5\u05ea \u05d5\u05d7\u05d1\u05e8\u05d4', - u'http://www.ynet.co.il/Integration/StoryRss3262.xml'), - (u'\u05d1\u05e8\u05d9\u05d0\u05d5\u05ea', - u'http://www.ynet.co.il/Integration/StoryRss1208.xml'), - (u'\u05d9\u05e8\u05d5\u05e7', - u'http://www.ynet.co.il/Integration/StoryRss4872.xml'), - (u'\u05de\u05d7\u05e9\u05d1\u05d9\u05dd', - u'http://www.ynet.co.il/Integration/StoryRss544.xml'), - (u'\u05e8\u05db\u05d1', u'http://www.ynet.co.il/Integration/StoryRss550.xml'), - (u'\u05ea\u05d9\u05d9\u05e8\u05d5\u05ea', - u'http://www.ynet.co.il/Integration/StoryRss598.xml'), - (u'\u05d4\u05d5\u05e8\u05d9\u05dd', - u'http://www.ynet.co.il/Integration/StoryRss3052.xml'), - (u'\u05d0\u05d5\u05db\u05dc', - u'http://www.ynet.co.il/Integration/StoryRss975.xml'), - (u'\u05d9\u05d4\u05d3\u05d5\u05ea', - u'http://www.ynet.co.il/Integration/StoryRss4403.xml'), - (u'\u05de\u05d3\u05e2 \u05d5\u05d8\u05d1\u05e2', - u'http://www.ynet.co.il/Integration/StoryRss2142.xml'), - (u'\u05d9\u05d7\u05e1\u05d9\u05dd', - u'http://www.ynet.co.il/Integration/StoryRss3925.xml'), - (u'\u05d3\u05e2\u05d5\u05ea', - u'http://www.ynet.co.il/Integration/StoryRss194.xml')] - - def print_version(self, url): - # remove from here - br = BasicNewsRecipe.get_browser(self) - br.open(url) - br.follow_link(mechanize.Link(base_url='', url=url, - text='', tag='a', attrs=[{'id': 'buzzerATop'}])) -# to here to stop supporting ynet... - split1 = url.split("-") - print_url = 'http://www.ynet.co.il/Ext/Comp/ArticleLayout/CdaArticlePrintPreview/1,2506,L-' + \ - split1[1] - return print_url + feeds = [ + (u'כל ערוץ החדשות', u'http://www.ynet.co.il/Integration/StoryRss2.xml'), + (u'מבזקי החדשות', u'http://www.ynet.co.il/Integration/StoryRss1854.xml'), + (u'בלוגוס', u'http://www.ynet.co.il/Integration/StoryRss3764.xml'), + (u'כל ערוץ הדעות', u'http://www.ynet.co.il/Integration/StoryRss194.xml'), + (u'יומני סבר', u'http://www.ynet.co.il/Integration/StoryRss3487.xml'), + (u'כל ערוץ הצרכנות', u'http://www.ynet.co.il/Integration/StoryRss5363.xml'), + (u'כל ערוץ הספורט', u'http://www.ynet.co.il/Integration/StoryRss3.xml'), + (u'כדורגל ישראלי', u'http://www.ynet.co.il/Integration/StoryRss57.xml'), + (u'כל ערוץ התרבות', u'http://www.ynet.co.il/Integration/StoryRss538.xml'), + (u'בלוג הפרעות', u'http://www.ynet.co.il/Integration/StoryRss4450.xml'), + (u'חיים בסרט', u'http://www.ynet.co.il/Integration/StoryRss3908.xml'), + (u'כל ערוץ מעורבות', u'http://www.ynet.co.il/Integration/StoryRss3262.xml'), + (u'בית', u'http://www.ynet.co.il/Integration/StoryRss4113.xml'), + (u'זוגיות', u'http://www.ynet.co.il/Integration/StoryRss4107.xml'), + (u'מלחשים', u'http://www.ynet.co.il/Integration/StoryRss4105.xml'), + (u'סטייל', u'http://www.ynet.co.il/Integration/StoryRss4104.xml'), + (u'קריירה', u'http://www.ynet.co.il/Integration/StoryRss4111.xml'), + (u'שף', u'http://www.ynet.co.il/Integration/StoryRss4335.xml'), + (u'כל ערוץ הבריאות', u'http://www.ynet.co.il/Integration/StoryRss1208.xml'), + (u'כל הערוץ הירוק', u'http://www.ynet.co.il/Integration/StoryRss4872.xml'), + (u'סביבה', u'http://www.ynet.co.il/Integration/StoryRss4879.xml'), + (u'בעלי חיים', u'http://www.ynet.co.il/Integration/StoryRss4880.xml'), + (u'שאלות חיות', u'http://www.ynet.co.il/Integration/StoryRss4926.xml'), + (u'כל ערוץ המחשבים', u'http://www.ynet.co.il/Integration/StoryRss544.xml'), + (u'סקירות', u'http://www.ynet.co.il/Integration/StoryRss2424.xml'), + (u'אינטרנט', u'http://www.ynet.co.il/Integration/StoryRss546.xml'), + (u'משחקים', u'http://www.ynet.co.il/Integration/StoryRss571.xml'), + (u'PC מגזין', u'http://www.ynet.co.il/Integration/StoryRss1694.xml'), + (u'טכנולוגיה', u'http://www.ynet.co.il/Integration/StoryRss545.xml'), + (u'קטעי קישור', u'http://www.ynet.co.il/Integration/StoryRss4540.xml'), + (u'מרושתים', u'http://www.ynet.co.il/Integration/StoryRss4541.xml'), + (u'מדריכים ותוכנה', u'http://www.ynet.co.il/Integration/StoryRss786.xml'), + (u'כל ערוץ הרכב', u'http://www.ynet.co.il/Integration/StoryRss550.xml'), + (u'כל ערוץ התיירות', u'http://www.ynet.co.il/Integration/StoryRss598.xml'), + (u'כל ערוץ ההורים', u'http://www.ynet.co.il/Integration/StoryRss3052.xml'), + (u'כל ערוץ האוכל', u'http://www.ynet.co.il/Integration/StoryRss975.xml'), + (u'כל ערוץ היהדות', u'http://www.ynet.co.il/Integration/StoryRss4403.xml'), + (u'במנהרת הזמן', u'http://www.ynet.co.il/Integration/StoryRss4542.xml'), + (u'כל ערוץ הכלכלה', u'http://www.ynet.co.il/Integration/StoryRss6.xml'), + (u'כל ערוץ מדע וטבע', u'http://www.ynet.co.il/Integration/StoryRss2142.xml'), + (u'כל ערוץ יחסים', u'http://www.ynet.co.il/Integration/StoryRss3925.xml'), + (u'News', u'https://www.ynet.co.il/3rdparty/mobile/rss/ynetnews/3082/'), + (u'News updates', u'https://www.ynet.co.il/3rdparty/mobile/rss/ynetnews/3089/'), + (u'Opinion', u'https://www.ynet.co.il/3rdparty/mobile/rss/ynetnews/3084/'), + (u'Culture', u'https://www.ynet.co.il/3rdparty/mobile/rss/ynetnews/3086/'), + (u'Jewish', u'https://www.ynet.co.il/3rdparty/mobile/rss/ynetnews/3443/') + ] From a6cc35fa22fa5810a20ed927ece2df40fd5f1f7a Mon Sep 17 00:00:00 2001 From: Michael Salaverry Date: Sat, 17 Dec 2022 15:48:48 +0200 Subject: [PATCH 0017/2055] fix: logo use a logo from walla itself --- recipes/walla.recipe | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/walla.recipe b/recipes/walla.recipe index 6392302f06..cfbe53c807 100644 --- a/recipes/walla.recipe +++ b/recipes/walla.recipe @@ -5,7 +5,7 @@ from calibre.web.feeds.news import BasicNewsRecipe class AdvancedUserRecipe1283848012(BasicNewsRecipe): description = 'The WallaNews.' - cover_url = 'https://upload.wikimedia.org/wikipedia/he/d/d5/Walla_logo.svg' + cover_url = 'https://images.wcdn.co.il//2/5/4/2/2542044-46.jpg' title = u'Walla' language = 'he' __author__ = 'marbs & barakplasma' From 7c0e77ffad7fb9566b4fe8ee260a831602faa4c8 Mon Sep 17 00:00:00 2001 From: Michael Salaverry Date: Sat, 17 Dec 2022 16:53:07 +0200 Subject: [PATCH 0018/2055] add new recipe: works in progress magazine --- recipes/works_in_progress.recipe | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 recipes/works_in_progress.recipe diff --git a/recipes/works_in_progress.recipe b/recipes/works_in_progress.recipe new file mode 100644 index 0000000000..bd5a6dfafd --- /dev/null +++ b/recipes/works_in_progress.recipe @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +from calibre.web.feeds.news import BasicNewsRecipe + +class WorksInProgress(BasicNewsRecipe): + title = 'Works in progress' + description = 'Works in Progress is an online magazine dedicated to sharing new and underrated ideas to improve the world, and features original writing from some of the most interesting thinkers in the world' + cover_url = "https://www.worksinprogress.co/wp-content/uploads/2020/03/logo-1.svg" + oldest_article = 7 + max_articles_per_feed = 100 + auto_cleanup = True + publication_type = 'magazine' + language = 'en' + index = "https://www.worksinprogress.co/" + __author__ = "barakplasma" + + def parse_index(self): + soup = self.index_to_soup(self.index) + feeds = [] + + for section in soup.find_all('div', 'issue-loop'): + section_title = section['data-section-id'] + section_items = [] + + for article in section.find_all('div', 'issue-intro'): + title = article.find('h2', 'issue-title').text + url = article.find_all('a')[1]['href'] + author = article.find('a', 'author').text + section_items.append({ + "title": title, + "url": url, + "author": author + }) + + feeds.append((section_title, section_items)) + + return feeds \ No newline at end of file From e5b664c162551c708708ac81e2196361c1c9c120 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 17 Dec 2022 17:44:54 +0000 Subject: [PATCH 0019/2055] Clean up the new composite splitter API to ignore case and ignore dups. Make Quickview use the new API. --- src/calibre/db/cache.py | 8 +++++++- src/calibre/gui2/dialogs/quickview.py | 7 +++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 80b925e770..82ed243a14 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2351,9 +2351,15 @@ class Cache: @read_api def split_if_is_multiple_composite(self, f, v): + ''' + If f is a composite column lookup key and the column is is_multiple then + split comma-separated v into unique non-empty values. The uniqueness + comparison is case-insensitive. If values are case-insensitive equals + then the last is returned. + ''' fm = self.field_metadata.get(f, None) if fm and fm['datatype'] == 'composite' and fm['is_multiple']: - return [v.strip() for v in v.split(',') if v.strip()] + return list({v.strip().lower() : v.strip() for v in v.split(',') if v.strip()}.values()) return v @read_api diff --git a/src/calibre/gui2/dialogs/quickview.py b/src/calibre/gui2/dialogs/quickview.py index 2a17db7c2d..3fcaa0e7c6 100644 --- a/src/calibre/gui2/dialogs/quickview.py +++ b/src/calibre/gui2/dialogs/quickview.py @@ -542,10 +542,9 @@ class Quickview(QDialog, Ui_Quickview): self.books_table.setRowCount(0) mi = self.db.new_api.get_proxy_metadata(book_id) - vals = mi.get(key, None) - if self.fm[key]['datatype'] == 'composite' and self.fm[key]['is_multiple']: - sep = self.fm[key]['is_multiple'].get('cache_to_list', ',') - vals = [v.strip() for v in vals.split(sep) if v.strip()] + print('aaa', key, mi.get(key, None)) + vals = self.db.new_api.split_if_is_multiple_composite(key, mi.get(key, None)) + print('bbb', vals) try: # Check if we are in the GridView and there are no values for the # selected column. In this case switch the column to 'authors' From b292148d86212e67fe5963e114e9b94c396b5f0f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 18 Dec 2022 07:08:27 +0530 Subject: [PATCH 0020/2055] pep8 --- recipes/en_globes_co_il.recipe | 3 ++- recipes/globes_co_il.recipe | 1 + recipes/walla.recipe | 2 +- recipes/works_in_progress.recipe | 5 +++-- recipes/ynet.recipe | 1 - 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/recipes/en_globes_co_il.recipe b/recipes/en_globes_co_il.recipe index 6f08d64af1..2ad5aac6af 100644 --- a/recipes/en_globes_co_il.recipe +++ b/recipes/en_globes_co_il.recipe @@ -1,5 +1,6 @@ from calibre.web.feeds.news import BasicNewsRecipe + class En_Globes_Recipe(BasicNewsRecipe): description = 'This is en.globes.co.il.' cover_url = 'https://www.globes.co.il/images/GlobesEN-144x40.gif' @@ -31,4 +32,4 @@ class En_Globes_Recipe(BasicNewsRecipe): (u"Telecommunications", u"https://www.globes.co.il/WebService/Rss/RssFeeder.asmx/FeederKeyword?iID=1386"), (u"Information technology", u"https://www.globes.co.il/WebService/Rss/RssFeeder.asmx/FeederKeyword?iID=1376"), (u"Transport and infrastructure", u"https://www.globes.co.il/WebService/Rss/RssFeeder.asmx/FeederKeyword?iID=1388"), - ] \ No newline at end of file + ] diff --git a/recipes/globes_co_il.recipe b/recipes/globes_co_il.recipe index 39da2e32f8..ffd5c71f9d 100644 --- a/recipes/globes_co_il.recipe +++ b/recipes/globes_co_il.recipe @@ -1,5 +1,6 @@ from calibre.web.feeds.news import BasicNewsRecipe + class AdvancedUserRecipe1283848012(BasicNewsRecipe): description = 'This is Globes.co.il.' cover_url = 'https://images.globes.co.il/globes/logo-138-35-2.svg?ver=1' diff --git a/recipes/walla.recipe b/recipes/walla.recipe index cfbe53c807..5760a71e58 100644 --- a/recipes/walla.recipe +++ b/recipes/walla.recipe @@ -9,7 +9,7 @@ class AdvancedUserRecipe1283848012(BasicNewsRecipe): title = u'Walla' language = 'he' __author__ = 'marbs & barakplasma' - extra_css = 'body{direction: rtl;},title{direction: rtl; } ,article_description{direction: rtl; }, a.article{direction: rtl; } ,calibre_feed_description{direction: rtl; }' + extra_css = 'body{direction: rtl;},title{direction: rtl; } ,article_description{direction: rtl; }, a.article{direction: rtl; } ,calibre_feed_description{direction: rtl; }' # noqa simultaneous_downloads = 5 timefmt = '[%d/%m/%y %H:%M]' oldest_article = 1 diff --git a/recipes/works_in_progress.recipe b/recipes/works_in_progress.recipe index bd5a6dfafd..4aacf976a9 100644 --- a/recipes/works_in_progress.recipe +++ b/recipes/works_in_progress.recipe @@ -2,9 +2,10 @@ # vim:fileencoding=utf-8 from calibre.web.feeds.news import BasicNewsRecipe + class WorksInProgress(BasicNewsRecipe): title = 'Works in progress' - description = 'Works in Progress is an online magazine dedicated to sharing new and underrated ideas to improve the world, and features original writing from some of the most interesting thinkers in the world' + description = 'Works in Progress is an online magazine dedicated to sharing new and underrated ideas to improve the world, and features original writing from some of the most interesting thinkers in the world' # noqa cover_url = "https://www.worksinprogress.co/wp-content/uploads/2020/03/logo-1.svg" oldest_article = 7 max_articles_per_feed = 100 @@ -34,4 +35,4 @@ class WorksInProgress(BasicNewsRecipe): feeds.append((section_title, section_items)) - return feeds \ No newline at end of file + return feeds diff --git a/recipes/ynet.recipe b/recipes/ynet.recipe index 160d3940d7..155d7459d4 100644 --- a/recipes/ynet.recipe +++ b/recipes/ynet.recipe @@ -1,7 +1,6 @@ import re from calibre.web.feeds.news import BasicNewsRecipe -import mechanize class AdvancedUserRecipe1283848012(BasicNewsRecipe): From 0f75c185b716e618df876a1569c558af2e248a16 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 18 Dec 2022 07:26:13 +0530 Subject: [PATCH 0021/2055] Make the execrable epubcheck happy --- src/calibre/gui2/tweak_book/editor/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/tweak_book/editor/text.py b/src/calibre/gui2/tweak_book/editor/text.py index dcf4aa389a..74584c139d 100644 --- a/src/calibre/gui2/tweak_book/editor/text.py +++ b/src/calibre/gui2/tweak_book/editor/text.py @@ -865,7 +865,7 @@ class TextEdit(PlainTextEdit): 'bold': ('', ''), 'italic': ('', ''), 'underline': ('', ''), - 'strikethrough': ('', ''), + 'strikethrough': ('', ''), 'superscript': ('', ''), 'subscript': ('', ''), 'color': ('' % color, ''), From c37df3f1088c037c25e9792711d533fd1154306a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 18 Dec 2022 10:14:44 +0530 Subject: [PATCH 0022/2055] Book list: Fix a regression in the previous release that broke drag and drop of multiple books. Fixes #1999995 [Unable to drag & drop multiple books to tag browser](https://bugs.launchpad.net/calibre/+bug/1999995) --- src/calibre/gui2/library/alternate_views.py | 8 ++++++- src/calibre/gui2/library/views.py | 25 ++++++++++++--------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/calibre/gui2/library/alternate_views.py b/src/calibre/gui2/library/alternate_views.py index 52c280b2f6..047c2cf3cf 100644 --- a/src/calibre/gui2/library/alternate_views.py +++ b/src/calibre/gui2/library/alternate_views.py @@ -119,6 +119,12 @@ def mousePressEvent(self, event): return qt_item_view_base_class(self).mousePressEvent(self, event) +def mouseReleaseEvent(self, event): + if hasattr(self, 'handle_mouse_press_event'): + return self.handle_mouse_release_event(event) + return qt_item_view_base_class(self).mouseReleaseEvent(self, event) + + def drag_icon(self, cover, multiple): cover = cover.scaledToHeight(120, Qt.TransformationMode.SmoothTransformation) if multiple: @@ -263,7 +269,7 @@ def setup_dnd_interface(cls_or_self): cls = cls_or_self fmap = globals() for x in ( - 'dragMoveEvent', 'event_has_mods', 'mousePressEvent', 'mouseMoveEvent', + 'dragMoveEvent', 'event_has_mods', 'mousePressEvent', 'mouseMoveEvent', 'mouseReleaseEvent', 'drag_data', 'drag_icon', 'dragEnterEvent', 'dropEvent', 'paths_from_event'): func = fmap[x] setattr(cls, x, func) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 297cfbb760..00742c8951 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -1229,19 +1229,22 @@ class BooksView(QTableView): # {{{ # ensure clicked row is always selected index, QItemSelectionModel.SelectionFlag.Select | QItemSelectionModel.SelectionFlag.Rows) else: - if ( - m == Qt.KeyboardModifier.NoModifier and ev.button() == Qt.MouseButton.LeftButton and - self.editTriggers() & QAbstractItemView.EditTrigger.SelectedClicked - ): - # As of Qt 6 , Qt does not clear a multi-row selection when the - # edit triggers contain SelectedClicked and the clicked row is - # already selected, so do it ourselves - index = self.indexAt(ev.pos()) - sm = self.selectionModel() - if index.isValid() and sm.isSelected(index): - self.select_rows((index,), using_ids=False, change_current=False, scroll=False) QTableView.mousePressEvent(self, ev) + def handle_mouse_release_event(self, ev): + if ( + ev.modifiers() == Qt.KeyboardModifier.NoModifier and ev.button() == Qt.MouseButton.LeftButton and + self.editTriggers() & QAbstractItemView.EditTrigger.SelectedClicked + ): + # As of Qt 6, Qt does not clear a multi-row selection when the + # edit triggers contain SelectedClicked and the clicked row is + # already selected, so do it ourselves + index = self.indexAt(ev.pos()) + sm = self.selectionModel() + if index.isValid() and sm.isSelected(index): + self.select_rows((index,), using_ids=False, change_current=False, scroll=False) + QTableView.mouseReleaseEvent(self, ev) + @property def column_map(self): return self._model.column_map From 28b48b4cd234fabc797a82c103425233b1e6f8b1 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 18 Dec 2022 11:29:00 +0000 Subject: [PATCH 0023/2055] Remove print statements --- src/calibre/gui2/dialogs/quickview.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/calibre/gui2/dialogs/quickview.py b/src/calibre/gui2/dialogs/quickview.py index 3fcaa0e7c6..3c77b86a12 100644 --- a/src/calibre/gui2/dialogs/quickview.py +++ b/src/calibre/gui2/dialogs/quickview.py @@ -542,9 +542,7 @@ class Quickview(QDialog, Ui_Quickview): self.books_table.setRowCount(0) mi = self.db.new_api.get_proxy_metadata(book_id) - print('aaa', key, mi.get(key, None)) vals = self.db.new_api.split_if_is_multiple_composite(key, mi.get(key, None)) - print('bbb', vals) try: # Check if we are in the GridView and there are no values for the # selected column. In this case switch the column to 'authors' From 45275a48242a530d4720a433bb25fb20d5ca5ea0 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 18 Dec 2022 11:29:13 +0000 Subject: [PATCH 0024/2055] Fix the docstring for the composite delegate. --- src/calibre/gui2/library/delegates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index 4dcfc988b3..eb9ac48bf1 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -773,7 +773,7 @@ class CcTemplateDelegate(QStyledItemDelegate): # {{{ def __init__(self, parent): ''' - Delegate for custom_column bool data. + Delegate for composite custom_columns. ''' QStyledItemDelegate.__init__(self, parent) self.disallow_edit = gprefs['edit_metadata_templates_only_F2_on_booklist'] From f1f597a06b45596ccc9f3ca3707129a1b06c1de5 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 18 Dec 2022 11:32:32 +0000 Subject: [PATCH 0025/2055] Change the implementation to not do case insensitive comparison, because no other part of calibre does that with composites. For correctness get the split value from field metadata, even though at the moment it can only be a comma. Return a list for compatibility with other field getters. There are a fair number of places that do isinstance(v, list). --- src/calibre/db/cache.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 82ed243a14..d3164b5ae1 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2350,17 +2350,18 @@ class Cache: return self._books_for_field(f.name, int(item_id_or_composite_value)) @read_api - def split_if_is_multiple_composite(self, f, v): + def split_if_is_multiple_composite(self, f, val): ''' If f is a composite column lookup key and the column is is_multiple then - split comma-separated v into unique non-empty values. The uniqueness - comparison is case-insensitive. If values are case-insensitive equals - then the last is returned. + split v into unique non-empty values. The comparison is case sensitive. + Order is not preserved. Return a list() for compatibility with proxy + metadata field getters, for example tags. ''' fm = self.field_metadata.get(f, None) if fm and fm['datatype'] == 'composite' and fm['is_multiple']: - return list({v.strip().lower() : v.strip() for v in v.split(',') if v.strip()}.values()) - return v + sep = fm['is_multiple'].get('cache_to_list', ',') + return (list(set(v.strip() for v in val.split(sep) if v.strip()))) + return val @read_api def data_for_find_identical_books(self): From e9e19fe6de0c0b35cea97bf14f388a113076a35b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 18 Dec 2022 17:08:46 +0530 Subject: [PATCH 0026/2055] Cleanup previous PR --- src/calibre/db/cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index d3164b5ae1..dd9c0bebbb 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2360,7 +2360,7 @@ class Cache: fm = self.field_metadata.get(f, None) if fm and fm['datatype'] == 'composite' and fm['is_multiple']: sep = fm['is_multiple'].get('cache_to_list', ',') - return (list(set(v.strip() for v in val.split(sep) if v.strip()))) + return list({v.strip() for v in val.split(sep) if v.strip()}) return val @read_api From 87da4098f564b3b4fa9d8f42d2ead43ded3d820d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 19 Dec 2022 11:10:59 +0530 Subject: [PATCH 0027/2055] Fix #2000039 [GridView' object has no attribute 'handle_mouse_release_event'](https://bugs.launchpad.net/calibre/+bug/2000039) --- src/calibre/gui2/library/alternate_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/library/alternate_views.py b/src/calibre/gui2/library/alternate_views.py index 047c2cf3cf..1a2106962d 100644 --- a/src/calibre/gui2/library/alternate_views.py +++ b/src/calibre/gui2/library/alternate_views.py @@ -120,7 +120,7 @@ def mousePressEvent(self, event): def mouseReleaseEvent(self, event): - if hasattr(self, 'handle_mouse_press_event'): + if hasattr(self, 'handle_mouse_release_event'): return self.handle_mouse_release_event(event) return qt_item_view_base_class(self).mouseReleaseEvent(self, event) From 6812d671eb53fb69ec067385fffc31b8949ebbf6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 19 Dec 2022 12:16:22 +0530 Subject: [PATCH 0028/2055] Update Harvard Business Review --- recipes/hbr.recipe | 97 +++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/recipes/hbr.recipe b/recipes/hbr.recipe index f0b0c0218e..799b6d64b9 100644 --- a/recipes/hbr.recipe +++ b/recipes/hbr.recipe @@ -1,8 +1,14 @@ -from calibre.web.feeds.news import BasicNewsRecipe, classes -from datetime import datetime -from calibre import browser -from collections import OrderedDict import re +from collections import OrderedDict + +from calibre import browser +from calibre.web.feeds.news import BasicNewsRecipe, classes + + +def absurl(url): + if url.startswith('/'): + url = 'https://www.hbr.org/' + url + return url class HBR(BasicNewsRecipe): @@ -21,75 +27,66 @@ class HBR(BasicNewsRecipe): remove_attributes = ['height', 'width', 'style'] encoding = 'utf-8' ignore_duplicate_articles = {'url'} + resolve_internal_links = True + extra_css = ''' - article-sidebar{font-family:Georgia,"Times New Roman",Times,serif; border:ridge; text-align:left;} - [close-caption]{ border:ridge; font-size:small; text-align:center;} - article-ideainbrief{font-family:Georgia,"Times New Roman",Times,serif; text-align:left; font-style:italic; } - .article-byline-list{font-size:small;} - .credits--hero-image{font-size:small;} - .credits--inline-image{font-size:small;} - .caption--inline-image{font-size:small;} - .description-text{font-size:small; color:gray;} - .right-rail--container{font-size:small; color:#4c4c4c;} - .link--black{font-size:small;} - .article-callout{color:#4c4c4c; text-align:center;} - .slug-content{color:gray;} - ''' + .article-summary, .article-ideainbrief, .description-text, .link--black {font-size:small; color:#202020;} + .credits--hero-image, .credits--inline-image, .caption--inline-image {font-size:small; text-align:center;} + .article-byline-list {font-size:small; font-weight:bold;} + .question {font-weight:bold;} + .right-rail--container {font-size:small; color:#404040;} + .article-callout, .slug-content {color:#404040;} + .article-sidebar {color:#202020;} + ''' keep_only_tags = [ classes( - 'headline-container hero-image-content article-summary article-body standard-content' - ' article-dek-group article-dek slug-container' - ), - dict(name='article-sidebar'), + 'slug-container headline-container hero-image-content article-summary article-body ' + 'standard-content article-dek-group article-dek' + ) ] remove_tags = [ classes( - 'left-rail--container translate-message follow-topic newsletter-container ' - ), + 'left-rail--container translate-message follow-topic newsletter-container' + ) ] def parse_index(self): soup = self.index_to_soup('https://hbr.org/magazine') - a = soup.find('a', href=lambda x: x and x.startswith('/archive-toc/')) - url = a['href'] - self.log('Downloading issue:', url) - cov_url = a.find('img', attrs={'src': True})['src'] - self.cover_url = 'https://hbr.org' + cov_url - soup = self.index_to_soup('https://hbr.org' + url) + div = soup.find(**classes('backdrop-lightest')) + a = div.find('a', href=lambda x: x and x.startswith('/archive-toc/')) + index = absurl(a['href']) + self.timefmt = ' [' + self.tag_to_string(div.find('h2')) + ']' + self.log('Downloading issue: ', index, self.timefmt) + cov_url = a.find('img', src=True) + if cov_url: + self.cover_url = absurl(cov_url['src']) + soup = self.index_to_soup(index) feeds = OrderedDict() for h3 in soup.findAll('h3', attrs={'class': 'hed'}): articles = [] - d = datetime.today() - for a in h3.findAll( - 'a', href=lambda x: x.startswith('/' + d.strftime('%Y') + '/') - ): - - title = self.tag_to_string(a) - url = a['href'] - url = 'https://hbr.org' + url + a = h3.find('a') + title = self.tag_to_string(a) + url = absurl(a['href']) + auth = '' div = h3.find_next_sibling('div', attrs={'class': 'stream-item-info'}) if div: aut = self.tag_to_string(div).replace('Magazine Article ', '') auth = re.sub(r"(?<=\w)([A-Z])", r", \1", aut) + des = '' dek = h3.find_next_sibling('div', attrs={'class': 'dek'}) if dek: des = self.tag_to_string(dek) desc = des + ' |' + auth.title() + section_title = 'Articles' sec = h3.findParent('li').find_previous_sibling('div', **classes('stream-section-label')).find('h4') - section_title = self.tag_to_string(sec).title() - self.log(section_title) - self.log('\t', title) - self.log('\t', desc) - self.log('\t\t', url) - - articles.append({ - 'title': title, - 'url': url, - 'description': desc}) + if sec: + section_title = self.tag_to_string(sec).title() + self.log(section_title, '\n\t', title, '\n\t', desc, '\n\t\t', url) + articles.append({'title': title, 'url': url, 'description': desc}) if articles: if section_title not in feeds: feeds[section_title] = [] @@ -105,8 +102,10 @@ class HBR(BasicNewsRecipe): by.extract() for li in dek.findAll('li'): li.name = 'span' - for h2 in soup.findAll(('h2','h3')): - h2.name = 'h5' + for div in soup.findAll('div', attrs={'class':['article-summary', 'article-callout']}): + div.name = 'blockquote' + for sidebar in soup.findAll(('article-sidebar', 'article-ideainbrief')): + sidebar.name = 'blockquote' return soup # HBR changes the content it delivers based on cookies, so the From ae1e9e19146b772cd7be41092f172da856589459 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 19 Dec 2022 15:51:54 +0530 Subject: [PATCH 0029/2055] Fix restoring geometry of maximized/fullscreen dialogs forcing them visible --- src/calibre/gui2/geometry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/geometry.py b/src/calibre/gui2/geometry.py index b3f12fc951..d3a6c95e1e 100644 --- a/src/calibre/gui2/geometry.py +++ b/src/calibre/gui2/geometry.py @@ -122,10 +122,10 @@ def _do_restore(self: QWidget, s: QScreen, geometry: QRect, saved_data: dict): self.setGeometry(geometry) if saved_data['full_screened']: debug('Restoring widget to full screen') - self.showFullScreen() + self.setWindowState(Qt.WindowState.WindowFullScreen) elif saved_data['maximized']: debug('Restoring widget to maximized') - self.showMaximized() + self.setWindowState(Qt.WindowState.WindowMaximized) return True From f33f902a52a8d764c45256bbef4b6d71a0e85ad1 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 19 Dec 2022 11:01:37 +0000 Subject: [PATCH 0030/2055] Small correction to the GPM language grammar. --- manual/template_lang.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/template_lang.rst b/manual/template_lang.rst index 84a1051ce0..e1894e4995 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -216,7 +216,7 @@ General Program Mode top_expression ::= or_expression or_expression ::= and_expression [ '||' and_expression ]* and_expression ::= not_expression [ '&&' not_expression ]* - not_expression ::= [ '!' not_expression ]* | compare_exp + not_expression ::= [ '!' not_expression ]* | concatenate_expr concatenate_expr::= compare_expr [ '&' compare_expr ]* compare_expr ::= add_sub_expr [ compare_op add_sub_expr ] compare_op ::= '==' | '!=' | '>=' | '>' | '<=' | '<' | 'in' | 'inlist' | From 85eac7a5b440b22671c5cebfd60a7f193470c46e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 19 Dec 2022 20:48:50 +0530 Subject: [PATCH 0031/2055] ... --- recipes/hbr.recipe | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/hbr.recipe b/recipes/hbr.recipe index 799b6d64b9..625e38d9f5 100644 --- a/recipes/hbr.recipe +++ b/recipes/hbr.recipe @@ -7,7 +7,7 @@ from calibre.web.feeds.news import BasicNewsRecipe, classes def absurl(url): if url.startswith('/'): - url = 'https://www.hbr.org/' + url + url = 'https://www.hbr.org' + url return url From 015bf3c71b0b05e9a5e4190337cb5a7fac12f395 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 19 Dec 2022 16:14:10 +0000 Subject: [PATCH 0032/2055] Enhancement #2000037: Check Library: Open book folder --- src/calibre/gui2/dialogs/check_library.py | 43 ++++++++++++++++++----- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/calibre/gui2/dialogs/check_library.py b/src/calibre/gui2/dialogs/check_library.py index b984725d73..fb8cafcb8a 100644 --- a/src/calibre/gui2/dialogs/check_library.py +++ b/src/calibre/gui2/dialogs/check_library.py @@ -11,11 +11,12 @@ from qt.core import ( QApplication, QCheckBox, QCursor, QDialog, QDialogButtonBox, QGridLayout, QHBoxLayout, QIcon, QLabel, QLineEdit, QProgressBar, QPushButton, QStackedLayout, Qt, QTextEdit, QTreeWidget, QTreeWidgetItem, QVBoxLayout, - QWidget, pyqtSignal, QSplitter + QWidget, pyqtSignal, QSplitter, QToolButton ) from threading import Thread from calibre import as_unicode, prints +from calibre.gui2 import open_local_file from calibre.gui2.dialogs.confirm_delete import confirm from calibre.library.check_library import CHECKS, CheckLibrary from calibre.utils.recycle_bin import delete_file, delete_tree @@ -107,8 +108,34 @@ class DBCheck(QDialog): # {{{ # }}} -class Item(QTreeWidgetItem): - pass +class TextWithButtonWidget(QWidget): + + button_icon = None + + def __init__(self, library_path, text, item_path): + QWidget.__init__(self) + if self.button_icon is None: + self.button_icon = QIcon.ic('document_open.png') + self.library_path = library_path + self.item_path = item_path + l = QHBoxLayout() + l.setContentsMargins(0, 0, 0, 0) + b = QToolButton() + b.setContentsMargins(0, 0, 0, 0) + b.clicked.connect(self.button_clicked) + b.setIcon(self.button_icon) + l.addWidget(b) + t = QLabel(text) + t.setContentsMargins(0, 0, 0, 0) + l.addWidget(t) + self.setLayout(l) + self.setContentsMargins(0, 0, 0, 0) + + def button_clicked(self): + p = os.path.join(self.library_path, self.item_path) + if not os.path.isdir(p): + p = os.path.dirname(p) + open_local_file(p) class CheckLibraryDialog(QDialog): @@ -301,9 +328,9 @@ class CheckLibraryDialog(QDialog): else: self.problem_count[attr] = len(list_) - tl = Item() + tl = QTreeWidgetItem() tl.setText(0, h) - if fixable and list: + if fixable: tl.setData(1, Qt.ItemDataRole.UserRole, self.is_fixable) tl.setText(1, _('(fixable)')) tl.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable) @@ -322,17 +349,17 @@ class CheckLibraryDialog(QDialog): self.top_level_items[attr] = tl for problem in list_: - it = Item() + it = QTreeWidgetItem() + tl.addChild(it) if checkable: it.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable) it.setCheckState(2, Qt.CheckState.Unchecked) it.setData(2, Qt.ItemDataRole.UserRole, self.is_deletable) else: it.setFlags(Qt.ItemFlag.ItemIsEnabled) - it.setText(0, problem[0]) + tree.setItemWidget(it, 0, TextWithButtonWidget(self.db.library_path, problem[0], problem[1])) it.setData(0, Qt.ItemDataRole.UserRole, problem[2]) it.setText(2, problem[1]) - tl.addChild(it) self.all_items.append(it) plaintext.append(','.join([h, problem[0], problem[1]])) tree.addTopLevelItem(tl) From 46297aea73f183d6e7b1e102c7b1e5f8d58f4f35 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 21 Dec 2022 16:50:28 +0530 Subject: [PATCH 0033/2055] Update Caravan Magazine --- recipes/caravan_magazine.recipe | 73 ++++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/recipes/caravan_magazine.recipe b/recipes/caravan_magazine.recipe index 1d5ffab35a..3ed3135d33 100644 --- a/recipes/caravan_magazine.recipe +++ b/recipes/caravan_magazine.recipe @@ -7,6 +7,7 @@ import json from mechanize import Request from calibre.web.feeds.recipes import BasicNewsRecipe +from calibre.ebooks.BeautifulSoup import Tag def classes(classes): @@ -15,10 +16,17 @@ def classes(classes): 'class': lambda x: x and frozenset(x.split()).intersection(q)}) +def new_tag(soup, name, attrs=()): + impl = getattr(soup, 'new_tag', None) + if impl is not None: + return impl(name, attrs=dict(attrs)) + return Tag(soup, name, attrs=attrs or None) + + class CaravanMagazine(BasicNewsRecipe): title = 'Caravan Magazine' - __author__ = 'Kovid Goyal, Gobelinus' + __author__ = 'Kovid Goyal, Gobelinus, unkn0wn' description = 'An Indian Journal of politics and culture' language = 'en_IN' timefmt = ' [%b, %Y]' @@ -27,16 +35,24 @@ class CaravanMagazine(BasicNewsRecipe): no_stylesheets = True - keep_only_tags = [ - classes('post-title short-desc author-details cover'), - dict(itemprop='articleBody'), - ] + remove_attributes = ['style', 'height', 'width'] + ignore_duplicate_articles = {'url'} + resolve_internal_links = True + + extra_css = ''' + blockquote {color:#202020;} + #fig-c {text-align:center; font-size:small;} + em {color:#202020;} + .article-footer {font-size:small;} + .date, .pre-title {font-size:small; color:#404040;} + .authors {font-size:small; font-weight:bold;} + ''' remove_tags = [ + classes('related-articles'), dict(name='meta'), dict(attrs={'class': ['share-with', 'img-wrap abs']}), ] - remove_attributes = ['style'] def get_browser(self, *args, **kw): br = BasicNewsRecipe.get_browser(self, *args, **kw) @@ -68,6 +84,8 @@ class CaravanMagazine(BasicNewsRecipe): def parse_index(self): base_url = 'https://www.caravanmagazine.in/' soup = self.index_to_soup('{0}magazine'.format(base_url)) + if magdate := soup.find('h6', attrs={'class':'magazine-date'}): + self.timefmt = ' [' + self.tag_to_string(magdate).strip() + ']' # find current issue cover feeds = [] @@ -94,10 +112,43 @@ class CaravanMagazine(BasicNewsRecipe): return feeds + def get_cover_url(self): + soup = self.index_to_soup( + 'https://www.readwhere.com/magazine/delhi-press/The-Caravan/5326' + ) + for citem in soup.findAll( + 'meta', content=lambda s: s and s.endswith('/magazine/300/new') + ): + return citem['content'].replace('300', '600') + + def print_version(self, url): + if not self.username or not self.password: + return url.replace('.in/','.in/amp/') + return url + def preprocess_html(self, soup): - for div in soup.findAll(itemprop='image'): - for img in div.findAll('img'): - img['src'] = div['content'] - for img in soup.findAll(attrs={'data-src': True}): - img['src'] = img['data-src'] + if not self.username or not self.password: + keep_only_tags = [classes('main-content')] + for fc in soup.findAll('figcaption'): + fc['id'] = 'fig-c' + for img in soup.findAll('amp-img'): + img.name = 'img' + if h6 := soup.find('h6'): + h6.name = 'h4' + else: + keep_only_tags = [ + classes('post-title short-desc author-details cover'), + dict(itemprop='articleBody'), + ] + for div in soup.findAll(itemprop='image'): + for img in div.findAll('img'): + img['src'] = div['content'] + for img in soup.findAll(attrs={'data-src': True}): + img['src'] = img['data-src'] + + body = new_tag(soup, 'body') + for spec in keep_only_tags: + for tag in soup.find('body').findAll(**spec): + body.insert(len(body.contents), tag) + soup.find('body').replaceWith(body) return soup From 5498a45aee6a8f874181334c90523a7869c8c518 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 21 Dec 2022 21:50:23 +0530 Subject: [PATCH 0034/2055] Edit book: Show a warning when opening a readonly file --- src/calibre/gui2/tweak_book/boss.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index 3d617903b2..4f34f49ab4 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -37,7 +37,7 @@ from calibre.ebooks.oeb.polish.utils import ( link_stylesheets, setup_css_parser_serialization as scs ) from calibre.gui2 import ( - add_to_recent_docs, choose_dir, choose_files, choose_save_file, error_dialog, + add_to_recent_docs, choose_dir, choose_files, choose_save_file, error_dialog, warning_dialog, info_dialog, open_url, question_dialog ) from calibre.gui2.dialogs.confirm_delete import confirm @@ -318,6 +318,9 @@ class Boss(QObject): if not os.path.exists(path): return error_dialog(self.gui, _('File not found'), _( 'The file %s does not exist.') % path, show=True) + if not os.access(path, os.W_OK): + warning_dialog(self.gui, _('Readonly file'), _( + 'The file {} is readon-only. Saving changes to it will either fail or cause its permissions to be reset.').format(path), show=True) isdir = os.path.isdir(path) ext = path.rpartition('.')[-1].upper() if ext not in SUPPORTED and not isdir: From 732a92b153d188f5f91e2c37bbe0af7f09f3211b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 21 Dec 2022 21:51:46 +0530 Subject: [PATCH 0035/2055] ... --- src/calibre/gui2/tweak_book/boss.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index 4f34f49ab4..b9dcacb193 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -319,8 +319,8 @@ class Boss(QObject): return error_dialog(self.gui, _('File not found'), _( 'The file %s does not exist.') % path, show=True) if not os.access(path, os.W_OK): - warning_dialog(self.gui, _('Readonly file'), _( - 'The file {} is readon-only. Saving changes to it will either fail or cause its permissions to be reset.').format(path), show=True) + warning_dialog(self.gui, _('Read-only file'), _( + 'The file {} is read-only. Saving changes to it will either fail or cause its permissions to be reset.').format(path), show=True) isdir = os.path.isdir(path) ext = path.rpartition('.')[-1].upper() if ext not in SUPPORTED and not isdir: From 213dee41dab0138bfb97327c8a52bf32f0679b14 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 22 Dec 2022 11:04:09 +0530 Subject: [PATCH 0036/2055] Economist: Dont download interactive articles They dont work well and break conversion to EPUB with giant embedded SVG images. Fixes #2000279 [Fetch news for The Economist fails](https://bugs.launchpad.net/calibre/+bug/2000279) --- recipes/economist.recipe | 3 +++ recipes/economist_free.recipe | 3 +++ 2 files changed, 6 insertions(+) diff --git a/recipes/economist.recipe b/recipes/economist.recipe index 43063f2c4b..977a94c143 100644 --- a/recipes/economist.recipe +++ b/recipes/economist.recipe @@ -307,6 +307,9 @@ class Economist(BasicNewsRecipe): sub = safe_dict(part, "print", "subheadline") or '' if sub and section != sub: desc = sub + ' :: ' + desc + if '/interactive/' in url: + self.log('Skipping interactive article:', title, url) + continue feeds_dict[section].append({"title": title, "url": url, "description": desc}) self.log(' ', title, url, '\n ', desc) return [(section, articles) for section, articles in feeds_dict.items()] diff --git a/recipes/economist_free.recipe b/recipes/economist_free.recipe index 43063f2c4b..977a94c143 100644 --- a/recipes/economist_free.recipe +++ b/recipes/economist_free.recipe @@ -307,6 +307,9 @@ class Economist(BasicNewsRecipe): sub = safe_dict(part, "print", "subheadline") or '' if sub and section != sub: desc = sub + ' :: ' + desc + if '/interactive/' in url: + self.log('Skipping interactive article:', title, url) + continue feeds_dict[section].append({"title": title, "url": url, "description": desc}) self.log(' ', title, url, '\n ', desc) return [(section, articles) for section, articles in feeds_dict.items()] From 48d08139de9812e50c9eecb3aa9880bde4a7a48d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 22 Dec 2022 19:21:39 +0530 Subject: [PATCH 0037/2055] Bump the version of the bundled zlib Fixes #1811 (Dependencies: zlib file url changed) --- bypy/sources.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bypy/sources.json b/bypy/sources.json index 8419f30e2a..c69d4ba43d 100644 --- a/bypy/sources.json +++ b/bypy/sources.json @@ -62,8 +62,8 @@ { "name": "zlib", "unix": { - "filename": "zlib-1.2.11.tar.xz", - "hash": "sha256:4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066", + "filename": "zlib-1.2.13.tar.xz", + "hash": "sha256:d14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98", "urls": ["https://zlib.net/{filename}"] } }, From c500ddc53533be8168d4a425fe5a7d1d046fcfc7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 22 Dec 2022 21:38:23 +0530 Subject: [PATCH 0038/2055] Add another place where trailing spaces are stripped from destination addresses --- src/calibre/gui2/preferences/emailp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/preferences/emailp.py b/src/calibre/gui2/preferences/emailp.py index 8d41df75ae..bc5731d5c8 100644 --- a/src/calibre/gui2/preferences/emailp.py +++ b/src/calibre/gui2/preferences/emailp.py @@ -147,7 +147,7 @@ class EmailAccounts(QAbstractTableModel): # {{{ elif col == 1: self.accounts[account][0] = re.sub(',+', ',', re.sub(r'\s+', ',', as_unicode(value or '').upper())) elif col == 0: - na = as_unicode(value or '') + na = as_unicode(value or '').strip() from email.utils import parseaddr addr = parseaddr(na)[-1] if not addr or '@' not in na: From 34bd5c1061c27fc90bf1840458d381b25e761b1b Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Fri, 23 Dec 2022 15:57:54 +0000 Subject: [PATCH 0039/2055] Add a tooltip to the new open folder button in check_library --- src/calibre/gui2/dialogs/check_library.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/dialogs/check_library.py b/src/calibre/gui2/dialogs/check_library.py index fb8cafcb8a..87b620b8f8 100644 --- a/src/calibre/gui2/dialogs/check_library.py +++ b/src/calibre/gui2/dialogs/check_library.py @@ -116,14 +116,18 @@ class TextWithButtonWidget(QWidget): QWidget.__init__(self) if self.button_icon is None: self.button_icon = QIcon.ic('document_open.png') - self.library_path = library_path - self.item_path = item_path + + self.path = os.path.join(library_path, item_path) + if not os.path.isdir(self.path): + self.path = os.path.dirname(self.path) + l = QHBoxLayout() l.setContentsMargins(0, 0, 0, 0) b = QToolButton() b.setContentsMargins(0, 0, 0, 0) b.clicked.connect(self.button_clicked) b.setIcon(self.button_icon) + b.setToolTip(_('Open folder {}').format(self.path)) l.addWidget(b) t = QLabel(text) t.setContentsMargins(0, 0, 0, 0) @@ -132,10 +136,7 @@ class TextWithButtonWidget(QWidget): self.setContentsMargins(0, 0, 0, 0) def button_clicked(self): - p = os.path.join(self.library_path, self.item_path) - if not os.path.isdir(p): - p = os.path.dirname(p) - open_local_file(p) + open_local_file(self.path) class CheckLibraryDialog(QDialog): From 298f8e8b7fa853c1af552dbe224aa12fb630625d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 24 Dec 2022 09:55:08 +0530 Subject: [PATCH 0040/2055] ... --- src/calibre/ebooks/metadata/sources/identify.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/calibre/ebooks/metadata/sources/identify.py b/src/calibre/ebooks/metadata/sources/identify.py index 83a1d1ab35..220eb4295b 100644 --- a/src/calibre/ebooks/metadata/sources/identify.py +++ b/src/calibre/ebooks/metadata/sources/identify.py @@ -633,9 +633,8 @@ def urls_from_identifiers(identifiers, sort_results=False): # {{{ # }}} -if __name__ == '__main__': # tests {{{ - # To run these test use: calibre-debug -e - # src/calibre/ebooks/metadata/sources/identify.py +def tests(start=0, limit=256): # tests {{{ + # To run these test use: calibre-debug -c "from calibre.ebooks.metadata.sources.identify import tests; tests()" from calibre.ebooks.metadata.sources.test import ( authors_test, test_identify, title_test ) @@ -678,5 +677,5 @@ if __name__ == '__main__': # tests {{{ ] # test_identify(tests[1:2]) - test_identify(tests) + test_identify(tests[start:limit]) # }}} From d185190c618b8388d749853e7637ee2909598ced Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 25 Dec 2022 04:32:03 +0530 Subject: [PATCH 0041/2055] Content server: Fix identifiers from third party metadata download plugins not becoming clickable links on the book details page --- src/pyj/book_list/book_details.pyj | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/pyj/book_list/book_details.pyj b/src/pyj/book_list/book_details.pyj index 947f628e68..6afdf57085 100644 --- a/src/pyj/book_list/book_details.pyj +++ b/src/pyj/book_list/book_details.pyj @@ -267,26 +267,17 @@ def render_metadata(mi, table, book_id, iframe_css): # {{{ def process_identifiers(field, fm, name, val): - def ids_sorter(url_map, k): - x = url_map[k] - if not x: - return '' + def ids_sorter(x): return (x[0] or '').toLowerCase() - if val: - keys = Object.keys(val) - if keys.length: - td = E.td() - url_map = {k:v'[text, url]' for text, k, val, url in mi.urls_from_identifiers or v'[]'} - for k in sorted(keys, key=ids_sorter.bind(None, url_map)): - idval = val[k] - x = url_map[k] - if x: - if td.childNodes.length: - td.appendChild(document.createTextNode(', ')) - td.appendChild(E.a(class_='blue-link', title='{}:{}'.format(k, idval), target='_new', href=x[1], x[0])) + if val and mi.urls_from_identifiers and mi.urls_from_identifiers.length > 0: + td = E.td() + for text, k, idval, url in sorted(mi.urls_from_identifiers or v'[]', key=ids_sorter): if td.childNodes.length: - table.appendChild(E.tr(E.td(name + ':'), td)) + td.appendChild(document.createTextNode(', ')) + td.appendChild(E.a(class_='blue-link', title='{}:{}'.format(k, idval), target='_new', href=url, text)) + if td.childNodes.length: + table.appendChild(E.tr(E.td(name + ':'), td)) def process_size(field, fm, name, val): if val: From 5c30cc142acfc34c6c6095b1a391735307251240 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 25 Dec 2022 09:40:20 +0530 Subject: [PATCH 0042/2055] Also warn about read-only files just before saving --- src/calibre/gui2/tweak_book/boss.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index b9dcacb193..23ce37a42d 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -107,6 +107,7 @@ class Boss(QObject): def __init__(self, parent, notify=None): QObject.__init__(self, parent) self.global_undo = GlobalUndoHistory() + self.file_was_readonly = False self.container_count = 0 self.tdir = None self.save_manager = SaveManager(parent, notify) @@ -318,9 +319,6 @@ class Boss(QObject): if not os.path.exists(path): return error_dialog(self.gui, _('File not found'), _( 'The file %s does not exist.') % path, show=True) - if not os.access(path, os.W_OK): - warning_dialog(self.gui, _('Read-only file'), _( - 'The file {} is read-only. Saving changes to it will either fail or cause its permissions to be reset.').format(path), show=True) isdir = os.path.isdir(path) ext = path.rpartition('.')[-1].upper() if ext not in SUPPORTED and not isdir: @@ -332,6 +330,11 @@ class Boss(QObject): ' Convert your book to one of these formats first.') % _(' and ').join(sorted(SUPPORTED)), show=True) + self.file_was_readonly = not os.access(path, os.W_OK) + if self.file_was_readonly: + warning_dialog(self.gui, _('Read-only file'), _( + 'The file {} is read-only. Saving changes to it will either fail or cause its permissions to be reset.').format(path), show=True) + for name in tuple(editors): self.close_editor(name) self.gui.preview.clear() @@ -1302,6 +1305,11 @@ class Boss(QObject): self.global_undo.update_path_to_ebook(path) else: return + if os.path.exists(c.path_to_ebook) and not os.access(c.path_to_ebook, os.W_OK): + if not question_dialog(self.gui, _('File is read-only'), _( + 'The file at {} is read-only. The editor will try to reset its permissions before saving. Proceed with saving?' + ).format(c.path_to_ebook), override_icon='dialog_warning.png', yes_text=_('&Save'), no_text=_('&Cancel'), yes_icon='save.png'): + return self.gui.action_save.setEnabled(False) tdir = self.mkdtemp(prefix='save-') container = clone_container(c, tdir) From 8e38105c5a16d8d58ba871fd723f8754ffff13ec Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 26 Dec 2022 17:29:49 +0530 Subject: [PATCH 0043/2055] Content server FTS: Fix page header bar not visible --- src/pyj/book_list/fts.pyj | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pyj/book_list/fts.pyj b/src/pyj/book_list/fts.pyj index dd4c439fbd..d3837b40e4 100644 --- a/src/pyj/book_list/fts.pyj +++ b/src/pyj/book_list/fts.pyj @@ -416,10 +416,9 @@ def show_panel(visible): if c: x = c.parentNode.firstChild while x: - if x.nodeType is 1 and x is not c: - x.style.display = 'none' + if x.nodeType is 1 and x.dataset.component: + x.style.display = 'block' if x is c else 'none' x = x.nextSibling - c.style.display = 'block' def show_search_panel(): From c544efa75a14147d8c27004044df2d85756cdd86 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 27 Dec 2022 08:40:25 +0530 Subject: [PATCH 0044/2055] Fix a regression in calibre 5 that broke using a file for the --extra-css option of ebook-convert More python 3 goodness --- src/calibre/ebooks/conversion/plumber.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py index e4fcc037e6..4aad0dddc2 100644 --- a/src/calibre/ebooks/conversion/plumber.py +++ b/src/calibre/ebooks/conversion/plumber.py @@ -1199,7 +1199,7 @@ OptionRecommendation(name='search_replace', if self.opts.extra_css and os.path.exists(self.opts.extra_css): with open(self.opts.extra_css, 'rb') as f: - self.opts.extra_css = f.read() + self.opts.extra_css = f.read().decode('utf-8') oibl = self.opts.insert_blank_line orps = self.opts.remove_paragraph_spacing From 478f91061126e56371556bd5da07f87676862ab9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 27 Dec 2022 11:01:56 +0530 Subject: [PATCH 0045/2055] Update HNA --- recipes/hna.recipe | 97 +++++++++++----------------------------------- 1 file changed, 23 insertions(+), 74 deletions(-) diff --git a/recipes/hna.recipe b/recipes/hna.recipe index b742143119..4649e420b3 100644 --- a/recipes/hna.recipe +++ b/recipes/hna.recipe @@ -5,8 +5,7 @@ __copyright__ = '2008, Kovid Goyal ' Fetch Hessisch Niedersachsische Allgemeine. ''' -from calibre.web.feeds.news import BasicNewsRecipe - +from calibre.web.feeds.news import BasicNewsRecipe, classes class hnaDe(BasicNewsRecipe): @@ -20,78 +19,28 @@ class hnaDe(BasicNewsRecipe): max_articles_per_feed = 40 no_stylesheets = True remove_javascript = True - auto_cleanup = True encoding = 'utf-8' + masthead_url = 'https://idcdn.de/west/assets/hna-de/img/logo--cf5324e1.svg' - remove_tags = [dict(id='topnav'), - dict(id='nav_main'), - dict(id='teaser'), - dict(id='suchen'), - dict(id='superbanner'), - dict(id='navigation'), - dict(id='skyscraper'), - dict(id='idHeaderSearchForm'), - dict(id='idHeaderSearchBar'), - dict(id='idLoginBarWrap'), - dict(id='idAccountButtons'), - dict(id='idHeadButtons'), - dict(id='idBoxesWrap'), - dict(id='idJSMainNavigation'), - dict(id=''), - dict(name='span'), - dict(name='ul', attrs={'class': 'linklist'}), - dict(name='ul', attrs={ - 'class': 'idMainNavi idJSActive idHeadHomeBtn'}), - dict(name='ul', attrs={ - 'class': 'idHiddenNavi idNaviSubcategories'}), - dict(name='a', attrs={'href': '#'}), - dict(name='a', attrs={'class': 'idImgLink'}), - dict(name='a', attrs={'class': 'idListLink'}), - dict(name='div', attrs={'class': 'hlist'}), - dict(name='div', attrs={'class': 'idTabWrap'}), - dict(name='li', attrs={ - 'class': 'idButton idIsLoginGroup idHeaderRegister '}), - dict(name='li', attrs={'class': 'idVideoBar idFirst'}), - dict(name='li', attrs={ - 'class': 'idSetStartPageLink idLast'}), - dict(name='li', attrs={'class': 'idKinderNetzBar idLast'}), - dict(name='li', attrs={'class': 'idFotoBar '}), - dict(name='div', attrs={'class': 'subc noprint'}), - dict(name='div', attrs={'class': 'idTxtLay'}), - dict(name='div', attrs={ - 'class': 'idLay idClStandard idStaticHtml'}), - dict(name='div', attrs={'class': 'idHeaderWrap'}), - dict(name='div', attrs={ - 'class': 'idLay idRss idClStandard'}), - dict(name='div', attrs={ - 'class': 'idLay idClStandard idLeadStoriesFocus idLeadStoriesFocusOverlay '}), - dict(name='div', attrs={ - 'class': 'idTeaserLay idTeaserFloat idMediaLeft idLast'}), - dict(name='div', attrs={ - 'class': 'idHeaderButtons idAccountButtons'}), - dict(name='div', attrs={ - 'class': 'idTeaserLay idTeaserWithImg idSize4 idMediaLeft'}), - dict(name='div', attrs={ - 'class': 'idHeaderButtons idHeadButtons'}), - dict(name='div', attrs={ - 'class': 'idHeaderButtons idSetStartPage'}), - dict(name='div', attrs={ - 'class': 'idLay idClHl idTeaserList '}), - dict(name='div', attrs={'class': 'idNavigationWrap'}), - dict(name='div', attrs={'class': 'idBreadcrumbWrap'}), - dict(name='div', attrs={'class': 'idBoxesWrap'}), - dict(name='div', attrs={'class': 'idBreadcrumb'}), - dict(name='div', attrs={ - 'class': 'idLay idAdvertising idClStandard '}), - dict(name='span', attrs={'class': 'idHeadLineIntro'}), - dict(name='p', attrs={'class': 'breadcrumb'}), - dict(name='a', attrs={'style': 'cursor:hand'}), - dict(name='p', attrs={'class': 'h5'}), - dict(name='p', attrs={'class': 'idMoreEnd'})] - remove_tags_after = [ - dict(name='div', attrs={'class': 'idTxtLay idStaticHtmlIEHelper'})] + def get_cover_url(self): + soup = self.index_to_soup('https://epaper.meinehna.de/') + if a := soup.find('a', attrs={'class':'edition-cover__link'}): + if citem := a.find('img', src=True): + return citem['src'] - feeds = [('hna_soehre', 'http://feeds2.feedburner.com/hna/soehre'), - ('hna_kassel', 'http://feeds2.feedburner.com/hna/kassel'), - ('hna_KSV', 'http://feeds2.feedburner.com/hna/ksv'), - ('hna_kultur', 'http://feeds2.feedburner.com/hna/kultur')] + keep_only_tags = [ + dict(name='article', attrs={'class':lambda x: x and 'id-Story' in x.split()}) + ] + remove_tags = [ + classes( + 'id-DonaldBreadcrumb id-StoryElement-interactionBar id-Recommendation ' + 'id-Comments id-Comments--targetHelper id-StoryElement-inArticleReco' + ) + ] + + feeds = [ + ('hna_soehre', 'http://feeds2.feedburner.com/hna/soehre'), + ('hna_kassel', 'http://feeds2.feedburner.com/hna/kassel'), + ('hna_KSV', 'http://feeds2.feedburner.com/hna/ksv'), + ('hna_kultur', 'http://feeds2.feedburner.com/hna/kultur') + ] From 771d37b3c194c28eee3146a1f793bca8b22da5e9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 27 Dec 2022 19:27:02 +0530 Subject: [PATCH 0046/2055] Fix #2000582 [When converting to newer file format version, original file is shown without a format icon.](https://bugs.launchpad.net/calibre/+bug/2000582) --- src/calibre/gui2/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 480b9e9581..a67dc74680 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -815,9 +815,12 @@ class FileIconProvider(QFileIconProvider): def key_from_ext(self, ext): key = ext if ext in self.icons else 'default' - if key == 'default' and ext.count('.') > 0: + if key == 'default' and '.' in ext: ext = ext.rpartition('.')[2] key = ext if ext in self.icons else 'default' + if key == 'default' and ext.startswith('original_'): + ext = ext.partition('_')[2] + key = ext if ext in self.icons else 'default' if key == 'default': key = ext return key From efa2ef3339bf8bebaeaa3e70ce1d5f740549de7a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 29 Dec 2022 08:42:08 +0530 Subject: [PATCH 0047/2055] Windows TTS: Dont fail to configure if one of the voices has no defined language --- src/calibre/gui2/tts/windows_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/tts/windows_config.py b/src/calibre/gui2/tts/windows_config.py index c3743aacd1..79e731d636 100644 --- a/src/calibre/gui2/tts/windows_config.py +++ b/src/calibre/gui2/tts/windows_config.py @@ -20,7 +20,7 @@ class VoicesModel(QAbstractTableModel): self.voice_data = voice_data def language(x): - return x.get('language_display_name') or x['language'] or '' + return x.get('language_display_name') or x.get('language') or '' self.current_voices = tuple((x['name'], language(x), x.get('age', ''), x.get('gender', ''), x['id']) for x in voice_data) self.column_headers = _('Name'), _('Language'), _('Age'), _('Gender') From eaa6bfe9017271c01e692c002dfabe512d4da146 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 30 Dec 2022 12:26:53 +0530 Subject: [PATCH 0048/2055] Fix #2000744 [Error when adding value to enum column](https://bugs.launchpad.net/calibre/+bug/2000744) --- src/calibre/gui2/dialogs/enum_values_edit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/enum_values_edit.py b/src/calibre/gui2/dialogs/enum_values_edit.py index 36c236caae..564d1dca93 100644 --- a/src/calibre/gui2/dialogs/enum_values_edit.py +++ b/src/calibre/gui2/dialogs/enum_values_edit.py @@ -142,7 +142,7 @@ class EnumValuesEdit(QDialog): self.table.setCellWidget(row, 1, c) def save_geometry(self): - self.save_geometry(gprefs, 'enum-values-edit-geometry') + super().save_geometry(gprefs, 'enum-values-edit-geometry') def accept(self): disp = self.fm['display'] From c7c9c79157d9903a103c5e4f194fb9349b7f3fdd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 31 Dec 2022 14:28:01 +0530 Subject: [PATCH 0049/2055] string changes --- src/calibre/gui2/preferences/look_feel.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index 138d5c1a7b..a010116a77 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -1846,7 +1846,7 @@ that don't have children.</p> - <p>The template used to generate the text below the covers. Uses + <p>The template used to generate the text below the covers. This template uses the same syntax as save templates. Defaults to just the book title. Note that this setting is per-library, which means that you have to set it again for every different calibre library you use. Use an From ba6a7bbccdc2a543f411961b9491bf978acd4ec1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 1 Jan 2023 08:45:34 +0530 Subject: [PATCH 0050/2055] Content server home page: When showing recently read books from across devices hide the entries for which loading the cover fails --- src/pyj/book_list/home.pyj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pyj/book_list/home.pyj b/src/pyj/book_list/home.pyj index c9e0f19ecc..f292e24954 100644 --- a/src/pyj/book_list/home.pyj +++ b/src/pyj/book_list/home.pyj @@ -132,12 +132,12 @@ def show_recent_for_user(container_id): if item.cfi: q.bookpos = item.cfi url_to_read = open_book_url(item.book_id, item.format, q) + img = E.img(alt=item.tooltip, src=absolute_path(f'get/cover/{item.book_id}/{item.library_id}')) images.appendChild(E.div(style='margin: 0 1em', - E.a( - title=item.tooltip, - href=url_to_read, - E.img(alt=item.tooltip, src=absolute_path(f'get/cover/{item.book_id}/{item.library_id}')) - ))) + E.a(title=item.tooltip, href=url_to_read, img))) + img.onerror = def(err): + failed = err.target + failed.parentNode.parentNode.style.display = 'none' def show_recent_stage2(books): From 39653fb3542ccd4c0e2f96b89b6f195b862c46e9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jan 2023 10:31:53 +0530 Subject: [PATCH 0051/2055] Update PC World --- recipes/pc_world.recipe | 141 +++++++++++++++++++++++----------------- 1 file changed, 82 insertions(+), 59 deletions(-) diff --git a/recipes/pc_world.recipe b/recipes/pc_world.recipe index 3ede833131..589745a526 100644 --- a/recipes/pc_world.recipe +++ b/recipes/pc_world.recipe @@ -1,75 +1,98 @@ #!/usr/bin/env python -__license__ = 'GPL v3' -__author__ = 'Lorenzo Vigentini' -__copyright__ = '2009, Lorenzo Vigentini ' -__version__ = 'v1.01' -__date__ = '14, January 2010' -__description__ = 'PC World and Macworld consistently deliver editorial excellence through award-winning content and trusted product reviews.' ''' http://www.pcworld.com/ ''' -from calibre.web.feeds.news import BasicNewsRecipe -from calibre.ptempfile import PersistentTemporaryFile - -temp_files = [] -articles_are_obfuscated = True +from calibre.web.feeds.news import BasicNewsRecipe, classes class pcWorld(BasicNewsRecipe): - __author__ = 'Lorenzo Vigentini' - description = 'PC World and Macworld consistently deliver editorial excellence through award-winning content and trusted product reviews.' - cover_url = 'http://images.pcworld.com/images/common/header/header-logo.gif' - - title = 'PCWorld ' + __author__ = 'unkn0wn' + description = 'PCWorld helps you navigate the PC ecosystem to find the products you want and the advice you need to get the job done.' + title = 'PCWorld' publisher = 'IDG Communication' - category = 'PC, video, computing, product reviews, editing, cameras, production' - language = 'en' - timefmt = '[%a, %d %b, %Y]' - - oldest_article = 7 - max_articles_per_feed = 20 - use_embedded_content = False - recursion = 10 - + encoding = 'utf-8' + ignore_duplicate_articles = {'url'} remove_javascript = True - no_stylesheets = True - auto_cleanup = True + resolve_internal_links = True + remove_empty_feeds = True + remove_attributes = ['height', 'width'] - def get_obfuscated_article(self, url): - br = self.get_browser() - br.open(url + '&print') + extra_css = ''' + .entry-meta, .imageCredit {font-size:small;} + .entry-eyebrow, .article_author_box_bio {font-size:small; color:#404040;} + .subheadline {font-style:italic; color:#202020;} + ''' - response = br.follow_link(url, nr=0) - html = response.read() - - self.temp_files.append(PersistentTemporaryFile('_fa.html')) - self.temp_files[-1].write(html) - self.temp_files[-1].close() - return self.temp_files[-1].name - - feeds = [ - (u'All Stories', u'http://www.pcworld.com/index.rss'), - (u'Reviews', u'http://www.pcworld.com/reviews/index.rss'), - (u'How-To', u'http://www.pcworld.com/howto/index.rss'), - (u'Video', u'http://www.pcworld.com/video/index.rss'), - (u'Game On', u'http://www.pcworld.com/column/game-on/index.rss'), - (u'Hassle free PC', u'http://www.pcworld.com/column/hassle-free-pc/index.rss'), - (u'Go Social', u'http://www.pcworld.com/column/go-social/index.rss'), - (u'Linux Line', u'http://www.pcworld.com/column/linux-line/index.rss'), - (u'Net Work', u'http://www.pcworld.com/column/net-work/index.rss'), - (u'Security Alert', u'http://www.pcworld.com/column/security-alert/index.rss'), - (u'Simply Business', u'http://www.pcworld.com/column/simply-business/index.rss'), - (u'Business', u'http://www.pcworld.com/category/business/index.rss'), - (u'Security & Privacy', u'http://www.pcworld.com/category/privacy/index.rss'), - (u'Windows', u'http://www.pcworld.com/category/windows/index.rss'), - (u'Laptops', u'http://www.pcworld.com/category/laptop-computers/index.rss'), - (u'Software', u'http://www.pcworld.com/category/software/index.rss'), - (u'Desktops', u'http://www.pcworld.com/category/desktop-computers/index.rss'), - (u'Printers', u'http://www.pcworld.com/category/printers/index.rss'), - (u'Phones', u'http://www.pcworld.com/category/phones/index.rss'), - (u'Tablets', u'http://www.pcworld.com/category/tablets/index.rss') + keep_only_tags = [ + classes('entry-header post-thumbnail'), + dict(name='div', attrs={'id':'link_wrapped_content'}), + classes('article_author_box_bio') ] + def parse_index(self): + + section_list = [ + ('PC & Components', 'pc-components'), + ('Laptops', 'laptops'), + ('Mobile', 'mobile'), + ('How-To', 'howto'), + ('Gaming', 'gaming'), + ('Windows', 'windows'), + ('Best-Picks','best-picks'), + ('Reviews', 'reviews'), + ('Security', 'security'), + ('Smart Tech', 'smart-tech'), + ('Software', 'software'), + ('WiFi & Networks', 'wifi-networks'), + ('Deals', 'deals'), + ('Business', 'business'), + ('Entertainment', 'entertainment'), + ] + + feeds = [] + + # For each section title, fetch the article urls + for section in section_list: + section_title = section[0] + section_url = 'https://www.pcworld.com/' + section[1] + self.log(section_title, section_url) + soup = self.index_to_soup(section_url) + articles = self.articles_from_soup(soup) + if articles: + feeds.append((section_title, articles)) + return feeds + + def articles_from_soup(self, soup): + ans = [] + feed = soup.find('div', attrs={'class':lambda x: x and 'articleFeed-inner' in x.split()}) + for item in feed.findAll('div', attrs={'class':'item-text-inner'}): + a = item.find('h3').find('a', href=True) + title = self.tag_to_string(a) + url = a['href'] + desc = '' + if span := item.find(attrs={'class':'item-excerpt'}): + desc = self.tag_to_string(span) + if byline := item.find(attrs={'class':'item-byline'}): + desc = self.tag_to_string(byline) + ' | ' + desc + if eye := item.find(attrs={'class':lambda x: x and 'item-eyebrow' in x.split()}): + desc = self.tag_to_string(eye) + ' | ' + desc + if itdate := item.find(attrs={'class':'item-date'}): + date = self.tag_to_string(itdate) + check = 'hours', 'day', 'days' # skipping articles older than a week + if not any(x in date for x in check): + continue + if not url or not title: + continue + self.log('\t', title, '\n\t', desc, '\n\t\t', url) + ans.append({'title': title, 'url': url, 'description': desc}) + return ans + + def get_cover_url(self): + soup = self.index_to_soup( + 'https://www.magzter.com/US/IDG-Consumer-and-SMB,-Inc./PCWorld/Computer-&-Mobile/' + ) + for citem in soup.findAll('meta', content=lambda s: s and s.endswith('view/3.jpg')): + return citem['content'] From caea357d6495f05781268d81a43af8eaed2b98d9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jan 2023 10:36:39 +0530 Subject: [PATCH 0052/2055] NHK News by Richard A. Steps --- recipes/nhk_news.recipe | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 recipes/nhk_news.recipe diff --git a/recipes/nhk_news.recipe b/recipes/nhk_news.recipe new file mode 100644 index 0000000000..61e906aa81 --- /dev/null +++ b/recipes/nhk_news.recipe @@ -0,0 +1,31 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +# feed source: https://www.nhk.or.jp/toppage/rss/index.html + + +class ReutersJa(BasicNewsRecipe): + + title = 'NHK News' + description = 'NHK News in Japanese' + __author__ = 'Richard A. Steps' + use_embedded_content = False + language = 'ja' + max_articles_per_feed = 30 + remove_javascript = True + auto_cleanup = True + + # This line added to deal with bots on site + def get_browser(self, *a, **kw): + kw['user_agent'] = 'common_words/based' + return super().get_browser(*a, **kw) + + feeds = [ + ('主要ニュース', 'https://www.nhk.or.jp/rss/news/cat0.xml?format=xml'), + ('社会', 'https://www.nhk.or.jp/rss/news/cat1.xml?format=xml'), + ('科学・医療', 'https://www.nhk.or.jp/rss/news/cat3.xml?format=xml'), + ('政治', 'https://www.nhk.or.jp/rss/news/cat4.xml?format=xml'), + ('経済', 'https://www.nhk.or.jp/rss/news/cat5.xml?format=xml'), + ('国際', 'https://www.nhk.or.jp/rss/news/cat6.xml?format=xml'), + ('スポーツ', 'https://www.nhk.or.jp/rss/news/cat7.xml?format=xml'), + ('文化・エンタメ', 'https://www.nhk.or.jp/rss/news/cat2.xml?format=xml') + ] From cd98541fd45761f1994f0c0dc88446c2a13bb8b4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 2 Jan 2023 17:59:05 +0530 Subject: [PATCH 0053/2055] recipe icons --- recipes/icons/l_espresso.png | Bin 0 -> 1071 bytes recipes/icons/la_gazetta_del_mezzogiorno.png | Bin 0 -> 1739 bytes recipes/icons/la_segunda.png | Bin 0 -> 923 bytes recipes/icons/la_tribuna.png | Bin 0 -> 1502 bytes recipes/icons/la_voce.png | Bin 0 -> 1121 bytes recipes/icons/lameuse_be.png | Bin 0 -> 1304 bytes recipes/icons/lavanguardia.png | Bin 0 -> 806 bytes recipes/icons/lavanguardia_corresponsales_es.png | Bin 0 -> 806 bytes recipes/icons/le_gorafi.png | Bin 0 -> 1651 bytes recipes/icons/le_journal.png | Bin 0 -> 1660 bytes recipes/icons/le_peuple_breton.png | Bin 0 -> 6934 bytes recipes/icons/leduc.png | Bin 0 -> 1076 bytes recipes/icons/leipzer_volkszeitung.png | Bin 0 -> 1449 bytes recipes/icons/lenta_ru.png | Bin 0 -> 198 bytes recipes/icons/les_echos.png | Bin 0 -> 2191 bytes recipes/icons/lescienze.png | Bin 0 -> 2335 bytes recipes/icons/lesoir_be.png | Bin 0 -> 2047 bytes recipes/icons/levante.png | Bin 0 -> 484 bytes recipes/icons/liberatorio_politico.png | Bin 0 -> 445 bytes recipes/icons/lightspeed_magazine.png | Bin 0 -> 2056 bytes recipes/icons/limba_sarda.png | Bin 0 -> 593 bytes recipes/icons/linux_news_de.png | Bin 0 -> 864 bytes recipes/icons/live_law.png | Bin 0 -> 1122 bytes recipes/icons/livescience.png | Bin 0 -> 1050 bytes recipes/icons/living_stones.png | Bin 0 -> 1368 bytes recipes/icons/los_danieles.png | Bin 0 -> 1409 bytes recipes/icons/losservatoreromano_it.png | Bin 0 -> 1590 bytes recipes/icons/ludwig_mises.png | Bin 0 -> 2356 bytes recipes/icons/luns_a_venres.png | Bin 0 -> 1041 bytes recipes/icons/lwn_free.png | Bin 0 -> 1852 bytes recipes/icons/macity.png | Bin 0 -> 1530 bytes recipes/icons/magyar_nemzet.png | Bin 0 -> 1321 bytes recipes/icons/maharashtra_times.png | Bin 0 -> 766 bytes recipes/icons/mainichi_en.png | Bin 0 -> 2214 bytes recipes/icons/malaya_business_insight.png | Bin 0 -> 1520 bytes recipes/icons/mallorca_zeitung.png | Bin 0 -> 1927 bytes recipes/icons/mandidner.png | Bin 0 -> 594 bytes recipes/icons/marine_corps_times.png | Bin 0 -> 923 bytes recipes/icons/marketing_magazine.png | Bin 0 -> 2519 bytes recipes/icons/matichon.png | Bin 0 -> 888 bytes recipes/icons/mdj.png | Bin 0 -> 1868 bytes recipes/icons/mediaindonesia.png | Bin 0 -> 1031 bytes recipes/icons/mediterraneo.png | Bin 0 -> 1410 bytes recipes/icons/mens_day_out.png | Bin 0 -> 2081 bytes recipes/icons/military_times.png | Bin 0 -> 1194 bytes recipes/icons/mit_technology_review.png | Bin 0 -> 1014 bytes recipes/icons/mobilenations.png | Bin 0 -> 395 bytes recipes/icons/monbiot.png | Bin 0 -> 743 bytes recipes/icons/mondedurable.png | Bin 0 -> 2005 bytes recipes/icons/moneycontrol.png | Bin 0 -> 1532 bytes recipes/icons/montreal_gazette.png | Bin 0 -> 1047 bytes recipes/icons/mwjournal.png | Bin 0 -> 2588 bytes recipes/icons/my_dealz_de.png | Bin 0 -> 1697 bytes recipes/icons/nachdenkseiten.png | Bin 0 -> 149 bytes recipes/icons/nation_ke.png | Bin 0 -> 640 bytes recipes/icons/national_post.png | Bin 0 -> 875 bytes recipes/icons/nature.png | Bin 0 -> 832 bytes recipes/icons/navy_times.png | Bin 0 -> 1396 bytes recipes/icons/nbonline.png | Bin 0 -> 1266 bytes recipes/icons/ncrnext.png | Bin 0 -> 705 bytes recipes/icons/nejm.png | Bin 0 -> 2273 bytes recipes/icons/nepszabadsag.png | Bin 0 -> 328 bytes recipes/icons/new_london_day.png | Bin 0 -> 1377 bytes recipes/icons/new_york_review_of_books.png | Bin 0 -> 629 bytes .../icons/new_york_review_of_books_no_sub.png | Bin 0 -> 629 bytes recipes/icons/news324.png | Bin 0 -> 1804 bytes recipes/icons/news_busters.png | Bin 0 -> 645 bytes recipes/icons/newsbeast.png | Bin 0 -> 2162 bytes recipes/icons/newsobs.png | Bin 0 -> 397 bytes recipes/icons/newz_dk.png | Bin 0 -> 1927 bytes recipes/icons/nightflier.png | Bin 0 -> 274 bytes recipes/icons/nikkei_news.png | Bin 0 -> 674 bytes recipes/icons/noerrebronordvestbladet_dk.png | Bin 0 -> 300 bytes recipes/icons/nol.png | Bin 0 -> 328 bytes recipes/icons/non_leggerlo.png | Bin 0 -> 551 bytes recipes/icons/nos_nl.png | Bin 0 -> 380 bytes recipes/icons/novinky.cz.png | Bin 0 -> 1352 bytes recipes/icons/novinky.png | Bin 0 -> 1352 bytes recipes/icons/nrc-nl-epub.png | Bin 0 -> 705 bytes recipes/icons/nrc_handelsblad.png | Bin 0 -> 705 bytes recipes/icons/ntv_spor.png | Bin 0 -> 1761 bytes recipes/icons/ntv_tr.png | Bin 0 -> 2171 bytes recipes/icons/nymag.png | Bin 0 -> 562 bytes recipes/icons/nytimes_cooking.png | Bin 0 -> 963 bytes recipes/icons/nytimesbook.png | Bin 0 -> 1021 bytes recipes/icons/nzz_folio.png | Bin 0 -> 327 bytes 86 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 recipes/icons/l_espresso.png create mode 100644 recipes/icons/la_gazetta_del_mezzogiorno.png create mode 100644 recipes/icons/la_segunda.png create mode 100644 recipes/icons/la_tribuna.png create mode 100644 recipes/icons/la_voce.png create mode 100644 recipes/icons/lameuse_be.png create mode 100644 recipes/icons/lavanguardia.png create mode 100644 recipes/icons/lavanguardia_corresponsales_es.png create mode 100644 recipes/icons/le_gorafi.png create mode 100644 recipes/icons/le_journal.png create mode 100644 recipes/icons/le_peuple_breton.png create mode 100644 recipes/icons/leduc.png create mode 100644 recipes/icons/leipzer_volkszeitung.png create mode 100644 recipes/icons/lenta_ru.png create mode 100644 recipes/icons/les_echos.png create mode 100644 recipes/icons/lescienze.png create mode 100644 recipes/icons/lesoir_be.png create mode 100644 recipes/icons/levante.png create mode 100644 recipes/icons/liberatorio_politico.png create mode 100644 recipes/icons/lightspeed_magazine.png create mode 100644 recipes/icons/limba_sarda.png create mode 100644 recipes/icons/linux_news_de.png create mode 100644 recipes/icons/live_law.png create mode 100644 recipes/icons/livescience.png create mode 100644 recipes/icons/living_stones.png create mode 100644 recipes/icons/los_danieles.png create mode 100644 recipes/icons/losservatoreromano_it.png create mode 100644 recipes/icons/ludwig_mises.png create mode 100644 recipes/icons/luns_a_venres.png create mode 100644 recipes/icons/lwn_free.png create mode 100644 recipes/icons/macity.png create mode 100644 recipes/icons/magyar_nemzet.png create mode 100644 recipes/icons/maharashtra_times.png create mode 100644 recipes/icons/mainichi_en.png create mode 100644 recipes/icons/malaya_business_insight.png create mode 100644 recipes/icons/mallorca_zeitung.png create mode 100644 recipes/icons/mandidner.png create mode 100644 recipes/icons/marine_corps_times.png create mode 100644 recipes/icons/marketing_magazine.png create mode 100644 recipes/icons/matichon.png create mode 100644 recipes/icons/mdj.png create mode 100644 recipes/icons/mediaindonesia.png create mode 100644 recipes/icons/mediterraneo.png create mode 100644 recipes/icons/mens_day_out.png create mode 100644 recipes/icons/military_times.png create mode 100644 recipes/icons/mit_technology_review.png create mode 100644 recipes/icons/mobilenations.png create mode 100644 recipes/icons/monbiot.png create mode 100644 recipes/icons/mondedurable.png create mode 100644 recipes/icons/moneycontrol.png create mode 100644 recipes/icons/montreal_gazette.png create mode 100644 recipes/icons/mwjournal.png create mode 100644 recipes/icons/my_dealz_de.png create mode 100644 recipes/icons/nachdenkseiten.png create mode 100644 recipes/icons/nation_ke.png create mode 100644 recipes/icons/national_post.png create mode 100644 recipes/icons/nature.png create mode 100644 recipes/icons/navy_times.png create mode 100644 recipes/icons/nbonline.png create mode 100644 recipes/icons/ncrnext.png create mode 100644 recipes/icons/nejm.png create mode 100644 recipes/icons/nepszabadsag.png create mode 100644 recipes/icons/new_london_day.png create mode 100644 recipes/icons/new_york_review_of_books.png create mode 100644 recipes/icons/new_york_review_of_books_no_sub.png create mode 100644 recipes/icons/news324.png create mode 100644 recipes/icons/news_busters.png create mode 100644 recipes/icons/newsbeast.png create mode 100644 recipes/icons/newsobs.png create mode 100644 recipes/icons/newz_dk.png create mode 100644 recipes/icons/nightflier.png create mode 100644 recipes/icons/nikkei_news.png create mode 100644 recipes/icons/noerrebronordvestbladet_dk.png create mode 100644 recipes/icons/nol.png create mode 100644 recipes/icons/non_leggerlo.png create mode 100644 recipes/icons/nos_nl.png create mode 100644 recipes/icons/novinky.cz.png create mode 100644 recipes/icons/novinky.png create mode 100644 recipes/icons/nrc-nl-epub.png create mode 100644 recipes/icons/nrc_handelsblad.png create mode 100644 recipes/icons/ntv_spor.png create mode 100644 recipes/icons/ntv_tr.png create mode 100644 recipes/icons/nymag.png create mode 100644 recipes/icons/nytimes_cooking.png create mode 100644 recipes/icons/nytimesbook.png create mode 100644 recipes/icons/nzz_folio.png diff --git a/recipes/icons/l_espresso.png b/recipes/icons/l_espresso.png new file mode 100644 index 0000000000000000000000000000000000000000..f716b2ad44a43b9e98c71293e10acd306e568e48 GIT binary patch literal 1071 zcmWmEk29Nh90%~P5R(Lr62F6*kRZs>R7WdFNP;xQQM9CM*KI{lf9YJeF2ZSRs!_XX z7wxT^Svfj7lln2UyUn#TuJgMh&y(kyCu+_*yLLzX=(ES&>-~AZ|ANnTr6Sdd?o9^( zILV~qOtJ%Z;$TZ&aZBw8S+p`~-gy8RyLVzUVZ5>h0EMGW&z3v`U=n~S0Hy($0bmw@ zxgC@_s?&1-=1I29c@EzIfWg^o0e}T(&jseLg}9`Jc##nRBcK><9F4qSV`P*uO^E=2 z06Ic(Mc#5j9NZ8l8}VTwvGK@(1SBB^Ns=MSazv4Z0)PS<3Y<}zFUs^l-TYBO3@T1T zr3y6T5UR*Q^NyJSFwuNXyZlW@3QWh}!vMe>{4hoU#@K`H=3uTI%#9R+v4Sxc7h?%9 z_Ylm3#KkA9n8#!+;1F7zw#CkSBn=y)Y~qhVO&?1Q;QN5wS2L4n_*0AQlP^ zK!FIxh@dbD?n{CPr7%GTMd>g%ALhRc%PyJsCYs~r=G07ch1&eF8sDFazaz#EO7MeH zT&%<;hw+p&JS81Z$-q;{or$L^acLHwbrjDk#Pd($6`xoxt1VZmET3vDRW%mv&BfaK z#d__FFPfGbZ!fjB{nhf_vaWf#N59hX{YrPwioSoPukY`Hp_g+L|4dDp#X`FTMDWAl5MR+PZaZV{2{u%^E?hue`Jp#0EiZ62ulkydem}Y9%%|iS2E2rk%Cz z1i+?CCQi(b9X;U?0z8-61A=0iRJ*IgtZKWBO)J%o^Nme4?LJS!;r!MxlfxaJcWn=d z9Vu-5?8c48+-io`s=QcIp=LeykWZHwdOp-yh1p&A^uMq=hJVay_YP92TUG4h0o&|K zskN71pqGu`>n%4dmKs{eb#d!+0$CeP^iGgsa&gZzos$S?KdK&2I0B#?uQhf>Xh#130q5lFxue1cxKkVTXXeDh#w}YH2(t$(CY30 literal 0 HcmV?d00001 diff --git a/recipes/icons/la_gazetta_del_mezzogiorno.png b/recipes/icons/la_gazetta_del_mezzogiorno.png new file mode 100644 index 0000000000000000000000000000000000000000..2529661adaa425cbbe800bb2d10ac8eba45f5c2c GIT binary patch literal 1739 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 Ef|^ac+W-In literal 0 HcmV?d00001 diff --git a/recipes/icons/la_voce.png b/recipes/icons/la_voce.png new file mode 100644 index 0000000000000000000000000000000000000000..174502abbf26f1f83e91cf53fb0641281429ac51 GIT binary patch literal 1121 zcmV-n1fKheP)IpG3w@f`CM=}{5)>Bt+;?|^J7YsJ|h_jq7UYdB?90{gC zAOs3jQW#@_Kp=^eRBV!^wWTGJPx4`OE*LM-pp_|G%vK|$=2@=pS@ymPG+dZo?z3!7 zbd~Gc;>|MOuKls9R|hiHc)^Pzq(!=2R#C0qNv!RvTvsiDt#6>cU6+P3Vz?F!(L*XC zG>s3c3Y0fXAKbS{*+&6c_H8n<~pH*X%s<8BOz0|Qb) zav}}_kPPCLrswb!w2%-N5-?s{ZlVhJwhRm5UM`;8YnFEOcfp{fBxyr>! zD1?CI|Hn`^ClY~x1Pq~ALy()cHliqw%(obE!I(2ci)d)NwzUJLI1;iXXdoyf1mau7 zujl~8i6EklF?$VA-i-%f(XwfaIWh$CIJbZQ_%|?ThFJsnR3C=dpdfbR=UI<;{Sl9H z2u96Oy=);MdrfdeA}~*+QRCzGXO7H?p{g5xn%A8PC1Sh#xSyYIa}5DYs146i9Sd=g z0weK6JPDMMAS9l(G{g*-H%(HE*2F3WM*@UEYg;lJI5`tCoZL*G+5h*0c?T%vYW@`O1TLc0mR20pfD2E|! z4!TFjZ*u+J+J-GKuHp;hs==8t&vc7!CP&5?giyzQa*$WK0R*dQVn2HsuoR)-M5x8W znGjzU6emkWlgi%LTq2re$m_OpQc`GF)s4Wo(Hc(_(Kr*rTo7Pb6iddKwV$;}ON1fK n6Wa(7h$Hbz28=l}7!3ag;<9MUh1?MH00000NkvXXu0mjf1mXE} literal 0 HcmV?d00001 diff --git a/recipes/icons/lameuse_be.png b/recipes/icons/lameuse_be.png new file mode 100644 index 0000000000000000000000000000000000000000..9d9960fb5226efc01eb62e151babcae6bc3151ab GIT binary patch literal 1304 zcmV+z1?T#SP)_*T8xkxP@=yf}O)-5aDJ@tk_@ZEG1u<1q7krS4v_1qPRuWD9NkM~EBua=W zQS+c+!6HSgY0@RBAf>S-G%;qgJD=y=nf;l~rmm37Wir`0=X~e?93h$?LkNrhNFhwi zwo?6;Guf6a2(L(&UMzPqkvc`6WJGUjmR1jwN-50b0n7Q&aOb4ED%`SgQFqJqBYh$Q zB9pSl*phU^+GBc`RXgHBn_nU$VqzgiCFFldr|(uOuQDsl>=1uRxWV?)114}ZOlBo% zpC-c&%q%k(i!6)I%6(j)!_{lJ_A3gDD3xM()3fX=l4*F% zyHDKIzH1}(A^dserwvP3cE5Dj!n7fif1i)kq~|{y*mpP<;XMa1H;+5ZSiZ}5ZUM;- zh%_+5F7;G0cPz;Q3b~GQz03yZ)^G zjlvS9zr)E>OiwV0EN$?hV82!ITqxEul$MQ03vTadw@NU8oj=l*Zx@xQRP9>u(xOQmw;MzWab$3KrN zbo2yks&YlqRdW$ZH?v$v&%D6TzNhfWL1wnH=z0u2M{w*}yp+f64;73OKgY(kLhTs6 zh^0a?xcUoD9TuAO0+oWl8`p2tjA*E=Q1|Nj{tC<#l`)LERsr2AYK6ulOt%VZQ`}hE z(3Zvdcd<~w<`(Hy*Dc<{JMY2rbj$N%y@&u}t)jl!aWFEvG6jcZZy!#+jMoP-G=kv| z@b)Oq4x#^5^gaP26VYhRnl`&^)a-aYscY9^&!EA=|61FO1XHJ6-AHKGuG_2ax^duy zJW|)))}hAjGSfKJnhl(Uj1M(RCp|Kq)@OaIBeOE}<<1QKk!C}G=hhar?b@#5YkhUBVd&+zx3%TB_kS(FRZ>$_)_2;}X-)bGyy)PlJvV#+ O00001KIqEP)l2HgS%QC|(q zH1F^l&(LaQ1QfCHDrA!ifU9Jd@H2DCM^*`BTeualNciKmK*T4feA<(zFTZ{J{`2S0 zA0Y7a+xPEp-@RYHW-qTo2+#!(l|aj8F4*$n!>1qLL27{D!-tP^mu%;d4kQpnJo3T% zc3Ejf6CXW!@gEGf?LKAeT`H~-#e}L-Oe4xQxa!22Yd|$~7VmHjs1VnPWFi~~qM*QK z6!D(DXdA?WGv{weYDHs>0~O=MJNF*nx&KtvG?_urlR?yn*i;16>=jY_>GM~h1;79N ziOJ{!#v!Jf(z@9|1)URDVl^7C1){#(azR^noq%{^<@y7hGJ)_2fGFUR3*Nc+^p`K+ z9R16&rXK?NfI-+Rr+n(ae}91%y#Mgo0o4>B+dZWE>(_5D4j*MrUEabV~H#cJCY0z;CbvWQK}f7ONq z5KnB~c>}nh@w2dcCNa(@w!&c+_no(N z$A1{O|M0oCbp~>_pwt3jxcf&p0wWn>LEp4BjAB02tTKUN%_|?WWA7=T>7T!Ra|@{= znEXkulYu4{RLuZduxa}-V4Z=d@FCX%5g$dv1Yq5eoZnAm^pf1F0M=IO7O8^7rA{)N kEufS`is_>*yFp1KIqEP)l2HgS%QC|(q zH1F^l&(LaQ1QfCHDrA!ifU9Jd@H2DCM^*`BTeualNciKmK*T4feA<(zFTZ{J{`2S0 zA0Y7a+xPEp-@RYHW-qTo2+#!(l|aj8F4*$n!>1qLL27{D!-tP^mu%;d4kQpnJo3T% zc3Ejf6CXW!@gEGf?LKAeT`H~-#e}L-Oe4xQxa!22Yd|$~7VmHjs1VnPWFi~~qM*QK z6!D(DXdA?WGv{weYDHs>0~O=MJNF*nx&KtvG?_urlR?yn*i;16>=jY_>GM~h1;79N ziOJ{!#v!Jf(z@9|1)URDVl^7C1){#(azR^noq%{^<@y7hGJ)_2fGFUR3*Nc+^p`K+ z9R16&rXK?NfI-+Rr+n(ae}91%y#Mgo0o4>B+dZWE>(_5D4j*MrUEabV~H#cJCY0z;CbvWQK}f7ONq z5KnB~c>}nh@w2dcCNa(@w!&c+_no(N z$A1{O|M0oCbp~>_pwt3jxcf&p0wWn>LEp4BjAB02tTKUN%_|?WWA7=T>7T!Ra|@{= znEXkulYu4{RLuZduxa}-V4Z=d@FCX%5g$dv1Yq5eoZnAm^pf1F0M=IO7O8^7rA{)N kEufS`is_>*yFpO*TY zt1qrH`(SAfF)gPvNhp<6V8Hc)%KL?Txv%H_^qg}MjB_k}_#fCA&v|~&+wXb)BwO(! zVG@!gk#Q)DjDu;4Vkpee6bFZ)Ifk*Zw2ft)EaSGb88#-3WnC;|2R(!oRTVTPB1sWZ zqzEe^AHZOY(o>xUIi5vccV2b|U|@u%r(=;=A{ONpNntSNFy*A_RF2JZ*)!cPhQ^|- zhGGdEj%b8t4WJQ3y31Z)U9x4}%9<60`Pmr`JNP}LEJ-piNRy#xZ~stF_h5K@+D6l9 z98-{=Sy;au(HOo@L=$Z-S7ULj2v~+*UswKm^JV}5vt|XFrr@}owk3-)8&;L|+#c|E z-f@zI-DzhW9Ep+Pqvqsj8sYf>=_!sk_CB}og{D-Oomha_Y;0A{vc>sXgY7*~9|DoH zS%x2Y`4@mY)%pJZU3<1R&=e*XEMUg%O=s)N`Kbs=&HyN?7MEm6Rp$U~9CP56?af;o zsK>EBD-KU8m+F{(RW&UlNHI~K1K75qcJGcxOY4%X`0kF~?z=ZS5#kd()%388r~2f&s8NH}lDgVzc{w10%;<+Q6t{m^Q2%M+OQ5hAhZRFcSOeY{yhE zGVfbKlA^qj5G6fDkWG&}#SC!edhfllNxhv(a9>qP5{|5p2$7Vchyo9Z&?wyNkN^3% zZ_~OeBQx5>RQSei|D~&4P&`pGx@eI%!<-+;$+a#&c#TPsRHKL1GDwchp{eZylx$RvytCCP)L{4BHk@NeJXD15KNqv>4%dG(~( z552WJCo_#kgrvji#DXWqX#*c%e$-YL!2x}kR3CqNY9bh+z*ROmJxo;+WEsp7B-B@j zAqf@wsJeVfL2jlc*GdX==9+svsDhWGgl2}Pd7y$>c=W%zbr1*ws%@!kDbNT)vIYc6 zJZk48k>E{_0OEjJEKFbs0&PWEMqnu}%4Q8%Oiqr~Qw-)2sToN#z!oIHN%#`zqDSDn zJ8ITo7`nPNuczx4BrzbNxS&GHps(9+`UXcPlS9)$C$clsiVM7E3pyo}0K?-!Xjq1( z<_+Q6|p5-M!=kznZr-eE7~DQwjA!0Adt*tnzspkboV}u3f*jE-WFar2AQ*0}E6>ex&+j&lU0q#S@VgK@x%K@VlQLJAAw~6p1a+x*$l$Tdp*}{aIK4eQQcX zk)Z|@qYnd!M^wcdJ-+L={OKt!peWpBA7vU?+U*YJ&KCK* z)0ct5`uCzpa7bee3yv$AwcV|C@S0@z4!Q6!C6V{smKR?nT`U)yfN zx*nbgB?r`x=Q~zcmE>lnCjo$ra6#7dGJAc8r2z0SE-{H=C`(38VJM`z9Pn9IMv8&1 x1%i?B!1UyF6b9M6nBek&A4CA4yeL9>{sA}YM8SK6V^;tG002ovPDHLkV1imz1s?za literal 0 HcmV?d00001 diff --git a/recipes/icons/le_journal.png b/recipes/icons/le_journal.png new file mode 100644 index 0000000000000000000000000000000000000000..a8c49ed9400e76ae2d4c4d8997ac3552cd4dfdfc GIT binary patch literal 1660 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 literal 0 HcmV?d00001 diff --git a/recipes/icons/leduc.png b/recipes/icons/leduc.png new file mode 100644 index 0000000000000000000000000000000000000000..5a40c3a78e159ad1a8dd46309891f104dafd97bf GIT binary patch literal 1076 zcmV-41k3x0P)RYMnuFLA(yFc{0 z-5=HNYAaT>rTrs%X0rEsE!(@U_YQ|)5|Z1V;#4}+fxC27USiH&zT?=6kDzM zEmdBK0&DV6{sVvrZYl=e*Hp1nmsd!VJI+Lm_RsTi*foG9#k#6oRmHk0`ikyVMUUt2 z_^8diGa+C>@16Bj3EuCjVpGyBNghi2NRn^j(0Mhv+8;iR!({`2?XRffjw*hZbVrh3 z1=$tkp~(MMWPv8f+PUM(g{f z0g(nQ8o>Gxp!ZH5iTsJk|KjOWo<5WLGf6I|*n4@o#sTCgAp4VNe~A1ykv~;*u1IH1 zqZ}?*3;1{hur|mr0CoowJu&v!0CZ%E_9gu=^beP-1;7#19VB`6#Uwv4jMl(AWaYjj zn_#NguFJ3UII!(;j9~<~8h%~rE*mXVVB9x_v}bJBc;C?g3Y&m>+o;(OoSyQK^?zEm zfcJ5@J|mWDtrjrC1#V6ish{YHDp!x7N%v*tuOIEaqx`W-ZUq^|P;4la{)PS875xmB~xs)Pl& z?rGP>k}N^YdBx`R6~drEzULG>I^73H;3JgBX@W8Keb0T~K-jj=dFWDtuN2vjg6dPY z(i*NcqSuJ7F}Odz<5YCU>f<4l#Yq~)FzW@5;vfhRvYh7;L-#7zdBa2Rg~7!bf05uz zY4lYZU5K%7Ds`OtL%`59DM?|@VG%`f7<#^MX{2k}_JNgR9SNWf-T)v;NtPFc@S?1Q zJYF{^XHUF&_T1}dUc*7)+LmkEhaaxv0ePB{92itEK2tZRPo17UKI?hj5pIPs0Fq>F z1PmgO3IJJ}9r<267!bvY#>6q?07sEAgwQKq&bAqjv36Kf9IoR+5UA4`7=p*Z_hHMN z@ur!9qMeB9BQG~axnG^SaFPADz3bfpDa+Amb-gNleiOCR5U`~H@K zpfKl*TjpYxLA8axzD46+aT~reBQ6e5w=8ChiH~H<_JhCeKQLJ`=Wh%6Kqm$D?shGu zRC%c<-!%8$b8^nPpL6c#ltfxhsBaW(pK6I>L7ik)$gDx`yW*`)^?fZu#SnT}0BEc!EV9>blQEvvs@X@M;=+e!>#KaA*qg zo-0K>3^t!cCTG-SGLyp>AYPs4J1j8eOn}Co%bXI~TD@0j_L!nc!=VQX^AQ@IwHkm- z`L+Q|=x#@RPO5)@71v}6RcReH6QE(&ReE9Z+HYLI;vze*Z6Scd@gULeXMrJwO>LrRTJ(sPad+Wk|g?eDY`}*$h7mv7Nog)Ectp`c3NtCTYCjpAz z(q#7fu1RBP3O3wA3dI7YuUzR9${lda+9Vb!JtC!BC~MF4QYyEJ5I-~%DZS+gUWWJ> zY+hPLqZe92)4Hy2(~Zn>4nN-PJD+pMlKTE%%@H7PBmNIiv9Zt%sfbkGTn`LRshfCK**ri}IZxxkM~gg@zM%VzGYo{)=we9U zgfj-pRlRUcq0#j_#^YOIhs>dnl1E? zWq6LxDpBL(bmqqNxvuYuF#<|u*5?D52LIB#CP;|mls`U~2a(o&#zg>OjVMJpt-nDRwJh^SPpi+#*AeWlbNGcv2G{eVbAK$S5ya-HFaa5m)x7wF!( zX^((9>!?_>S8j;4?EfS4J}jSUyctX8Mu8>v#28MBwC@Qud)4pWEU)X%sp5ovXogB- zq&AIR=N*A5+Cp9sS)Rnmml#VWhO*SxS|q4Vs|q*p*qh3;q{frB@uckwzs7>XqAXQl zfxFtyu}rigvCbU_itWMIMs{sscl2qINWFNZ-|-_FyiaFwUjtpUd6vKfz5mjZQw~=K z8&1=A?Ing~-oVl9zF``E%qgzQ*NN-<(4#=U+(ccQD6p?hM?i&k&x_S?UT-@Jl+IBK zmG&}2-%K=*fGX?fD;X&q#|#9}^j|MvucICi_xk=D%>) zq0*S#igq}gHoK0i@e6wIg?j7ps^$T%)Idoy!HV)9znqTO8|$R_00000NkvXXu0mjf DG`qhu literal 0 HcmV?d00001 diff --git a/recipes/icons/lenta_ru.png b/recipes/icons/lenta_ru.png new file mode 100644 index 0000000000000000000000000000000000000000..2e25b9ac138f204b25603c86ba430f16e94e20dc GIT binary patch literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk@BpAX3RW*PVQ%R6tFatx`|VNd>D9JI-td6`zxkP-^4M4?b94OrtDpAZ rpyXWzfraM|xYbQ!X5N-KeI|p~ZYRxi>;0AiZDsIu^>bP0l+XkKL;OWb literal 0 HcmV?d00001 diff --git a/recipes/icons/les_echos.png b/recipes/icons/les_echos.png new file mode 100644 index 0000000000000000000000000000000000000000..f5ee26289a5e2d701e263b889a5211987ff50d8e GIT binary patch literal 2191 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 literal 0 HcmV?d00001 diff --git a/recipes/icons/levante.png b/recipes/icons/levante.png new file mode 100644 index 0000000000000000000000000000000000000000..37451323d8ef83580e78bce7e33fa3e28f9af4cb GIT binary patch literal 484 zcmVR_#Owo5wrnRRrMVW%DJJDp|8}J!2p2K*T9HP+sW6!Fak_8 zK=Xy``viEaL91~bS3Q-akK?o7S4ptYna|~pDO5ek=JJ`{Rv;|F7!;`#F2f*qo&q?1mRnj literal 0 HcmV?d00001 diff --git a/recipes/icons/liberatorio_politico.png b/recipes/icons/liberatorio_politico.png new file mode 100644 index 0000000000000000000000000000000000000000..489e2f83f47fa5dee63a70a9898e6528df676535 GIT binary patch literal 445 zcmV;u0Yd(XP)zrfGX-{9}?)=7~V0002+Nkl1D300000NkvXXu0mjfEM?S9 literal 0 HcmV?d00001 diff --git a/recipes/icons/lightspeed_magazine.png b/recipes/icons/lightspeed_magazine.png new file mode 100644 index 0000000000000000000000000000000000000000..8d695519b488e50919500a484055e142b6aaec85 GIT binary patch literal 2056 zcmV+j2>17iP)ulaWX9_^4q5wdUgi>J$L#nEZARYviBuE7m2t}QUDnJl0fc1<=5&#KR0M<-M z2`H$PL=^;KNFPWM7HR;ZvOh^G;GiXyqLWFdld1)U00EIPOj&rOPk80i*j(P7aMBA? z@O?zV0UMTPi<|EuMS7LQNQD+v@=gYwJK}WEvq)F@*{q*P6mz+7d@P;IHxW(NRFM{H zjIqZ;cQ8m@dHwa5ufEv#eE=%*>HdRHKmK@j;8?R!LmC>8s+t!$<>XK(2mQp{w$>l7 zxqRu_^Q%1s#3ano(cIR~58inB{?!VeNTKpWC>h?){gLp8x>Qx(lzo z^T|!!n_g)3P1^GeAfg445s?F-bXV1WZ{1Bly!65u{rj|MuZ*37D>0E`U4tw*|09># z_@}@0z5V72kJGGY3!+t-OqCVayBnnf0MIk%E>EY^d4;2as){<5ykbDK%TNB`=5@DS zyu^R|*>Io_B7h=#2D;Jp(FNzWZMbv&U4_D)_c~e(jX)#J=_?`&6b6w`L@iZyKu)zM zlm+eWH+*Gz(D8x{^}-9`3o4GjKqtaI?=<&y-n#C_;hL#^p_mv~1Nh2{c^WfERaIbE zQj9ts@`J0c{Q9*1Ph{*UeW9-mU}|f;nX^nL$sqDAx&0mOv#QQszM?x6Q>LKOR|G%+ z`U<0!3{;_H2uzY*b#2Rn*?N30tVD~wnT^FYH}|c)(Oi5TGpXn!!m-TIfn^sg8wSb& z3@XP*pzuzUAEYS67zd=Y+38bKS{$w{_7<+#^l|Ua_dd8k)9lrs74Kl389&xIb4JPf zHmW2N2$V*mf;uBWGzTagKtmePR|FEzC4!Sf!@G9vI({m*Fq38w;|&y22_Jr7%NZzr z)o9KrjYbtrf{4unC&qKb6UByXRttksdeZ;VH&%c9ovp3=5B08Eb%mXMK2$-v>c*bU zyG^NB2>|#?#;l1jh!BXB#S%%w;`r#kBLn9(H0xM*G%Z7W7u0n;aL@YZUwY%7r@E4f zAkwUnQy+G`_S!60DhG&Y$~Xh{Vr_$}3P`m;hDc6My#98_6}PNS^l#VvC;*@+-Tz+L zICA@UuK4rc-+A(>XYwZpr+cQFXeJtbV+dsoGAb-$jDZp$qx6u4&LX#N+463WGYh`T zq#tv}D@p%*(+@rU)9aRQxbN3rnm<2KX*QVaaT;k5i>uO?vXy@kScVt|NRu-?CI*f_ z{QJj0(Isa7r6%dqSNcNhbZmd}^C!N$r1;9ipd&>HAG!=s&@zqs#_9pMU|bFIl_P3$NP2YK+XtEcU~|GvBJqQ#$wIx1>ZRim>8 zTDEd|Xq<?~Rb(IhRbR?7CdC97_Eib+p)09yiRDQh&07YO-G?J^l;L& ze)*~k@7egTyMFV>NAJIFUd=0&vht1rmuF zbqSt)6@~ujWGO${III5I=brD}{jGT|O)Ts~tp5|4lxKs$#+N!3zQzHlp5>}l7Xd8e z3;=~Lx0pS6t0*d^{ZFA->{oVJI#hp0$DgClsW*WQuhIMT8OsNo! z&+Bwzo0;4m;J*g#w6kXX^2Jy8UNE;MF}Yne4*mNhO3AM&QxQ=7N^3TVI({ zESNILir|bJ&3ClL|1sF2s@uGI%O|}JsBLFp%8N_zCh3>r*TACo(rvrBg0XDyOJFd#tqRjcwzP{o$dZ z9i7XpnM&}glz)KUyp^jKEk3v0*!}+@A|H0{9QdHK9&$_|GwK(&wzamkG&MEU)=n82 z8SCxsfB(H5yE-~*MuxuZvEH`{6|Tz!!=n9)anMDwWQ?N1s7fmf3$0dd^q4VbLNOpR zAUbNS^E?`(;(X_7MH(&Zjj1w703K=U466+5!J>$O1Zb!VrD}9#GVUEMkRXzP0j8+d z7}KOEKpjF2l_~*|_PvZ}l7<$DIwG6$OpLO?(vvstMJ! ms1Tr06PLdwe8QsAM&m#9-uXHz<#H|n0000Q}piu literal 0 HcmV?d00001 diff --git a/recipes/icons/limba_sarda.png b/recipes/icons/limba_sarda.png new file mode 100644 index 0000000000000000000000000000000000000000..b079189a2f0f129e1dccb1e711268a4da470907e GIT binary patch literal 593 zcmV-X0zUESwD$`S%aako(uJKp6l2{i9kC9bdV8X-Q$J zxIi2OgE|Al?&%YOf`5Mff?6_k@Z24jm3-U|3R^-EOQsKQ-p~#&z?O z<3RrV^9O1{ZEYoxtgNhziHY|1_Lw+vLQG8L;loEZZrl(a9s-oAuCDs{@%@PtC(_bV zFI~DAA0Ibw-n>0~cb++QIwLcE@3k9C1Ke+3xx9PYlrtOGUO#*whJgVk4iXaLe*XMb zQc_e=QSRViKX>k&tgQ426Z&&=voti+feQBR+nnw6F5;o;iZ*#We5%a*MzEzL7# zOt-hTte-k{TX|t314AhjQ zKj-r03vb`PxqSH&P{E27OINL0`Rv)#_wU~yJ$mHbyEn&<9h)*`(t`*0U%!0*@a?;Y z=g(~E?ryWtfBEDw4fDaXJGY=|?DsDe3x0#ZUu1yB|Ba~vL(M;Uf<{WX)X&MIh25wH fqZW)>Kso>b)$L}-fbfn<00000NkvXXu0mjf4*ne) literal 0 HcmV?d00001 diff --git a/recipes/icons/linux_news_de.png b/recipes/icons/linux_news_de.png new file mode 100644 index 0000000000000000000000000000000000000000..3fdb7acb87aac17de7d55e9acb7dccf35ed4029d GIT binary patch literal 864 zcmV-m1E2hfP)Pb=k-6|`OZ22Ygy{kzIIX%P!4v0 zIQR){pL_#1!B#K|ge<2I3c?`9fs+9YY|;U)gN2~LY4DBUJu7$ecLO*Lj(X$_xDDEs zkk_E*uLy>HDQIJbAK)063raw5sq7J%*dP!E=NJVU<0bnKiGVTSmKuC7D9*Z89zN0+ zC;1#HH5YQ9s0^h-LFu*9U0F9sp%u>%MfM$*v zx0vxZBT=L(#<(42A4eSo^pY8<=q=Jw!0!mdoJfcI-}AxX9?zI2OKp$K9t6~>_9s9; zRX&G1OGL!f^I~pIY10yffE4S!0kc&OMHm?=tI3ss!wh8y>mE`GH;=Q=sen4;xP1$z zGuwR&SkBpf01I3Skc2+KGT#E0fUoSNy9hKhv*q^-Q0Ya$B~a*b3F^Tr3YMhY%>&Gp zfFzUXq7MObypG@`;e%`2!?fX=00E;){{h$R@yAhxq);8rUeG5qP$lYYH8Qd`iux&Y$(002GZ93Nes^T)`lsdUv zOzbQI#4GG%orG~4mePBq4XUNg!81^%Dpt}fwsnITn*FA0!vBff5sxy4hIhmi(5el| zN&b;@f1SEcWi5xZ!PG981_nAGN8~v{(*r#uW@rtFg`elA^sAvr^X88?r1%Upm@- qyk6rxR2n@XNAd-*hc78%bMQYR{WEc4DfOxV00006ocUa literal 0 HcmV?d00001 diff --git a/recipes/icons/live_law.png b/recipes/icons/live_law.png new file mode 100644 index 0000000000000000000000000000000000000000..01bfc1adbbac37228c26cdac89cb539a28b10ca1 GIT binary patch literal 1122 zcmV-o1fBbdP)jwQBcmosFJ(&CO#2z0gM8ceUe8q?*Ro*+ zvh-Toyhqcjur4RW^#u1HeQQyY? zgM3}AJo4g#(~>oSL4Do~gFb88qIFf&cckR;9sCJ6I00}Yz{Ln~R2v;cfUt;QQ#5wo z$ho(?o>&Rff7f2dsh|9fU;QE2N5S}DssBqb_S6)fnlk{p~?YOeEg3YiIGE@12$_$Mk zpVjyx(evS>&}3_zsZ7(f)88KDD&>?Jb2)Lt&{++0(&bfVEUAUEJ8fW^HZFal+rkmDN?OnM|hDYR;Ui)hgC}(QbA+ zk#n!z?nwQ?u-R@ycP?BYgm|70 literal 0 HcmV?d00001 diff --git a/recipes/icons/livescience.png b/recipes/icons/livescience.png new file mode 100644 index 0000000000000000000000000000000000000000..6e0d767b697bfa6c747a38374d49c97d5025ff24 GIT binary patch literal 1050 zcmV+#1m*jQP)p#FR}kvM5M&;tYevB`&Bz+lNL& zW5sGf$AN$wZ9!$u`R<+G`^})EU>I~vn)v19=4S49@9#VRbM83<&d>b77X@I<$;fTK z1~4aQeeiq(=sAdsX+t?(&EXw3=i8r{ZzN*PR~+RbSO9n!FdyjBM$UWIq;qUw0LB92s))D^E|3C zplPn~hAL-q+l>KB)Y<*&=Ofi_z&~x`^T`?~kCp(HA#EO74Ol$`3&rX(-ubM508=#) zX$SzLqf*rDaq<^-Aw5}R-2n5~RI@+`MKy79tc^$EO~qJ89&r1wy|e2b0Fu-(r_d+> z>vD460f<8dz!Ht7udsBO40Vwja4|Q{rL!qm7C@GGcTRr@Kq??RzeE61vwm~~I@+)p z=u9G7=VMhU9`UDZ+3fL6-hPKYI8Sw3u~dUbe>^sZ%!~Vx$OYI@Bt1xb19;Nt$OSw; zQDQ_$9r%70h>f?(WWcS7-T_pJR;U1(>m5yWQm0(pOqK-R-TCHFu6Rh%)zSTn03;6p zkg{#J8Wk=+SP>I-bREf`-W4Hr7&TNC#+ok4*UEy>IG8M6dQcz%-|RWq_q7n#-r(4) z2DuP~Zdf%FPlRD*Fj>5d!4HRu0fr}M?qmu+Y_Z!~S|7CvYq{O{mpj=6gFD%Si*$Jt zR!qhcKuaW6j`KFi+xHg$Ooy;|fJp#B0OQngf^aM6K#}MBimTlHc^u0$SRUZ5MBDe} z156EBn65Fb8Wp?7Wh3L(j)+QyA+z)=zy0Ixz4vREt-d=Cs5Z$fz@|NU0HL8vG9Y}p zYDsd8NCsUBOpOKz7OOBbI&ni*OG`_+uK5*r|9F-u0Qg0LB9h0{H~Sq<{bX?|)^t{KTFt z_xChkdt3XW^_!;|(u$9oJnr?U|EuuS!B;Wp^ASnxZh7j=_W)<_cJ(;`&i@|q UUKGjX8UO$Q07*qoM6N<$f_%~9r2qf` literal 0 HcmV?d00001 diff --git a/recipes/icons/living_stones.png b/recipes/icons/living_stones.png new file mode 100644 index 0000000000000000000000000000000000000000..06734c4d73ba86401c03159b1d3fff6cd3b17a23 GIT binary patch literal 1368 zcmZ`(X*3&X6#Y~zu`|Td45^N)Jqc>5rPPuzs5C)EwYC&RVwsfCv6X3Q&^klx)djUS zbWvMr(5PB!?P578gKApT(pY*-{G1~MTW z96(f<-=k@?4L(6G*|^vMz|0ok^AkES128xj0BFYmNV*2V4qr-I0^koM0LvEvFlPXu zNGWb{x8NJX{?3keVE>@*v{&Zx6(|LBE*b#+qX!U(ePB)Hiy|>t7kiO;sI zHh-xj*3Jg+#VVO3UMCQgL{?|bqT#Pi=;}>~)?pznZL&Sy$xOs9N2W!_#^9DaWLj11 z25q$*;Veqaq_Im|a=Hp^S#n0&+S>AJzeV0XE>|?-wD{(m-_`!6!BWF}b16MD@pbJl z_V-(BS9YnaB-BVmezv4Ud1kd2nZtc&ot1dm-Tt3|%Zq+C$z;x4`Nt%Os{1O4 zq4>oSl}=vMqWW9Ro6POz&O58qLy{D{hq^3dB_@mF@JH57+R{8%quDB$yJqw|9!UmW z5pBvJJQ>&9bsuHyMlk=>FYV5bHJUiA;p@?iI<;H7efpgoYzY=$Y1p_@gqnTzlUGB* zsv{+N4s8_S&Qu??tP{6O1uoT7(;|-MrGv8TKe;cg)LaEU#%C@=c+lF;lbjj2jwaMd zFyXnpDFK*d{HzeDtQbZMF8qk{IgXi1c1e5Fk^k4B1k+N(cPMzbn_q{G_oCsxxD;ol zaI+fW&Vf$B=hGWWlnac9#Ad;#b8@;bqnUZRI9J=ENI}?~iVvO0d_sB-XB=LSZUP_9 zFqrV|VCW;=s^nRwSoT~zT=i>A)-?qx$9L)+J5+dWygY0?gRa5Ze?2xeTm@xUD0a58 zgjmht_oq7$q6wq29gGKz|`bzMz7nfOir8mlO| z!p!xeNW&EgQKPlrE!;kNe!khjdeRL2xN(R)T=iWxq;Q&TyK)=HHGg9uZ<^fj2xibr z3%TW>wbYCsDli+DLI)}Kuc~ed71TA`uPjB3T)6FA>~MBDu-7&B)Xd8Act$aHs0%km z&))bn=a%3pl10uUjV4AHLZS#^PMS-=x2A~DKvhDR5IKmge*$;~F}`jJ;=610V-Y23 z`D+?8IeYD*`<$-`$57WiST{P^QfUZ6O)rBjyQw@Nv2ZV*wO4#bc4UP-9z8hCS#ob2 z&G=Zh^fhIosa$Ot+W7DS?qg42T~mqt<`I(MD9~&B=;*Fawt)7py zBq9y_&wqhwvAuEVzLaAmRR4ENJe!P_97v@VJg)Y%iUs17fE;#YRK-yur+G|`OE9t+ zP_K$IKxedP=_J}VkvNMuT2`-1fxHm?xR4qlaF91V7M_t~QULi(1qe)9bTz0Yk1 zxjU&=t8ZO3WtAw8qwK8p2v2^cy>H;IRr{0D(l=6e{2uCa)gZ2Qdr~|d{rKMEMNg-J z?kRx|RhA+SLC($a*-rf^VwKhjnwRg3vx4oG1FB%47+yDRo literal 0 HcmV?d00001 diff --git a/recipes/icons/los_danieles.png b/recipes/icons/los_danieles.png new file mode 100644 index 0000000000000000000000000000000000000000..4c3dd25728dc6d97a901f6ee59e5263749f3c245 GIT binary patch literal 1409 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 literal 0 HcmV?d00001 diff --git a/recipes/icons/ludwig_mises.png b/recipes/icons/ludwig_mises.png new file mode 100644 index 0000000000000000000000000000000000000000..fc3d77af188b50916e0a344f5bf5db2ef3c0191c GIT binary patch literal 2356 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 LNkvXXu0mjfQOxv# literal 0 HcmV?d00001 diff --git a/recipes/icons/lwn_free.png b/recipes/icons/lwn_free.png new file mode 100644 index 0000000000000000000000000000000000000000..a189eeb9bfc03d103e055f05baba53b166c70802 GIT binary patch literal 1852 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 literal 0 HcmV?d00001 diff --git a/recipes/icons/maharashtra_times.png b/recipes/icons/maharashtra_times.png new file mode 100644 index 0000000000000000000000000000000000000000..3218c21cae2838f86b460acb837459e761e24cfe GIT binary patch literal 766 zcmV*Nm{kqCed?qOI0Y1$)w;S;m44i%$)C>Igc5Hdc~tv3-CVzRJ|h2`Ri3Z z8T9y&jz?M^AOA&K7t-^H@zn;XSP_}Mrv^e}lI)aZha|gZWd79>9G%i%8joN*2)?@_ z@{`HbiEBX|+2H`SMMRzMizCv{`af-hAqWnKSSth1>6;VN$y{V1Cna+E&F`ghrSNqJ zeiF?rtb}9B?=l-;2PHn{;k1|0?8*Kh>aGR&T?3=5mjaYQusY)e(En(bG76eL zf8BGShGe_E@had$uDEZ<9jW7o9Wes9`tS`h2mr1dKyxD9Bf>2v8PHsS^!xG4FX#@z zIv!UBfbdOjbly3a{tR9gp$0l(uj@Mm)c*X2J=!<`cS4uvf-P)w#4~GK;AM5=o7I`H zRW;&D-m%XI-)0&FkWOtI2<5;0IK1&Im5KtuU}$T5cehk7SG{cSl;m7dUfEN6xd1se z2*5qciEzzWScdC?0n_wpbrV_qxG=0p^#$PHVW)y3!!}hjK8g`1V_D)Qnp%Suf*Y6d z(LmC=6t^i`ooAEhmOc7xI(=qnN@e!IrUJHx^Q-jDm;+$odKjQPUC>mBZT6t;?+ozx z=L#c$yN(!Hc2^!K0~|ErPy_y+kR%ygADu<|rUG!OO)!Si*{i&qQq3+2}pX7Pe<%>V!Z07*qoM6N<$f;hujiU0rr literal 0 HcmV?d00001 diff --git a/recipes/icons/mainichi_en.png b/recipes/icons/mainichi_en.png new file mode 100644 index 0000000000000000000000000000000000000000..95bbfb0b84215a59cade3d33c9d5ddacbf9d6a04 GIT binary patch literal 2214 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 N002ovPDHLkV1l`lgD(I8 literal 0 HcmV?d00001 diff --git a/recipes/icons/mandidner.png b/recipes/icons/mandidner.png new file mode 100644 index 0000000000000000000000000000000000000000..edfd965347ea93d0522b60dddfa305e74ac5a435 GIT binary patch literal 594 zcmV-Y0fX)S*ACR3pVlsx?h%ZHw-P zcKyv=`aoGnBcY0Dh5kHMpn~;nx|=)o3&Z4y@);yF?ezJ9#x1SaS=pd7uTleOB~Y*_ z1!TvPIxV2xoWd+UoPKYSLX+uLCp@=uC*kwQ&kS z$1Vk0(54$?Ck7Pc=V6#yq`szEcX_=I5E-cQ5cUK_8fd|4m<1E_RUumH;t>{vIe{$T z;bfRr47FfoqmHQtxfU>tT0nmb>-L*j@~%fT+Aia{#Xf;0RKFpbKJE gIj~M21;Z2o095(j874)TlmGw#07*qoM6N<$g1y zsYN|#J2-%VfNMNBL_|b3Gc&tLKfXynuCA_qKRPxxHlUiCi9kC|O-)NTHK3rNn?pQ5 zKR=#CJkHL}szp6QLqp}|<*i0OY&!bw0tH8jOaKtnb(c6D`6H#MZ7 zptZHNQaCnwd3lqLjZ{=rqC`EXr>Af{I7UWBnVFeMN=m~?K!QIyu&}T}H8jRbKsY!! zqN1X?xw(CPeL+D%VmddbMLp2a(6vWCtE;PYJvnc0Z!znnwr$B*GXB6;^ zRjWFlagyQnrL$Ya0KgX5em@j>24-RNnaa&uwHp$0nsay(%2a2Z-+rAs54K6LHNq?m z*udRBegnNN!HI3cpTH{znw78*_F~w-0&W560-P&hnA^P_Y3@%AcVZ)jsXCOi0%-nU0Bi@&E0=)hn1G3cyds&cgrz002ovPDHLkV1g=OmQDZw literal 0 HcmV?d00001 diff --git a/recipes/icons/marketing_magazine.png b/recipes/icons/marketing_magazine.png new file mode 100644 index 0000000000000000000000000000000000000000..97764e03bb6746556cbd9e2a11503f05f57a09e5 GIT binary patch literal 2519 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 literal 0 HcmV?d00001 diff --git a/recipes/icons/matichon.png b/recipes/icons/matichon.png new file mode 100644 index 0000000000000000000000000000000000000000..b7bee6ed4fa3f5becdd29324f26c700e3c6f81d4 GIT binary patch literal 888 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 QK>z>%07*qoM6N<$f^{sH>Hq)$ literal 0 HcmV?d00001 diff --git a/recipes/icons/mens_day_out.png b/recipes/icons/mens_day_out.png new file mode 100644 index 0000000000000000000000000000000000000000..f0ea881dd4c356bc3d1b4c59e740426398bd119a GIT binary patch literal 2081 zcmV++2;TRJP)mVT^o2)5OnPa6jX(OhNXoF^%%SZlD52m=QL1BU=y ztS+i%ohu-Wy3PuQlc9DA&$!_P2q-nwfP-R?prNRt>Sh)=7-Vn=#{~og6dVUQTDFpB zG6)G;-~kQ-MhPWgpadjn5Gd53fPxTcLK=ZM61cld$^`8r1MB|uwqiTSrTm&wb!3m$- z*FA6Qq?vuAzj)H;zBYB@%rPh3edBesk5jZT#{w6uNpTQMRDwVSWeY1X!KrHuK-`-R z&_DvW(5fnm_TZ8Qb0>`&cKYOT3&(cM>^NcJ*wHUMvJ$B{W=wVByPP%gGdF8-i zP{DTn=Y#*QS-IyA4>PzQZ-Y=A-}}VsosX?N_~Bc%G!tY9sfntA1pyEc0A3Fs-S+OA zcl`8<#pAn{jO&~=>V)Z?!!P>wS%d_d@n|02vFffDmoKf~-$-*G(el09UjN|__S|_J z2e%lNf(5t{seyt6hf6Lud-z5wyAQ_rHDP z`b)Mxb`L|lsXpDh{_!nWU*eD4Mcxy%UBDm!X<$S$6#@*vrELE5)eZCdIv4ehnlj`f_W7$W zI4^S9P6a4MV(8EZ{VN!FcmD&w{MQXX`Eb=841Kia#*2?UbQAUeXziyBCFYcPgMc6) zB_S-T0+pX%wS0bG?}FYj(@q-xt$A~g?b}ftL9J_w)y!-II?B2d}?Gvl7xOBtqw++0tk>+u#3K7gPYZw>=6h#Y7 z)a8m>Zl2lGHKSwH?5@!lEMEM{=FLL~4uY4O6@`ju(>jbckwFgcq&`M{jCzoI2r1A+ zs@%k&aBF18zzJ|d*RB2Il&+5X6UWW(>z&uzb6Rijl#UKeaZ{p3g&ZVEP>~0THuZ7p z<5)p8=&4YvE6E8;D4=0rVBrxKQa|W`1AqjL{v7 zPnmjr_byy>u9Ino1TC=y88WVT?K$HaGL%HyX*m^?&@fOUaL`n@qS z7mV#YWyJ6q-D93#vxZa=3WP%tfhYm(qB2OP+0DQpAb>zJrZg(ahG$KIf)LU?uy^js z6Xs7GH?ymAPS2S66DPdU-;X3hJtq{DsK9Xra3=x)&}^Oz0|f=4VWEK=9;jO7QWELL zA1$9grfb2ZiL?89r*@1w_lz^1c<8}>J3oq)qOAvZ?0e_6O)ow3;)droyt4k~m-;t6 z_sp~F``5kq_NGI7_fusQ1TrKVfoDw?YN3IGXw|yz%FCzq^voL9H)q`V={-GDd%72& zHv8HiU$N$QtF~_b@W>|zC=3HbQkIm%)lI;0#~gK%l0m!yWR<|Procf7eRbXQ*DPCh z{biRu_={g{e(kTV0|%^VXf{3utzztO-P*6g!jAhI*Z2+nope{+WSOzB) zb3|xYlqD*Jf`WqsA}L8NsA9F}R&J$`Og21FP|)zKQZ)!tl7IjMgNOi6tpalt%*hgx z-PJ;q)PfprxZ;W{uDF8Y3W^zSxZ#F32s9gDU|>)u5fK6$vSAQ_2QFyffg7GfN~x+; zt6M-8t0Y&VwTgrr7$`Jw2*NQ)Mr{}bxQ15Zwc-${s-T90yDJ_91UM*CW*hO00000 LNkvXXu0mjfJ?qNp literal 0 HcmV?d00001 diff --git a/recipes/icons/military_times.png b/recipes/icons/military_times.png new file mode 100644 index 0000000000000000000000000000000000000000..326ebb450d8d24427d57f17953f9dea3eb0180c3 GIT binary patch literal 1194 zcmV;b1XcTqP) zQYkGB2}x*a6Pl0%eUnnFGqkR!LF>$T8qaRQp);bWc;g4y0TI0MMnnW*-Pr{hQ9RtJ z7bYLx32`Cy8Jn?(>?4-V9%rD-@|x5{rn|7*OgJ>80W?Q>Dr<`JhN;AW1@0 z^_L6YpxdqZg^1`akj0-RG0L*6;0t;^1{f^ErT`AV7&Y})h$UJ>K&=VtaaE2PTAL=n zA?n+JQfp|P><#gq)arfTdJ@ z3uy&Lr|d5^v{r~kP*r-BED-<=s#N9klCbffWjU`$M_QU(6(ViAQkNg-@Om^zHnmn$ zZzoDUb2kxR0E$$SPkxd7&m;Lgq7W)D6T14^)Z28qCaJvv`32-!6r|AXR{RyF-W(zw zvRDG`L~9O{_g;_cqHMBITxfPlV#I0&!=x097(&dk=nIF*JIlA4Yzw9~9Q&Uq12M0SlJ0~Y6i;IhAXJ^yX(^XYfTU%S}>+7qls|dZcw6w9Yv9`8W zU0rRn**K0xPLI6F;6hqcQ`61OO>b{+ad9zHSy)(r9UdM&Jw07sUY?nmIX^!~xXa7S z@$vEd4Nx#QHwSPMz&-w{z>Tb|ECBF8Pft%Y8ij+$!0YJfI668)#L>~wi;Iih-CbY? z2M3YU(9qDx$jJQs{MFSJY(_=~D)B8qV`C#q)ZN_;07B2s&cb$ebpbFrIXN*gfmHhX z`i_r}x3{-Zz5f3G&CSh~m6gN8L%9|1VfH1re2kt7LiFDokxhr_VA#9}ZQG)=R- zyd2$z)6&us2m~OR5E75aAqeZ={~v&XI>KVe;5Zx(SeMI{mzU>A9;qtBfmqBB^ulIl zW@5vm)qmuNgMDoBQ=D?XwkgN@5Z|rb``?KE^wWO={67GH11^(13r)-S^8f$<07*qo IM6N<$f~zMmdH?_b literal 0 HcmV?d00001 diff --git a/recipes/icons/mit_technology_review.png b/recipes/icons/mit_technology_review.png new file mode 100644 index 0000000000000000000000000000000000000000..b80ffc1404034f238beefbe8c4dbaa49b98ae374 GIT binary patch literal 1014 zcmdtg`%9Bi7zglk-mP9Q8DdeSvn-<9v=)hTV08-331wq@(^_t=rgLUabDAsLd|gHL zLnSD7^Rlj5UZ(EiEyE&0V$QR=WobSW=;lT6oT$W^KMn%L% z005%0#OYE1ASodT@4x&Dlq8d%w*=4A==LG3X%ri6Q5}kaKXjf8G<9d&1?$;}h6D;c;WaJK^zS z?PO(XFODWPsD!D)B|HYX`vvQ)+W|P zqre_L`;giQ<)-INAWB%b6a_N%tR_wziH~zm!`Se(3-L7%&vF?ZXO~ffFg9#0-M*zR z-ZF&Fuzu<%4C#|;{KYC_8_D;LH7>YGr&ds2t^vq4~Cz)*h{)Vfo`0?ANa;9ix;pnepJLXsa c|2hqSIoF=L+DcyfXXYEr%E(Ud-mNnK0~OX}(f|Me literal 0 HcmV?d00001 diff --git a/recipes/icons/mobilenations.png b/recipes/icons/mobilenations.png new file mode 100644 index 0000000000000000000000000000000000000000..7ae3c71f860b233d6a4b66ba630a0fe6ab5f2bcd GIT binary patch literal 395 zcmV;60d)R}P)!Zy}$VxDCw*mn)<8DB4q&*vGwc3tjD z9!)tX&gNK##GUordE$@l3}3s>ZMA&3g94=O2(?P)P)t-s00030 z|NsC0|NsC0|NsC0|NsC0|NsC0|Ns9cCMG8*Cn+f@Dk>^0EG#W8EiNuDFE1}KF)=bS zGBYzXH8nLiH#ayqI5{~vJv}`?KR-f3LPJACL_|bKM@L9VNKH*mPft%!P*71(QB+h^ zRaI41R#sP6S6EnBSy@?HT3TCMTU}jUUS3{bUteHgU}0flV`F1vWMpM!WoKt+YHDh2 zZEbFDZg6mLadB~Sa&mNZba{DsfPjF4f`WvEgocKOi;IhljEs(sj*^m+nwpxRprE9r zq^72(r>Cc>si~@}s;jH3tgNi9t*x%EuCTDMv9YnTva++Yv$eIgy}iA^zrVu5!pO+T z%gf8n&CSrz(Ae16-QC^a-{0Wi;O6G$=jZ3@>gw$5?CtIC@bK{R^78ZZ^Yrxe_V)Jp z`1t$#`~Cg>{{H^||Nns9+=&1H010$bPE!B^2ue(-vb5}0evpCy00BcuL_t(I%axN? zU&25T#19N16BHX(5EWFgpkf2;6)P$zb`b1^{r<0RL>}NI(RtX+UjBEN-0wyRVN+C% zev#%>Y=UKXP+d9fJm2I!6E>lcsTAQf2UMY%1DYTU{S|mgu?m&JQii+7Dxf&k3@lfG zYrs8mSF=SzN6XWXkLid?->?|$!gkpf8l#w&fW+t)dOp7GrZ0(z1jND*O*k{$d*{v` z#w1`qL`xVpT&neXzwD}jxe(m~H~f!-D|CQ>1k8>QMGz;t(sOi(ehG;3qFJ~Kv@k<- z1iu7KGVs}l^?TO^(J{OdFh0a(7oJsriRcvF5)ck@dGi2CerC`i0f7OdZlL{@j1}B9 zK-vd*)5a;UaTjj`dQYl={5dPnCR^lkX>_^&T^JL<^3tm2fNC8a%jj8#m*d+|C;qAt Z|1a%yb8BPwxAy=5002ovPDHLkV1oHhXFvb| literal 0 HcmV?d00001 diff --git a/recipes/icons/mondedurable.png b/recipes/icons/mondedurable.png new file mode 100644 index 0000000000000000000000000000000000000000..09fd91115d77d20f171db0ab6d759d0940f47eb9 GIT binary patch literal 2005 zcmZ{kc~p~E8pgjW?&4CVh@wUjkwLNv0iq&MmLRJTMPv;mfv_)OM+QYiMFms}7NHi9 zr7%`-Y89oyv7)pBN+6JhBxX-Y!XjjW5az=(=Zw>H=8yNj=e_s$KF_`9o_nwQ`+69z z-?<(D07FlVYXF!b%dvJPIJ!1o>jjfRjI)n30CbdX(BW2r^;hWu9xlMc0aNKR8I1|> z0f5|t08n@d0OrA|LJ0t5zyUxO2>|G705DFu*LCU`0IZ<-dt=>(A^V5-?xXJBJG=)n za`4c{Xo9@9PTA6`Z2d`5-yq1y9EBlhmR6FIE7F@~qfq!qJVEhez2b-aoRCn4lk?=m zpBHCmmzI|F3k$Qqzh<8f8Zk4Udi;2CPQ?icn`~?2#gOReW3#=zdYx`zVq#t_)@!v| zE|(J&EG@k*F1$cB-7{u`Qqx9NL)3}tyQ2I8rmH){(TNuuH}|f8c%K&?zz#f3``clfmG$hKxAS~HzjJ?i0*Kde)PDO!3Xm~eds-sgk zH8t)ZpnUK^uhwuQa3dC$k}FqrO6AhxqH17p+{c%nl*~^|VmP}9vvbrm+Sn0W&biQ; z=P&g03!f-G?0~>ATRTNV<78XA^u|r`rDEOWBq#WsAf2rGFr;O(xuIeF`{Bb$j;p>)F@6 zA92wv%t^`B(vJk%&c)ge|}O5H#|air&3mNOJ4VbUOTTI8xy3aG2Pr*o+n4) z2xUvlxZf$p31`*&_wyo=@4tvO&8|m zaYMtI?ie01hT(XE8xhG3ix~Iy=LCloD6^T%Pb139!_{~Lt-1^>)}>&A)4`1S9PWMb zj^G=J_jGl}?l9?pSrT$1b;oM1Sp6G*duQ$zldalL4`kU^G-0x`_WE_+mWu9Q<=sK5 zKiGMb-D=T^sakS|b{or1Z|uGu9>pbMD|%aYj@f+cUgmxGF@6r)lTG;Z*71@HHPZuk zt?YpQ+oL5<^0gZdaYM?Bbq1~LANy~0D*eW6v9xL}#Ou2N3?!IwvEQH)M~48yx3`2= z*mmq)#|x(+r;CF7jZy1b2X?ML_xPLm(bN{SjMl~h;6EDDTRK*}hofIvV_e}iHnza< z*DIZBK2V;my6@)oByN25(39QFtSybVZ|da-3eQO1K}~>d^h!A`Y~xUDS)F`@=~RDD zZU)$N!D<)h*Id1jShzOIKBc^8qp1AsSCPESo6N#qHS9NPdVNJx(XgV=;Lq$$VhLh3 zD$qPtZja<>)*%tc;)Vp4fz7F3AR!*5pX7pq-vB8fb}#3)kgCTS(>=8>|}< zrCqRa7nOH;u2*+6G-TkneP>_ttc;}CZ9x@nqps~?b=zwMyso)jPZ67z(h~OD1^q6w z4*At^Zi-$fA52(bXSCAJDy!$F?kH2v=(Z@Bbj)NA%GMY>d0JEwupQ+wgQJ)&mi^Je zxA3j2pRgJ9ws}+=_AmF2-cCX2J8f7c^uVRu%VCFeEvxa+#;*M07rBbu>@iW?&Qv}z z>I(l%r%fzo=E*9%?)0igsvG-L7iB?JR8rP{=Z+1G=70aztHD>HL*xf!Y z`$b(7n9NxD(sS5fn9rVQbS!}s6H5jV4#>?wS~18Nf1&dqCWwS5$1Wq(_I+zX#N-Rahm=Sl z=cEvT&%1)9B*mQQJmdQp9Um-60SFsA6w(2SL^!-FIQ{}eHh-ZLNXjJua2J<5^^S6I z>C6{Prvp3zA}N6Y9v_yNL{3PKfe~?;r0C?Nbl86{21djmaj><;M-v=~c!UF3;B2Do kk05b)B!U=a>i~y>9Z+z8RqNk2f)0SEo3HCbm&l9%28*)NumAu6 literal 0 HcmV?d00001 diff --git a/recipes/icons/moneycontrol.png b/recipes/icons/moneycontrol.png new file mode 100644 index 0000000000000000000000000000000000000000..e0ba36a8c588382ebf7d779ee712d502d46bc202 GIT binary patch literal 1532 zcmV$L_t&-8Lh$HkK9!q$MN^)d(NDh z*`4fcyIl$`m6k^zD!~wf8Zd!NBNEgbgI6ZT#Kgoa{s8_2ZwZk*LW~-pf_Py7jUhy- z2C!+hmA0~U>EpJ$J2U6^JLl^Rw6sgBG2rVZrc$3gV(Pxfxb1!xcjDp-14k`W9=O85 zPzJ{h#MFh89Qh-!{(`BL$XTRB&fKtFGb0dFDj;0o;K-<+ zRu1J?p5ydUQVN8?(w_9l56beLlqR_qj~H0N4FXiC5Fvt~G8337V9)`B3SvrDmiKYX zepcRKydEfphrd^L-o}!kVn)n3GvW$?z_~;q5E7+jej8Lvw?CK=mly~f1QEnX=UC*7lx|?I~76;fi`XC#kEO)ZFJ-K?DDybJJu#W zJY;54_rkuLk4;A1w@%%WV$Loui^*fAqJ|ma<$=4Ozj^skV~w>PJUR{{`EYJ^(HckW z?TqvA!p4r=4r29GOMkH1TV8qN^{?}e5go*3F5dg9f4Z+Y;hh1~~NU;EAL z&wO+Llb_sm$N7tAetz)TmGM8ip-;hut8$H)tpR5%7pAb@p8dkUFModTLyvv>TM7ZV zZ_k4d-}cDO+ivAOckjCIi5DIp*Rj!QrOI^}9w2Z%!Zwg6w|4t)Q|U;F8Sr>pz! z#eF|~`N=1K`Rx$EmeurUiKvy`eYHxnH&2E)~W_VvwsEBJ=su2%- zi1WVUwR&wRl#ILATHUM5)QVEQmQvlB3SJE78kXhAxgMu>W6xwE#8?KVpb+}052cb* zCz>oMQ3d9xBCZip0p~-6drfiv=(~r8wsi8sO1HCZGjASv>rW>)PL?tW>K$HvvsVUZ zH&1tBzfz0{;u=cdc{ZzJ4X%V`%2)3F$-?lhvG)v(o6V+-x}8A^z2fc0G#icLRMT0or)T- zeXkKw&DC8Wx7*jxFJleP)nf9H5vt5M=*a1N^6~r6tll*$G5Z)7-qH^G{n7l~`e1Nw zX>fL7aHiWo5jvYFG~Qb~efx>ypItdUB(&Lu_<;ECGl&@%2tn!Ll~6HHR3M||%I>(~ z0U5QX&PM81N-f1kqXj}kY^Y*N;?x0g15R&* z3pd0TjK4E9u}1 i`FBsVagNj{kNyqp*Tz#Oz9Pl|0000Qy8g{80cB&h9svUQ#A9<@Fd8;CO ztRs7@Bz&wSe61yXttEY}Cw;Ccey%8gt|)=BD2u%*ey%BvzA25rDu1skfUhc!z$<{S zErGBufv_%uurGqKFoUr%gR(J%vN4y)GK8`-gt9Y*vNMIVGnvUVnaVVUvNVRXG@8mZ zn#(qavp0vdIf%45h_yMO&^n2=JB+zJiM2e6wmpirK8vSSew9Eo4{GD(_5UuTb;pM zu-9Cj!CjrgU7o^Tp2A+9!(X4mV4%Zcpu=LJ#A2k!W1+-kqQqsQ#bu+!WwhF6qs3;U z#%QF*X{E+$rN(Qf$7{FSY^KL9o<}wbJ6a)Z@9-Hu*yhCB=*8UV$=>SE;_TAo?bGD# zyIc<<0004!Nkl^p!o`4B0T%rz3b4u}DZpkv zvI6Y-5ejhVhbzFPAEp4eJVXIb^Pvh%aqEXDP+}Ma3S@UEGxWFOGhuf#%r_KFQeIX!Gc~fIJ$QXvz;as$tz``J9M}!P3JUiZ z&Z*wWur6*%HCs|0!|HzBMO}P8E7_*m7=qHI5@Xqdz9~(cnO8(h?E)!SwOMeH5l`+y z*J-g%U>=uoz`8p>PLJUu%CmjxaIyv`ojw3U`w002ovPDHLkV1gm|4$}Yt literal 0 HcmV?d00001 diff --git a/recipes/icons/mwjournal.png b/recipes/icons/mwjournal.png new file mode 100644 index 0000000000000000000000000000000000000000..bf4a27dfa08cba0416fcc77edfff1cbbfe0f057c GIT binary patch literal 2588 zcmV+%3gh*OP)Tq>$g!o!wsujJxG%%u?9079 zQc|F3?*lrmVtA5<&zM65lzwb#UvFVHt*lizxCUd(3z?;G=*i$kdf|ZecsF z?}e26(K;MSmK9aTEJpZ0H;PWNCMfA*CY{aYJH5gA_0(Vp;bg*si@OrVF^NK#SluL= z)|d62d#(D)3NLEi!T9v_%rK1MaL5Y+^y%{A^78UZmc&dxlS$_s*YP}8l%#tPck8uf z=v6ZDJ`T=!JFr;aXT+aM6o)?JPL*(6)KRTmuCMK6vKhFqVy@rqj0S_33llm`7&7b) zPR`D>1e7W<==IJ|F1*0oYBulRzprZQ?UQPKHeQgyY!=$%h>4JhDWRCSLNKEKG$xuC z#{t0b?zo=sEUe3RI|_qxxmH>(rS-I`s*)hdk`%_#$?0jYcP-0mbG!MYgCGQ?1;iOg z4raIlBVkPAP>$_1w)mXJCvvt&Zm!OK*G33UCSxxMppGP@U~+A3g9V@=zz0{YR(;R!Kmxo4VY zt-A8yVXLvRadCQ~X>u}|6eYe^tA}AIhyoPtqsKp6Z8T&>S}YdN|MlE)oYwsZ+pQK$ zHU`7N@tB#p2y?N`GohZ&J=lFD=_S_>dY$$^pM87Px%&N=UvBTTAm*f^cKZX#L<=$+72=Z($KdimY%PzGz?D z@6Q4CBIX}`n1*Zwi9b6zJ32mwR1{S~7;zw0fiDVNI+J#6=l$_9kV6%v-N(CN9iCX$ z;@fYYO^5vlk9KXxvy+&hWWwm~KcJ^xT6@YK)qtbd~MOM{ezu(?J zxb9rb3009qmg&@1YC9j?V<~-oaPa-}`w>Mjl9+gTZ-*tM!F^D-RIMfw`lFt{W zAGjbm9LGT^;TL#ND3r>}m87nNr|w{IetCX=dafo^S(IRW++wj-$!7D_a@n5GhojMK zZc>B{yNZb*_u5lBhZvZ;XWnK>zU3BSL5xgv*7T7eqD{U}9rC z;?f*WB$RAg4>;v^j#45?LL5`5z{TmQBFoMDO-<8)iMKk(3!)@rjC1*%q{v<9d$+%^ z-JPvwyw~{Q4$N37X_ZB9yo~!Op@x#N-mdMSy}OX z-?OaoVBm(X6F7rGuU4y7%H>=(+rDf=9=2^cj?M9s=XvI&H!&;&=ChE+tf+diIhZay zcR@IZVPO*>>fM2;H`g|jdQuXl$z(bh-yYao0w=gUy{J{H^-3jME+a~=Z~9Rj1CU%d zFlG~HHt=HBRdfP8fIfJ!(4WV?>#>Y8nHr|;7MGT{HrGI(8I~?A6Z+QewCA%KfLPab z*gr<&>Ge&wS{uN400~KGiYQ9+iShQ`A!uqbpDnK43y{9>31(T&kClKVQ5f(r=Z5fb zY`I=4A+sQ!2>E^prh)zNg~b>q9`PxSBipej)9LNb#B(gi&5c?6^8EPljp6uuu7=em z6luVG3??WmMp8CHoWPBBJquV|j3=+(9EOx`tZ$|>G82=jGL>YJ5Hc7|6fL=_Y5?L} zS=JID@aF91`|n?!og7M--Q0Po6dS`pazfZwD8AkFU@=lsl;L3#WR`d(p|vkN}M2YoubI6ppqYdKzH_wjn`v5gBomqsKM z(A`!6qROzW8%uGVQh*#9DHJ>$*`uqYzjiONtYXX+ie=bT*0$DPzd1-H)JkO;`my)o zyMtFRsO!p_oX?e-wO?i`+c4a%kb^itM#1#mW(P?U8W0pvHKIu27_BG(@x0@Ad#?`$ z!^v{7@X4>#Mq_nDYCie+V_;}DTL{DG-P_|o{rPWT;_0WqzW>25%9|e=v2rzb<2V*j z40eCoi(u-)tmIf0)|Hr3Jt`oo0#s9Lsohk=TudbtC9_4uOIa=P^G`nUVb$PeC!(wO zc0bRr5iG$9NzH*}HBD6j yS)L!*Zs3O$loJVBh-8oOZis@I{uJ$hoqq$tmyN)&;sv?@0000(=_j;Y16c6LsJ@>ex!ZrYxCL?pe<=Z(Z%URL=iuxxapjN(}`dBF?CLaAfgPV zuYl5l*%G!mw;7epPew$R>Ch!p(F6Gll+8E-LrhJ=7s21a3sj7eHnCxm&bh_P)YzQcEaufl2_sd# zrF7O0zW6p)yQUtj=%e3{@(2k+-I9Pv=(&zN@`*??zLJ(YY_vSg8#! zi?(-n^!5LA_TsmzYu-at0>bn_Fhs)1f-N5HTKe6OD(*kHKR!kJ`um*om>H{zMn*=a z&L8IM5D9F>rU4u5uVdeg+*6vtxG-;Y? zMr_F0OJ~Q%#&#Uu2^tBEc#Q+{97WCvCTofL&d?p$Ha0$X;mQR?fI{p@>kocEv z+v>JMhk1w9x>)_bWBZ|V<7S^}mRVXZvhT}q?lK#e z>qv`O9#nV_+4DQR(DF{OnQJl#J^9Q7I!YHN0@@99^}%hEq)$p3GPEq8E%C&=+d?by zja${T&g8-2h@ST4pW@j4HebCn(#Zv5o^ZPtX2Km_{SuwP*hCBha)1r-9(oSl-E(IA zPqO1sjdF#;*JQ^EFvDtXVV6G`W5aFU)Yk)6gI+;3laeVo9BQ-K|3!rSj_ITnJV;wBQEGqOtwvQLcAAP(k<3L4Fifm zl6h+#pePjc1bG1_Kh8x?agORNdM52YVBumGn4f@2HCPNBh9R_&;0zS`(JVGYFW0&v_H=0UGuP3ZaczyY*qf&N7^Os zPprU6Fg9TeFs>|6Vm7>J^=zngU6&K3?Q^*n3)5oEjydvpH-LdpfER7^gVV4jzk>rS zojWZOuM8DEf21)9rif5E&F*wC(wwU2sYI*{a}(}j50t{8-|qf+r?>X}r=^KG5#H}< z&f;1HJjZdpuf-ZV>17Yv4COk~3MU(T$*q8?2z9X<3>R{|l?95eRVLCQk!34zhD6NB z?xTB#9}ai*bT^$kc5nFZ<)Pjq-`BTa<$t(+?%KU8MIX+B>Viv&R~K*;!F8mrOe372 zRP=J28F`ozyQP33^~q5eP6t&`P0dBtYyH=6{XAH>vZ%hNc5rC0zO_N-mZRwFE(a7x z0Yg1sopYQ?A5o|Ta2){#oFJKBA@WFMJ_X?xVIBhT5HHFe&r}r4;1Wg1X+CY>22m4= zCSQq%i}>?Wq5 z%LieH+CUf5Vvh{+jdV(6xeDvXO!#HnY{s?Z-+7jygy4!iPm#6Wn1%tye05In%yYm7 z^)dQ|&#^sA?2@9yn>?0C0Y=Knbc;2Pt!96N3&Imp%H7NI2e54~1N6kc|? ro+Qm;IHf2}JgfNs6G30rUMGJ5lhOQn!r;|I00000NkvXXu0mjfBMdYb literal 0 HcmV?d00001 diff --git a/recipes/icons/nachdenkseiten.png b/recipes/icons/nachdenkseiten.png new file mode 100644 index 0000000000000000000000000000000000000000..f820e333ad9f9aafb58eaaf799775ab29b76f8ef GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv&H|6fVg?4jBOuH;Rhv&5C@2@; z6XLp`TlR>Y{s}$1bFRU+ifivSbiUfK?cKqnU#{Q!|NlSFT&*miN?A`A#}JL+gTe~DWM4fksmCC literal 0 HcmV?d00001 diff --git a/recipes/icons/nation_ke.png b/recipes/icons/nation_ke.png new file mode 100644 index 0000000000000000000000000000000000000000..c8229479e7519b7a24fae966f868a43823864205 GIT binary patch literal 640 zcmV-`0)PF9P)1R9J<@*F9`aVI0TtPibigk5TH4y4u#6l!YEMjO_n1q2vFmyqJco|ekMB}Zuy@vn2(cYeO^xoT({*ou>B+vhO z&Nu)2zrW}I5ITt>1}a7oeb}uw?L-0ngGLcAFn@4hE1s1mlg3A^sR3XHx+?bij;%?- zYw)Qk?1jk=Wv@Dk_!OI@W}a0W~A0_ewOTouKW zq(wN3S$P07;0KQ4ZpQl6ID*Ne4N!*Y!G3gS#BISYj2~@)vbwPkpHk+V(2k9H0l+Ypj|)5LkQJTHJEuHlj}tRyWEML9PQ0Qikg+|GzuiQ|}>2LNH!?Rc6Ivq^Y=V;%s6 zN$nL!bdnlH|FAhP0Jtv>rQVeJDdN~&l@|b9$3@|qNt%swm?zy$>jLP*Y23<)X~QW@ zPYW1Pp=9VA4vN3tij0(=Cy1XRQKJC>k8nt2q$-UT0NfNs zR~RdReq6wfu>v64xDGrRD**T)obuIJ0T36F1Nd4kz{JwCN&Oztg%c|70ab0=wVVZ-7hF-&Zu)a|Cn6sxBOfUvA1fsvEG8cQ2K{p&kIUGkk8%R7GOFtV-KpRa#8&5(TQbZb4MH*E{8d^&k zT}>HYP8nZL8DdcwXH*z#SQl?w7jaz{b6ys6Ulw#<7JFnBeP$JZXcd5H6@h6KgliOq zY!r!b6N+&Yi*XZp>5}14vnS2tQe-WL45uSk&pMepdf)SvD z5ut(*p@R^jgAk*I5Tu0=rG^luh7hKQ5T}R`sEH7%i4d)f53P+4u8j|`jSsJm53`UC zvyl$9k`B6-4!oERzM2icnhn654aA-d#h?tvpbW>M49KAj$f69%qYTTW3(KSo%%uy? zrwh=i3)ij+*{=%RvI*a`3E;H};k60mxCrFB2<5p5<+=#xx(MjK2AVQ*y$9>Q z2kgEF?7#=@zX$HX2k*iM@WKc2!Uyrg2lB!P^1}!7#0K=l2K2=S^u`AD#Rm1p2KL1U z_QnSG#|HPu2KdJY_{awN$Oih!2K&kd`^pCV$_4z(1^vtg{>%mc%?1C?1^>?l|Ih{h z(FI;zwVeO}0Zd6mK~y+T-OyE60$~saVC=?jvAYlx!LC)Y8@n+uu)#nPgH^(rZ}`1l zfF5=|9PfFrpEL1F;GakW2_O*yAQ2%FF=QkJ&Xu-q13K5bR6WbH?W&QPv4v+8OgL=_ zQ8Kt_jc&O*XbP$_EXs<`(HF3%>|_J7oAOIWM^Xj3wc+&w0IpIA$KXx<2&k7T7#dSE z?$H-Gl_^<9tIk~yHd3bmP1qCG}BqSvzrKP2nm6cUhRn^qgw6wHzbaeFe z^b8FRjf{*;O-;?s&8@7gtgWqWY;0UyTwGmUJv}|6qM~ABW8>oD;^X5}Q&ZE@($dq@ zb8~YG3JMAf3yX`3OG-*ADk^GfYHDk18yXs#nwr|%+q=5D`uh4NPo6ww%9MHY=FOi! zf8oM~8#iv;v}x1k&6~Gu*|K%()@|FiZQs6q=gytGcJ11?Z{Lw4M~)pkcJ}Pq3l}b2 zym;~Q<;yp2+<5Zj$%_{+UcGwt`t|F#Z{NOq_wN1s_a8od`1tYT_wV2T{{8#!-@pI= z|JST6l>r8>Y)Oz`Far}aiEak-ak@1)J^YY^fK8R>iiNEOUcyK39k_Qf;r*hdqfN0>{{NpRT_n!=Y0poSnR8?G zHt*&LoS`$vT)}IS#D^j=;jGq+@AzB|9ZjBWlm1$2EPv3-rJ}g_?z8@fVn_aLyT2>= zK(2zt*6+8k9dtZr@cT~qdYKe+ZGvsE?KYFrr?c>E~W0q@uWHYch#MS%!`uWvg z@4lP6-ReQ6pJ|J%K*MUs$G^Q2^M9|tcc6Cus{+Z1rYkO; zeNU#ZZK#{>|L};^oKJfljGj(tk1-5oJM77HV@6T0s#3MUS%ttwZ4aNTH8&jj&#N@) zpSed4^Jn2R-k0wC%xkQf#bWk}DaZASkTix$~Q&NR>Nh8QU9&Y_Uvpxp2O9d`!_{w<>SC-u7irO9-9z)X9Fs%0)Wj;>NcQ z{E@W0V^s8JVQRl^b>WTG$K&EQ{c8fEyubAfQ|%|+lH2<6C@3Fzy85}Sb4q9e0AG1o Ad;kCd literal 0 HcmV?d00001 diff --git a/recipes/icons/navy_times.png b/recipes/icons/navy_times.png new file mode 100644 index 0000000000000000000000000000000000000000..b73323725a5ffdb2970433beab6fe73acaca1332 GIT binary patch literal 1396 zcmV-)1&jKLP)J4K}3G}U$|+Ko`bm84*QVyw|>nv6ObC-eK}{oi|g-aH7^ zwiGdTsqzCy?B8H-q~IRYl7 z#28aTleLfnq@%(%et9w&SOFhVBw|m5$4pBsg`|wGRf+`01Y-vIkur{#02&Hz?sr36U2>Af3{94guCfPKi`e7?FC(OjD`lJxLv*Tc(y$GKL_WVwDNUtDp!+MxIcY zG#{zW8p-#VsVpOrUtPqKCrmt#K;RezE?8#;E*w5Z=uf+oh{YArqEK*V(wt<#03l7U^@DW$E* ztT*l9;lc7sWp;kvk0jzzTEV)oK`B4@l}nVOIFrssDHU-0)uMp|jO5?L2h%K`L!5u{ z`Ki9%u4ET-`Q=yB?;SlnGc)u42Ok@%9tJ+cG>SZE2m`MWrn@dOdUK)hFjPRZOr#6# z$TZdWeF$+C2Z0Xj+t>#KE^uwn^9Bb8OQq6<^XC^Aey@~DKi#?&dQMtbe77~cW4qn9 zRmO&g1{?LNpo(p`+pX%htsCm)B@KuMD@Ya!h5r8jN~IDHP}-ShyLay%9UUDT8=IP% zf;oEo_H9_6IB^0(n7MiL(c%&)e>_}(=iv%+AhUx^!uJdfIWE3JM`0f*RAyAfYNc3Rtu* zhy`KPAXqD-6?L2nXaN~OL1a+Cx>Oy>MiTDLa>LDb)A-njKAiK-`Of_2n>lk%q$Y3j zn&&?c0N};tu+tq%`CM+!j?QlVdB7nqg>gx70Cb%8u;#jcUh+BVNdSn00H{0$fZ1VH zDgY>@12D4_0A>RKzQX3tv?u_aNU4dL@pii%H=s5c!F76kZqAP5xK?LFv^GR%n?-E1 zFxK6x6<4X*n^o(Q;B6&VnF61~jIHf(&K}kJU#MK`(AH+pShu07-$b3@p})j^Jjq-C{8fjjF%PF!c@L7zXEwq=2OGTWua4 zx7%z=N~GL#h5mS*-C{+X+LS9|Wq>UAq8YE>F`RA2^{B4AS_=GTf?}*PC7dHv_%kdo zUm368MkVdaz-TF0G!qhMnS70-Mzol#YmLx%tU+RYW58u-`mBY5h2@icFFM zf5jq(sb@gp8>X+lfHt-2kDbvZWNSs0mcQR(4|)_|tyagRTi*PGHeOXyqimX4tg~0m z%2G$A8LxNfkDrq{2gtw@L{Mrt*E|yxi#A=85yU5#CGDDo9PNQBWnc_?`FEL9fXrp7`RQ|2Bu~5d zAW~SSNzIe{gks%&roj=pCk-k23G3?9iEH3INMBQL{V;7)stk3PkRmb6FV!9S#n5R~dT&UGoA=RVUN#hb)O+KZwPV|7)^kG+)x^vvR=IFk&d7LAZRfRJ zYP2@{juTxF%{%4Uz3~yj>Bh~gyhn6u(T92sjU%4#iquniJ2UQzhnricpdb~il3H9? zdwbnP75C0*=}UMaZHUM1dbaV@i4MU*=Bl2s{DN4u6aRZ@S4SBmhCtqMpOoB?eRvZo zuX}BUVvFVL6a13Ef#|OEv+DDgKiz12*GM2+?w2qJuO+& zxP^U9qz`_tzu22%h`eb16unfK@8PpNa`c;sBY~rJ_kNIP5r}KD`rb}F?lBsICfde4 zetmay@Q;yEC#T8QQ!kzefRw{Aox$!8quTIy@LzsP2vH56#GY>W?F~xC-tQn~^3G>f zi9#Ir4}{{kK>4|lfXNpL90C}027M)c{Ypl7W>`3r9>I)=2&FTaboy43@Ya8TJ^S*D z3d;WE5aI)Hp!t)|(`|nijrKHUc!VoW z2hLQFReTR0s!u}Qm(D&EcIBGK`Oeh4nUvvIFNgX^`pNyo;Y!XG-YPKTy8zO$BI+Fx NfE%C8zQx*k^k0(7b!h+q literal 0 HcmV?d00001 diff --git a/recipes/icons/ncrnext.png b/recipes/icons/ncrnext.png new file mode 100644 index 0000000000000000000000000000000000000000..a663796757265600bc460cc31706bd2141665c63 GIT binary patch literal 705 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0U}O#O332`R@85qgfH07`Xd;-( zkQHFEFLMf9<^m$Y%iKbj`9v=BiCz&Dzak=aRZ{+{q{4N1)$0msH`H}+8C%}5b-rWo zddI=_p1aRIFaLX90r!0Z?+1oG2#I_co$x3&=}~<0lZ@OaIfc)P%b!=(J+B6$`d6L3 zAJ(k@xN*y;ox4Bn+Vg4m-Y-Xwe>;Ei+oj7tZr=KN@BYsR4}U&<^y}%fU(cTZdj9;^ zirAaP*GiRLs^srDaXVQBsXONKSIJRuX<_!ttFuV<+0_^sA3b)M9s6~7kS zGMtrO=zY@2+Ls}HW7x7=D>Aqbw9RqLFf#ltw1auWAC;~43;ADH%g)#vr1VVq@jkW! zp{2)k#h-1sd+)E0KF?;`B-N`^rT1?+Q>OcJ{muVYGHGU?j29*I+S^;cs_;GjwJG6N h=^6WV)l2>|C|x>{f9L%3SHLJ>@O1TaS?83{1OS@lh2H=G literal 0 HcmV?d00001 diff --git a/recipes/icons/nejm.png b/recipes/icons/nejm.png new file mode 100644 index 0000000000000000000000000000000000000000..66d3507beb64252a82df0bf7210fce2fc3c8b7cc GIT binary patch literal 2273 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 literal 0 HcmV?d00001 diff --git a/recipes/icons/new_york_review_of_books.png b/recipes/icons/new_york_review_of_books.png new file mode 100644 index 0000000000000000000000000000000000000000..a30ab4d154473a069731a1258c2fb2ea4b92e62b GIT binary patch literal 629 zcmV-*0*d{KP)awq8J#X8yln^9;F{2r63@tBO|CMC#owesw^z3EiJ4vGOaW;t~WQZIy$jEJ+VGM zvqD0%LqoGfM72jpwMa;|N=mm(Ot(%>xlvKPSy{YVTE1RhzF%LzVPU~!WWi-+!)t59 zY;44CZpU&?yV(9rGE)9%*R@Y>q);NbM+SRCP&JORB26)*~X~`H;|f*ZX9=x-i|;33b4DsQFi3RwtfBaIGoBrCND=z8D|L!e0R9r3y&0k-{a76F*5 z+ie?AGz(vzXB#Dl4_My#un55PqaA0E+cnOgvbsMIgOKFnLOlX-Lb8Z}x@Cp zIoF5rQ2=Akxo%{ga}%vItNG^o?!N$h10Ny+Ai3ekJ4BKW61(3WJU@M(?DsYFM0@)J P00000NkvXXu0mjfqyI6k literal 0 HcmV?d00001 diff --git a/recipes/icons/new_york_review_of_books_no_sub.png b/recipes/icons/new_york_review_of_books_no_sub.png new file mode 100644 index 0000000000000000000000000000000000000000..a30ab4d154473a069731a1258c2fb2ea4b92e62b GIT binary patch literal 629 zcmV-*0*d{KP)awq8J#X8yln^9;F{2r63@tBO|CMC#owesw^z3EiJ4vGOaW;t~WQZIy$jEJ+VGM zvqD0%LqoGfM72jpwMa;|N=mm(Ot(%>xlvKPSy{YVTE1RhzF%LzVPU~!WWi-+!)t59 zY;44CZpU&?yV(9rGE)9%*R@Y>q);NbM+SRCP&JORB26)*~X~`H;|f*ZX9=x-i|;33b4DsQFi3RwtfBaIGoBrCND=z8D|L!e0R9r3y&0k-{a76F*5 z+ie?AGz(vzXB#Dl4_My#un55PqaA0E+cnOgvbsMIgOKFnLOlX-Lb8Z}x@Cp zIoF5rQ2=Akxo%{ga}%vItNG^o?!N$h10Ny+Ai3ekJ4BKW61(3WJU@M(?DsYFM0@)J P00000NkvXXu0mjfqyI6k literal 0 HcmV?d00001 diff --git a/recipes/icons/news324.png b/recipes/icons/news324.png new file mode 100644 index 0000000000000000000000000000000000000000..f39e1aa486e879fb12a8629ce4de48d607392c7d GIT binary patch literal 1804 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 D%ycb^ literal 0 HcmV?d00001 diff --git a/recipes/icons/newsbeast.png b/recipes/icons/newsbeast.png new file mode 100644 index 0000000000000000000000000000000000000000..9c3a01185c42980d0b33c783df1fe63f77e585b5 GIT binary patch literal 2162 zcmZ`)X*ARgAN|{A%2HEfiO@{8$vSq4hp{yFBw1sOF@~(e*uzNKcPc{mMAob!OT?gu zqD5JnBwHpKyRkg__{DZ0HB@?0DQiAZPzu9 z4KT^jR3AKd@*D&%!}*nU{8$1x8>97rqrX@9syy>p_-&|%vb4ER`{)t3famF6TV3C; zIhx!j9Ql9Y4{{|01T{I-wm+Zqbo|VLv#Sfi$Qc&BON5m)Lta;wkxi89sVBm50yt5NIJ%Mug`af5O;j3c_q6SVha9?45~jAlb1fK0%h%$sj#$ICY~2TI1$X};L{R!#gzYDKSuJwM}DuXoknN#7C_O5r?Pb3u1g)!ZytV7QvG5#O#-zca?* zKs*Rv@$=B(`4Vmh@su(_d2@z5xxsxo18-h|s%Xydn0Rhf5~UTe&0u-v5U$#+U)Xr( zV|CUj%7d;}20^eV4l0yVdbp`>$lc8`jSWNFC*rO2^li`ypH^^Lm;r>wTjp(njgtrc z3_%R&zZ7;m{c{&amXFJ>bjg2JsiNWi=v0R)IcFC3AZ-KV$ZYN&XNTJsfCVfO2mrbR6RyZ1lzc66aMAO zIG>=_(+&fh)oQ@8L4?>N!yhS_E=6tH*VPw{sEE6jkq++Dlo;PT znx9-sY2x}=N+x&aQv{b0JCZ*xN?O02nw&J7RD-_Rk*ezbvO0=!N`h*AE27ET6oIcv zvNv8-4i~k9yI*a4ad+^B;3(=+q6G+PkPfHu&~y0dYQ6K9G8)a4uM^M$=)42Uw;5qr z$1J=uTG)|l5>GLl-FXIs#Vh(rA<({>$(nV_0^ ziqXavu-TJ6<7pQZ{xTz@_U0wgRA;(0nhow)og{hDWF67xU?~8f7DwE#eAwfkromQk zAb@>5AS>9uTJgQutxJ<^KOU@1INP#g^F+di_4?hoH>BLsQ7!cLQu&hR8pmvTgfV>% zI>Cq0PLYLO=c`K-P3wF23!cBl+cG(w(-by!RK8Np6mG5gmS;O`(PCFxMBlc~jrEd6 zMtG$7bP#z?ZWLw|Eg5x>9P2oqnjaw?%I~)V$q+Me1e_jAVF}+CpFTK{^IOFfgnM*T zK7k@z{b@0N)89nex}_L}(qD`jl!6fENp(<4VtyFgm~5asZ@gW$h$7Xxr*C3uA^nrQ zIkOQwU*)~u`c%{BDGQ^prCgTE7LC50r;^dX_7k&~y=41lS!LhQFDhuoRtm>9@)4KA zckD+mo)d6#TlVYtj=jC$5HrRcOkFAvH^6RTChNd-#XZ=a`Q zsu_=#w68>9xN`fs!I&Od!Mh!%t-GYxPzfU1tdzLcl;r3yv}wFm*c-7yQL*&m8p}bq zIb=B=@Cs3f@X)DoZGeOtwu`xk75X7wAx|i$<ZEZboUe-xF7`ktwfAnrtY<#SJ4d+8tHE^f1=MzKuICk3}$o__KOd%g}2#}R?Y)v zoq2xK62_T>R_)4;=P03?V>$e#pY`2SJ87AmrcY3`d&-OHlC-Mcu?^|_L+^X>=Q!U( zp%75^mxJtHFeI~3Co(|n#|VFjKi=a^&JoB`HhXrdVAa0_k$+pJCGRz_-uQTSui`VM zV&aZKRkSyd_Ve?R9$7Ped`}L* zFx(?;wgYX-x;K-4fn0xVs^;`pT#)Z~uBuJu@$mFcc6rH!_th+E#MQm9gUSyPx!9wB zom0A6J?U)ujo2Uf`0CA}t1p`Ui~{NO+SBSol80Tnc&tkZaS;D^rL99PZAcc_AegV8kE^>U9!3iB#lzfv zNjLxq&ig^ckV6%h6&VYdZ2?@sv3eGe%T%IuD=7I(HH*9!z;J}*;7d}6D z@bklm|NsAgmNE4Mn#NiZ8+PNiw+y`xCE-7d#)h( z?8f)~Sz9F+%mTYP0t?8K2b`#TJxm`m}6TOjVdNz3soq#@W-KC`zvX z@YCxGSLCS=*N^NjSe!do<3pG6#3_tEt3vFBT}$QNH~M}LRNWqbN$a!qwtzbmdMf2_ zeKJ_N_xX3T({sM|&DVgKzB4$kn`jx@c)z4*}Q$iB}PXnf* literal 0 HcmV?d00001 diff --git a/recipes/icons/newz_dk.png b/recipes/icons/newz_dk.png new file mode 100644 index 0000000000000000000000000000000000000000..5e5dded4bd8a74c0f2c719ffa10a8ba38066a138 GIT binary patch literal 1927 zcmaJ>X;c$e6i)GIMX|WF($b1!P>VGrlK=ryqXJ0;OK402B8rk_2mvw^laYjolqIZI zM2jL6WD83OgoG_DQ8p1Rbpe-Ft$IAR(AFxb$1bSHPE>4vl+HOb@7;I4@4NTi`<=<* zglt+c&v_mWhg%R7$lzitV0-7z!oCb$?rCiK0A+@wJR}-b2^0{{PmBm5AV?;VKwL;5 zPTYMKT8G2U!b|z#XgE8VCPHLHfek}c%j6gvhg-KnEf`HJxy)&uL*G)zDSp#nfHi-(moH68y_mxk?a$0R)P z5`xCj@vn*sXLEo6L;(R_M9^IXQbB-9CXzioy}i8&00kt2B#=TPQ{2hkG!F_51cBEN z9?PZ>N7J|r*6UnYM90UVsGLS3sZ=VW%7cg~BqXx0udhvmLUG3s?#e_M6{y`|rR%f; z15%0Q#t-v1O<@)P9{i3=%_Jqi&vxM){fDmg7c0SQnR88%D%*gx}vJG zd~IrirMWUbR%~u4S)P5f#aQ0kWjJz3>$1$5lWjfOsh7jCJ0iF$6mF<3N=-(a>c6h9 zIaXa!&~I(%>8cOqZf-Fi_sky2&d^9DqMC}Y2766?)`sodw&dle)K?$vYSm??f1%eM zX)~4ARTVVW6_*z6??2I4rzkVb7goK&z<1*WlqL7gDutNhxaI8I6lUxFFD=cVzuZKRZ2(4cXBe) zL{X7BnQ5LmqXQ?6*%_$=J;sifsxt%4xmoEaI%~x>0=t!0@YFlk(fpWL3x7%Q8u4*$?_F4_QA|r+dTALe7PxY9SHHZX?NVs!fKk)4oU=IN1~}-p2FC-lgF*&dw+MBlkTU8T55=x&~=s^m?(G4oeJ6#Iv9Vs zp}y|gkBJmRU0B%dw}#ei9vr*oI|Qx0O^e{|<>}qV>@9Dv$=eS%5{?CrrYq^5oBm8% zFH|O$>$p2&9n<>wBB$V~!E+bHK0oXtd^U==9?-eWN$>k3{qTa*uxsJe2y^Msdl#QvS}oXBtPXW4JE%(^4q=T0g_pXu z9%0U*_qdUYFJm8`l@e$Fl||y5d>*RR*_pdJOU$XSM>xEA; z1yetyF1x#8*^Mm!ibdfYTCZI-7H9a6Tb4L(rJUOr(-y7YtD@e^IO_Mnuw-J)xpD2n zG15}jyto;qAK{C)!t*Wq~Kq4sQ;!C zpXI`kz)4Nk&Jj_sW}QbsQ(wM-{yR(fb7|_G>kG}9n#&g_$CuZCMPdoxHG{xozKAF!vYS65Ed>47KH~QyADVOvGnvFJf;-Qy-ng~qJn7q zg*klRdwF~wFs%B1e$LDWA&2yHb6i*=1v}QCb1-cXy!qlJBZJD?s&M^ literal 0 HcmV?d00001 diff --git a/recipes/icons/nikkei_news.png b/recipes/icons/nikkei_news.png new file mode 100644 index 0000000000000000000000000000000000000000..a04b60782af35551cf472d36c1d61802f376c7f1 GIT binary patch literal 674 zcmV;T0$u%yP)Yprt#XvFbd|Aom$G=6vw4}bg`&EK zqq~QsyNRW|il)7dsK1k~!j!JUm9E2)+<>;OFh->+tFB^6KyN>+kdJ@$~Zb_w)7m_W1eu`TP3&{QLX-{QUj> z{r&#_{{R2~v1@e?00008bW%=J07QJV<@ovj=_#r>00036Nklz`CmW3ht9cF>ZUNKv^@F=Pl)nb)_WpaHW>MNThmhnz3Inef znEKe-tBzsGXr&L|dUoC|E57UoIZ&w3HfocYaf;;FCc-8XPp6THaeUn_`I4s?jxvo= zq$rE?XX>q&y^0vGZFbcy#DMf^rEf14N2;DwUao(~rhcyf9}I~?XT|ds4FCWD07*qo IM6N<$f{KY-Q~&?~ literal 0 HcmV?d00001 diff --git a/recipes/icons/noerrebronordvestbladet_dk.png b/recipes/icons/noerrebronordvestbladet_dk.png new file mode 100644 index 0000000000000000000000000000000000000000..05f107580a440e778ae50dddd11075d6fd1379df GIT binary patch literal 300 zcmV+{0n`48P)F)3J_W%F?D1%b}0001wNkl;sf5&<1F405h{bfEJh+ yo&eyOAiXZcz?VEhwUM69tV-%$C-|H{@H_xBhAhtQEf6?aW`LcJ7RS` zWQbXMi(P$;UVf2egOq89pn8?El&Q6tthSl0x0?C|OC@apdK?(+He`2GC-{{H^||Nk`8ld%8*0C`D7K~y+T&B{p*fHMYsO{J12{YmaRW$mxK9|123G$R1s68KC2_?!Xoiomk~ z;5~sb1>kCs0=G-M>lCPG!M6Rnr+{|u6@$D1ppt-&1jcUw+7K9C0caio=s=)&0MH@= aKkf?~TScaE(X0&s0000Wnb4KP)*Q+-S>5?fDA+!qGwyS=C zNXtn7K)ZI)&uCK!1<@+1mFqTcTZjfNA}h5*uXn~fbI$qRo~I-L7|a3yC<%})3ZMx9 zl@>hwZ+HB3b?K-l%cJ{GSNqGu^R!Uq(A#`_w`>2##dojw%5?Acy;on_y>qKK%!|xT z=7USD=3M^1I*Px?lu#1_IOt zeO;XAm+yJ=02To%XghY}II;QsC{I>## z00BzCDL0SG3zdlg0<;M>Pki+8f;p940|BOq5dZK&$7*kXpG*)yNAvtS7vA8}$+O4o zHKd@>UxAZ3uYL0=Y?b$x%);!mEHh`8-C{j{czwyaxBvR%@G!33ym1*(N@~Hh9e3m6 pEI2-$mtnT5XrUwk&{_om{s)wyvT^y+{?7ma002ovPDHLkV1g}8@CyI{ literal 0 HcmV?d00001 diff --git a/recipes/icons/nos_nl.png b/recipes/icons/nos_nl.png new file mode 100644 index 0000000000000000000000000000000000000000..78b6c7293f287fd20aedbd429d69a45b0dfd627c GIT binary patch literal 380 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyegQrqu0Z;>ipqTjg~u{7uM|XI z%Zt2_5q~Z(3PdmDM4rouJe3o9CNKO9NWzgkPy~q!7nc*6z_#%O&_v0SAirP+b`DMn zNnKAbpZbQD{^>Igp167I@u$yU|J}QP=M+%U5lv!j6vQFEsgf1|W71&%VJ^w_>64n$BBhO7fn*vi- ypG>^?SWUbw&ax=;<<{l=?RK`mD)yiM&;Mke)lB2*7rq1i!{F)a=d#Wzp$P!jcbMY< literal 0 HcmV?d00001 diff --git a/recipes/icons/novinky.cz.png b/recipes/icons/novinky.cz.png new file mode 100644 index 0000000000000000000000000000000000000000..ca7f003d87ecb08484b88dd0ecd3a604210c5427 GIT binary patch literal 1352 zcmV-O1-JT%P)Fmt9ey;JH+R_4s!>#YCZ|6coK zF9n!%o9g3P0c!J@%Bz&7kb3pi&+tEfBZqqxp#MWG=>ud98a#+Np28V!`xmpb4rybXQ!#u zgTn+)GaEVab9?jZbuD!bfSquiO27ekeK~g4L*<^}UZqsQNPs)0g@y}OXxYk!6Q=F9i0HU3v&rpO(ih31pm~@xC?SH zydI*BcWC>)j;5Xald=7ne{o>!0+&}A^K}2%0L(i79RTe$XQ^AfmV#%VpnU5FFpNIq z^PYZ`QL7elYSD|t+QNOt0Qi;ral;T*p+y+T9TPx{UyoN}Hp`chq7|2siBz?24zPFvH;cKDYWsB6CFeCzkQv>M?deuqlv-Hov7P;n%YWuWT znUzB!fW%Khx&rAZrKb%v7ZrWRXchaHAr~Qo7h$jM7$9$}M2{L@#cmzcS~~CT7^Z(4 z2958-wU*9Zv8F@Dfv%e!of6wOln;?3X}SV^#7la7Umg~F&w;L+9SX=^?<*~P*vP9U zemCf4IhExA#KK*4T)d7aKL`JWF#tqvwGe4+NsG^#HW|YcPt|(ze6M^0R&}&__9pAf zWkvjM@BYF!^Q{2p+P(;kw1tUnUfosu4q%yp7cefyB7e+rTA~% zL&@gExfMGE0Mt1;A{TY*z8OZYxG`q{?%_#})sFSirr3p74s*ASgzR*;XQBWf-3_Uk zaHmI9s~A6Oo>{QYhq+7*u{jm!?84q#YlpUc!c}M04rr`D0?BPiA(8Bq=a3y(Vvc_@ zD6TmkjA{n0000< KMNUMnLSTa5I)McM literal 0 HcmV?d00001 diff --git a/recipes/icons/novinky.png b/recipes/icons/novinky.png new file mode 100644 index 0000000000000000000000000000000000000000..ca7f003d87ecb08484b88dd0ecd3a604210c5427 GIT binary patch literal 1352 zcmV-O1-JT%P)Fmt9ey;JH+R_4s!>#YCZ|6coK zF9n!%o9g3P0c!J@%Bz&7kb3pi&+tEfBZqqxp#MWG=>ud98a#+Np28V!`xmpb4rybXQ!#u zgTn+)GaEVab9?jZbuD!bfSquiO27ekeK~g4L*<^}UZqsQNPs)0g@y}OXxYk!6Q=F9i0HU3v&rpO(ih31pm~@xC?SH zydI*BcWC>)j;5Xald=7ne{o>!0+&}A^K}2%0L(i79RTe$XQ^AfmV#%VpnU5FFpNIq z^PYZ`QL7elYSD|t+QNOt0Qi;ral;T*p+y+T9TPx{UyoN}Hp`chq7|2siBz?24zPFvH;cKDYWsB6CFeCzkQv>M?deuqlv-Hov7P;n%YWuWT znUzB!fW%Khx&rAZrKb%v7ZrWRXchaHAr~Qo7h$jM7$9$}M2{L@#cmzcS~~CT7^Z(4 z2958-wU*9Zv8F@Dfv%e!of6wOln;?3X}SV^#7la7Umg~F&w;L+9SX=^?<*~P*vP9U zemCf4IhExA#KK*4T)d7aKL`JWF#tqvwGe4+NsG^#HW|YcPt|(ze6M^0R&}&__9pAf zWkvjM@BYF!^Q{2p+P(;kw1tUnUfosu4q%yp7cefyB7e+rTA~% zL&@gExfMGE0Mt1;A{TY*z8OZYxG`q{?%_#})sFSirr3p74s*ASgzR*;XQBWf-3_Uk zaHmI9s~A6Oo>{QYhq+7*u{jm!?84q#YlpUc!c}M04rr`D0?BPiA(8Bq=a3y(Vvc_@ zD6TmkjA{n0000< KMNUMnLSTa5I)McM literal 0 HcmV?d00001 diff --git a/recipes/icons/nrc-nl-epub.png b/recipes/icons/nrc-nl-epub.png new file mode 100644 index 0000000000000000000000000000000000000000..a663796757265600bc460cc31706bd2141665c63 GIT binary patch literal 705 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0U}O#O332`R@85qgfH07`Xd;-( zkQHFEFLMf9<^m$Y%iKbj`9v=BiCz&Dzak=aRZ{+{q{4N1)$0msH`H}+8C%}5b-rWo zddI=_p1aRIFaLX90r!0Z?+1oG2#I_co$x3&=}~<0lZ@OaIfc)P%b!=(J+B6$`d6L3 zAJ(k@xN*y;ox4Bn+Vg4m-Y-Xwe>;Ei+oj7tZr=KN@BYsR4}U&<^y}%fU(cTZdj9;^ zirAaP*GiRLs^srDaXVQBsXONKSIJRuX<_!ttFuV<+0_^sA3b)M9s6~7kS zGMtrO=zY@2+Ls}HW7x7=D>Aqbw9RqLFf#ltw1auWAC;~43;ADH%g)#vr1VVq@jkW! zp{2)k#h-1sd+)E0KF?;`B-N`^rT1?+Q>OcJ{muVYGHGU?j29*I+S^;cs_;GjwJG6N h=^6WV)l2>|C|x>{f9L%3SHLJ>@O1TaS?83{1OS@lh2H=G literal 0 HcmV?d00001 diff --git a/recipes/icons/nrc_handelsblad.png b/recipes/icons/nrc_handelsblad.png new file mode 100644 index 0000000000000000000000000000000000000000..a663796757265600bc460cc31706bd2141665c63 GIT binary patch literal 705 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0U}O#O332`R@85qgfH07`Xd;-( zkQHFEFLMf9<^m$Y%iKbj`9v=BiCz&Dzak=aRZ{+{q{4N1)$0msH`H}+8C%}5b-rWo zddI=_p1aRIFaLX90r!0Z?+1oG2#I_co$x3&=}~<0lZ@OaIfc)P%b!=(J+B6$`d6L3 zAJ(k@xN*y;ox4Bn+Vg4m-Y-Xwe>;Ei+oj7tZr=KN@BYsR4}U&<^y}%fU(cTZdj9;^ zirAaP*GiRLs^srDaXVQBsXONKSIJRuX<_!ttFuV<+0_^sA3b)M9s6~7kS zGMtrO=zY@2+Ls}HW7x7=D>Aqbw9RqLFf#ltw1auWAC;~43;ADH%g)#vr1VVq@jkW! zp{2)k#h-1sd+)E0KF?;`B-N`^rT1?+Q>OcJ{muVYGHGU?j29*I+S^;cs_;GjwJG6N h=^6WV)l2>|C|x>{f9L%3SHLJ>@O1TaS?83{1OS@lh2H=G literal 0 HcmV?d00001 diff --git a/recipes/icons/ntv_spor.png b/recipes/icons/ntv_spor.png new file mode 100644 index 0000000000000000000000000000000000000000..945d732e068b03e6ec64b6bb124ab0a53dedc9a0 GIT binary patch literal 1761 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 x1Hykn_%bTX!7~D3pQ3km=L literal 0 HcmV?d00001 diff --git a/recipes/icons/nymag.png b/recipes/icons/nymag.png new file mode 100644 index 0000000000000000000000000000000000000000..0a70ab0d045d738fb0dade287857e6c6a531b664 GIT binary patch literal 562 zcmV-20?qx2P)I9@$#UTqH!cku!pj zj9#0Wy>y{ak}fVHqszMZ;G{`f3AHY4L^qU37mZMq=w>pb(1(gZ?E+EuF!;A)5{9E^ zzmF-&P6oQRD9?jI;s2~?dloQ>xh8ZnIY=HBVkzmTfPd)JUMVLgp118gPeQ4r> zEQJ&I$y)8eIeV7fWG;3v!Nvm;!}^q7DUdBv>=8?)IMEqbLZ_=oXo@h@KpA3MEch!> zNy~F%NU&Q7f$S15QnpN1p5-r{qF2LaeO8xb+7oocgS$p#>F)w3sil*a3o)KsC-0NfF`?P8K2=BV9Fkw(h%jZ#wmT3Zt zc)wQ)7P%&@;*aWTNZ-#%-ASmQ- zhBlSfI(DAN1{>D1(47lfwrd{AQ8?2B3DOEXY=9l?q}j57Y~t^CsN0}9I>UK5z6L`M z6DauV>-<3Qb~H4H43;ovK514m(lrgx(wNeI1Kbg`^7AthzyJUM07*qoM6N<$g3zk- AXaE2J literal 0 HcmV?d00001 diff --git a/recipes/icons/nytimes_cooking.png b/recipes/icons/nytimes_cooking.png new file mode 100644 index 0000000000000000000000000000000000000000..e93832934fbfb256fe8452a0d5998de7f93a0c58 GIT binary patch literal 963 zcmV;!13dhRP)e^BR_JM1>1|x;Ze8hbUg>dQ>2+o5abfCmV(N2a>U3o4 zc4q2#XX|)q>v?JGd1~u=YU_Jz>w9eLd~NG~ZtQ_`?1OadgLUkLb?k;V?Tmlzj)3luf$oxo?v#k{mWl6|i|?9_@0ySBp_cHXm++;V@uZsZrJM1lobjif z@u{Hks-W?!q4KMu@~xxtuBG#`sq?d{^R%qW@ci=d{POYr^YZ=m^!@hr{rC6%`1k$z`2G3#{rUO*`uhI)`2PC({`>p> z{QLg={Qmv@{{H^|{{H{}|2|fV`v3p|l}SWFR5*>rlI2&!Fcif@={C0Eu0w~ryTfpI zcX#*UJ{*RWHqBp3y0OEWjgK7erzH1x@?LHp0DmHxQflp(R@&PcSE}5F3OQ*@^4E2D zhf`&~w!*HR>!S(3;}Z#j4=WX6jzPQVBT5Y98GQru_MnG|FA@1RbBGX^E^crKUDg&h zEb3SjaO5m5(a$6UU>ls6-h1)^ahcO-;t|1GH5D`8?f~e}@bb3NQcsH%yka|_hh*~9 z<$VR9)K+40T7p-BIeRQRw+=`~AU~+8#@%5+5^o7)F^Ek9RR+MW>86+>eCakq?*T@@ zZHz;&bp)Y$LFmcD2)GV&NMAz$DI7w2KU0b3+aj_=HV%i4{RV(E0Uw3P%z-!@G2dbW zgl9^-6y|{UBet&nst~KyPzIoKLo^Wdn()tC^{bgmyra~#=CM)SM20XA@F+*{fud-Z z(MJ+VPC((4*^8N;p2-7-httH!0hcXkHZJ^`KOp5k!2C?zafF4B8%^7MHQWO4yM3(q lLL*_CuJPg4f?w=^(-%e?qdu{Cp=baA002ovPDHLkV1k|;EoA@z literal 0 HcmV?d00001 diff --git a/recipes/icons/nytimesbook.png b/recipes/icons/nytimesbook.png new file mode 100644 index 0000000000000000000000000000000000000000..741976ef4d9c77c8c21e058b1b2fd550ea390c05 GIT binary patch literal 1021 zcmVkdg00009a7bBm000XT z000XT0n*)m`~Uz0uSrBfR2Uhh!Ci<{Wf;Km|NEZVc9-^Z&Uwz+Is4Ac?zrok%lZME z+G-k7gj?8`cA=2;0d^B)8cB7Nf>emuMvzR63JTM5Bnhb_17Q^2L4JkFl z-c60?Z{zj3ivYtVS;#lL_3nLl#2wDR%7gS{_)kN_=Q(99V5!RFiF47Ys8mEnqe@2S zo#o(@k8Ua84RE6`UpwVTvVdh8Qy@0-v8enh6H=0rBteo8 zOyfbgTvQU76crT}l_V8FH9&Y@q;bU^&+L;!{C@|Zcfrx<7x#1i5W|4s5-hA4TDvvd%wyk`PQFcQ3AC)*{M-G| zU&jy&0E49luZ8A_19SAmXV)5LXSTJSOerrpWiAc@Pgq7bp+uqWxV+lZ=U` zi@nN=r@3=AX9~&)^?;!&nBuvF)e4@9Np zlo4=EG8X8pBD#6!Glre9#>yx&0Yi1plVhS%ij_B^qe;dB%@oo6t^$zXl0QHiuo$Xy zo{Wo1Dbmg_7SPcU9f>raaLO(&>gl2jsOy27X`&j392AYpZAy{rQ^-*?EasFQOzEZz zLb3(+&`1^55o{NY%ak{&zJ;Ch_G;!Qw?Eq3`P$;cEoU>DNeaq#z@xf5Y&WuACWA_l1QmbM z*R%%eYoKVL?WI6DMk`>rB+n78mS07s5>{rFJ4>^Q;Y3t)WhzVc)^Q4er6w;=rV#E9 z&-sd?Xp8z4r<_~*{89ldmt`>n65i5&m3 rSgwM>BSjr4>PX>XxO#m+AGQ1ii6=+s^R4=J00000NkvXXu0mjf7I)*k literal 0 HcmV?d00001 diff --git a/recipes/icons/nzz_folio.png b/recipes/icons/nzz_folio.png new file mode 100644 index 0000000000000000000000000000000000000000..7283bd9d2ab243aef8fcc660f9630fc5ba6118ff GIT binary patch literal 327 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk@BpAX3RW*PVXMsm#F#`kN5fEmas?8@26#VJw z;uvCaIypgtwS*&>vA5S_o-?scXpq9 zTCAz7`0}cu*bd42`yvetc9a@e95FOd`Dc*8@iir3#g7FY8&ck1692@rtyf{$ujfu1 zCj4CQp%VV)sYPYy^<|7FlB~D)3aT6`FK66j{N2dI_GTJ?(1rg_GCG^yi1BY~a^~S# zcE68NwBql_gam Date: Tue, 3 Jan 2023 07:16:01 +0530 Subject: [PATCH 0054/2055] ... --- src/calibre/ebooks/mobi/writer8/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/mobi/writer8/main.py b/src/calibre/ebooks/mobi/writer8/main.py index 29ac173c6e..fe056e129a 100644 --- a/src/calibre/ebooks/mobi/writer8/main.py +++ b/src/calibre/ebooks/mobi/writer8/main.py @@ -42,7 +42,10 @@ class KF8Writer: def __init__(self, oeb, opts, resources): self.oeb, self.opts, self.log = oeb, opts, oeb.log - self.compress = not self.opts.dont_compress + try: + self.compress = not self.opts.dont_compress + except Exception: + self.compress = True self.has_tbs = False self.log.info('Creating KF8 output') From 08e25915e6c5e8daf346e38e5e6bed8ee72aaccb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 3 Jan 2023 15:16:38 +0530 Subject: [PATCH 0055/2055] recipe icons --- recipes/icons/oba.png | Bin 0 -> 760 bytes recipes/icons/observa_digital.png | Bin 0 -> 1731 bytes recipes/icons/oesterbroavis_dk.png | Bin 0 -> 300 bytes recipes/icons/onda_rock.png | Bin 0 -> 1342 bytes recipes/icons/onionavclub.png | Bin 0 -> 487 bytes recipes/icons/open_magazine.png | Bin 0 -> 2141 bytes recipes/icons/opindia.png | Bin 0 -> 1072 bytes recipes/icons/opinion_bo.png | Bin 0 -> 771 bytes recipes/icons/oregonian.png | Bin 0 -> 784 bytes recipes/icons/oreilly_premium.png | Bin 0 -> 2600 bytes recipes/icons/ottawa_citizen.png | Bin 0 -> 1208 bytes recipes/icons/outlook_business_magazine.png | Bin 0 -> 849 bytes recipes/icons/outlook_india.png | Bin 0 -> 1891 bytes recipes/icons/padreydecano.png | Bin 0 -> 628 bytes recipes/icons/pagina_12_print_ed.png | Bin 0 -> 672 bytes recipes/icons/panorama.png | Bin 0 -> 830 bytes recipes/icons/paperli_topic.png | Bin 0 -> 409 bytes recipes/icons/parisreview.png | Bin 0 -> 1421 bytes recipes/icons/penguin_news.png | Bin 0 -> 1437 bytes recipes/icons/phd_comics.png | Bin 0 -> 2484 bytes recipes/icons/philippino_star_ngayon.png | Bin 0 -> 1624 bytes recipes/icons/phillosophy_now.png | Bin 0 -> 167 bytes recipes/icons/piratska_strana.png | Bin 0 -> 865 bytes recipes/icons/piratske_noviny.png | Bin 0 -> 1124 bytes recipes/icons/plus_info.png | Bin 0 -> 1273 bytes recipes/icons/poche.png | Bin 0 -> 532 bytes recipes/icons/politifact.png | Bin 0 -> 1260 bytes recipes/icons/portafolio.png | Bin 0 -> 264 bytes recipes/icons/portfolio_hu.png | Bin 0 -> 1715 bytes recipes/icons/poughkeepsie_journal.png | Bin 0 -> 2145 bytes recipes/icons/pragyata.png | Bin 0 -> 3125 bytes recipes/icons/pravda.png | Bin 0 -> 1710 bytes recipes/icons/pravda_ru.png | Bin 448 -> 1644 bytes recipes/icons/pravo.png | Bin 0 -> 887 bytes recipes/icons/prekshaa.png | Bin 0 -> 3636 bytes recipes/icons/pro_physik.png | Bin 0 -> 2006 bytes recipes/icons/projo.png | Bin 0 -> 332 bytes recipes/icons/prospectmaguk.png | Bin 0 -> 1176 bytes recipes/icons/protagon.png | Bin 0 -> 1285 bytes recipes/icons/psych.png | Bin 0 -> 608 bytes recipes/icons/publicdomainreview_org.png | Bin 0 -> 1731 bytes recipes/icons/publico.png | Bin 0 -> 439 bytes recipes/icons/quanta_magazine.png | Bin 0 -> 1412 bytes recipes/icons/queleer.png | Bin 0 -> 812 bytes 44 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 recipes/icons/oba.png create mode 100644 recipes/icons/observa_digital.png create mode 100644 recipes/icons/oesterbroavis_dk.png create mode 100644 recipes/icons/onda_rock.png create mode 100644 recipes/icons/onionavclub.png create mode 100644 recipes/icons/open_magazine.png create mode 100644 recipes/icons/opindia.png create mode 100644 recipes/icons/opinion_bo.png create mode 100644 recipes/icons/oregonian.png create mode 100644 recipes/icons/oreilly_premium.png create mode 100644 recipes/icons/ottawa_citizen.png create mode 100644 recipes/icons/outlook_business_magazine.png create mode 100644 recipes/icons/outlook_india.png create mode 100644 recipes/icons/padreydecano.png create mode 100644 recipes/icons/pagina_12_print_ed.png create mode 100644 recipes/icons/panorama.png create mode 100644 recipes/icons/paperli_topic.png create mode 100644 recipes/icons/parisreview.png create mode 100644 recipes/icons/penguin_news.png create mode 100644 recipes/icons/phd_comics.png create mode 100644 recipes/icons/philippino_star_ngayon.png create mode 100644 recipes/icons/phillosophy_now.png create mode 100644 recipes/icons/piratska_strana.png create mode 100644 recipes/icons/piratske_noviny.png create mode 100644 recipes/icons/plus_info.png create mode 100644 recipes/icons/poche.png create mode 100644 recipes/icons/politifact.png create mode 100644 recipes/icons/portafolio.png create mode 100644 recipes/icons/portfolio_hu.png create mode 100644 recipes/icons/poughkeepsie_journal.png create mode 100644 recipes/icons/pragyata.png create mode 100644 recipes/icons/pravda.png create mode 100644 recipes/icons/pravo.png create mode 100644 recipes/icons/prekshaa.png create mode 100644 recipes/icons/pro_physik.png create mode 100644 recipes/icons/projo.png create mode 100644 recipes/icons/prospectmaguk.png create mode 100644 recipes/icons/protagon.png create mode 100644 recipes/icons/psych.png create mode 100644 recipes/icons/publicdomainreview_org.png create mode 100644 recipes/icons/publico.png create mode 100644 recipes/icons/quanta_magazine.png create mode 100644 recipes/icons/queleer.png diff --git a/recipes/icons/oba.png b/recipes/icons/oba.png new file mode 100644 index 0000000000000000000000000000000000000000..66c8427f1bdbcb83274365830c85feffeb576700 GIT binary patch literal 760 zcmVshPfScxQBhMNiiwAfi;ItqjggO!mzkNEmzSWQov^O1z`nl8$jHpg z%h1oy(9qE5008I!0O}0@>JI?x3jpg30P7C`>m~r}CjjgS0PG0>>#=`uzO-{Qv*``T70(`ThO<{r&#_{{H{_ z`~Us>|NZ^{|NZ~}|IRlle*gdhHc3Q5R5;7klhslKK@de7cMIs;25p-@e^F!XF_C1*o8N0$>L-brfwtCuw|RMo-JJ&v z2bd5u`T&S<M2Ku6QW@b=? zq{*(Out*koilY10&uw5*WR|c3LhOrlLj-+UyR>ScE>%o>3No_lmOQB{|*k z-z5Ss1pM07_dw5A0I0Pkfb5urzGO+Dm=c*sDImzbNIOK(ecFW$+)1Ntvoc@ic>81% z7?gsX0{|M5q||KG+G$*?OT;BrBV)~yYqxr3^Vn%M3|qtMG>3KT@g6x60Pf!EijL`L qd)#~5k8$Jv$HFM}>&hkg&-wt{%b@-PhlF(i0000Gk-y3K=2q_eq=B>=uV0pRce zkV99T&j2uy01TZ60G|NBdXL+Wj~@VFnT_>Pq6y%z0NWc}XDV@6nnBmMM+;Di(dfjO zow1>7LfFfevP^A0=cG6z{&iJZcW2M|*!YZWVn+7Y5;bd8OabamL}+*9HD>=uS+HZ^=z$Ulroua*|0 z5~9_3eyRb!yo%D6p~1+i7rzL&`nQDD{k@9$`SiE-@EcJ|Y;VJqgq-Ha&EbK2<07Wz za$ZG=Rxtaw*wF0e#-9b;)xm7>yC%Pq;*|k@h#QfOp9ch$Wh=No^oJ7p+}!-aLfFd+ zb)FyfLGIA>7wgQsu*i^xPXm&+ck2b*wS51|-k#F#4$nu03yTYe$v5~f${cbt!z#;{ z`CnOBTug1Kl}=CZPm2E{pY0qS*%r+|nvtR&com-%KQS|7mX<`3`#%C-5(9 zZ+$;9j82GN%jYE2ywVQgyzcLN()mFrDkP(+zPO`(do=%L-$%Whu~wOPs`@_ej0xQp z8>}+0Z z^B?KSs$Abq;en2MS&22TY6tp1f0>$`of(tKiaXwO%bscmU0cq%vP&4Q6UAQ@c&&)I z1^R73$?1q0Ve@TeBULC^>0y4#3-adQ%(VKVAp~?<5R8Z$HpZ394u@UdtU4}Jc$vym zqf!myZb!q7j}h#tXMYM7CSoKpB^K=x+3PMpm$7N~VBu)8Xsd5YgYi0@Mh!ul-7wB= z`+F1hM&+i`!&OxcDHnF zQxwW$jn)f|I%1I|NJCEz86lk^J?E>`R1>$LJcW0X!@MJ^cMLTgwAUkbTuF^Sny{}r z0AF)U+_l@5e;!?{e`b6uwT4)mV!}Ib^ik@#t*joWnLiE;@H)1&Lhiu!vWJ4cqCfRhVZFnJU3< z;ZC1cCjIH&G_qfr(>^#gs@+y_>7o3!CP$qm9Mx(w8MR|R-eVo0U>uV6ml?GjR#7_O zn0T3|R-RTxj7))DNI&JHQhsGpn8Uo8Rf(b2C#}c!28PC8c()_u3ka@)}j$|%-YCwqKcYcRxbu0MO|LFy+Jota+B(aQ^>R=T># zPSjoMcdX6Y@ao)`**C59xNf#^ZXMQtQq;pL9y8#Gv4vJt>d_Xe4T%E>d3?jU=KB@Onr*#$XQ;iAN%Jd#U;VDR85cX)ZqhU*Pzx zdo5I8y41mo?!sV^JSo7!jYOqblBhK2i!dBqAHGl500yLT@YQE1q46b|OfV0hpW z2o{S4r!CQtK{TB1N$m(4fds*ZB}Jku)6EHwC(xalZWMP09&ZhyUI3N3_yq9-^Q|Wy z?@FOjT^RsnfL;we10iPpcRK$tCun4b>k`r=)=`6yjo%@bG&c&v$AbdCO@;7qryi2n zTm7J8NrWhX(!-!pC>$DNaOZx#7(!Nmr(;X=r2wR{u`tz83Z?A7OWE0202kUN3iN!0 zn>*tYor-WFdDEQf?p}z07lRF)3J_W%F?D1%b}0001wNkl;sf5&<1F405h{bfEJh+ yo&eyOAiXZcz?VEhwUM69tV-%$C-|H{@H_xBhA-n*TA>HIW;5GqpB!1xXLdi zxhgx^GDXSWj?1RP3TQxXYDuC(MQ%=Bu~mhw64+cTAR8pCucQE0Qj%?}lpi<;HsXMd|v6mX?QuH2l8TKVu0lI>C=&9P?%@Wo(1whfBp<4MMOlz#KgqK z#U&&pBt=CfB_*Y#q@<;#Wn^Sz#l_^r#njZ)G&D4{q$PEAb@lZ0^!4>^ZEfxB?CkCB z9aQ8U9UWaF;}i8Yl9Q8D zQc^N9G73#~i;Ig(N=hm!DypigYHMrj>gpOA8k(A#+S}VZIy$<$yZih5Crp?ybLPz1 zvuDq7wVX3&&b)c^=FgwMXwjl&%a$!)zI^4%m0P!N-L`Gp_U+qu?AWn$=gwWbcJ1E1 zd+*-82M-=R9PE7b=+R@xj-5Dh;^fJbmo8npeEIUVYu9evxN$4S>+apV4<0;t^5n_0 zXV0ELfBy32%QtV{ynFZV{rmSHK79E2@#CkIfbVHRKYsl9`Sa(mU%!6;{{83ApT8Nw zfB*jdml^UOi2nb#*1z!*7y+UsL4Lsuyu#XgdKOm6DbtoLzjXWK=U;#R{X0JSp+5ry z6N{&dV~9m>>*Vz4;6Q;Ee#ImoWx@L`NevBbno3Fv3T6r+6P2{2tb~-37$%;Zd;eW~ zb?`h1`RA|Cyt}#CI@w1~uU}uFDd52K!kPOYerrFe{HA>R1<`v_?bW82ise^)3={c0 z^QQn^kK{ujZ68FTLyAdt2gh`HvrcCA*Alcg~r* z>FyE!Ey4%$drsf~r=><-li`g#dqK@sF~MnDg4gd2>D=q0 zBCoM+6_-*>nIkB+)ot?A1@k>q6;^MbAbcjf`aMgCwWriuE+6Ty<5#$ym!@{=;>SZ<5RcP z6gzXLT44jlvqHaD@;^Aaeio~ytpMw-;7fizOTNt5u)1K)i>4)u*KV!#yj^(f+!w(= zN1vw|B**_Yh&I6xt@{F5^c-<7FVw`)J(c(-KLprsjyFbZ<$Vb(ygXP_udw5 w)7N-+Pg8o1P1E*M?&lpnzxEexKynR6xa^r>mdKI;Vst0NijguK)l5 literal 0 HcmV?d00001 diff --git a/recipes/icons/onionavclub.png b/recipes/icons/onionavclub.png new file mode 100644 index 0000000000000000000000000000000000000000..10e417ef47ecbad542b6a8e3d6722dc6c650518f GIT binary patch literal 487 zcmVo^W%lgE1{_SY9rbHTDVj7E=pf@TOsYUyTXf?v}N`->S7b=oPE@ zh9?>{l@uMuytFD`!jYU1JA~^cd<7oKnIJ75YssU z;A|p5g%ddeHeeW1R|oO3@<1oxJ6`DHML1;?ChG_v-eT|A=8ngaHjfSoZ7SB(7m17yqOe=h{6(# zTZEUCVbuakDkE#_$OvFu06-UtwQ_~Fv6+&{u-yE~q!hHUNdJxY*z|#EVg&tPg&q!tR(=Us%+~9}4_lvk*cWSq~Rd-D&z>8be7gqA!@Bw-#r z`{*|Q2 z5_Lw7v3IAJ(k7t$yq3Z9Q#*DMJU+_e;`s$IzF1KSn$N~KT)d2@>*$*Q(?HN+kN;F) zZtlT>16UplD=enP5(F>?cPYTTE?w8hEQ~ZPnE)nbX{A={dQFxeQ3 zV>)y=I5QV*wb$#nSm%Co0vCuOOJp1ZNaYIyC&R(u7ioGi-0;m_bxIn#A&-_w;rg_q z9!42z3WtILmn&#^RVbhzfTB}vTm1BHe0iNBpKpahi}A>lkLVRv`3wqjsWqrtt$J-XgsrQ z!^2v69mK~=%eBp|WJIqLbQk(jBh$9D;N=x?MY39KK65VMaDD;eymiai(XGkKfhK2Y zRa>nlLC0$On}-t4f6j4ICauL^P~VzERl711k^EFqNbL3eP3H5UZg}qv3c-Zz?oR0 z&S!uz(Bvdq`LyNYd+|+nj{OsG)k&!&ze?Y>g?@6AWqQ>%Xfn(KU8}I)7+qe|DjV_A3QbBH#wmVyoC1Pfph?d}lTzSa~_&b zkn3>BHgtJ<-+ml+HQ?s6z&bF8T>t`%ZR8tzc9>7U8+N-Br}yrE_@f`2&tF(Vv5?CX-Ks0- zRy*u-N+^cph|+RhQ%h`R7I3kAwqB*0`~91Kn{CZ51a2>BQ2XCLuneBDoO#ziI_e%D zU$bYJj9rVCb7zgcy*Qf-3kP7CmASg~Ib3yW7WI_UeEJ>V!ffKwUk-(WA(t!YaKH(< z-J!@#$E#b&?Xg|Grfb{EtRp}NsslY`@;KWf5NR^9(3}m%?q@9ToCaZu-9PY^wk1X# zur21HYg5nfgVF=JqP7J%UoB*u9izrDg`%3M@rq<;I@oW$blEra`0I^VU}3>NGV<`q zQ94os@Dl{Ayoh086pS%m5s#N>9odt}I+RlgN-%r;XSPf4JIBUcpWRy`NH4fYt!{~= zT>l*hKWVvgZT9tF>N>md(lXczSPnyb#ySGJP#m+4Ys{~-mNCC&JUp`B$;v9KuHM+* zG46guKynUC}>KdD2 zydVsky*_H+(=xH*QbH)C<#Nz9LQn&49vT+4@0r=Kz3I)3+UAz1eHQ_GG{FA=Q)?^O T*l|)U00000NkvXXu0mjf{mCag literal 0 HcmV?d00001 diff --git a/recipes/icons/opindia.png b/recipes/icons/opindia.png new file mode 100644 index 0000000000000000000000000000000000000000..1a4e4c37145c0a35ed258a730de8f70f207a7581 GIT binary patch literal 1072 zcmV-01kd}4P)QK{h@aJe?$EI{QFS=0002^`1o>ia<;a%fPjGO>+1;#3H)#_%*@RFp>^Ba z+y4Ik;^N{tIXN#cFB=;hhARO3R0RE#VEmF`{jZ7t>frzQ^!=J^{Btq>)x!Vr>;BQa z{@>92c{==oNB{cx{CPY7`}zLi(*2rh`%wY?n{58Sq5O0+)KeBbBpLmeXZ^R8`&kV8 zRR;LJqx^L>{ESxpv5o)w_xoKE{Dn>Yid6i4LI3vh{DMgQhfn-$C@U)~>PrCpjaU4H zOZSZ^2nYz**Vp@95g8d7{iuM*K>(AJlQ1wa|Lfrg1q7}^4Aj)rxVX3# z6%|KEN4vYbQc_Y23k#b$3l9YVhK7b{WnV?( z8}#n%{C`C7@bGtccj~8y4-XIgZYoYrPIPp1|NZ?E5)%Egj{J^VAt53Bd_cK804XUc z{@BH9YirNX&jJDhgM)(v1O)%>Gcz;Z-roQC_4W1j?(Xisn~V8S z0Of5o@{L^li&X~#0RH9KHW2_d5di#rKg2%({?^01JOF7S0I)d#5Cs6PH~{)l0QOG+ z{@2A(8UO(R08JSHMiv15qj>aL5BppY{^r{+4*;%t(ZB!z0gXvSK~xwS#ldA;Qvm=4 z;6t7>y1~Q*Q8B?*z)r+KMFG3JJ80|{1OrS=#12qVG5(d?bGLafo1b}Kbc84McOO2A z#(zZJYPcU8cTe#*j>3zAE%9|R0G@_C(B<7|Z$U_Y(BVtBAHy#AqE4)bRcP+K`GvIe zyAyd6$E4MPFu7i{fBQnZ6&+tIW#gY$jaRGo&}`)r&4k2&aiq3OGtzpxsZ&MY8+ zei;}|hb}O@c&S#>7Z}GWz*LSMgW+`O48t=if%NemPx{Q5Msnn^G&*#Jp;o#^FFJLD z;ed3CUL?XPNZLzJJ-WkbhqR3z`u2j$25IMtrL^)-hMR4pmM>cqd5C6%V3H@T0!q)6 zSDXwFKe8@|cKu)yxJX+M!Vpuexg!RX><^RKixqYu6o`&E8y346K<}i1sjvv%wC{Z7 q>9V+z2pIYdNP(4Y*SRPgI>KMl30O-zkSo#v0000EHxMF2u}077>FLU;f|dH_Uv07QELMtv1Rb`?T*6+(CwM0gcM zdmKV{971>^LUtlTcOpV~Cqi~7LU$`bbt^)4D?)cJLUk@eb}~S8GD3DpJ#bPzZ&f{T zS3Pf6J#bk&ZCO2TSv_!HJ8fq>Yic-WYCCFcIB9J=YHm1Xb~t8xIAwe_V|_Sdgf(K0 zGF^`~Uy?FhlQLSEE?1r|SD!9bq$^UZD^9K}POm3QuP02fCrYy?O0*|PwkJlmCrY^@ zM!6?Ox*|loB1F6*M7||GQK_KFo1mE-W}Aq2nws~2ZcUx`3cJn59b)8)tgT16_WZ`rj%u) z(JZ61oQVj~0zimlT7hH%(!T^kd+?-6*04O+Rr4js26G%}7vDel0gCsa@s-6yjc1|n zBGnewit)zNyuUNk)aPw!g0DYq5JHMYLh{W$`#kWGAHUUgty0m9(oQH~2V8^}H&0Wk zlX%Pt85c2>4002ovPDHLkV1lXG BDT)99 literal 0 HcmV?d00001 diff --git a/recipes/icons/oregonian.png b/recipes/icons/oregonian.png new file mode 100644 index 0000000000000000000000000000000000000000..2d2b43981102bc1796640cdfc77fc49085668eab GIT binary patch literal 784 zcmV+r1MmEaP)r85D|%pXb}-HG_w#1xr9ixkkA%0SB~e-`McfwuFm<+eCK`U=gj%0X>V_@i8@IM z*$2D;o&pcGHGi^ln(77M%@=_qK4gI#+L{k(dChe)?*ZOM%mDOsVZd8)g0hHpwAb=9`6tD~& zkI;{4Yj(Z)GH@)#EJ>qE$nI1CCFD`uif)gDOWK-W{`z5!<`qq*0sxP3pHM|Rz+B|M zJmL zods|>=L@$B0@x5hNO`y19|ZwEh5$kfu2Tt_we^;309XkDd=E5i0ZwMTY+75hTfqD; zuwR%&0p2Gh^v3R^>!PHsc~Za}U{PDM83K5dkT6iW@W1MQT!}TIt(hul4lonFiO%l| zZZ6|PKB$B|T2kb60($Ia{^aOPHJmzrv1`0Pew|}A>b~NDhBw5Td)h2 zgN72a4S0sDs@8yU-;q@1J{$Ui^|A-)!d1>~xSj1Mu<4DTYin|sasL3~N9Un|DJ<3i O0000(*IfYM-+TG;!EX$G% zHV{Do2M!##aK`>m;7SC+g$oxTSR!l|BS$E3z}WK4pk-RKXnIk1S9Nz)Wo@}HZ{_>Y z{^o}tzT5fXpYDJAy*^Ck)f|}ZAcSnrh>{8;tqEcv3&x5Z%fduL0>liY0AMmpqfs!Q z230sWf~dI61CE(ZYN0gGa<<@{6xw0RR7wHX;Mj(XOaP^X&{`{vq{5&80T>ct0A&CG z*2)2mOhioW5l0DMq$rd{S`ICGS+Ol>=c;k!1%=|X)iRE<`SVG`57pqu^A}K^B^kiPm;))7N00NC#;TBTokfb0Y1Qu>O)S<36d0K*jWnKzIz&446Y!lN;Xr$9HnMUO>PELaC z_#$CxW+UEg60;DJ14RT;Y%mFiA~4JJS_M%FK_ymbm?uWZ_Pg~)y;;MAnK;u@C50s{ z&+&+%vp5}|4BxyukFydvZoy!drb!evZCpr03t<{yV}gl6Ooh27aK1oP>bC`~!R5I6 zd^jou>o=YI+k>smE~!zP=VTc5DRaC8K4c z<}0n3c0E8CBm#_pA|L>cF1bh*C{27^80z)O@>lcd)5g)GgWhl4+1}piLWhQ{;^OJ+ zH!mm0C(}`qRE{0n))&{)`V?1bQPWl?wGEx ze$Xxb&RIbI>-p>_FOSE;^tNr^dUzWi-91kE-~ZDu{^iHNI2#AMyZhh$qmPWu?oWPo z_|u=iC_`hzX|KC>$1^B}49P(8MIoyKkqQ&Iu~9ao3B@HX?16WyUb{c5Km9sColK%- zo@N5-Eg0M`ru-`7*8rOUx9;tJ?|0sN6NR7r{EJg92$MT)_r|81$?A&>b$Ojf1sBM) z0Xq~CAdqACRCN#Y_(4&$T=t!NZS=vt@8HaXXk&j1S`C1jPyB;UtHLnY9c&-;nqBXm zmiwdczjtI=EeE}I;0*?i;k^8OxMZnt0J(L;wvc6N0@1t#b5z(z95yuHqHL?fesHJu zo9H&D!g{d5*BU02R#;J*KcRtu~8BIMOVTO;V>BT&W628@G z_nU5k%1Sc=aZrMP6?I0e##{zY3~GTaRlik7ojUBVnGFhR#ioF8?C2928Awqu>G+x5UfXql|D%KQ?D)k`tCz$1tN(ue&UVxPwGAQi zS)47B%CzW4tG?Ez70M?`R2CJXhGilGItA1O8d;tIcH)t3ZmnwziyMVU`TP-@C;J(iAhjiX(5zQT5G@v5vaycC@5`(sB$Ps z20?%VAW})wmD)0+&^nFIUJU=?A3yuMPcFtFH`*qz(U?h?XLecE>cm~Q<2lP%R&gBh zDiyfGCP4^kMMxMxYD3k>!lulDOhOuA?c1%c)$Afdfk)Z#`S9@cVmdDk+D6XJeyh>1 zCkT(HnO0TTcXziMSryLbEM+i9T&LPY(KcY@o=NlRpw$@z6j>SII@Ylwb|O- z>gNJ|_4WAlY*})H86?q6VQFH70TXb27g25Kp|t)s?kC08pF2S_TbvyZ7r`ldu)Tlp z#?Ibt8xbpC<>8eozSYflgQ>c%>vpyzP(Ec%O2-ke-UKf3v?8*ew7-7xRb znl-RIkG5LP<;pY*$>sLFgPS+EEMDYKuV0>AKmT$>SibAfCUW!Ky4ErPUJ(IWk$nGs zqpr<)S`Py_oko`*pDn)nk1xf8UmkS_1Anu*{r$IfDnXmXvg-3?gDA>bqt|3b^8ESv zlf&WvoiB#76;y1kW;0?B3u7#KA~`aoZGfTw>@VpmuK)7ioB#IL^w~f2$){XTi=5BT za+c8TTX*Z*cX6jFERDD%5^TqE9RtngPoBK_tN%Rwo5$ZgzFd|CZ<&}l&IPu(~Y<6QTFI^+!AP2kX>r`)Aryq0mZzE)k)Ig@I$q;r{_!W(A{t264my0000< KMNUMnLSTa1CiM>h literal 0 HcmV?d00001 diff --git a/recipes/icons/ottawa_citizen.png b/recipes/icons/ottawa_citizen.png new file mode 100644 index 0000000000000000000000000000000000000000..7ff3ccc05b661a20f55e9e93f3869c4a6f48f292 GIT binary patch literal 1208 zcmWmEc}$aM9Ki7xHjKhRqZF3`%bbRlD`FfA#u-72!ej&oabU+*FE)o$h7P83qsXBh zqMYR@P@uGZOAF<&l$O5qqNVo@v_&eXs9Zy)bM9&K`F)>Xp1+?wC2_GQ-CaL%g&@d1 zItue0I6rgD)(=22q*4($wxvZSU56kKfn%I113kPU$T>PLCLuC|{5Z3-^3Tqyn*gQy z7PaOUt@bvpHj7%D1=nTK2{|w!r>j1fUZ3AXEMPSk_O{$%67PWawcceC@3LC%_O%ub zwA}-qv=_5GN?D{*PG=dHg6Gr!;?v7{aQQI39O)__VLTjRR3MBB0kcBb{fJF{B`5IUXV)nQ35r32l2Z?;fT0E@rvX&OZBTO?RYQ#` zZlijrQO#{~;5BJ@L^Y47;WcZ9TL3M;RnXt6<&(5X8=ynlbtCQikq*6ptQV3E!cK#b zBH>UBB8ovwHHxT4G0j0jGl^lN1O_!p=(1tDUQ9Phy3C_pW+_9?XP8GBU>TP(!fU}ifvU7YNdmqHVwz7 z=GZht4qC2W&b4Z|Q(EqnjyI(nehTRM4h966HjFqJ1%TZsw3|eeS`nz-EPiH|JR6tH z028A#e zd^u-%Id5B&r_Ut1mXzUcFvl+1$AJF4h@5 zDpWKkA|Xt6A&qqcI*AVVI?wnjp~&fYjq|=esasMn`Jr(Kiu^-T@=N@kP~;G1IMfsz zlfU<9!q!NqM?Q%AYK+OV260dOQkYVZw|D0bBw?6zp9c9Alk)VQN%{WXMb76^FS;1c z&%4&>;!xy+&HlL1OpMd)64|vz0G~!Bo(gFWP+`6&KA`07l5ZjU)}vkb!D?{7ClIIk zT>!CTqFlp%JAevH=c7X00unDsv9gc|R9a@#7m5{s7+oCbA4Kc(Sl{015g^B-)!ID2 zn(hxjm09A~UcXT!TaRY9jcP(u-X8M`a*OpiByl@K@j27I{#bnEl>fJi**UH>)&V73hTX>Re*cRD> zb2<3N*S7)5Jd3@CPp2jS+`zKQ)88T);?4Vo$rPN+3>AjUkc`B$*YGTQ^4tJ5An;f0 zl`#@24>^7&Al#*B<49%XfB(p`|Ml9P)UPP{shW$ hnCFh>9SqwELGOeA&h`2Dc@FrBpyZ62~N)-*U)pn9h=HcR; zYj195Ixa*v^M_&XJ?Hz*_dnnBGQa=>{Ljc`7pmn-@>s%Z3}XSu(16L=+P_aA8qVQT zY>EF97(pG=_y)VM51kZ|)A0z-K)Z*n4(6~2A7J)x0@3&?Oko?wPeFPbFX9y(k0d6r zyN_OiMSOxs@ogbx{F!)*a-Rd%;9890^IEgMc%#+$p;3+X~ zDH7XvjIKN1^0+tfByPd2xDk(GJ|(jPtMb^2^HLjiJdPLe!{lsj274oc=b~p4T)5Eb zT*ERCBqW;A=m{Lo=pMp>qLA%rRPi1s%WZPDrdqDF@j7nD#zeToW%xLA#*h%!n}iUQ zaFq}WkH-cYL%Mch}Ul=YOY%Qea3WDpqp^j(q2IQk~8olZ`K`Zl~ z%Z1$R7KUyZ!QHqIBSj%LX)kPyLUUb|SL1>u);WBrHQ%tbt2Xf(_6wKH<6_()RKi7q z7v_ujPKZx}O}H7ms^v-&|77n@T$v%Tw;c?o(Zsv>D797)ymWtjUx~Z&cpJZ@1Xl`8 z>0Yd@mMcR-i=Dy~Lir?^F6DAnU1mxbjKs6k7%vK0{}vujZEO&-bV_JO=i(A!FePYV z=hkCC?#KGf!9&FLbWmcpqP) znHtAqf)~g1F5{NaNA}?}e1&GO50X6A;%p%lCxl!$YUy`Cf?Onjs$^^nqqtw68DM|` b{#*P7GMUWuxPB)A00000NkvXXu0mjf+}ebt literal 0 HcmV?d00001 diff --git a/recipes/icons/outlook_india.png b/recipes/icons/outlook_india.png new file mode 100644 index 0000000000000000000000000000000000000000..2e57cc7ed5354ba6e3fedf60ae0bda135db48da7 GIT binary patch literal 1891 zcmV-p2b}ncP) zW{PNh+)tlARnwO*yW}&JFD>QL=4J+;AvQEZZucth-^RvfQz(=}!C*dBR*ullol|UF zh>eU`I(X?RJ;x@j&CPUUbkt9Mef3&b*9m(2_B#zqH#Rb9bu|mWi*lTU0h$(zBNn0T z>{W3hKYxKycxAM-+@oX1dNrSK%0iTN=}F4tu*d_?K7QOyj~_SF{rl&ryZZ-PT+EZb z5P<#gB-Jt@jZ=ySA1ki8?S)!wI=UUhfhiBt=EDx)pOA0>n?B zeowb=Utt8!NR}(eu@4EU1gppCxiU;46N%_?B%%`5_V$ZXB?k_?HVwdLwUF8E?HB0o z-HS9jT5dt8l2Ywe@2spf^7+PT$BwU@{TmYF;o&N-f?P2KXYt1Q%*+k03J;&vI42HB zesOi^7W9bwix>5rY)v8r3y>p`;JFGFgdzK}e7*^~di8gX=^chPL=PUErR&#!m84V1 z0fA^V%{qgHGJW&rARI6az!&}fb%atI8>_H3!h>iOFsE5?BNV3hA*!wYOJdp7^k-af zF5S5COVW9ir~RCnXIacAa5AP_-gk91Q%A>Hfl;l9ElEvdTmpY?ZqR6GcuuEIJ<`g` zCX*B@L=!o&wM;{yY?_?(C72N9F*taL8P4<`@dE71YP$hg1qJie-27Oc{QP;F!LFp7 zax!dUU%Diu+r6$cqh&MfWi&Ola|@Xy$_w5lT*=E@&}wR4QccaPq%+}gI4k%UfhdoYA?-c!dA$b?QVxEmQ|sucTy} zNh)Hi>r8G~yX0}^QYtwhxWdADDlMJV#dB5f|HA;Iq$I!w1T^--`hIoQqkZ_Wm+)W+ zapr(ClsqeFrKRJvXU~wygsOXeOL-hRwtNmvgg+;8AQ;S*-l0kvbnSEsI&7hoT&k`8 zTQ|JkAVwukHJoo5!F#ma+$H9Lpduy%lMy!-4V9Ek!A4RJ*bVS{7uov{%p(Ij;1nvh z8;}YO)m$)YDPao>dDPo`RA@X9DEZei#{a6y@PBpnCqf9|lLjb2Aa3{Ce;NR)#bl%w zmoHB`mw0}-Ow-fJ1~|JXzGI|~y1IU9Z0ykr3m43h?8uP;j7;p=T1p7*-YhM7si&t= zAVe8qczN=qnTf32%5HlDb2_qp`!t<7(?Q3NcZ1GN1FEVQ{@&qi0djTHj4CtH-)rfBJ0GZ*>0reGbEny{dHS&A74D*Y`^@PE3^2!-uDN zy73CfD+HWGrP^y^+A$=gYHwE7DzoMk!#&8(wQ=kpS;`?7`N9V4R+~hlX=-{VcSYuC zt+n+>&g|_;Q*rVH>}*;wxeH(qN)`9Q6DJ<=MdS}jbi3zJZe7jP2!(@1MRRoebSF-3{k)u?k z&mTSdLWrWJrJZlW`)FjON-iiU*X3o8+!@Wx4l^?@OOhdv{Qgn!JE3F3W88}EIa$iAiQGtMLFT@Kf(21tQSpT@8*c^r#C2S^#&KL& d?E`Ck{R7XvJLd|Y7m)w}002ovPDHLkV1k-hfhqt1 literal 0 HcmV?d00001 diff --git a/recipes/icons/padreydecano.png b/recipes/icons/padreydecano.png new file mode 100644 index 0000000000000000000000000000000000000000..141191c35f1feda7db99b196afdac50694eabd69 GIT binary patch literal 628 zcmV-)0*n2LP) zg@1!sD2QN_LaZ(PC^V!{un-YckYK_IBgT(HEJRMQ2qGduLI^V!dke?j?7O?S8*Ti- zHt*+i@668Z2^AHb83x9H{lHMiyac=k=EVATm3mU4@dR)II8YG!5||e2A2k9HoEZde z0~2KmcIgQ)CDt$X4G^5!20RD0S8SfD4IC2dKUD?@&TIl+0XzF_-VHG-)_;pRjgoky zf#v|iBuf}obOOPdagxzDsY$W^Vb+*8fr|yjTEJ0Y3$PV93A`)FIUh6jOdvS38ThKe z%>ZY`dY{SL06ai{TXu-`pSumQCgV!;HgGQ696+pJ1wf`vl>DCBF@T|nE!A7am+Fn;8!Q7bpcLpCeMGAzVpIT4yGq22D88R{0Zs*Ha&v++ z1HhD$|GqB3Zs3aIpGKd+Le`+Icb+Zk%+O<)!{0*sP;?QHBoeF5fyXWE5T7XYz-8MxnhYU^*j1^)pm18Dbo z>CIIK;Au|m8j;&B&U*a}S;Rrs)$tO_lz*}+(_r}(Qv?GoxUSVlW z@~uSTmYm|}OBRlOeVXWPjRH2+SiFi~m;^oX0nKp z9Pcq9!KbYv?2(gkq2`ibprNCgrEFym_8#qz;lo%_j>R)-vmTfc#V+Jv>$3!uy_a9$ zS;1J^=I7wvWd)m*--NUAJ;I&ycT&BuPFR*K&ckh>TI~#`V3BYUT@^OwK;f*f!RUhg zJz2q~9JO0Y2pW-)e#+rU`?friS_JgPcI}rV%fDi6(JVSEU=;3_DEt$LFg7OmpKjQQ zD;QsZxhZVK5m<%=xQ1hxmX*+?+!v?_n=T9c02>mnwC&PW%xf}tg@?yk+{(cf`D5IVVowRru+EZQDHway37*}QyFg;!TQD{V zi-q+q;`oS>aJrMlzhaEgX0twXn@twldltuc%p;uVq1Y(Agj!SnE{P3EFhdgJgSa8Q zrgvZo`U(4@1$R;+>aZHiF)k^55u1gVvP$<~vu*)hE#MDg?TEdEFRvj00000P)$8um5SOQ)E_kej|1IU0aUHsE1>{jbb{{%Q8elrEI zwTn*sVt-Rwj*Ije3u09!H>CB)aAJ*Xi|KDjzXTlB2z)7pNl}lwL*MvjL~mXC+Zuuz z@DW%5-UAI_zbqc|k5TAZ30 z?V&iLkgUf5RDlRRQ5~Nl{2bNx_ZY%a0Mu|tm74X5j9rlbK7&b4Z6`n{1Ynd1=nc1w zn9BEM;EJsC`B2*n+X0~AJPcgJ@p=z;=Es0@z-i)4s+n*;WIV$WO^nT|l2j-uDK9*N z@GVUu=vCB_uD5p}^J)x6_GZX2I4bS1{RsG&Pt$!4+!8-SKovk8ml>-7lxqQ7SJ)!N zwj1KZO97TdKK!Q$^npd-4sf!(^PSv*K5$ilPRf4`cYF!1@L0wbJRRVVaUP_3b-EY$ zgg*j$ySu=_l3q+SWe-Rw0+YbL3gan7>a7t@O!jGdjraq~n6Tq_J-TB_0-VKBXb)EpUIb1Nr&7%PLjm%Q;sU+|o-;^LLHWvX={4%p zE3ec44#jasqodhNmG#_0b%Iad>gh$B0uGE~phi(rXH{qeVxLxO`UIfaY*Me+hm@@N zj$kLOmy69Qu^-)3Rn4|-X*3#nx7(%NZqsVDSX)~oO@~kRFa9&evrPYd3;+NC07*qo IM6N<$f_;2$_y7O^ literal 0 HcmV?d00001 diff --git a/recipes/icons/paperli_topic.png b/recipes/icons/paperli_topic.png new file mode 100644 index 0000000000000000000000000000000000000000..c4532ba7579b96619ed512935995efb20d90bf16 GIT binary patch literal 409 zcmV;K0cQS*P)qH8K@8-yxcbIzzYE0ozfoR`X z;%n_|7tjI#N&o=b0f5xn?papv-Q1n7^oa;)?7TF{?(!oo$^D*)fR7Ve%i!tZ;FiX6 z3*K-GFCWnI7#@xovNi=71cI418O$SrOpAsazY#q!*CCrph|*PtJcK_C(jBsugods$ z?^2mLM?gA!|4QROOOfW2IB(7*!pN_vnxEX>4Tx04R}TU|^o$=;GwcB*4H>P*hUn9^@Dq5gDbxex8Aiftdje5|fJy90LM; zKxTdhXgqT*)0qVOgts26(GF~&iMtEMVaXtB?^X?mX->RDfvmM3c;1dC8@c^ z3Z8k%`9%f!MTsS;DL}PQJ3xTfCBH<$Jux#+!N@?BM0XPepm2n+5aELmLxAMcqA~~z ztVn=Cfx&>mfgyk)k)fVp0mD&-H;m$pE{vs&D;V!Fi7^E+O<=mfEX*9qypZ_~izQ13 z%Pm$t)>hVgY?f@(*uJrcv+w3m;^^Y|!kNfFNw(Qwf(Vw1#;#IHzHNXkhblggKtmOddeXkJ*BtGYgGzVO;mrX9aQhq2-Q^5{GfF}dy-D7u9cpk-Yfkh1`7;pjbe;# zP2^3PO<$T_FyCph#BzdFopr8FoNb_;tG%^@v7?TYnzNFNf~&lng1fSZx~Hy}iMNf9 zyKk^xf`5KMec;5PrNO&GE{47gXNr)Ew2F#|u85f#yC?2J0$ZYPQdn|B%9_-h=^Pmr znVDI0voGed=h^316l^MdSFBxb7qP0ll{*!Ux>Us_&Wcs{JRSunm(C+e)V<1 z_wb(rzfS#W{pa}qKL8B?4>Sph2oC@N010qNS#tmY7ZCse7ZCxn4spY5O~FocMpbw)yiyw=l=hHro$L{>VYhS-v=}Wzvr1MO5XVYf3vXl zn*T2~8CZMJ6s-1)H&;LN|9h=)^sWDQ9%AE9lND7|D zMdn5%`nqOh21ewiwPehjUI7U;p%a_(yYuHF6!drm z+XjaFmvn@CrUVo=O`Ta@UELqxdmpCYLzH(wXpp;ea7Jx@Y(jo*PwB*p(r8!j$uI@y zecXJ4!h_rPJuVFjPM+}o^5hyDmneJRre9D6@B3^cT^rVX`~SEsE+V$^-T$w5ydAw= ztXIR`a5pEV@b;hoe`aM@E>6gK2^3jrVc=%41+L)NlQnOE)a&BlGp}+3KpKB6sOevS z7bz~lY3N=|;fp_$eZcYb@Aogv6m>bj<;(x$2}M6q)8xNdL}ts72F3^@M(|IZnVF_Qi7ClB^@Wcr1ry4W3h@%wJwPwdJ5$Gy{Q@_gN* zdTraE<4X2_e}BHUsG&ZhX35#te{f}-zdv5wy|lKsp=Z(YpE&a7KVV|}_x;hq&0omN byl4ObGM_3!H}-nF00000NkvXXu0mjfQ18m@ literal 0 HcmV?d00001 diff --git a/recipes/icons/penguin_news.png b/recipes/icons/penguin_news.png new file mode 100644 index 0000000000000000000000000000000000000000..5b7656665982634c2ac9ca6b71e5f9a040e196cf GIT binary patch literal 1437 zcmc(dYgE#A7{`C2rm5AeR+(aiIlByxRxZ*yP5Wz-myy?6=`=5;Yi34~H1#hbDs)Ok zvz(T?JT6VsWtMo!k}e8f2IVbOG}62S0z(x3qnCTJ*L%)+p6_`spYyzUo=l?ekyR#} zO#lF^2%eCiPBCBHSYH>R`;6B*F*xJu;|f6SP1EIY!!OMUPd^_3DBl18(*RKGO7Ld@ zE;#@&6$XIwZ2&gL7qX8X006}#dIz}c29W>Hv|8=l+?-OO)M&Kq1~!dKZD!ZK;&9qt zwvCRCE-o&%G&RvK6Jw9>baC2qA}F|}kzH0=ba1cD4%=Tq3x+D?_6^32#_>qwj0bejRH3cW9*xJ}MH8y@2{6I}jwX)cpb>l{D zO^txh&(6wz~LSu5|lj?=?8EDlB)?Hf=e zpq@<-VpL0=!sv4INwk4y^YT!ReQ>1?(mU;S1kyuLxM5vlL?ZaO8;K28nd6-qgp!fC9N^Ulst#tbQ)a1cE^Fn=FsDDuWHR z>p5lsp17H{8nC$Z@SPa;(fg)i$3u+A0N&?*z$G*^&6LLz`dc{mL}X@UJbd6CZB>(+ z7sya(Ey4G&{b^j5ajGohTNq%%_FMO_9DZ|EawXjhi#@ktOi%kr&h%d|Bn}3`f5Fo$ ze2dOJE!M=CaPr_^GmHuiPGYf=-kGfv!pW1uuguZm9cEa*9X2-ogZK7_bX@N-rs3+B zDHtMoT8#bsa^L<#amnu!c9kD?I_&?V>qte?t%3Ta=BuGtq@uun&T&D(Lh-I14&fEK zM2@p5^Olhw$7Il)ySQ|16y>gzk4}2`KJ|$*UOvmGQ#R2?nNO4xflw)>+|X?$T(W85 z5FdxE$$15N<-JB4byu+^xgRG*hLfV4BNC!?0(Lv>cECC8#X0T?aNOtY^qsR4ewV{; zX9tHB56V&h0)9Ck855oO-vC*@r9}q>Qv!~Y{KCnY_=NM(G3TN%q{R3rOiVl}0)Q)p zA7y677lO8L@A!esBYg4$`ZlKa5vJ>G)_Jkk>#xH=@ZPpaeVfMt7;gW&w;UeFietou cJ&TXpgQ< literal 0 HcmV?d00001 diff --git a/recipes/icons/phd_comics.png b/recipes/icons/phd_comics.png new file mode 100644 index 0000000000000000000000000000000000000000..1459d2e4be7487f95d4b1bbcc32f824cd8ccf1e6 GIT binary patch literal 2484 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 K&t;ucLK6VO3N$MK literal 0 HcmV?d00001 diff --git a/recipes/icons/piratska_strana.png b/recipes/icons/piratska_strana.png new file mode 100644 index 0000000000000000000000000000000000000000..692825ca491803927e79f1a2ec848f6f54fd6a61 GIT binary patch literal 865 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0UXt2AgoK0`85sox1fD*9nw*@>!NI}F$yrubcJScALx&DIIyzpwc=6Y-Uw{7m zadL7>NJ!Yab?e{1e|PWRotT)|+}ylx-#!Heh2Y>|9v&WEUS2*vJ{A_1>({T}ym|A< zlP5AVG8ZmfP*YR8bm`LN%a=7ZHFb4$@7%d__wHR26B8yTrsvO}zj*QD)vH&pU%zH% zW_ELP^YHNS_xBG93Ys-*)~Qpc&YU^J%F5c+)y2lfCM+ymQ&W?jot=}DBPuG|(b1uz zqH^rmvE#>&U%7H+-n@CXwzl^6_Wb<(j~_pdjEpQSEG#N2ijIzsjg6I+m3{N(&D*zc zzkmNOCnxv&_wV!P&s$kp_4W15m@z}r)J+x`F2W^2e!&b}0%{sYQN8_h=byd$`t8^6 zzyB`uipv28{aQ~K$B>G+*2$sGs|^I)GIx7NFW6EdwCCNvckk-1-i`gYUq9ugz^(S* zd*=zy@JwqlC^Xbv{d14j@}oNp=1OY1&zTmeC#~E5WXViU8SgCBy|);qxrORyu9P@0 z+;z9CvHSM+sV*ndji-6W@4fky=hjA}%@uZ=xAAFk*gqz#XLviNf1 z&h3kTF4<;3KfGT#E#g$Htmf{=A9p#rnQ?wPV8O<4*>BUoEq1I4`y=etAJ^PvT9AA8 zeVEox0gelQC8X{%-kJ2|ZoD76$by~y$J7}v6c;+Gg!(mH*Rc7+8lbpnLYdT_0QDKZ z4Q-Rfo``)o%gEtoT3;M={0A6RAx{5^W=v!PwZ znXK!f`%c^s?`DkqQN*lcIy1lccKWyT-3LFgFJ5!@!=ku~59b$@J24*C<68eq|K<)p zg=G_Z+xts@mWZl75>x$lC%5dwo5(4fo~Id4-Qp`>v(8-+aL?<#y-Y|KtSu za#P;CRP0~bncUy){PWlC9Tq#Pjyk38sha8(=zVP2G;?X4{Il_KHvjH_-OqaSUO1n{ SorPtfjN<9)=d#Wzp$PyEuzGa> literal 0 HcmV?d00001 diff --git a/recipes/icons/piratske_noviny.png b/recipes/icons/piratske_noviny.png new file mode 100644 index 0000000000000000000000000000000000000000..d991b2a5a4d104c9c9278d0637f7261f8c7e6025 GIT binary patch literal 1124 zcmV-q1e^PbP)EX>4Tx04R~Akg-bwQ5?s=YGLG+Lk$fLH%LP$ge^f!*&u|7m_b&{-Fc_MX~&%s z(b&-H;8M|&MgKu<(LWFbK{Yj0b3;J{A=meLf)G0Oj`w~azVG*a_ud!q*G;YL49o!K zO4ZIL6Y^SqT@Jn=h*5+fVP4T3YcZ2aq3`&5fKSx>m^iC{U(XRL=#B>XhRAc8Wmm}y z3po65yA}a?#_cVAW&s7FjFVWwJ?* zl+BvfxsP~>q*qo~SSQ=lK^93QAVWnBTQE^YjJ3kddi&ySUmDx=Xk0Dq!lt8$61`EX zHJ+-o8q{@uCObGp_J1>VjM=#N2Wc4?e0AN=X&`tEv<_VN=hSsuO(5_D+%&rW?FQp~ zr(W-BkrN=i4_sb%HT4|0V#X8CmSQVyDZuGB4B+De7|8=;%zAj^zx!U^oqaSPf!rRD zx&;o8fGPHeH-7;w$#lD=*v8=i000SaNLh0L01FZT01FZU(%pXi0007uNkl^B6AJ6+dFHn}!NEZazCc5)q0Ex9w z8k7G{h*s%|!a>5z0fAnR`vtF#1Qad-5NWs46Izr_P}&uv^OOv5_7BY7ia50MzK7{grbDQFT}u#r<()DenLFu~zL4?Gxr0@o7> zEmIxK>5vSBuBlvVM?F9%rMl!}B#41DO#(c8Ra)HyT8i8T-`R|_KeR$yCvST*nMfoo6$5S6XKZ%n`@Plu7SSZ8@Ec;s!?{25VHXkXunOP zGrp_OQ*!lW;rmUcjz`gN9gO!!ot+*{9Ef6o=$)zgQb5}x9(&=^zkFdgM1KKQV?YI%=DL{x0000uDH%8A5C za*Oh6RgEM^rQS_t8$IqT^=6V;m6`Z_cokb{s0WqHs()oWp$+^*_sl3(GR zJ#A!L>>8sLTagiZG80qu7(^DBCKh-FTiC{u92XNzFvCF1BNHpj$fB6pr^N_E3^Bw2 zP0@xiIxAx=(PE(7iWx?tL#wnHilH*BXu?2gM1xTn*r&z)IEr|QT)k1Ep@(Rf{ic=FH45#aE00|v11vo zC=;ok9K!2j8$J1(*ibHFfUBxH$$89h1zptCK5a2W8(J)>sY)j{sGcmMNZ!J>n3Aq| zJ1j6qhu>l$W@yDr?l1JVoti^8kX_C-a%4GhdBmll*T?SIijqEu{eY&2E`}B zJ5fv0h@(o2mRd%RVNoHfcYrEuiQ`zqF)YQ0uSML_#S?b zE9meIJddL|fgYFe1b&G#_yHO`iPx}(=kN$N@qKjmH25yQjIZMo9>9zEDjM92Q&__h z+=tURjX9?HGroj#_##Fa;9g8{AHIoK(AnoE{0PsZ#l5%#_v1W1h4=6fF5@1o;a!aI zSG2BR0pkRA| zPlzj!u85qT8!@RcYFbIy; z(O$U;d;j*;r>~!X{QUmw_pd+y|NnnyVg3&26v2`pzhDLqZY5<^eS`3hnI|v2{QBcp z^=y|f3=E87o-U3d7N@5M-!5x%5Rje_6%nDD(3%vzMK{3o|Nq+B3y;n<9lyNi?9DeZ zMv@Z+6LW%QKQ}Wi)ChJ-YZcTMt~~EJ$yw2#>8tHwmYI{E+)QFCv-~%|mi>*X`vk_> z>$hhuJGHc*g`q3$h5a@=WkJEpUEkv6Dzd!ur+K)Xk$89VT(ZQ(56k>-PTZ@u#;J4e z2D`~y(~kJDGTfgm;ud6nXQH~VhihlZ#4A-1f!4D%Exw;en>szvf zgjIuV)#6STFFoG#HNt(If_#OBxUPP{uB`~9c^-?p~~ zCP&X22>Iy5Udg?FN5?$>7So1q`)Gan8~@Hd)cIFj)!+a9-_QR{MO@amXBB9g0E3Ug M)78&qol`;+0Kj(mtpET3 literal 0 HcmV?d00001 diff --git a/recipes/icons/politifact.png b/recipes/icons/politifact.png new file mode 100644 index 0000000000000000000000000000000000000000..9e7e0d8edacbeda3f4037e17d4c17dc3adfa4bf2 GIT binary patch literal 1260 zcmV+UKKZ)Ruqe-ceR_zlf_!>g|EtFEr< z2L5Bv>gPY%WXNuX4;cV37y<#nxd8_+%4N&?tBc}OQ*Yc`p6^6QHb)cZ`B73DP-y6c z*n(qaf80ia9*X(~cZYNu3T=F(#v4GYVDS*2c_Z%Z+UGtysv&!Ca&*U6O=n|gY?q1T z^;KPVFb&dCss<-a=xiY+Zc6kd-sB1WRvF`V)r~xz0Pr?;faKu#zU^n)C#-$;$eW7F2R7>7*c4$3y@gU*eS(0Or+9YTpkNqvrzh4O`gYGs zdx7%>1DXCLlZ#b>s+P{%zFsTR zS^dp-*ZxP*+K@{jh)QnN`US7N?4;!c2pFv-=t}X!KyRLHc0^D@8kzcxGR-O{01VDL z5n$WtSHi7N9~@F+?$qEKObn2oyRZ6F*-LCp!!8pAh}KU%eAi=vl}P7Nkg_T&TrM59 z0zq2^ffNvkTQvG8d5xm(AlT8i=FdD0u)HRgagGAQisdTnJFsMAjjPhR|7P6Y6>Oz>ZN1nZrXNO?^#or zXmg_gA|@2AX`0VLVhB|i`q&H2cEUDwL?*h0C@)Y|1xbKp)!cwBI4;7~%Htf_r?8v+ zso@P>Q9A(>GOy#8LUvS$n7{;h0C~JoP$EXTuawnn>*Q}rAut={0va3C#2{v<1c<9v zU0VLwgz(DPzU||SEpeA&)Fk_Q{YT%8ESiHh*d}>?7BHY1yi1Q8$g~O!zyKiw;}25& z0L_j~zWv~j%Q;m5q==TYnHA7{Apjr)3N9ni$k^!pr#o@*Pcg59DZolr)hup)jRdsH zFu#fknX7KCS0$i@=AGvHfdQFYh8;KNe@(z#tb80@V*%G23j|#66j&v)WMYtsL2MAj zB_s=Pu`5H9#N_8{Vi-gnHX?yu3bo4@)TJqyScz-8#Bsb7LSnU$(V0r!#}4pNB&vMA zBLT+9yV+mAy>Cl{0s%O2IyUsc%#{eZZU4#2so}e~*SKPOXN>-tq4 W9ZjAIsyw{_0000gMet+hy^t3jWr`~Cj+`~2tg z^uyufy4>HP&(was#CW^GX|uXisIgC`t~;Ei$2|@C0001QNklXKYBJ3pXhl4P-}wxETC3omNf$B#!xS@ZK)Fy8_t}FsG>i O0000-b){}7pfF1P+syVFQ={#3UpG+qlh@SU@OSW z;XAN7gFs@PfQZS)P&Y(0L>;~zW|(DU8epj<8L9I15U|P-A`>Cx2qlL; zjmiPfp`Q&XIgQK#)+`#4N4LVIJj4RS90c-je5wDyyiw#I0odEi`R8)lOaFBz+lKw0 zHvZgTHrzO{s-4L?fAZjJvV%>wv&aMC)Q5gnrIQtktDMNTP-1Y6WmIOloNO6QBF>1( zy{oJWN2+2u(TiKgk}a2HmN6M|Qb_4MsPbjR*|n5zDRD|fn4`%?CNUr;PRCL^eJvN0 zsO{clTNv38NFIqKTSCbCKx&5%RpVuS*MaDdCHi8>mSCcXZ{6ZRl`bXvV<@c~aRQ12 zkqtpqtvA^kLT&e2SXk)ZklY7Pbdw<~Jsa9rA3AGi->sji`5V2pk@U46Km4v8_uo7F zxXD*M_WUbB(t|s3ll)8P{AjI4WJc71u&2oPZhrIrRz!$g{4(ZSuJrcE@E)K2(RQUr z>1I!c?M5Q1ltgI6?L=(NuY9RCRS15 zC=1tm7bMurN5aBFPY3wZ8PmMz?Or+Zd#a4O;f7UAZtAi6oRoJ$?OkSO=CDD@ zqX*AiR0CigGr@IVY-slpOdcj+znvT>Z$~S4O`T9V9sa0kLsDvTx-6|lwx_YZgTDD{ ziPG`#jq&SOClKS*d5UHZ%)S3)No%FR-7?xWJ?(*pu~`v&%o$&33I_&E<9YJOhejmn zTRf%ppNUN-X8xK!ADO?o%U;#ewZw!9soTG{q-4ZtS^2vTa!t!8>x?*$arqfTaeACP zRA(M)xZ=UPIl26;0~MNm;FFt2P5qrijNUjSGQB%Dm7#M_bk7B6f|g}VV(vOMGGow{ z4;-s|944Fl%MAM84U!A9_S>YToMFd#_t2qrCj7!Fd%l?@?i`n}BHWsK9jnLp^D8`p z2P4Jqx5UcI@W|m5qVwt8vDOj0Zl^Sqkv-?zva7RV`tFDM#$RY5;huU2j`(xM#%sUL z&U1%V$7K_V%MOW#yg#sZp5%$yY=MC7x_TvZ?#4KMBIi7UdWHpLI3IZ8?X|r?XF_|b zjn;w!ML|njW{92mr-J&Zr`?5HzDIw&zSW!Exc_UWUr={&$k?aBn;t#XbTJgufW0xE zd8(Dkf;S<=us+{Vs5EGBb)^Ox5QSnQ7Ajz2B00vzxqO@_j6hKwMaiMFKfIEl(-&%s zYX3c<`H}Nxn2=zrP^B+2)F>-8K&Dd`Ytod(TJ_cd0Xv#4WQhV`NgfO3v#_XuOiU0^ zVlb5B98OJ54O?rY;ea$-Us*iWFb@TRmrYS#Qms?rxKyvM)@jNNIGzE~DuA6>ILCR7 zc~KL`OElWz5(B_QQEs5J9x^Ky>HNjyX$uV{HsnrkpaVp_7a?g{oyJgGt^qH);*^&a zuN}-ye@!P%4k>`~1*nLRae2bg4d5U|+!pC%Yj8XteCYt0k5l)GptCrbv3w51xj zeU7foP^vHH6e+i9)%vn3&ff>a5em2hHOj||inth$rxYnMO*BuXR7Y|7{348pv0(-q U_V@JO+>0M3Tc0lNk5?G~08OQpMgRZ+ literal 0 HcmV?d00001 diff --git a/recipes/icons/poughkeepsie_journal.png b/recipes/icons/poughkeepsie_journal.png new file mode 100644 index 0000000000000000000000000000000000000000..b80f4c2d0698d587984de7a6791e80daa78eb9f9 GIT binary patch literal 2145 zcmV-n2%h(eP)W!-NFY%Xa_(-j$J_VbzV|hRLSazb-%sKHVVI!u9UL5oAEfY|%wiOsTn5uX z(a8<+eZu3(3=%SsI*LnfnBys&px7YqVDlda6iukjMe9UaT#q&2IY{YrZ1KHpQOla7 zhY1!(DIht9#&Jxrh8(E5zx0$zC12dUzj~hf)1{G%E=X#S$C4mHfP#n;?jX6T$$D}Y zwbxT}3C?Rq%Rd~Bqi}*dA6R+e85K*{)aUnZR&KjI6~k>O?Ae0dP z>HCTRp{Rp4*`g^Z_qMcu5Qy9HO!Jq3YjcFgQD#`PGSvQR8zGV2d%?VpN0xob_V)-# z23u)d0W)D3LSTaiHW=VQ7|QUoc7J!zY=+{TUR@ z(g3|(#Nx5XHwS>c%_zZPZW>74v5^#k?7{wYk&p_WxTS7KX7beJo>D5Z*BbhvP&-BF?adB^MC(M1ab?{)X1u4=gyug^vfSYIz%f0!3eV)52In8 zG9^kss;;Dmg)bFUs%6i3H#6ByOdz8u1PWfPkPdjNz%ctZR;CVi4`im6O3+gU14Z?v zLJH-o2N^Nk2ZjKHLdF2~7th%)^5Pxnk*(Rkp7&nG^9{`Nb+I{n32oojn0ET7re zl}tdc#2rK{{xVpF#OWn|<6|7+2~dGV^wds;pyN`+H+{*b-3P}?hoPZDwD>(SHy13* zXOo+|g7mg;XiqeMlikv*l#~P?UDa<((J`0-_qi)zS`Jjc(D2T^<*yiLUc6?$xhv3^T^iNOr?)PW?+%Q6YsuIz3gfSu zH3ue?>Uk`GqMt^A0!S1_I2xn$$_C2^2|YrRr8v$tz1TBqc0VBZ83Qx(b330S&#PCO^=jahT^IIYebx8u-z$odJWh^T&%PE3V9R?O~YY866gB^48NBb5(8@wKev{Y?bNc%X_?MV}<;Px3?PhF6?@^@VXhVXSgHV1#A^p{Hk4fC{iBrdALtejW!V^4kq}KXP6Z z1^WF!vEp09V&2L9Xf2@?KjH#nximuJg)glhq&;h&jIm+A+17-n;W&a3RvO*R`KZmG z2pcz>0ik;ZMmP}R{>Wmy22H1;y8E-t&9-@ET|uU>C5NeaNZ^Y47c#a%smtm3)f6GCqW(s zP(Z2(4Wqhl&FaGTw^6}L0D{A;yrqZIDX-(jTSR3wW*NRjf&w=BI6h9Fp7}H*Z*P8c z&bB3UcRu)XF{e!}Q$T{k2+ww@h{Xe+Yy zvW@!j!4mMvx)veU7oSasiYV%9i%?%r9>wzzR92T#a?YoNr$w)Rb=AjPfMLtx2#piC zMu24V^G|NRsW2rnmV5*LfXOh5A0pF5RNL7xOb9hXy2wN&x#Npx+ScBA&nh5f7&*t` ze>1`y5tw(^wATx~D4+ORO4%mmw^H`-SIHZ);i<2gG@DTZqGpH_D0~moG)pzFJM91l zI8b-?oS$9uldeRzCdxzi{{b?LGF6I7D+UGz_$~@R%rL+J4?G-)V;Lrgs5lNde2;$v XIJu3dXB~`J00000NkvXXu0mjfYSsE@ literal 0 HcmV?d00001 diff --git a/recipes/icons/pragyata.png b/recipes/icons/pragyata.png new file mode 100644 index 0000000000000000000000000000000000000000..6bdfec22352d7320dbb2bd39568af38e0ce41b1d GIT binary patch literal 3125 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 P00000NkvXXu0mjfj_mxX literal 0 HcmV?d00001 diff --git a/recipes/icons/pravda.png b/recipes/icons/pravda.png new file mode 100644 index 0000000000000000000000000000000000000000..c7975f2f0173015b47ee65d4fb925a1e6c67ee22 GIT binary patch literal 1710 zcmV;f22uHmP)9c_o!dTZul22Ot-ZDY?5m>RuHk&AV+pI>oT-k-GcW#`N94*$j%^>U{Nnn`xUFtC#u$hc z%&)*}7ht3WqVy(1L>xZTt~3lpFg^^omm!K$G_37LBmjwpT5RpdMi`|twWN~=6}-v=Cj)NklY+CK&5+XlH}%OYqg(E*J8O3Z3KdE`=U1&9p5DQ z48pAPSer9O0~p3jtLa?VU7bPzvjKCD$3pjYGXklxlJnkKSgyBMhow4v{!vSLD1~40 zi1}1`R_fz!Be<0ir04_>-b18ecB#new>9_ggaHjRGcfg8$HZupB*}5ir)vtm8J0~D z-(zXNEC`?!7zG6r<6O+&ULWH1S(qrn^m+K&lZK~0wM?)*-si-07#w$E(_>TG7IZ`b z&cot|G1gU>TdDAu*GBm9%SEcd%jXT>e-@rUGDmr!M&i0dDY@)1!e+?xjfM1BoB#z4 z>#ZWk|1rtW&uc0d4RIA7zsK^)YLPG9U*|J-&M|0Wl4K*!ohBy7Gqb_y-WsrlM8LX( zp+b|NJh9AIA0EQFCdCLQM%O48n`j;5ZMyS`^yupet=aUUU$N=KgWzMzgHXOb|1KqD z;&RPN*rJG$uLFJy*5_jp_31wx*nE7YvxjU6qja)iOAgS}$!wK|MDr;1y60po-DMR{>%lz$& zc)x=4X#*Ohd(#l<*-Ww_8L7P|WSw(t#4uwFd)E`fa7cfPG|Vnn`1QGQ{&~5`^t_>B zAa3Tvi1`)%Ixe{}xvYjVuSMXycRAkag z1p^27!xIOZ2+h>XoF*mEXu)9A318?_$l3q|X`c;XrGc;RL0z5WjJ)t|%EzAOTrSHM8m&}QU8;e*|!c?owy!a;`=^V{=bNbpPiuG0O*dS7j zPy!-yv!)>PP;Rnilg4~K!7n!mrD4n+caa{=zQHRR(C^K1@xi^E+f(JCp&=?O4WynR zO%E*(O$SYzU3>1=9jx2jNOJES)GgF45;U@F7kYdf?_9dZGsjLaec?aZ;k12J{@HVk zf8`KU)d>!VTR5KIg19Wr=Op5-+8i(a;S8sq&mGVI3!n5nNi_r;Q~&?~07*qoM6N<$ Ef?|m(YXATM literal 0 HcmV?d00001 diff --git a/recipes/icons/pravda_ru.png b/recipes/icons/pravda_ru.png index 6cae699c89a320bab5875804b55b75a0be056f4e..f85660b5ef60a5f81977bb9059f806511c5c7841 100644 GIT binary patch literal 1644 zcmV-y29x=TP)>snlBKugI18i|706`FSUw66SCPR{N zxe*~>SEt`%HeSvLp??vCmOv~`v2t1`r(tI(vWY+qB>4iN>DPv6Xh(=iq7P07aJxaY zmZn*SV%5&MTbo5q2||=eQw%9%WW=#W%JkXUDhy!#y>g0WBvvJi%mARcMnP zPz^L?DKaVU5Q(B-*I`IjP61UPRPgTp@N)iKU-a1yIM8LXt`P?035~w zLOaV#QXU-vNJdzdB9$yngLH5sSnbp_ik$M%7=O^_{p?r^`2rz%dGD1++O}S^w!^d^ z`m#Oau~d}fixdQcT6B9yQs$8bvG88k_3NS=K?}^x>s*C@VQFhW0uavikSnlPWe?=E zEaujjYrCy0o1ev7G0~qRrB5UPSnx7fQgS?#F;sBHuZKKHgbkQ%uPkdD@jZIulN8w8 z4c{GFL;4Trl$PdxyL-Wpi-eF00IY&$T?GYvG@2Hs9x)FXIFtZz>6kKu6;+u1!AM{? zTs*RdjDDn^F8$lJy16}Q^Nc#DN30H>kC}ffRVRD^0PW|`MMI(VL~|Vh$Z2qZ$Ix2B z_M=<6e2web=vpNg_@QF~6`5XibCD?U79SY3Bb4j%`fPtZPdd2?Awyo?A%Gy9%;Y2h zq|#iMvAM^L&OMYDx1#&q3u{}!aP-Ie9`wzFYxkb>alYrh1tH3ApBgUyNeXTjThMv+ zYAhViTq;-$0Kot$>NCDx{IrR3?`GdDN8dhGHuu4?U#s;f>lTq$xVW-brqo<6{AFck zR!=0HhaN6vhPn|E6<=(hn_y8ed%;9#i z%*!Lq#NeJ{*<)dv2|zsOZfkX@>gTLuk7^)$(7gxcS8fy`GM}KY$WR_awBgBCYH@3qfZ}$W3^CM zlIqhGNciM&VVV3HPJV>}#%po$z(3(@dv;dmg>!8nF9E zIa6<7+8x~uO5G$e2FAV);64e^qQ@0Kl0Nh$9xOU-tUG0@@Q8DfUXcyb^*IW4Vhj{-e=Rt>%vwwU000014Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>jcnzkjE&sD8ZnXs(3$mVor>JUajX|6e(??Hy1% zW0JSKi@?-PygEP*dx@v7EBiBcDGo^;wYIg>fkI)PE{-7*;d6UExegfcFo&vtyav>r z`!={%M^>QC@z4SDbyX|B9$a)yh|4gBVQ(ta%mX!!0Y}QL!j4|NCi$)FhCr6GdFYyx zyH+J^{}ZQXdo*vCa*Mo8*AAxV$#*a50&P$&ag8WRNi0dVN-jzTQVd20Mn<{@=DJ2^ zAqEy!My6Iq=Gq1ZRt5$=vt1ccH00)|WTsUDH5dXl80#8Zgcz7w0reQ0LNqM6q5c)9 nK?80>NoHIP)^@R9M61m&?}eSfo_sz z(m$k&t_$7wzbRzbbr%UOfe`v|gIrVDkO#!ZGA7v8YtHH79ywP^t=w1&Ui89nuddGA z`Q|&{d5oADr+EcV`6?B%QXK|e1h^7{zRgJ9vkL^U@fb*nZoQ6myFv&CTn83`wlAPQ z#F-IPMat#h%8QGC6XR$QW;Sx@;UT+s?yv)~S*g&3pPFDz==ptkXtPqW9cXtOjhkkM zsm9~J|8@pMpjs7f-%gx8TSoQsJ~u!Feg^8mhrnIn98*&*yLWG@v%30qsaE?HGeP9! zBD79N?BKvGges~Rhvt3{JoM-Lz*hod03pakqCi31$qT>-x!vyqn{IXvxJc&kpg@?P zUz;?5t3&i}0x$jDCqNZ|UN72}E5!Nvy1UWF1<;rIa^EX{f08p>03spuY=6HLmY4V9 z+}wr(&=Ui&Y$ONp<GBNakvI}s-tu6td9RWxn z@X(33Nkjn(D??kE27d4bZu##7lnv?%zvQNm1Hd!^N?Mi-O1~0Tf$wr6OI{0}8R?q@ z9k4Ji0P$j6POu<@ivAY((NnPcw-k78a3HS>)mVv0EEC`bg->NL0v-U{z|PP@;+as3 z6#60zpsHt*_!}TLRBXMKpn-B4rY|qJVWn`0m))8eUw_Qn#G8{7T{l_a{Rjh2qStm0ch{; z);f&MYmpX|<++=fJaL83+NU zr?D70t_^#qO!%tNxZVL+Ul&_j!)w5Sv-bwmQmLTZ+b}yzk(Phdxj+EhfAQi*dvEWO z#Mt(BY!i4ivgbELVr2I6Wh8`CJ(x`3Xl52{Fx(1@TncDF{`aTje*qRPkR60s2A==` N002ovPDHLkV1l%vkDLGi literal 0 HcmV?d00001 diff --git a/recipes/icons/prekshaa.png b/recipes/icons/prekshaa.png new file mode 100644 index 0000000000000000000000000000000000000000..7f8155c1622936887c6447e02e2adb123de93dfc GIT binary patch literal 3636 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 literal 0 HcmV?d00001 diff --git a/recipes/icons/publico.png b/recipes/icons/publico.png new file mode 100644 index 0000000000000000000000000000000000000000..bbbd19fee23e08234437ebe631128230f3346fae GIT binary patch literal 439 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvmUKs7M+SzC{oH>NS%G}U;vjb? zhIQv;UIIBP0X`wFKADA=8MyGHe+!RK1ghsQ3GxeO=u7-o9AdOd<>`$qGt`g&mYu zJ2Elx8 zf};Gi%$!t(lFEWqh0KDIWCn(cIgdZ_a1@4VXq@stea7=?5CgL^w_Y;0u(GiCWD#az z1(ybs!zs+ln?n>%-?(z($eANDN7zp{cr5VJV|XPlSn|oqbSlsa22WQ%mvv4FO#mg` Bmo5MR literal 0 HcmV?d00001 diff --git a/recipes/icons/quanta_magazine.png b/recipes/icons/quanta_magazine.png new file mode 100644 index 0000000000000000000000000000000000000000..13ada510129402d9dd8160d143f6ceaffa722690 GIT binary patch literal 1412 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m^Cs(B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk*h0bFQqR!T z(!$6@N5ROz&`jUJQs2--*TB%qz|zXVPyq^*fVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$DAddqG<*}2 zGxI=#nqXbNzE+-j#U+V($*G<$wn{*A^fEJ3tPD+EEnJP93|ved9Ssd#&790E&Dz*Q}aq-dQ%X34RPuPC5YStpv^9+MVV!(DQ-pixe8#9TV>*O zi=!(}^PqZDaJt3O4X0jxpkwqw(Tfz_Fd<+X0x{u<7s!Dp|I|ESnlAz-?yr^+Z-5zN zV}MVHE0F#V1Z@mM1!+wPYy~>nswBuS7?|-{SlKx^`6MKz6_r&~H8jmF9G#q9Ts^#f zeEov*N-8RAI(z!3Em*pI-Grz_2C%QA8TKL=RTbdV-iHgkG;w}YK zfycY^8Lt0-&}zefcjc$U`j4zOJZ8{aE3%;SyBFV6ch&4U`{YhGcWwPDH1&;2RpGXZ zP2ww})OJON?VHtpUcJj|^3h=5yWNk@{;IFk+{I`bCocB<>zNLq*BLxr{an^LB{Ts5 D@izW) literal 0 HcmV?d00001 diff --git a/recipes/icons/queleer.png b/recipes/icons/queleer.png new file mode 100644 index 0000000000000000000000000000000000000000..62d58fb0923c0920226a88b7a5494b5d088bd94e GIT binary patch literal 812 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyoCO|{#S9F5M?jcysy3fA0|TR1 zfKP}kP*PuCzrMb{p`l^=^y&Zq|7T=mWMX1!YincRDy~;^N}t;{&qW+uND> z`PtdoSy@>vEiD-&B&@BiZG3#XySq6!IJmjFd3bn$Brh2B_VxmG0RfO9E+)olXvojc zF8~J2CZ<5~Ns}fC3JUi1^#RGrlP5bkIP~}Ti;9XmIXM9tKv@wHk!jPW0bRx9=PxcU zE+HXd)7T`Utqo*|iHWg?g-J~I}`03NHb~a1ez2Mp*eDIUP_v+>d%|97jPY(PNao{pJ z;Nxn-x+~%0i~@xYR^zP~MI=%eGa8$)9(%#%b5Z1laL@uyvCv1Y%zP%Sj}ln(9&|i0 zJ|HB1CNV)&Zj;j_tH7IP5lW|7jKw u1Jjg330K~^f1Cfrd})z1H0I+q)@Km9T(k7eIZt0uOnAEbxvX Date: Tue, 3 Jan 2023 19:43:34 +0530 Subject: [PATCH 0056/2055] ... --- recipes/history_today.recipe | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/history_today.recipe b/recipes/history_today.recipe index e536682f35..f0140f9935 100644 --- a/recipes/history_today.recipe +++ b/recipes/history_today.recipe @@ -29,7 +29,7 @@ class HistoryToday(BasicNewsRecipe): br['pass'] = self.password res = br.submit() raw = res.read() - if 'Session limit exceeded' in raw: + if b'Session limit exceeded' in raw: br.select_form(nr=1) control = br.find_control('sid').items[1] sid = [] From 6779e93f7e8226e0e0769d07646ab4728fd6ce73 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 3 Jan 2023 19:44:28 +0530 Subject: [PATCH 0057/2055] http -> https --- recipes/history_today.recipe | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/recipes/history_today.recipe b/recipes/history_today.recipe index f0140f9935..61f17128d8 100644 --- a/recipes/history_today.recipe +++ b/recipes/history_today.recipe @@ -23,7 +23,7 @@ class HistoryToday(BasicNewsRecipe): def get_browser(self): br = BasicNewsRecipe.get_browser(self) if self.username is not None and self.password is not None: - br.open('http://www.historytoday.com/user/login') + br.open('https://www.historytoday.com/user/login') br.select_form(nr=1) br['name'] = self.username br['pass'] = self.password @@ -40,13 +40,13 @@ class HistoryToday(BasicNewsRecipe): def parse_index(self): # Find date - soup0 = self.index_to_soup('http://www.historytoday.com/') + soup0 = self.index_to_soup('https://www.historytoday.com/') dates = self.tag_to_string(soup0.find( 'div', attrs={'id': 'block-block-226'}).span) self.timefmt = u' [%s]' % dates # Go to issue - soup = self.index_to_soup('http://www.historytoday.com/contents') + soup = self.index_to_soup('https://www.historytoday.com/contents') cover = soup.find('div', attrs={ 'id': 'content-area'}).find('img', attrs={'src': re.compile('.*cover.*')})['src'] self.cover_url = cover @@ -69,12 +69,12 @@ class HistoryToday(BasicNewsRecipe): if len(subarticle) < 2: continue title = self.tag_to_string(subarticle[0]) - originalurl = "http://www.historytoday.com" + \ + originalurl = "https://www.historytoday.com" + \ subarticle[0].span.a['href'].strip() originalpage = self.index_to_soup(originalurl) printurl = originalpage.find( 'div', attrs={'id': 'ht-tools'}).a['href'].strip() - url = "http://www.historytoday.com" + printurl + url = "https://www.historytoday.com" + printurl desc = self.tag_to_string(subarticle[1]) articles.append({'title': title, 'url': url, 'description': desc, 'date': ''}) @@ -88,4 +88,4 @@ class HistoryToday(BasicNewsRecipe): return ans def cleanup(self): - self.browser.open('http://www.historytoday.com/logout') + self.browser.open('https://www.historytoday.com/logout') From 0c779e323885b1d7c174b521895079f2ac306181 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 4 Jan 2023 08:40:02 +0530 Subject: [PATCH 0058/2055] Fix detection of Tolino Vision 6 on macOS/Linux Update libmtp device lists from upstream Fixes #2000877 [Tolino Vision 6 not recognized on Mac M1](https://bugs.launchpad.net/calibre/+bug/2000877) --- .../devices/mtp/unix/upstream/music-players.h | 58 ++++++++++++++----- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/src/calibre/devices/mtp/unix/upstream/music-players.h b/src/calibre/devices/mtp/unix/upstream/music-players.h index b98f5714f4..9747f95c61 100644 --- a/src/calibre/devices/mtp/unix/upstream/music-players.h +++ b/src/calibre/devices/mtp/unix/upstream/music-players.h @@ -662,7 +662,7 @@ /* * SanDisk - * several devices (c150 for sure) are definitely dual-mode and must + * several devices (c150 for sure) are definately dual-mode and must * have the USB mass storage driver that hooks them unloaded first. * They all have problematic dual-mode making the device unload effect * uncertain on these devices. @@ -1558,9 +1558,11 @@ { "LG Electronics Inc.", 0x1004, "Android phone (ID2)", 0x61f9, DEVICE_FLAGS_ANDROID_BUGS }, /* https://sourceforge.net/p/libmtp/bugs/1007/ */ - { "LG Electronics Inc.", 0x1004, "LG VS980", 0x621c, + /* https://sourceforge.net/p/libmtp/bugs/1924/ */ + { "LG Electronics Inc.", 0x1004, "G2 (VS980)", 0x621c, DEVICE_FLAGS_ANDROID_BUGS }, - { "LG Electronics Inc.", 0x1004, "LG2 Optimus", 0x6225, + /* https://sourceforge.net/p/libmtp/bugs/1924/ */ + { "LG Electronics Inc.", 0x1004, "G2", 0x6225, DEVICE_FLAGS_ANDROID_BUGS }, /* https://sourceforge.net/p/libmtp/bugs/1386/ */ { "LG Electronics Inc.", 0x1004, "LG VS950", 0x622a, @@ -1712,6 +1714,9 @@ /* hartmut001@users.sourceforge.net */ { "Sony", 0x054c, "NW-A45 Walkman", 0x0c71, DEVICE_FLAGS_SONY_NWZ_BUGS }, + /* https://github.com/libmtp/libmtp/issues/130 */ + { "Sony", 0x054c, "NW-A105", 0x0d00, + DEVICE_FLAGS_SONY_NWZ_BUGS }, /* https://github.com/libmtp/libmtp/issues/81 */ { "Sony", 0x054c, "NW-ZX500", 0x0d01, DEVICE_FLAGS_SONY_NWZ_BUGS }, @@ -2504,7 +2509,7 @@ /* * Motorola Xoom (Wingray) variants * - * These devices seem to use these product IDs simultaneously + * These devices seem to use these product IDs simulatenously * https://code.google.com/p/android-source-browsing/source/browse/init.stingray.usb.rc?repo=device--moto--wingray * * 0x70a3 - Factory test - reported as early MTP ID @@ -2691,14 +2696,17 @@ DEVICE_FLAG_SWITCH_MODE_BLACKBERRY | DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST_ALL }, /* https://sourceforge.net/p/libmtp/bugs/1551/ */ - { "RIM", 0x0fca, "BlackBerry Priv", 0x8031, DEVICE_FLAG_UNLOAD_DRIVER | + /* https://sourceforge.net/p/libmtp/bugs/1925/ */ + { "BlackBerry", 0x0fca, "Priv", 0x8031, DEVICE_FLAG_UNLOAD_DRIVER | DEVICE_FLAG_SWITCH_MODE_BLACKBERRY | DEVICE_FLAG_BROKEN_MTPGETOBJPROPLIST_ALL }, /* https://sourceforge.net/p/libmtp/bugs/1658/ */ - { "RIM", 0x0fca, "BlackBerry Dtek 60", 0x8041, DEVICE_FLAGS_ANDROID_BUGS }, + /* https://sourceforge.net/p/libmtp/bugs/1925/ */ + { "BlackBerry", 0x0fca, "DTEK60", 0x8041, DEVICE_FLAGS_ANDROID_BUGS }, /* https://sourceforge.net/p/libmtp/feature-requests/264/ */ - { "RIM", 0x0fca, "BlackBerry Keyone", 0x8042, DEVICE_FLAGS_ANDROID_BUGS }, + /* https://sourceforge.net/p/libmtp/bugs/1925/ */ + { "BlackBerry", 0x0fca, "KEYone", 0x8042, DEVICE_FLAGS_ANDROID_BUGS }, /* * Nextar @@ -3153,6 +3161,12 @@ /* https://github.com/libmtp/libmtp/issues/74 */ { "Lenovo", 0x17ef, "TB-X606F (Lenovo Tab M10 FHD Plus)", 0x7c46, DEVICE_FLAGS_ANDROID_BUGS }, + /* https://github.com/libmtp/libmtp/issues/127 */ + { "Lenovo", 0x17ef, "Lenovo Tab P11", 0x7c6f, + DEVICE_FLAGS_ANDROID_BUGS }, + /* https://github.com/libmtp/libmtp/issues/116 */ + { "Lenovo", 0x17ef, "TB-X306F (3rd id)", 0x7c97, + DEVICE_FLAGS_ANDROID_BUGS }, /*https://github.com/libmtp/libmtp/issues/111 */ { "Lenovo", 0x17ef, "TAB M7 Gen 3", 0x7cb3, DEVICE_FLAGS_ANDROID_BUGS }, @@ -3311,7 +3325,7 @@ #if 1 /* after some review I commented it back in. There was apparently * only one or two devices misbehaving (having this ID in mass storage mode), - * but more seem to use it regularly as MTP devices. Marcus 20150401 */ + * but more seem to use it regulary as MTP devices. Marcus 20150401 */ /* * This had to be commented out - the same VID+PID is used also for * other modes than MTP, so we need to let mtp-probe do its job on this @@ -3406,7 +3420,7 @@ #if 1 /* after some review I commented it back in. There was apparently * only one or two devices misbehaving (having this ID in mass storage mode), - * but more seem to use it regularly as MTP devices. Marcus 20150401 */ + * but more seem to use it regulary as MTP devices. Marcus 20150401 */ /* * This had to be commented out - the same VID+PID is used also for * other modes than MTP, so we need to let mtp-probe do its job on this @@ -3616,6 +3630,9 @@ /* https://sourceforge.net/p/libmtp/bugs/1900/ */ { "Onyx", 0x2207, "Boox Nova Pro", 0x0015, DEVICE_FLAGS_ANDROID_BUGS }, + /* https://github.com/libmtp/libmtp/issues/125 */ + { "iBasso", 0x2207, "DX170 DAP", 0x0017, + DEVICE_FLAGS_ANDROID_BUGS }, /* https://github.com/libmtp/libmtp/issues/82 */ { "Supernote", 0x2207, "A5X", 0x0031, DEVICE_FLAGS_ANDROID_BUGS }, @@ -3822,13 +3839,13 @@ { "Bravis", 0x0e8d, "A401 Neo", 0x0c03, DEVICE_FLAGS_ANDROID_BUGS }, /* https://sourceforge.net/p/libmtp/bugs/1422/ */ + /* https://sourceforge.net/p/libmtp/bugs/1467/ */ + /* https://sourceforge.net/p/libmtp/bugs/1922/ */ { "MediaTek Inc", 0x0e8d, "MT65xx", 0x2008, DEVICE_FLAGS_ANDROID_BUGS }, - /* https://sourceforge.net/p/libmtp/bugs/1467/ */ - { "elephone", 0x0e8d, "p6000", 0x2008, - DEVICE_FLAGS_ANDROID_BUGS }, /* https://sourceforge.net/p/libmtp/feature-requests/234/ */ - { "DOODGE", 0x0e8d, "X6pro", 0x200a, + /* https://sourceforge.net/p/libmtp/bugs/1923/ */ + { "MediaTek Inc", 0x0e8d, "MT67xx", 0x200a, DEVICE_FLAGS_ANDROID_BUGS }, /* https://sourceforge.net/p/libmtp/support-requests/289/ */ { "Jinga", 0x0e8d, "PassPluss", 0x2012, DEVICE_FLAGS_ANDROID_BUGS }, @@ -3848,7 +3865,7 @@ DEVICE_FLAGS_ANDROID_BUGS }, /* In update 4 the order of devices was changed for - better OS X / Windows support and another device-id + better OS X / Windows suport and another device-id got assigned for the MTP */ { "Jolla", 0x2931, "Sailfish (ID2)", 0x0a05, DEVICE_FLAGS_ANDROID_BUGS }, @@ -3906,6 +3923,7 @@ { "Garmin", 0x091e, "Fenix 7 Sapphire Solar", 0x4f42, DEVICE_FLAGS_ANDROID_BUGS }, /* https://sourceforge.net/p/libmtp/support-requests/299/ */ { "Garmin", 0x091e, "EPIX 2", 0x4f67, DEVICE_FLAGS_ANDROID_BUGS }, + { "Garmin", 0x091e, "Forerunner 955 Solar", 0x4fb8, DEVICE_FLAGS_ANDROID_BUGS }, /* https://sourceforge.net/p/libmtp/bugs/1920/ */ { "Garmin", 0x091e, "Tactix 7", 0x5027, DEVICE_FLAGS_ANDROID_BUGS }, @@ -3986,6 +4004,8 @@ /* https://sourceforge.net/p/libmtp/bugs/1287/ */ { "Gensis", 0x040d, "GT-7305 ", 0x885c, DEVICE_FLAGS_ANDROID_BUGS }, + /* https://sourceforge.net/p/libmtp/support-requests/300/ */ + { "realme", 0x22d9, "Phone", 0x202a, DEVICE_FLAGS_ANDROID_BUGS }, /* https://sourceforge.net/p/libmtp/support-requests/182/ */ { "Oppo", 0x22d9, "Find 5", 0x2764, DEVICE_FLAGS_ANDROID_BUGS }, /* https://sourceforge.net/p/libmtp/bugs/1207/ */ @@ -4125,6 +4145,7 @@ { "GoPro" , 0x2672, "HERO8 Black", 0x0049, DEVICE_FLAG_NONE }, { "GoPro" , 0x2672, "HERO9 Black", 0x004d, DEVICE_FLAG_NONE }, { "GoPro" , 0x2672, "HERO10 Black", 0x0056, DEVICE_FLAG_NONE }, + { "GoPro" , 0x2672, "HERO11 Black", 0x0059, DEVICE_FLAG_NONE }, #endif /* These Ricoh Theta cameras run Android but seem to work @@ -4259,5 +4280,14 @@ /* https://sourceforge.net/p/libmtp/bugs/1911/ */ { "Oculus", 0x2833, "Quest", 0x0183, DEVICE_FLAGS_ANDROID_BUGS }, + /* https://sourceforge.net/p/libmtp/bugs/1921/ */ + { "Tolino", 0x4173, "Tolino Vision 6", 0x8000, DEVICE_FLAGS_ANDROID_BUGS }, + + /* https://github.com/libmtp/libmtp/issues/122 */ + { "FLIR", 0x09cb, "C5", 0x100b, DEVICE_FLAGS_ANDROID_BUGS }, + + /* https://github.com/libmtp/libmtp/issues/135 */ + { "Honor", 0x339b, "Any-NX1", 0x107d, DEVICE_FLAGS_ANDROID_BUGS }, + /* qemu 3.0.0 hw/usb/dev-mtp.c */ { "QEMU", 0x46f4, "Virtual MTP", 0x0004, DEVICE_FLAG_NONE } From 9a1ef22b39c1ac67bdd3a88cea1811f9041b7a25 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 4 Jan 2023 10:11:20 +0530 Subject: [PATCH 0059/2055] Fix windows not being moved onto the current monitor when they were previously visible on a removed monitor that was to the left of the current monitor --- src/calibre/gui2/geometry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/geometry.py b/src/calibre/gui2/geometry.py index d3a6c95e1e..af765daac1 100644 --- a/src/calibre/gui2/geometry.py +++ b/src/calibre/gui2/geometry.py @@ -146,9 +146,9 @@ def _restore_to_new_screen(self: QWidget, s: QScreen, saved_data: dict) -> bool: sz = QSize(min(saved_geometry.width(), available_size.width()), min(saved_geometry.height(), available_size.height())) if not sz.isValid(): return False - max_left = available_geometry.left() + (available_size.width() - sz.width()) - max_top = available_geometry.top() + (available_size.height() - sz.height()) - geometry = QRect(min(saved_geometry.left(), max_left), min(saved_geometry.top(), max_top), sz.width(), sz.height()) + left = available_geometry.left() + (available_size.width() - sz.width()) // 2 + top = available_geometry.top() + (available_size.height() - sz.height()) // 2 + geometry = QRect(left, top, sz.width(), sz.height()) return _do_restore(self, s, geometry, saved_data) From c5427c5ebeb6c47cd1982b779c5977dbe40eb42a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 4 Jan 2023 13:32:42 +0530 Subject: [PATCH 0060/2055] Prune deleted books from recently read by user list --- src/calibre/srv/code.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/calibre/srv/code.py b/src/calibre/srv/code.py index 2b98282c64..142c5f232e 100644 --- a/src/calibre/srv/code.py +++ b/src/calibre/srv/code.py @@ -146,6 +146,16 @@ def custom_list_template(): return ans +def book_exists(x, ctx, rd): + try: + db = ctx.get_library(rd, x['library_id']) + if db is None: + raise Exception('') + except Exception: + return False + return bool(db.new_api.has_format(x['book_id'], x['format'])) + + def basic_interface_data(ctx, rd): ans = { 'username': rd.username, @@ -169,7 +179,9 @@ def basic_interface_data(ctx, rd): } ans['library_map'], ans['default_library_id'] = ctx.library_info(rd) if ans['username']: - ans['recently_read_by_user'] = tuple(x for x in last_read_cache().get_recently_read(ans['username']) if x['library_id'] in ans['library_map']) + ans['recently_read_by_user'] = tuple( + x for x in last_read_cache().get_recently_read(ans['username']) + if x['library_id'] in ans['library_map'] and book_exists(x, ctx, rd)) return ans From ed930aaf87be1a2564a5b4ae887257e99a8779d2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 4 Jan 2023 13:50:29 +0530 Subject: [PATCH 0061/2055] Fix #2000831 [[Content Server] "Continue reading..." disappears upon no connection](https://bugs.launchpad.net/calibre/+bug/2000831) --- src/pyj/book_list/home.pyj | 3 ++- src/pyj/book_list/main.pyj | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pyj/book_list/home.pyj b/src/pyj/book_list/home.pyj index f292e24954..29f05c6edc 100644 --- a/src/pyj/book_list/home.pyj +++ b/src/pyj/book_list/home.pyj @@ -37,7 +37,8 @@ recently_read_by_user = {'updated': False} def update_recently_read_by_user(items): - recently_read_by_user.items = items + if items: + recently_read_by_user.items = items recently_read_by_user.updated = True diff --git a/src/pyj/book_list/main.pyj b/src/pyj/book_list/main.pyj index e5d144f5b3..cb0fd9cd69 100644 --- a/src/pyj/book_list/main.pyj +++ b/src/pyj/book_list/main.pyj @@ -138,6 +138,8 @@ def do_update_interface_data(): if data.translations?: get_translations(data.translations) install(data.translations) + else: + update_recently_read_by_user() ).send() From 9473599b2889002bc130df7562a9e2673c4ede77 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 4 Jan 2023 14:06:02 +0530 Subject: [PATCH 0062/2055] Book details panel: Fix HTML comment tags in the comments breaking display of book details. Fixes #2000881 [Description Format Breaking Book View Display](https://bugs.launchpad.net/calibre/+bug/2000881) Apparently QTextBrowser flakes out when it sees comments. --- src/calibre/gui2/book_details.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 3971129a4a..8e6628af25 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -5,7 +5,7 @@ import os import re from collections import namedtuple -from functools import partial +from functools import partial, lru_cache from qt.core import ( QAction, QApplication, QClipboard, QColor, QDialog, QEasingCurve, QIcon, QKeySequence, QMenu, QMimeData, QPainter, QPen, QPixmap, QSplitter, @@ -197,6 +197,11 @@ def init_find_in_grouped_search(menu, field, value, book_info): '{}:"={}"'.format(g, value.replace('"', r'\"')), '')) +@lru_cache(maxsize=2) +def comments_pat(): + return re.compile(r'', re.DOTALL) + + def render_html(mi, vertical, widget, all_fields=False, render_data_func=None, pref_name='book_display_fields'): # {{{ from calibre.gui2.ui import get_gui func = render_data_func or partial(render_data, @@ -227,6 +232,8 @@ def render_html(mi, vertical, widget, all_fields=False, render_data_func=None, p comments = '' if comment_fields: comments = '\n'.join('

' % x for x in comment_fields) + # Comments cause issues with rendering in QTextBrowser + comments = comments_pat().sub('', comments) right_pane = comments if vertical: From 8ad001a64de9b8b56b267accc6f3a83a2411daab Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 4 Jan 2023 14:46:46 +0530 Subject: [PATCH 0063/2055] HTMLZ output: Fix images referred to in CSS stylesheets not being converted. Fixes #1999956 [EPUB3 -> HTMLZ conversion does not fix background-image url](https://bugs.launchpad.net/calibre/+bug/1999956) --- src/calibre/ebooks/htmlz/oeb2html.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/calibre/ebooks/htmlz/oeb2html.py b/src/calibre/ebooks/htmlz/oeb2html.py index 219a67ce8f..3ea8c2c952 100644 --- a/src/calibre/ebooks/htmlz/oeb2html.py +++ b/src/calibre/ebooks/htmlz/oeb2html.py @@ -8,16 +8,18 @@ Transform OEB content into a single (more or less) HTML file. import os import re - +from css_parser import replaceUrls from functools import partial from lxml import html from calibre import prepare_string_for_xml from calibre.ebooks.oeb.base import ( - XHTML, XHTML_NS, SVG_NS, barename, namespace, OEB_IMAGES, XLINK, rewrite_links, urlnormalize) + OEB_IMAGES, SVG_NS, XHTML, XHTML_NS, XLINK, barename, namespace, rewrite_links, + urlnormalize, +) from calibre.ebooks.oeb.stylizer import Stylizer from calibre.utils.logging import default_log -from polyglot.builtins import string_or_bytes, as_unicode +from polyglot.builtins import as_unicode, string_or_bytes from polyglot.urllib import urldefrag SELF_CLOSING_TAGS = {'area', 'base', 'basefont', 'br', 'hr', 'input', 'img', 'link', 'meta'} @@ -130,7 +132,8 @@ class OEB2HTML: def get_css(self, oeb_book): css = '' for item in oeb_book.manifest: - if item.media_type == 'text/css': + if hasattr(item.data, 'cssText'): + replaceUrls(item.data, partial(self.rewrite_link, page=item)) css += as_unicode(item.data.cssText) + '\n\n' return css From d1a6bfa7b500ab86e140409384633a67945ac8a7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 4 Jan 2023 15:58:41 +0530 Subject: [PATCH 0064/2055] Windows build: Double the stack size Python assumes a stack size of 2MB on windows. The windows default is 1MB. Presumably whoever builds python.exe changes it. Do the same to avoid crashes due to too much recursion. Fixes #2000888 [BS4 str(soup) crashes Calibre instead of raising RecursionError](https://bugs.launchpad.net/calibre/+bug/2000888) --- bypy/windows/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bypy/windows/__main__.py b/bypy/windows/__main__.py index fc62b117b6..c9bd1c7d83 100644 --- a/bypy/windows/__main__.py +++ b/bypy/windows/__main__.py @@ -496,6 +496,7 @@ def build_launchers(env, incdir, debug=False): '/LIBPATH:' + env.obj_dir, '/SUBSYSTEM:' + subsys, '/LIBPATH:%s/libs' % env.python_base, '/RELEASE', '/MANIFEST:EMBED', '/MANIFESTINPUT:' + mf, + '/STACK:2097152', # Set stack size to 2MB which is what python expects. Default on windows is 1MB 'user32.lib', 'kernel32.lib', '/OUT:' + exe] + u32 + dlflags + [embed_resources(env, exe), dest, lib] run(*cmd) From c0884b14c47efb0291973adb4404d5db04e4b920 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 4 Jan 2023 16:07:02 +0530 Subject: [PATCH 0065/2055] calibredb list: Allow specifying multiple fields for --sort-by. Fixes #1982532 [calibredb sort on multiple fields](https://bugs.launchpad.net/calibre/+bug/1982532) --- src/calibre/db/cli/cmd_list.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/calibre/db/cli/cmd_list.py b/src/calibre/db/cli/cmd_list.py index 88c73ae91b..ad2c934249 100644 --- a/src/calibre/db/cli/cmd_list.py +++ b/src/calibre/db/cli/cmd_list.py @@ -48,15 +48,17 @@ def implementation( else: fields = sorted(afields) sort_by = sort_by or 'id' - if sort_by not in afields: - return f'Unknown sort field: {sort_by}' + sort_fields = sort_by.split(',') + for sf in sort_fields: + if sf not in afields: + return f'Unknown sort field: {sf}' + sort_spec = [(sf, ascending) for sf in sort_fields] if not set(fields).issubset(afields): return 'Unknown fields: {}'.format(', '.join(set(fields) - afields)) if search_text: - book_ids = db.multisort([(sort_by, ascending)], - ids_to_sort=db.search(search_text)) + book_ids = db.multisort(sort_spec, ids_to_sort=db.search(search_text)) else: - book_ids = db.multisort([(sort_by, ascending)]) + book_ids = db.multisort(sort_spec) if limit > -1: book_ids = book_ids[:limit] data = {} @@ -274,7 +276,7 @@ List the books available in the calibre database. '--sort-by', default=None, help=_( - 'The field by which to sort the results.\nAvailable fields: {0}\nDefault: {1}' + 'The field by which to sort the results. You can specify multiple fields by separating them with commas.\nAvailable fields: {0}\nDefault: {1}' ).format(', '.join(sorted(FIELDS)), 'id') ) parser.add_option( From 60fa1b8b84721ffb2ad38feaa2da7a67597cc2aa Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 4 Jan 2023 17:41:02 +0530 Subject: [PATCH 0066/2055] recipe icons --- recipes/TheMITPressReader.png | Bin 0 -> 904 bytes recipes/rabble_ca.png | Bin 0 -> 190 bytes recipes/radio_prague.png | Bin 0 -> 262 bytes recipes/radio_praha.png | Bin 0 -> 262 bytes recipes/readers_digest.png | Bin 0 -> 1553 bytes recipes/readersdigest_thehealthy.png | Bin 0 -> 1698 bytes recipes/real_clear.png | Bin 0 -> 1254 bytes recipes/reason_magazine.png | Bin 0 -> 208 bytes recipes/red_voltaire.png | Bin 0 -> 1320 bytes recipes/regina_leader_post.png | Bin 0 -> 1302 bytes recipes/republica.png | Bin 0 -> 1435 bytes recipes/respekt_magazine.png | Bin 0 -> 384 bytes recipes/reuters.png | Bin 0 -> 582 bytes recipes/revista_cromos.png | Bin 0 -> 1148 bytes recipes/revista_muy.png | Bin 0 -> 1179 bytes recipes/revista_piaui.png | Bin 0 -> 2207 bytes recipes/revista_semana.png | Bin 0 -> 575 bytes recipes/revista_summa.png | Bin 0 -> 1343 bytes recipes/rga.png | Bin 0 -> 964 bytes recipes/rian_eng.png | Bin 0 -> 2440 bytes recipes/rian_spa.png | Bin 0 -> 2440 bytes recipes/roger_ebert.png | Bin 0 -> 617 bytes recipes/roger_ebert_blog.png | Bin 0 -> 617 bytes recipes/rt.png | Bin 0 -> 746 bytes recipes/rubikon_de.png | Bin 0 -> 463 bytes recipes/sabit_fikir.png | Bin 0 -> 1125 bytes recipes/saechsische.png | Bin 0 -> 467 bytes recipes/sage_news_opinion.png | Bin 0 -> 2617 bytes recipes/salonica_press_news.png | Bin 0 -> 2438 bytes recipes/samanyolu_teknoloji.png | Bin 0 -> 3099 bytes recipes/sardinia_post.png | Bin 0 -> 1209 bytes recipes/saskatoon_star_phoenix.png | Bin 0 -> 1006 bytes recipes/satmagazine.png | Bin 0 -> 1295 bytes recipes/schwarzerpfeil.png | Bin 0 -> 2698 bytes recipes/science_advances.png | Bin 0 -> 720 bytes recipes/science_news.png | Bin 0 -> 1217 bytes recipes/scientific_american.png | Bin 0 -> 445 bytes recipes/scprint.png | Bin 0 -> 1777 bytes recipes/seanhannity.png | Bin 0 -> 1143 bytes recipes/seminar_magazine.png | Bin 0 -> 941 bytes recipes/serverside.png | Bin 0 -> 1398 bytes recipes/sfbg.png | Bin 0 -> 2132 bytes recipes/shacknews.png | Bin 0 -> 1893 bytes recipes/shortlist.png | Bin 0 -> 654 bytes recipes/sigma_live.png | Bin 0 -> 1791 bytes recipes/sign_on_sd.png | Bin 0 -> 562 bytes recipes/singtao_daily.png | Bin 0 -> 2445 bytes recipes/singtaohk.png | Bin 0 -> 1314 bytes recipes/sisainlive.png | Bin 0 -> 1389 bytes recipes/sizinti_derigisi.png | Bin 0 -> 1896 bytes recipes/skeptic.png | Bin 0 -> 1065 bytes recipes/skeptical_enquirer.png | Bin 0 -> 2794 bytes recipes/slate.png | Bin 0 -> 666 bytes recipes/slate_star_codex.png | Bin 0 -> 2116 bytes recipes/slovo.png | Bin 0 -> 1572 bytes recipes/sme.png | Bin 0 -> 1038 bytes recipes/smith.png | Bin 0 -> 573 bytes recipes/sn_dk.png | Bin 0 -> 3738 bytes recipes/snopes.png | Bin 0 -> 2960 bytes recipes/sol_haber.png | Bin 0 -> 965 bytes recipes/southernstar.png | Bin 0 -> 1704 bytes recipes/spectator_magazine.png | Bin 0 -> 1490 bytes recipes/spin_magazine.png | Bin 0 -> 317 bytes recipes/sports_illustrated.png | Bin 0 -> 483 bytes recipes/sportstar.png | Bin 0 -> 1088 bytes recipes/sporza_be.png | Bin 0 -> 807 bytes recipes/star_gazetesi.png | Bin 0 -> 1363 bytes recipes/stars_and_stripes.png | Bin 0 -> 1242 bytes recipes/stnn.png | Bin 0 -> 2306 bytes recipes/strange_horizons.png | Bin 0 -> 1741 bytes recipes/strategy-business.png | Bin 0 -> 157 bytes recipes/substack.png | Bin 0 -> 338 bytes recipes/sueddeutsche_mobil.png | Bin 0 -> 1277 bytes recipes/sunday_times_magazine.png | Bin 0 -> 324 bytes recipes/superesportes.png | Bin 0 -> 1755 bytes recipes/svt_nyheter.png | Bin 0 -> 1101 bytes recipes/swarajya.png | Bin 0 -> 2295 bytes recipes/t3n_de.png | Bin 0 -> 569 bytes recipes/t_online.png | Bin 0 -> 629 bytes recipes/taipei.png | Bin 0 -> 4036 bytes recipes/tanea.png | Bin 0 -> 872 bytes recipes/taz.png | Bin 0 -> 3834 bytes recipes/technology_review_de.png | Bin 0 -> 1496 bytes recipes/techtarget.png | Bin 0 -> 666 bytes recipes/tedneward.png | Bin 0 -> 1790 bytes recipes/thai_post_daily.png | Bin 0 -> 936 bytes recipes/thairath.png | Bin 0 -> 1333 bytes recipes/the_athletic.png | Bin 0 -> 377 bytes recipes/the_baffler.png | Bin 0 -> 570 bytes recipes/the_budget_fashionista.png | Bin 0 -> 279 bytes recipes/the_clinic_online.png | Bin 0 -> 624 bytes recipes/the_conversation.png | Bin 0 -> 774 bytes recipes/the_daily_news_egypt.png | Bin 0 -> 1596 bytes recipes/the_diplomat.png | Bin 0 -> 1036 bytes recipes/the_feature.png | Bin 0 -> 672 bytes recipes/the_federalist.png | Bin 0 -> 1090 bytes recipes/the_freeman.png | Bin 0 -> 1870 bytes recipes/the_friday_times.png | Bin 0 -> 15463 bytes recipes/the_journal.png | Bin 0 -> 726 bytes recipes/the_manila_bulletin.png | Bin 0 -> 691 bytes recipes/the_manila_times.png | Bin 0 -> 2339 bytes recipes/the_marker.png | Bin 0 -> 1587 bytes recipes/the_new_republic.png | Bin 0 -> 370 bytes recipes/the_philippine_daily_inquirer.png | Bin 0 -> 1250 bytes recipes/the_philippine_star.png | Bin 0 -> 1870 bytes recipes/the_register.png | Bin 0 -> 481 bytes recipes/the_saturday_paper.png | Bin 0 -> 613 bytes recipes/the_sun.png | Bin 0 -> 1082 bytes recipes/the_verge.png | Bin 0 -> 800 bytes recipes/the_week.png | Bin 0 -> 2317 bytes recipes/thecodelesscode.png | Bin 0 -> 1085 bytes recipes/thecultofghoul.png | Bin 0 -> 274 bytes recipes/thedgesingapore.png | Bin 0 -> 1057 bytes .../theeconomictimes_india_print_edition.png | Bin 0 -> 1156 bytes recipes/themorningpaper.png | Bin 0 -> 1413 bytes recipes/thenews.png | Bin 0 -> 361 bytes recipes/theprint.png | Bin 0 -> 1518 bytes recipes/thn.png | Bin 0 -> 379 bytes recipes/tijolaco.png | Bin 0 -> 1127 bytes recipes/tillsonburg.png | Bin 0 -> 971 bytes recipes/today_online.png | Bin 0 -> 594 bytes recipes/tomshardware_it.png | Bin 0 -> 1460 bytes recipes/toyokeizai.png | Bin 0 -> 1560 bytes recipes/tri_city_herald.png | Bin 0 -> 332 bytes recipes/tyzden.png | Bin 0 -> 291 bytes 125 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 recipes/TheMITPressReader.png create mode 100644 recipes/rabble_ca.png create mode 100644 recipes/radio_prague.png create mode 100644 recipes/radio_praha.png create mode 100644 recipes/readers_digest.png create mode 100644 recipes/readersdigest_thehealthy.png create mode 100644 recipes/real_clear.png create mode 100644 recipes/reason_magazine.png create mode 100644 recipes/red_voltaire.png create mode 100644 recipes/regina_leader_post.png create mode 100644 recipes/republica.png create mode 100644 recipes/respekt_magazine.png create mode 100644 recipes/reuters.png create mode 100644 recipes/revista_cromos.png create mode 100644 recipes/revista_muy.png create mode 100644 recipes/revista_piaui.png create mode 100644 recipes/revista_semana.png create mode 100644 recipes/revista_summa.png create mode 100644 recipes/rga.png create mode 100644 recipes/rian_eng.png create mode 100644 recipes/rian_spa.png create mode 100644 recipes/roger_ebert.png create mode 100644 recipes/roger_ebert_blog.png create mode 100644 recipes/rt.png create mode 100644 recipes/rubikon_de.png create mode 100644 recipes/sabit_fikir.png create mode 100644 recipes/saechsische.png create mode 100644 recipes/sage_news_opinion.png create mode 100644 recipes/salonica_press_news.png create mode 100644 recipes/samanyolu_teknoloji.png create mode 100644 recipes/sardinia_post.png create mode 100644 recipes/saskatoon_star_phoenix.png create mode 100644 recipes/satmagazine.png create mode 100644 recipes/schwarzerpfeil.png create mode 100644 recipes/science_advances.png create mode 100644 recipes/science_news.png create mode 100644 recipes/scientific_american.png create mode 100644 recipes/scprint.png create mode 100644 recipes/seanhannity.png create mode 100644 recipes/seminar_magazine.png create mode 100644 recipes/serverside.png create mode 100644 recipes/sfbg.png create mode 100644 recipes/shacknews.png create mode 100644 recipes/shortlist.png create mode 100644 recipes/sigma_live.png create mode 100644 recipes/sign_on_sd.png create mode 100644 recipes/singtao_daily.png create mode 100644 recipes/singtaohk.png create mode 100644 recipes/sisainlive.png create mode 100644 recipes/sizinti_derigisi.png create mode 100644 recipes/skeptic.png create mode 100644 recipes/skeptical_enquirer.png create mode 100644 recipes/slate.png create mode 100644 recipes/slate_star_codex.png create mode 100644 recipes/slovo.png create mode 100644 recipes/sme.png create mode 100644 recipes/smith.png create mode 100644 recipes/sn_dk.png create mode 100644 recipes/snopes.png create mode 100644 recipes/sol_haber.png create mode 100644 recipes/southernstar.png create mode 100644 recipes/spectator_magazine.png create mode 100644 recipes/spin_magazine.png create mode 100644 recipes/sports_illustrated.png create mode 100644 recipes/sportstar.png create mode 100644 recipes/sporza_be.png create mode 100644 recipes/star_gazetesi.png create mode 100644 recipes/stars_and_stripes.png create mode 100644 recipes/stnn.png create mode 100644 recipes/strange_horizons.png create mode 100644 recipes/strategy-business.png create mode 100644 recipes/substack.png create mode 100644 recipes/sueddeutsche_mobil.png create mode 100644 recipes/sunday_times_magazine.png create mode 100644 recipes/superesportes.png create mode 100644 recipes/svt_nyheter.png create mode 100644 recipes/swarajya.png create mode 100644 recipes/t3n_de.png create mode 100644 recipes/t_online.png create mode 100644 recipes/taipei.png create mode 100644 recipes/tanea.png create mode 100644 recipes/taz.png create mode 100644 recipes/technology_review_de.png create mode 100644 recipes/techtarget.png create mode 100644 recipes/tedneward.png create mode 100644 recipes/thai_post_daily.png create mode 100644 recipes/thairath.png create mode 100644 recipes/the_athletic.png create mode 100644 recipes/the_baffler.png create mode 100644 recipes/the_budget_fashionista.png create mode 100644 recipes/the_clinic_online.png create mode 100644 recipes/the_conversation.png create mode 100644 recipes/the_daily_news_egypt.png create mode 100644 recipes/the_diplomat.png create mode 100644 recipes/the_feature.png create mode 100644 recipes/the_federalist.png create mode 100644 recipes/the_freeman.png create mode 100644 recipes/the_friday_times.png create mode 100644 recipes/the_journal.png create mode 100644 recipes/the_manila_bulletin.png create mode 100644 recipes/the_manila_times.png create mode 100644 recipes/the_marker.png create mode 100644 recipes/the_new_republic.png create mode 100644 recipes/the_philippine_daily_inquirer.png create mode 100644 recipes/the_philippine_star.png create mode 100644 recipes/the_register.png create mode 100644 recipes/the_saturday_paper.png create mode 100644 recipes/the_sun.png create mode 100644 recipes/the_verge.png create mode 100644 recipes/the_week.png create mode 100644 recipes/thecodelesscode.png create mode 100644 recipes/thecultofghoul.png create mode 100644 recipes/thedgesingapore.png create mode 100644 recipes/theeconomictimes_india_print_edition.png create mode 100644 recipes/themorningpaper.png create mode 100644 recipes/thenews.png create mode 100644 recipes/theprint.png create mode 100644 recipes/thn.png create mode 100644 recipes/tijolaco.png create mode 100644 recipes/tillsonburg.png create mode 100644 recipes/today_online.png create mode 100644 recipes/tomshardware_it.png create mode 100644 recipes/toyokeizai.png create mode 100644 recipes/tri_city_herald.png create mode 100644 recipes/tyzden.png diff --git a/recipes/TheMITPressReader.png b/recipes/TheMITPressReader.png new file mode 100644 index 0000000000000000000000000000000000000000..1660ab53b593ee8dbacff36ad9c664580229ae98 GIT binary patch literal 904 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+0817lc#Pl)UP|Np;z`}XMJ z!^4LTIojDDJ#u8vuH9N{>ihQY-LreQwua`uz59Or`0?e-mp_00eEs_M=g*(Ne*OCW z`}f>Ab5atMu3x{NnU=m_-u&&`wnaxoUcPjxs-mK+qjT5JojMwtd-v>}(AQs9QhMd` z<=Cj`ZCkfB)i+p}n4UR(+SbylrKx$*f`!`Z8pn!2ScLPM%!8blKy_k1t=k{N?kPE0?c4dHm$nt5+A!U-+w5MIs*znhwYxJI`b<_hK5!q2HFNjRt5%EpMSfc zXvob^$xN%nt-*Vp^ADf~NstY}`DrEPiAAXljw$&`sS2LCiRr09sfj6-g(p)%Asb#5 z5>XPASgue|l%JNFld4csS&*ubSx}P9z)&&g@h2XR!Y~buQ~syVcs>ncU{>bVOXe0< z7WSSj!Yr)d(qM8pg;{xXh{EX`S56!`b42C{`{@Rc1zvg#ufzpQJ~^3A1zN%2>FVdQ I&MBb@0OH1oApigX literal 0 HcmV?d00001 diff --git a/recipes/rabble_ca.png b/recipes/rabble_ca.png new file mode 100644 index 0000000000000000000000000000000000000000..3d471ac6526ff9b5f3067749ceec267c134efe3a GIT binary patch literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk@BpAX3RW*QAtEY=&h{fsTAN~vmJl3rBb^rf; z|NsAf|N4Ik;UB7tybd=1*I&NFeecnA58CycU)(%?F@`5>eSx+`)UjhaLS4sOJTGl3 zZI}ChaHYeA$n3}dbM`T4B}|O`_1xZ$wP)k}e~&{CZ2o6o$EuXhp&?*vz3Rb#etWgT oJzW2C=5PF9`p@Ne{)99c2Av~2zWln$SqpNQr>mdKI;Vst0PVC;N&o-= literal 0 HcmV?d00001 diff --git a/recipes/radio_prague.png b/recipes/radio_prague.png new file mode 100644 index 0000000000000000000000000000000000000000..d5282dc40a743c2783245bcc177b3c7beff81b37 GIT binary patch literal 262 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 )St( z$JO&F_|xpH{pm+qx}3q#Q=v{_VXVVB#%GS4@dt{Z(FQH11-iz%@6kfYurD<)@!)zR d()^6%X;RPz7qMp^YJtBv62slc?Yc`i?|=Che6|1p literal 0 HcmV?d00001 diff --git a/recipes/real_clear.png b/recipes/real_clear.png new file mode 100644 index 0000000000000000000000000000000000000000..929d08085da7950a79fb9e0dd135190b1cc0d3b5 GIT binary patch literal 1254 zcmW;K3rv$o00;0BL?_H-q7`(X3JqAhiZd7%Q0poyTM?`sw1O2>q=<^54t#`{!10o{AAOe=TA-yO7Rp1gy3@<={&&e;a>?bNO{4y>+J2)w z0ARI0g&YJ|myh!^TbSfZPA6PeU8Ed|1Hi%Y<6I_Er@RBe<`^xI?)Tx-nAeZajp$~F zG_&d<-R)9se!iB=)#h?FnVDKnjy5+J?zP+-+8cSpH}bUEIRgNogdM8n6qxGdlwJV( z0O$wcB>?bf06_+DI3WN+fDvLKM2H|l0zm|T2w+4Q2oWI=R)ochNF*@;Vt^52AViEn zSTT`^Vh|LAMG*u=VB%0L4#g4Rb11kB#|H#1xbVGQaVG*2$4a|Fj-i*Ed01EDoV}_lZPLdpNv$TjZws$ zSDcSk#$Hff{7D%Xue^Lk6&J69c`ZSekf=&bQl+P>lakfRP>MR`x|+>avs2ZnX+!MP zp|td&^bAc}hUS+nO;)xR%I19fJudt=?ahK=sGw-LxOk+XXr%Dhkz2P$ZWWJ|mFajq zJ+DMxQmQX4(^piCmRF2cRgK=M9)qf@$7}fGwGYMxg7L;iL%qNt5EvR742_M(dV#U= zp|PpS_~`LOQ}aY~%S21d#M4&O<0q!JHd9->sr~QCe>x^RI$n2my?*iERCmu*PtR0e z-E7JP6Rb6X{ge*-=LL4f8=ONYGuG)Z z0z21`tt+ma`EeJI6w2Z5C-(3w~({jJ%fECtpx7B7FLb6x61t02s z;XljEwaw&nLP|JjXRD)U(8S>{?A9J?>9#99=Vd>=S5AnCbUz}*d$eB3<5m5<&)bbr z8GYMAulE+*dqOJLg_bvlJx{tC{-|$>vEAj;O_r$rBSj=tCBQbg6(=+k!yb=Z}tYpt_^P<`rtjO*9Y5i4>I21oANv?+WSk=A(Bx#lh# zdwAvcOfRCrBzEyzLnG~!ukH0--mv*uZ@{)CTTq*^$o2c977LpL{Qaop2fioL{s*a; BmT&+7 literal 0 HcmV?d00001 diff --git a/recipes/reason_magazine.png b/recipes/reason_magazine.png new file mode 100644 index 0000000000000000000000000000000000000000..11b0b6b1bfebae5a45bbc77bc8b87a7431c4ab3f GIT binary patch literal 208 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvmUKs7M+SzC{oH>NS%G}U;vjb? zhIQv;UIICC0X`wFpL0yU6xn>vxA_0>-}ku{-@4=fef#wL_{P5vum0LGSNgHG15jmv zr;B5V#`$Cc7iJzV9*u^H$Cwr^Ji+vE>ZIIhCz#HO9X<9;gX!3@V=J_J&Mw=rh-=A$ zTaR*V7-J7G`X10p2;gujS-`2mbxBhyfyv2B!$FYY*mZFy#lu_n1I=deboFyt=akR{ E0H}OS-~a#s literal 0 HcmV?d00001 diff --git a/recipes/red_voltaire.png b/recipes/red_voltaire.png new file mode 100644 index 0000000000000000000000000000000000000000..87ffd581b59b50d12fb08bb878337d29a5a80cd1 GIT binary patch literal 1320 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 M07*qoM6N<$g4p0@+5i9m literal 0 HcmV?d00001 diff --git a/recipes/republica.png b/recipes/republica.png new file mode 100644 index 0000000000000000000000000000000000000000..27485c6607e4f3154461f7a3ad45deb7f1f90679 GIT binary patch literal 1435 zcmV;M1!Ve(P)KP+rO2n0$&LJ1*A38F}X zVri*jN=;~Nm0IJq?lvCvY&>h@ky=em8e42)8Z|Mi+fBU!)_6tNb9Z&uW_8s)^w(!* z-`BdvV>fCC9%km9dFS_gj(KKA0zg9YwoofbQb~%BK(3P{3mBW2tnrrcA(6xZciF(< z7jaF8fW@&TLgQ0maGk_O;yfa82^bJCxdq-K=@CA<4yL15h7&P*88#yzC0X?iXJc3J zja6F8<&_=`Q_2V&^G5TnpsYPTGXuNb;Q{U(9)_*B#9IPv(lL>M(_8Ib>+5K3Ye#Qy zzYsNUm)dpDus|MV_15ou6z5g~ds22d9MDfp@b-h2KXCWnub!HZj=>`H$3JHS;hLKE zHXdAF5n$#nEGR%vcQ8VUbI)*8ap>7wk?z1fQIoq>U=U^wbnqDY~T zV=4P<-gfl%2@tL~cfmSo7#R_PxGPt9e~PT2zLvuSaKj@2M|t!wein_jhfOfD_WP&c z930xu5<3$RwO?FZghDPyLVP^$&&d&TIA*zp`L>Ao8&Y!)Zccyp{0eJ z-rm+iTf#!Y=m)dEj1yT&pmWDG?;C-cnORsY7KDa`@*vdf^_V3K)YacYeqk|6$|}(D zR0EUI1Q(H8Sy;ewSOk_%%CL0q1H6*>!Kw&=mHxGTUku0cvTp?XpFgKGYe!CYHYcFU z$Uty#FoJ?UL`CHd?g}vz&Xc7gqN5Og`ZOZ^{BXfvig39M;W8N_$%T=zmk=2f!{=>F z>8}$I2OmG_z$>d2TJ4{lfWN;#E}TD)uC7k>5O9V%3)f4^xs_O{t*PPjq@=Y(DuaYb zY>J-*el%v7oAaK4rc;ZC+jme)NhdP$Bfj{TadB|a;p3#ORo~D^8Sxs;>*Dj7i`X1F zfy)R~6xNqg9dza_=C@_fJqZl_J&2Sv6?%I5Fg`wx0EL1B)YaAT@remNcSA*G6;@VO z;dD50F*?RgLMG>+Ojw(JV-{lP$-Xfl5-^)B$jL1r7wCAuxw)Ay#C8SCj$&F?sxmP` z2N5gzd3k(}g0>$6my!0&bpZhZJDGoPWNJ>$nqeCO4 z?NT=Zg(rd0krCerY%T4NJ9xyGmRG`Nvq7y=@kJ--Zosl(Vp1x)dwTiY*w16#1ZnG? z2spvpHC5FIyAQBjaQBxcp7mb7oWPyNMmGTp%)u8!NJvbEk@ola*x07_BDh+m-YM{W zzmkb0di(nEGwlMl_#6e*@Aq5qV^u9bw3KOSo9 zR;sf24rqP!h=Vc-4GDopqoK3JwfpUP2MfGQV)N!jWHcD)USPnBp<%SPwqsym0H*>2 z$pY!n>2&l^?8002ovPDHLkV1iefs=ojL literal 0 HcmV?d00001 diff --git a/recipes/respekt_magazine.png b/recipes/respekt_magazine.png new file mode 100644 index 0000000000000000000000000000000000000000..73cb5e80da77dd6e637bf9f0c8e0d97296c7fceb GIT binary patch literal 384 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyy#YQUu0R?MFf%g?2na|>NJvUb z%E`&8sHmu_s;aB28yXrqIy$<$yL)LGzjqbP0l+XkKb_|qL literal 0 HcmV?d00001 diff --git a/recipes/reuters.png b/recipes/reuters.png new file mode 100644 index 0000000000000000000000000000000000000000..702998fd2bbf3ae2479179c5578528f30d34783c GIT binary patch literal 582 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyoCO|{#S9GGLLkg|>2BR0prBcR zPlzj!{{R2~uM`H7&@AKaM}Rs7OM?7@85jf<3>*Ry3L5(7uitHYE_dL{oS`{k{=(J5W89mzAx z(CSvr=5uC#^JX2KwPv!p8ta9}6RxI}_;B4@BYf!krjutTB+RYZd1YaxMFOAh^$Sa0 zXW4ji9%m}vRudnfwd{$F?8`8=IZDPV{fnz2{N(yxC8W+t%;R(SS6!Z0T)URN5T#b(7hBw=H_QLGprILCWDhXOB%b zcZ>2kVxafZW9Is_+m$<|_inxEe9L3jSxK%Q{e_)XuR6C-(qp5!^-q(t+LaT#F11a~&6m5@qRHyN z+A8k&*52t6HJ`lpUAcSX(`%*QYtjO){y4nkz?|1gyCok?`n-()+T9;(CnbOI&6DnA nx4dco-z0z2ile*x<^M4CJQtBYl~uI}7=sL+u6{1-oD!Mm@aCCNSZf|aLbZc;OZgqEWb$4iOZ)t6BZFF|vZf|LCacFLDT3=w@U}02O zSW#70>{3!sQ&j6rOh!pcMMz0SNlHUUM?pkIKtn|9K|we>JU2Q!>N`6zGc@QjG9e=* z7Z@2B85tNE8W|fL6&4o`4i60u4h;?u4h|0r3JM1Y2mc5M1Ox;E0s;U40RI61Hyakj z0001!bW%=J009940s{mG2nh=g4-gR%6BHH~7Z@5IAtNLwC@3i^D=jZEGB!9kIXpZ+ zLPADHM@L9XOH55pPf<})Q&Uq^R##bEUS3~gXlrY1Y;ALMb98lfc6oh$etv?6hlhxZ zi;a$wnVO!Tp`xOrqobpzsIjxOxw*W*!o$PF$H>dj(b3b}h1IN^z`@m`279T z%iYfa00G2FL_t(IPo0rhbJQ>tMvpfV#4&`>76JrVdNC|5^bmS)riETiH-r{C7y^XO zLeTXO=0Pj3A(NR5-_zBZGuNMF0AN*V1NgLqPdz*1cswBjA#xG2EX%U&9fUpz9c}zSQ*sKo_C_Z(||L07CJZUnUYb zGP4d!v=Rw0!-5rWfk>8qwXH(&VV5;_IG}$M33&aCB&t{p;8|9P29^|?dcdssa<%6I zi{q$z_6z`0gO6Ym7|6i^3ywK3uM{HqKtJx1;aLF=YZ10Bu?MQT1U`c4^$j3fXp&xZ(XX)6e<`A2Bp$0)BM z9HdDXgzppEuJ_qfWaL56{=Sz+?1n`*av?#j9V+PcP$K`tYViIR0FGDSs4J-txe~cB z^-HC2i`kEzuw10(qsDFfCKWhgYHgoZYXwT#qgHxEfBz3#8KVD2v~jlp O0000^Zws@qHc&RHg~4c9 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 literal 0 HcmV?d00001 diff --git a/recipes/revista_semana.png b/recipes/revista_semana.png new file mode 100644 index 0000000000000000000000000000000000000000..274baea0d58d2edfd62e65c57c615fc4e46282c0 GIT binary patch literal 575 zcmV-F0>J%=P)^3;*9U%PS;r!v^__w(5V`c0) zJOBOt{N&~EW@zj@Kl|C)>n<_=^7H!0$?|k|>oGLyAS3$A%n}0> z^Yr$gq3R(d`pnJpczXTl=<<7f>L)4fKtlcN?EUWU>MAYmLq_?;#qn@*=^P*IH8}XT zxAAjy?MX}NA0q$!{QTqO|NsB@tE}{rlkjM2``Fm)EimgdHtHfJ{pRNKd3*l)`uV@V zFytk=0002;NklQth=@qL8z^8CVDxIknM1{luv9EKKySxRIcT-@B)fGh_|EB3 z_vU`Z^SWafVcv>+=Qfjf08~HR>mKm|(lIdK$k^@yU^xcf*sYoF24G(ST$s0i0xP0` zG0vvK>_p0kr(*?FPxn^skj=CbRQMv2@n#p{y_Eaosvo0Z=zmtcegNUuxc_%E)#(5L N002ovPDHLkV1my8JT(9S literal 0 HcmV?d00001 diff --git a/recipes/revista_summa.png b/recipes/revista_summa.png new file mode 100644 index 0000000000000000000000000000000000000000..b1efaf3e921d78759fb8bd5fa4c7c7d0c306c251 GIT binary patch literal 1343 zcmc)I`CHNl9Ki7}Qh7Ajwp_W=Y98f@h~ia9Di|sfqO;sO)LBmRX}V0CI;Ui1l!aEN z)>Et5>`*jK%*xD8RBGI)334ckgcr#r(7yc@d!EbBOIj{CD+owHr^ z*@s=T-4ACUbh6&JwVNzjK zP8pR-qe^A`pjuL^mu6;`=H^WE^QHyRf=Qz>eFS|pYqe&*-fS>fj7G~6XvtzSSht&1^pbR@q6g}bMVK=EfG=v50pUw ztwy=w-?RQOApK;mOHxtCYT7`{{Bv*NU8Z%ybSg80J{WS)S5;Ij^My;>d68u)3{GABA|(JM-e> zfZazg=2*AgypR{aUPQgblq(#dZA~Ot#f=@w%9pi^um1re=*%UwpBc#b~N&Bd^#EujZ7fhe6WGfXs%7q zzi4WHVmjYX&UC9hjcM%9z1d#wX;Lq)5}RxhGeWPBjzBkV!}om4LGv=gs)(nx4n+{` z<3HORMZ$qpe`2LqKe_La%F-Tg>9T&)hah6pyrQ12p0Qthy|b|YY0s1U_ej4f)+agK zc>4eX)0x%{lR1&wnHbQMW&Mf1B^zzY5)KL literal 0 HcmV?d00001 diff --git a/recipes/rga.png b/recipes/rga.png new file mode 100644 index 0000000000000000000000000000000000000000..a4ac92f9c205d31f1799f2fb828db4bee4be89c5 GIT binary patch literal 964 zcmV;#13UbQP)*%cMp930sq zBH24T+5iCB6&2bX9NHox+9xO4D=XSEGTJye+D1m(Cnwu0E88wE+cGlSH8tBfINLir z+d@LyU|`%kJKQ}z+(1CwL`2+1M%+k9-9|>;N=n^KOx;jW-BeWGS6AL#T;5`0-eY6m zT3X*-UEg3}-(q6lXJ_DIV&G$A;ALgtXJ_DQYT#>Y;Bau^ZEfLiZ{cuo;c{}}b93Q? zgW`90;(B`Gdwb%7g5!60<9&VOe}ChGg5-mP7bzLqN3@eqv@oi>ZYdZr>E+usOqVy>Z+>i zrKRhprt7Dt>!_&fsj2I#s_U$*?5(ZrudnR0v+T6A?Y6e=x3})NxbC{T?!3J3yu9zg z!0^Gr@yEyV%ggf4&h*mK^w!q()YSFX*7e!h_SxC?+}!uw-S^+$_u%07mS3=lbjG`|Rxd?(X~W@ci)b{PFSp^z{Ao^#1wz{`&g=`uhL-`~Us@|Nj2} z|NmMF?%eB+sSe~ZU{C2L=pQyMpSAJcD1FGUMkvIUR7YT( zN-X}0$_>6T#4lWm^gp2xcTFy0-~|-}x{K=#Gs@?DVkwZ5?Pu_PC)bTqC!Znu3=%aIPRRnj3-TT!qTgTq zgS0!_wzJ(*wYwkwyI>dG$*l;o`jPsMu7OWcqxyadD=h=a3c}$}ngAYl_v=NLkf5cH zK@vx00wz0<9J_@OKK7Wp0%F^2E8F>0euGAKw;%_p}~ZpsK>pJl@lS m5y}Q|;zIbhC?Vx}&HfLt?RpgQlR3u#0000UM1J?d)-_^}Wx{a-x<} z6;ZBB{0bxyrBN&Z;^%0A1%VYpl54Hm&*#oe{x9NxBZ&p^0Wa|Xpa`)bK9HH_muUE9 zBtij-018f65)+(FiO7gh zDQY7mp*kFPM_t<4{SGK5Fgf$-?sL2|oGR z-y592{L#}*aEW@nHX3wwIBa7w8jQQHY1^X1Gf$tsck0C2&Hwn`x9

pZ)3aYp=b^ z6K&gup+p3dYVG^%=X27g;inYBiQU)I4m?>s6@BfOvY(zjapuz2*-IaP`k801of|Zq zr5;a~CS#9BgIrx2G~;167!Deqx$x9`Pj7N_JQ=+8-S7PCjoIw@;qvnGVMLi;zy9XY z-rioa5D}!!6JkLMskBY@(+cEdZA%wF_JvdHThCkz)h~^Q!xv7U`Ot~=watFmm1$d4 zC*wgk9Cov%@nC;E?3$%c=L}JhgHL*bCR! zK6UNd(rh~W;nLF5qja6W{np#;?d_?k?K-kn7P_U)&6Cgm=I2jbdhw-hvi{}TAHUf5 z^Nm{Pk(n&)MpJ&Mh#e7|D3+s?U86TM{hw)$N{O?fGDPKGY7k9ZuD0Fi(;vQaw&~@2$ z0}s1S?z#u1&)cTXGFeaKC^1V@`|9H~J6)R(9=~_U@$s~5Z@)WPTOKZNomgJIeC5io zt*C)FrDZ9X|vE9dLZ8Ey>!nM(b7rxwgOP@d3+d0=zDUOVcQ12A|BjAC8 zX|1*I`?6bWjulL6%`<&dE@F3WDLclsW^<~hyQY~R4-fizH*&78@gd-M1A2*X0y~9f zOmpJmnc>+_{z1EO;iZ0dc(FfvEUX&QI3O&~Ebb$22HM7q(_Az}gx&^P;IlU82yPkf z0%J)_B}zZ*`{~}^-n_b5AB{&BufKKU@WzcBukP&bJS>B&zfW1e@DYtx{wSTFa-GXC zLkdJ=AWhod2X0Gx$7a*XXN)_+zZrI%ey(P&)mq=FnePZqF@sE>tmzX{beUe8&g#l& zG(LX*BUgX)TVMR`x0}hg-q4viobAOj?Timo&cA-|DNV~JW1saC>d)YX$xHVoE z+Hm@w2Yx-BX>uj;GZE1y)M`)rnnOYB++#YOvvq21{S()&U7n2F?NZLY_#3YEs_hqq zPC{Vggpvm1iXE61*f#!q<9)-m%sgN1r8GY&q3=uFHC#+{&GwmEYhSCkYvyCyAbh+CM(dm7=YWe!5RAMNa}lsNa|XF*$%u1H$v36tPp!GKMGu{}z&1?-3X z4M}f43)RQmIHH6ez`l z5Ye+BYQj>&r>Pu>L0X`uMC><-H?_;oDuaY;JFZULCB2Ko~1eL z(~-#xXE&eER6;7c@VU!GS;fABa|48=1OhBD3o-~Wq5>n^XKioTc^7djBIZb|Hm7W# z5KL^#P;FPi$j;N5xuw$7rW?H*&G6mA#m^WF)}!9 zWU8?u6fzPEi4g`Po)}0v4_w4OHRGTrx4I^FF(sYYt~I@4djkd~O2f{h2lM-PuD{Yx zkN&yw^?xwSPu=0v)f)^~e?4o3I*kQ`lFZ^jAe_O1sw=k1FbS$Qwy`}FoHOy1K_)CP zxZqlvZb(bZ_N`YRz43#uzkL7JYcDs<_ThNxcfNM>`+xhR+pm1HKic0x3N?h1Bz3bMAVcHYtv5El>J$M{J4L3>w4Y2^N&Z} z@NbSD-G1lMy*HR1A2dK!iC!SUEQr#X!7A7V&sSMFbCR>4InV0(i^~2A{n2xZ@*JVH zkPI=^LWnj(3TZ#YjWS$%v$TWP>Z2QPP2c>+joi6;uQY=PCg#)q2V_4LU?jx*1Q9F< zC}d^94A&}KABCmUOUingI(;7LGNDx|49o(t&`LpwL&>+J8@&-td%Zrsdw+WC-=}%- zP=FMTBn-|4un|8a{+oGUG|(+U=>j~!IR;ID7GWTTSO`8)g^!4Gh;&3htvTI?Iu~Fh ze#Yhr@jnP6fC!*KicEtONWlE+gjA$bP*_lL)^r7%pZYIu5qt&ARnJBM0000UM1J?d)-_^}Wx{a-x<} z6;ZBB{0bxyrBN&Z;^%0A1%VYpl54Hm&*#oe{x9NxBZ&p^0Wa|Xpa`)bK9HH_muUE9 zBtij-018f65)+(FiO7gh zDQY7mp*kFPM_t<4{SGK5Fgf$-?sL2|oGR z-y592{L#}*aEW@nHX3wwIBa7w8jQQHY1^X1Gf$tsck0C2&Hwn`x9

{date}

{img}
'.format(**data) - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(html.encode('utf-8')) return {'title':'Page %d of %s' % ((num + 1), title), 'url': ('file:' if iswindows else 'file://') + path.replace(os.sep, '/')} diff --git a/src/calibre/db/adding.py b/src/calibre/db/adding.py index 372b19e936..58452b32a0 100644 --- a/src/calibre/db/adding.py +++ b/src/calibre/db/adding.py @@ -244,7 +244,7 @@ def add_catalog(cache, path, title, dbapi=None): fmt = os.path.splitext(path)[1][1:].lower() new_book_added = False - with lopen(path, 'rb') as stream: + with open(path, 'rb') as stream: with cache.write_lock: matches = cache._search('title:="{}" and tags:="{}"'.format(title.replace('"', '\\"'), _('Catalog')), None) db_id = None @@ -276,7 +276,7 @@ def add_news(cache, path, arg, dbapi=None): from calibre.utils.date import utcnow fmt = os.path.splitext(getattr(path, 'name', path))[1][1:].lower() - stream = path if hasattr(path, 'read') else lopen(path, 'rb') + stream = path if hasattr(path, 'read') else open(path, 'rb') stream.seek(0) mi = get_metadata(stream, fmt, use_libprs_metadata=False, force_read_metadata=True) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 0cd4a35db3..19b941dd1d 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1273,7 +1273,7 @@ class DB: shell = Shell(db=self.conn, stdout=buf) shell.process_command('.dump') else: - with lopen(fname, 'wb') as buf: + with open(fname, 'wb') as buf: buf.write(sql if isinstance(sql, bytes) else sql.encode('utf-8')) with TemporaryFile(suffix='_tmpdb.db', dir=os.path.dirname(self.dbpath)) as tmpdb: @@ -1467,7 +1467,7 @@ class DB: path = self.format_abspath(book_id, fmt, fname, path) if path is None: return missing_value - with lopen(path, 'r+b') as f: + with open(path, 'r+b') as f: return func(f) def format_hash(self, book_id, fmt, fname, path): @@ -1475,7 +1475,7 @@ class DB: if path is None: raise NoSuchFormat('Record %d has no fmt: %s'%(book_id, fmt)) sha = hashlib.sha256() - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: while True: raw = f.read(SPOOL_SIZE) sha.update(raw) @@ -1538,11 +1538,11 @@ class DB: else: if os.access(path, os.R_OK): try: - f = lopen(path, 'rb') + f = open(path, 'rb') except OSError: time.sleep(0.2) try: - f = lopen(path, 'rb') + f = open(path, 'rb') except OSError as e: # Ensure the path that caused this error is reported raise Exception(f'Failed to open {path!r} with error: {e}') @@ -1564,7 +1564,7 @@ class DB: return True except: pass - with lopen(dest, 'wb') as d: + with open(dest, 'wb') as d: shutil.copyfileobj(f, d) return True return False @@ -1578,10 +1578,10 @@ class DB: if abs(timestamp - stat.st_mtime) < 0.1: return True, None, None try: - f = lopen(path, 'rb') + f = open(path, 'rb') except OSError: time.sleep(0.2) - f = lopen(path, 'rb') + f = open(path, 'rb') with f: return True, f.read(), stat.st_mtime @@ -1620,7 +1620,7 @@ class DB: os.remove(path) else: if no_processing: - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(data) else: from calibre.utils.img import save_cover_data_to @@ -1651,7 +1651,7 @@ class DB: windows_atomic_move.copy_path_to(path, dest) else: if hasattr(dest, 'write'): - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: if report_file_size is not None: f.seek(0, os.SEEK_END) report_file_size(f.tell()) @@ -1674,7 +1674,7 @@ class DB: return True except: pass - with lopen(path, 'rb') as f, lopen(dest, 'wb') as d: + with open(path, 'rb') as f, open(dest, 'wb') as d: shutil.copyfileobj(f, d) return True @@ -1720,7 +1720,7 @@ class DB: traceback.print_exc() if (not getattr(stream, 'name', False) or not samefile(dest, stream.name)): - with lopen(dest, 'wb') as f: + with open(dest, 'wb') as f: shutil.copyfileobj(stream, f) size = f.tell() if mtime is not None: @@ -1822,7 +1822,7 @@ class DB: def write_backup(self, path, raw): path = os.path.abspath(os.path.join(self.library_path, path, 'metadata.opf')) try: - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(raw) except OSError: exc_info = sys.exc_info() @@ -1835,12 +1835,12 @@ class DB: raise finally: del exc_info - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(raw) def read_backup(self, path): path = os.path.abspath(os.path.join(self.library_path, path, 'metadata.opf')) - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: return f.read() def remove_books(self, path_map, permanent=False): diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index dd9c0bebbb..c7508026bc 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -1687,7 +1687,7 @@ class Cache: try: cdata = mi.cover_data[1] if cdata is None and isinstance(mi.cover, string_or_bytes) and mi.cover and os.access(mi.cover, os.R_OK): - with lopen(mi.cover, 'rb') as f: + with open(mi.cover, 'rb') as f: cdata = f.read() or None if cdata is not None: self._set_cover({book_id: cdata}) @@ -1787,7 +1787,7 @@ class Cache: # message in the GUI during the processing. npath = run_import_plugins(stream_or_path, fmt) fmt = os.path.splitext(npath)[-1].lower().replace('.', '').upper() - stream_or_path = lopen(npath, 'rb') + stream_or_path = open(npath, 'rb') needs_close = True fmt = check_ebook_format(stream_or_path, fmt) @@ -1807,10 +1807,10 @@ class Cache: if hasattr(stream_or_path, 'read'): stream = stream_or_path else: - stream = lopen(stream_or_path, 'rb') + stream = open(stream_or_path, 'rb') needs_close = True try: - stream = stream_or_path if hasattr(stream_or_path, 'read') else lopen(stream_or_path, 'rb') + stream = stream_or_path if hasattr(stream_or_path, 'read') else open(stream_or_path, 'rb') size, fname = self._do_add_format(book_id, fmt, stream, name) finally: if needs_close: @@ -2711,7 +2711,7 @@ class Cache: pt.close() self.backend.backup_database(pt.name) dbkey = key_prefix + ':::' + 'metadata.db' - with lopen(pt.name, 'rb') as f: + with open(pt.name, 'rb') as f: exporter.add_file(f, dbkey) os.remove(pt.name) poff = 1 @@ -2723,7 +2723,7 @@ class Cache: pt.close() self.backend.backup_fts_database(pt.name) ftsdbkey = key_prefix + ':::' + 'full-text-search.db' - with lopen(pt.name, 'rb') as f: + with open(pt.name, 'rb') as f: exporter.add_file(f, ftsdbkey) os.remove(pt.name) diff --git a/src/calibre/db/cli/cmd_add.py b/src/calibre/db/cli/cmd_add.py index 414fdae4b4..90d4c132ff 100644 --- a/src/calibre/db/cli/cmd_add.py +++ b/src/calibre/db/cli/cmd_add.py @@ -113,7 +113,7 @@ def book(db, notify_changes, is_remote, args): data, fname, fmt, add_duplicates, otitle, oauthors, oisbn, otags, oseries, oseries_index, ocover, oidentifiers, olanguages, oautomerge, request_id = args with add_ctx(), TemporaryDirectory('add-single') as tdir, run_import_plugins_before_metadata(tdir): if is_remote: - with lopen(os.path.join(tdir, fname), 'wb') as f: + with open(os.path.join(tdir, fname), 'wb') as f: f.write(data[1]) path = f.name else: @@ -121,7 +121,7 @@ def book(db, notify_changes, is_remote, args): path = run_import_plugins([path])[0] fmt = os.path.splitext(path)[1] fmt = (fmt[1:] if fmt else None) or 'unknown' - with lopen(path, 'rb') as stream: + with open(path, 'rb') as stream: mi = get_metadata(stream, stream_type=fmt, use_libprs_metadata=True) if not mi.title: mi.title = os.path.splitext(os.path.basename(path))[0] @@ -157,7 +157,7 @@ def format_group(db, notify_changes, is_remote, args): if is_remote: paths = [] for name, data in formats: - with lopen(os.path.join(tdir, os.path.basename(name)), 'wb') as f: + with open(os.path.join(tdir, os.path.basename(name)), 'wb') as f: f.write(data) paths.append(f.name) else: @@ -254,13 +254,13 @@ def do_add( cover_data = None for fmt in formats: if fmt.lower().endswith('.opf'): - with lopen(fmt, 'rb') as f: + with open(fmt, 'rb') as f: mi = get_metadata(f, stream_type='opf') if mi.cover_data and mi.cover_data[1]: cover_data = mi.cover_data[1] elif mi.cover: try: - with lopen(mi.cover, 'rb') as f: + with open(mi.cover, 'rb') as f: cover_data = f.read() except OSError: pass diff --git a/src/calibre/db/cli/cmd_export.py b/src/calibre/db/cli/cmd_export.py index 022892ffdb..38eeaf8f94 100644 --- a/src/calibre/db/cli/cmd_export.py +++ b/src/calibre/db/cli/cmd_export.py @@ -115,7 +115,7 @@ class DBProxy: if self.dbctx.is_remote: if fdata is None: raise NoSuchFormat(fmt) - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(fdata) diff --git a/src/calibre/db/cli/cmd_list.py b/src/calibre/db/cli/cmd_list.py index ad2c934249..9907b26001 100644 --- a/src/calibre/db/cli/cmd_list.py +++ b/src/calibre/db/cli/cmd_list.py @@ -166,7 +166,7 @@ def do_list( ascending = True if 'template' in (f.strip() for f in fields): if template_file: - with lopen(template_file, 'rb') as f: + with open(template_file, 'rb') as f: template = f.read().decode('utf-8') if not template: raise SystemExit(_('You must provide a template')) diff --git a/src/calibre/db/cli/cmd_restore_database.py b/src/calibre/db/cli/cmd_restore_database.py index 2ac1e2296a..3488d6585d 100644 --- a/src/calibre/db/cli/cmd_restore_database.py +++ b/src/calibre/db/cli/cmd_restore_database.py @@ -76,7 +76,7 @@ def main(opts, args, dbctx): prints('old database saved as', r.olddb) if r.errors_occurred: name = 'calibre_db_restore_report.txt' - lopen('calibre_db_restore_report.txt', + open('calibre_db_restore_report.txt', 'wb').write(r.report.encode('utf-8')) prints('Some errors occurred. A detailed report was ' 'saved to', name) diff --git a/src/calibre/db/cli/cmd_set_metadata.py b/src/calibre/db/cli/cmd_set_metadata.py index e672471203..3e4b2e8d7d 100644 --- a/src/calibre/db/cli/cmd_set_metadata.py +++ b/src/calibre/db/cli/cmd_set_metadata.py @@ -143,7 +143,7 @@ def main(opts, args, dbctx): opf = os.path.abspath(args[1]) if not os.path.exists(opf): raise SystemExit(_('The OPF file %s does not exist') % opf) - with lopen(opf, 'rb') as stream: + with open(opf, 'rb') as stream: mi = get_metadata(stream)[0] if mi.cover: mi.cover = os.path.join(os.path.dirname(opf), os.path.relpath(mi.cover, os.getcwd())) diff --git a/src/calibre/db/cli/main.py b/src/calibre/db/cli/main.py index 01e52e20b5..0aaaeecb0a 100644 --- a/src/calibre/db/cli/main.py +++ b/src/calibre/db/cli/main.py @@ -119,7 +119,7 @@ def read_credentials(opts): from getpass import getpass pw = getpass(_('Enter the password: ')) elif pw.startswith(''): - with lopen(pw[3:-1], 'rb') as f: + with open(pw[3:-1], 'rb') as f: pw = f.read().decode('utf-8').rstrip() return username, pw @@ -173,7 +173,7 @@ class DBCtx: def path(self, path): if self.is_remote: - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: return path, f.read() return path diff --git a/src/calibre/db/utils.py b/src/calibre/db/utils.py index 04dbc96ea5..2d3fc1deae 100644 --- a/src/calibre/db/utils.py +++ b/src/calibre/db/utils.py @@ -227,7 +227,7 @@ class ThumbnailCache: if hasattr(self, 'items'): try: data = '\n'.join(group_id + ' ' + str(book_id) for (group_id, book_id) in self.items) - with lopen(os.path.join(self.location, 'order'), 'wb') as f: + with open(os.path.join(self.location, 'order'), 'wb') as f: f.write(data.encode('utf-8')) except OSError as err: self.log('Failed to save thumbnail cache order:', as_unicode(err)) @@ -235,7 +235,7 @@ class ThumbnailCache: def _read_order(self): order = {} try: - with lopen(os.path.join(self.location, 'order'), 'rb') as f: + with open(os.path.join(self.location, 'order'), 'rb') as f: for line in f.read().decode('utf-8').splitlines(): parts = line.split(' ', 1) if len(parts) == 2: diff --git a/src/calibre/devices/__init__.py b/src/calibre/devices/__init__.py index bf5db6eb5a..d1427d1ad7 100644 --- a/src/calibre/devices/__init__.py +++ b/src/calibre/devices/__init__.py @@ -184,7 +184,7 @@ def debug(ioreg_to_tmp=False, buf=None, plugins=None, ioreg = 'IOREG Output\n'+ioreg out(' ') if ioreg_to_tmp: - lopen('/tmp/ioreg.txt', 'w').write(ioreg) + open('/tmp/ioreg.txt', 'w').write(ioreg) out('Dont forget to send the contents of /tmp/ioreg.txt') out('You can open it with the command: open /tmp/ioreg.txt') else: diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index facd064009..1f965f2581 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -385,7 +385,7 @@ class WEBOS(USBMS): if coverdata and coverdata[2]: cover = Image.open(io.BytesIO(coverdata[2])) else: - coverdata = lopen(I('library.png'), 'rb').read() + coverdata = open(I('library.png'), 'rb').read() cover = Image.new('RGB', (120,160), 'black') im = Image.open(io.BytesIO(coverdata)) @@ -402,7 +402,7 @@ class WEBOS(USBMS): cover.save(data, 'JPEG') coverdata = data.getvalue() - with lopen(os.path.join(path, 'coverCache', filename + '-medium.jpg'), 'wb') as coverfile: + with open(os.path.join(path, 'coverCache', filename + '-medium.jpg'), 'wb') as coverfile: coverfile.write(coverdata) fsync(coverfile) @@ -410,7 +410,7 @@ class WEBOS(USBMS): if coverdata and coverdata[2]: cover = Image.open(io.BytesIO(coverdata[2])) else: - coverdata = lopen(I('library.png'), 'rb').read() + coverdata = open(I('library.png'), 'rb').read() cover = Image.new('RGB', (52,69), 'black') im = Image.open(io.BytesIO(coverdata)) @@ -425,6 +425,6 @@ class WEBOS(USBMS): cover2.save(data, 'JPEG') coverdata = data.getvalue() - with lopen(os.path.join(path, 'coverCache', filename + '-small.jpg'), 'wb') as coverfile: + with open(os.path.join(path, 'coverCache', filename + '-small.jpg'), 'wb') as coverfile: coverfile.write(coverdata) fsync(coverfile) diff --git a/src/calibre/devices/cli.py b/src/calibre/devices/cli.py index 1497f81ae4..1fb276d728 100755 --- a/src/calibre/devices/cli.py +++ b/src/calibre/devices/cli.py @@ -300,7 +300,7 @@ def main(): if os.path.isdir(outfile): outfile = os.path.join(outfile, path[path.rfind("/")+1:]) try: - outfile = lopen(outfile, "wb") + outfile = open(outfile, "wb") except OSError as e: print(e, file=sys.stderr) parser.print_help() @@ -310,7 +310,7 @@ def main(): outfile.close() elif args[1].startswith("dev:"): try: - infile = lopen(args[0], "rb") + infile = open(args[0], "rb") except OSError as e: print(e, file=sys.stderr) parser.print_help() @@ -361,7 +361,7 @@ def main(): return 1 path = args[0] from calibre.ebooks.metadata.meta import get_metadata - mi = get_metadata(lopen(path, 'rb'), path.rpartition('.')[-1].lower()) + mi = get_metadata(open(path, 'rb'), path.rpartition('.')[-1].lower()) print(dev.upload_books([args[0]], [os.path.basename(args[0])], end_session=False, metadata=[mi])) dev.eject() diff --git a/src/calibre/devices/cybook/driver.py b/src/calibre/devices/cybook/driver.py index adf991af72..c0e12261d2 100644 --- a/src/calibre/devices/cybook/driver.py +++ b/src/calibre/devices/cybook/driver.py @@ -49,7 +49,7 @@ class CYBOOK(USBMS): coverdata = coverdata[2] else: coverdata = None - with lopen('%s_6090.t2b' % os.path.join(path, filename), 'wb') as t2bfile: + with open('%s_6090.t2b' % os.path.join(path, filename), 'wb') as t2bfile: t2b.write_t2b(t2bfile, coverdata) fsync(t2bfile) @@ -89,7 +89,7 @@ class ORIZON(CYBOOK): coverdata = coverdata[2] else: coverdata = None - with lopen('%s.thn' % filepath, 'wb') as thnfile: + with open('%s.thn' % filepath, 'wb') as thnfile: t4b.write_t4b(thnfile, coverdata) fsync(thnfile) diff --git a/src/calibre/devices/hanvon/driver.py b/src/calibre/devices/hanvon/driver.py index 3c9e2b3ba7..acb2977b05 100644 --- a/src/calibre/devices/hanvon/driver.py +++ b/src/calibre/devices/hanvon/driver.py @@ -126,7 +126,7 @@ class ALEX(N516): cdir = os.path.dirname(cpath) if not os.path.exists(cdir): os.makedirs(cdir) - with lopen(cpath, 'wb') as coverfile: + with open(cpath, 'wb') as coverfile: coverfile.write(cover) fsync(coverfile) diff --git a/src/calibre/devices/kindle/apnx.py b/src/calibre/devices/kindle/apnx.py index e2c5856d25..2f80656f34 100644 --- a/src/calibre/devices/kindle/apnx.py +++ b/src/calibre/devices/kindle/apnx.py @@ -57,7 +57,7 @@ class APNXBuilder: apnx = self.generate_apnx(pages, apnx_meta) # Write the APNX. - with lopen(apnx_path, 'wb') as apnxf: + with open(apnx_path, 'wb') as apnxf: apnxf.write(apnx) fsync(apnxf) @@ -71,14 +71,14 @@ class APNXBuilder: 'format': 'MOBI_7', 'acr': '' } - with lopen(mobi_file_path, 'rb') as mf: + with open(mobi_file_path, 'rb') as mf: ident = PdbHeaderReader(mf).identity() if as_bytes(ident) != b'BOOKMOBI': # Check that this is really a MOBI file. raise Exception(_('Not a valid MOBI file. Reports identity of %s') % ident) apnx_meta['acr'] = as_unicode(PdbHeaderReader(mf).name(), errors='replace') # We'll need the PDB name, the MOBI version, and some metadata to make FW 3.4 happy with KF8 files... - with lopen(mobi_file_path, 'rb') as mf: + with open(mobi_file_path, 'rb') as mf: mh = MetadataHeader(mf, default_log) if mh.mobi_version == 8: apnx_meta['format'] = 'MOBI_8' diff --git a/src/calibre/devices/kindle/apnx_page_generator/i_page_generator.py b/src/calibre/devices/kindle/apnx_page_generator/i_page_generator.py index 62b2265c4b..124a606101 100644 --- a/src/calibre/devices/kindle/apnx_page_generator/i_page_generator.py +++ b/src/calibre/devices/kindle/apnx_page_generator/i_page_generator.py @@ -48,7 +48,7 @@ def mobi_html(mobi_file_path: str) -> bytes: def mobi_html_length(mobi_file_path: str) -> int: - with lopen(mobi_file_path, 'rb') as mf: + with open(mobi_file_path, 'rb') as mf: pdb_header = PdbHeaderReader(mf) r0 = pdb_header.section_data(0) return struct.unpack('>I', r0[4:8])[0] diff --git a/src/calibre/devices/kindle/bookmark.py b/src/calibre/devices/kindle/bookmark.py index 1d9b240d1c..6c2196bd53 100644 --- a/src/calibre/devices/kindle/bookmark.py +++ b/src/calibre/devices/kindle/bookmark.py @@ -47,7 +47,7 @@ class Bookmark(): # {{{ user_notes = {} if self.bookmark_extension == 'mbp': MAGIC_MOBI_CONSTANT = 150 - with lopen(self.path,'rb') as f: + with open(self.path,'rb') as f: stream = io.BytesIO(f.read()) data = StreamSlicer(stream) self.timestamp, = unpack('>I', data[0x24:0x28]) @@ -144,7 +144,7 @@ class Bookmark(): # {{{ # Author is not matched # This will find the first instance of a clipping only book_fs = self.path.replace('.%s' % self.bookmark_extension,'.%s' % self.book_format) - with lopen(book_fs,'rb') as f2: + with open(book_fs,'rb') as f2: stream = io.BytesIO(f2.read()) mi = get_topaz_metadata(stream) my_clippings = self.path @@ -175,7 +175,7 @@ class Bookmark(): # {{{ MAGIC_TOPAZ_CONSTANT = 33.33 self.timestamp = os.path.getmtime(self.path) - with lopen(self.path,'rb') as f: + with open(self.path,'rb') as f: stream = io.BytesIO(f.read()) data = StreamSlicer(stream) self.last_read = int(unpack('>I', data[5:9])[0]) @@ -216,7 +216,7 @@ class Bookmark(): # {{{ elif self.bookmark_extension == 'pdr': self.timestamp = os.path.getmtime(self.path) - with lopen(self.path,'rb') as f: + with open(self.path,'rb') as f: stream = io.BytesIO(f.read()) data = StreamSlicer(stream) self.last_read = int(unpack('>I', data[5:9])[0]) @@ -285,7 +285,7 @@ class Bookmark(): # {{{ if self.bookmark_extension == 'mbp': # Read the book len from the header try: - with lopen(book_fs,'rb') as f: + with open(book_fs,'rb') as f: self.stream = io.BytesIO(f.read()) self.data = StreamSlicer(self.stream) self.nrecs, = unpack('>H', self.data[76:78]) @@ -297,7 +297,7 @@ class Bookmark(): # {{{ # Read bookLength from metadata from calibre.ebooks.metadata.topaz import MetadataUpdater try: - with lopen(book_fs,'rb') as f: + with open(book_fs,'rb') as f: mu = MetadataUpdater(f) self.book_length = mu.book_length except: diff --git a/src/calibre/devices/kindle/driver.py b/src/calibre/devices/kindle/driver.py index 647498f754..3b72b9b200 100644 --- a/src/calibre/devices/kindle/driver.py +++ b/src/calibre/devices/kindle/driver.py @@ -121,13 +121,13 @@ class KINDLE(USBMS): from calibre.ebooks.metadata.kfx import read_metadata_kfx try: kfx_path = path - with lopen(kfx_path, 'rb') as f: + with open(kfx_path, 'rb') as f: if f.read(8) != b'\xeaDRMION\xee': f.seek(0) mi = read_metadata_kfx(f) else: kfx_path = os.path.join(path.rpartition('.')[0] + '.sdr', 'assets', 'metadata.kfx') - with lopen(kfx_path, 'rb') as mf: + with open(kfx_path, 'rb') as mf: mi = read_metadata_kfx(mf) except Exception: import traceback @@ -443,7 +443,7 @@ class KINDLE2(KINDLE): return bl def kindle_update_booklist(self, bl, collections): - with lopen(collections, 'rb') as f: + with open(collections, 'rb') as f: collections = f.read() collections = json.loads(collections) path_map = {} @@ -503,7 +503,7 @@ class KINDLE2(KINDLE): thumb_dir = self.amazon_system_thumbnails_dir() if not os.path.exists(thumb_dir): return - with lopen(filepath, 'rb') as f: + with open(filepath, 'rb') as f: is_kfx = f.read(4) == CONTAINER_MAGIC f.seek(0) uuid = cdetype = None @@ -531,7 +531,7 @@ class KINDLE2(KINDLE): tp = self.thumbpath_from_filepath(filepath) if tp: - with lopen(tp, 'wb') as f: + with open(tp, 'wb') as f: f.write(coverdata[2]) fsync(f) cache_dir = self.amazon_cover_bug_cache_dir() @@ -539,7 +539,7 @@ class KINDLE2(KINDLE): os.mkdir(cache_dir) except OSError: pass - with lopen(os.path.join(cache_dir, os.path.basename(tp)), 'wb') as f: + with open(os.path.join(cache_dir, os.path.basename(tp)), 'wb') as f: f.write(coverdata[2]) fsync(f) @@ -566,7 +566,7 @@ class KINDLE2(KINDLE): count += 1 if DEBUG: prints('Restoring cover thumbnail:', name) - with lopen(os.path.join(src_dir, name), 'rb') as src, lopen(dest_path, 'wb') as dest: + with open(os.path.join(src_dir, name), 'rb') as src, open(dest_path, 'wb') as dest: shutil.copyfileobj(src, dest) fsync(dest) if DEBUG: diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 6cf6eb7465..0be7234504 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -1084,14 +1084,14 @@ class KOBO(USBMS): fpath = self.normalize_path(fpath.replace('/', os.sep)) if os.path.exists(fpath): - with lopen(cover, 'rb') as f: + with open(cover, 'rb') as f: data = f.read() # Return the data resized and grayscaled if # required data = save_cover_data_to(data, grayscale=uploadgrayscale, resize_to=resize, minify_to=resize) - with lopen(fpath, 'wb') as f: + with open(fpath, 'wb') as f: f.write(data) fsync(f) @@ -1111,7 +1111,7 @@ class KOBO(USBMS): ''' for idx, path in enumerate(paths): if path.find('kepub') >= 0: - with closing(lopen(path, 'rb')) as r: + with closing(open(path, 'rb')) as r: tf = PersistentTemporaryFile(suffix='.epub') shutil.copyfileobj(r, tf) # tf.write(r.read()) @@ -2842,7 +2842,7 @@ class KOBOTOUCH(KOBO): debug_print("KoboTouch:_upload_cover - Image folder does not exist. Creating path='%s'" % (image_dir)) os.makedirs(image_dir) - with lopen(cover, 'rb') as f: + with open(cover, 'rb') as f: cover_data = f.read() fmt, width, height = identify(cover_data) @@ -2902,7 +2902,7 @@ class KOBOTOUCH(KOBO): # through a temporary file... if png_covers: tmp_cover = better_mktemp() - with lopen(tmp_cover, 'wb') as f: + with open(tmp_cover, 'wb') as f: f.write(data) optimize_png(tmp_cover, level=1) @@ -2910,7 +2910,7 @@ class KOBOTOUCH(KOBO): shutil.copy2(tmp_cover, fpath) os.remove(tmp_cover) else: - with lopen(fpath, 'wb') as f: + with open(fpath, 'wb') as f: f.write(data) fsync(f) except Exception as e: diff --git a/src/calibre/devices/misc.py b/src/calibre/devices/misc.py index 1c35bf4dc7..4cd63e2d1e 100644 --- a/src/calibre/devices/misc.py +++ b/src/calibre/devices/misc.py @@ -98,7 +98,7 @@ class PDNOVEL(USBMS): def upload_cover(self, path, filename, metadata, filepath): coverdata = getattr(metadata, 'thumbnail', None) if coverdata and coverdata[2]: - with lopen('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile: + with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile: coverfile.write(coverdata[2]) fsync(coverfile) @@ -118,7 +118,7 @@ class PDNOVEL_KOBO(PDNOVEL): dirpath = os.path.join(path, '.thumbnail') if not os.path.exists(dirpath): os.makedirs(dirpath) - with lopen(os.path.join(dirpath, filename+'.jpg'), 'wb') as coverfile: + with open(os.path.join(dirpath, filename+'.jpg'), 'wb') as coverfile: coverfile.write(coverdata[2]) fsync(coverfile) @@ -193,7 +193,7 @@ class LUMIREAD(USBMS): pdir = os.path.dirname(cfilepath) if not os.path.exists(pdir): os.makedirs(pdir) - with lopen(cfilepath+'.jpg', 'wb') as f: + with open(cfilepath+'.jpg', 'wb') as f: f.write(metadata.thumbnail[-1]) fsync(f) @@ -345,7 +345,7 @@ class NEXTBOOK(USBMS): thumbnail_dir = os.path.join(thumbnail_dir, relpath) if not os.path.exists(thumbnail_dir): os.makedirs(thumbnail_dir) - with lopen(os.path.join(thumbnail_dir, filename+'.jpg'), 'wb') as f: + with open(os.path.join(thumbnail_dir, filename+'.jpg'), 'wb') as f: f.write(metadata.thumbnail[-1]) fsync(f) ''' diff --git a/src/calibre/devices/mtp/driver.py b/src/calibre/devices/mtp/driver.py index 7452c5cf86..6d12c601e0 100644 --- a/src/calibre/devices/mtp/driver.py +++ b/src/calibre/devices/mtp/driver.py @@ -344,7 +344,7 @@ class MTP_DEVICE(BASE): if iswindows: plen = len(base) name = ''.join(shorten_components_to(245-plen, [name])) - with lopen(os.path.join(base, name), 'wb') as out: + with open(os.path.join(base, name), 'wb') as out: try: self.get_mtp_file(f, out) except Exception as e: @@ -435,7 +435,7 @@ class MTP_DEVICE(BASE): close = False else: sz = os.path.getsize(infile) - stream = lopen(infile, 'rb') + stream = open(infile, 'rb') close = True try: mtp_file = self.put_file(parent, path[-1], stream, sz) diff --git a/src/calibre/devices/mtp/unix/sysfs.py b/src/calibre/devices/mtp/unix/sysfs.py index 0200209d6e..95159069ea 100644 --- a/src/calibre/devices/mtp/unix/sysfs.py +++ b/src/calibre/devices/mtp/unix/sysfs.py @@ -28,7 +28,7 @@ class MTPDetect: def read(x): try: - with lopen(x, 'rb') as f: + with open(x, 'rb') as f: return f.read() except OSError: pass @@ -50,5 +50,3 @@ class MTPDetect: continue return False - - diff --git a/src/calibre/devices/nook/driver.py b/src/calibre/devices/nook/driver.py index cdd5bfe9eb..ad43a2a153 100644 --- a/src/calibre/devices/nook/driver.py +++ b/src/calibre/devices/nook/driver.py @@ -51,7 +51,7 @@ class NOOK(USBMS): if coverdata and coverdata[2]: cover = Image.open(io.BytesIO(coverdata[2])) else: - coverdata = lopen(I('library.png'), 'rb').read() + coverdata = open(I('library.png'), 'rb').read() cover = Image.new('RGB', (96, 144), 'black') im = Image.open(io.BytesIO(coverdata)) @@ -68,7 +68,7 @@ class NOOK(USBMS): cover.save(data, 'JPEG') coverdata = data.getvalue() - with lopen('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile: + with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile: coverfile.write(coverdata) fsync(coverfile) diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 0bd4d7b015..8ae6ee8922 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -134,7 +134,7 @@ class PRS505(USBMS): except: time.sleep(5) os.makedirs(dname, mode=0o777) - with lopen(cachep, 'wb') as f: + with open(cachep, 'wb') as f: f.write(b''' @@ -295,6 +295,6 @@ class PRS505(USBMS): if not os.path.exists(thumbnail_dir): os.makedirs(thumbnail_dir) cpath = os.path.join(thumbnail_dir, 'main_thumbnail.jpg') - with lopen(cpath, 'wb') as f: + with open(cpath, 'wb') as f: f.write(metadata.thumbnail[-1]) debug_print('Cover uploaded to: %r'%cpath) diff --git a/src/calibre/devices/prs505/sony_cache.py b/src/calibre/devices/prs505/sony_cache.py index 7fa4f3788f..c40de0dc46 100644 --- a/src/calibre/devices/prs505/sony_cache.py +++ b/src/calibre/devices/prs505/sony_cache.py @@ -105,12 +105,12 @@ class XMLCache: if not os.path.exists(path): raise DeviceError(('The SONY XML cache %r does not exist. Try' ' disconnecting and reconnecting your reader.')%repr(path)) - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: raw = f.read() else: raw = EMPTY_CARD_CACHE if os.access(path, os.R_OK): - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: raw = f.read() self.roots[source_id] = safe_xml_fromstring( @@ -124,14 +124,14 @@ class XMLCache: for source_id, path in ext_paths.items(): if not os.path.exists(path): try: - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(EMPTY_EXT_CACHE) fsync(f) except: pass if os.access(path, os.W_OK): try: - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: self.ext_roots[source_id] = safe_xml_fromstring( xml_to_unicode(f.read(), strip_encoding_pats=True, assume_utf8=True, verbose=DEBUG)[0] ) @@ -724,7 +724,7 @@ class XMLCache: xml_declaration=True) raw = raw.replace(b"", b'') - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(raw) fsync(f) @@ -736,7 +736,7 @@ class XMLCache: continue raw = raw.replace(b"", b'') - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(raw) fsync(f) diff --git a/src/calibre/devices/prst1/driver.py b/src/calibre/devices/prst1/driver.py index 647023d6fe..0c74864763 100644 --- a/src/calibre/devices/prst1/driver.py +++ b/src/calibre/devices/prst1/driver.py @@ -762,7 +762,7 @@ class PRST1(USBMS): if not os.path.exists(thumbnail_dir_path): os.makedirs(thumbnail_dir_path) - with lopen(thumbnail_file_path, 'wb') as f: + with open(thumbnail_file_path, 'wb') as f: f.write(book.thumbnail[-1]) fsync(f) diff --git a/src/calibre/devices/scanner.py b/src/calibre/devices/scanner.py index 073448b818..b8f340a893 100644 --- a/src/calibre/devices/scanner.py +++ b/src/calibre/devices/scanner.py @@ -107,7 +107,7 @@ class LinuxScanner: raise RuntimeError('DeviceScanner requires the /sys filesystem to work.') def read(f): - with lopen(f, 'rb') as s: + with open(f, 'rb') as s: return s.read().strip() for x in os.listdir(self.base): diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py index 91fba1a388..26ff658aa8 100644 --- a/src/calibre/devices/smart_device_app/driver.py +++ b/src/calibre/devices/smart_device_app/driver.py @@ -703,7 +703,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): def _put_file(self, infile, lpath, book_metadata, this_book, total_books): close_ = False if not hasattr(infile, 'read'): - infile, close_ = lopen(infile, 'rb'), True + infile, close_ = open(infile, 'rb'), True infile.seek(0, os.SEEK_END) length = infile.tell() book_metadata.size = length @@ -816,7 +816,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): try: count = 0 if os.path.exists(cache_file_name): - with lopen(cache_file_name, mode='rb') as fd: + with open(cache_file_name, mode='rb') as fd: while True: rec_len = fd.readline() if len(rec_len) != 8: @@ -853,7 +853,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): count = 0 prefix = os.path.join(cache_dir(), 'wireless_device_' + self.device_uuid + '_metadata_cache') - with lopen(prefix + '.tmp', mode='wb') as fd: + with open(prefix + '.tmp', mode='wb') as fd: for key,book in iteritems(self.device_book_cache): if (now_ - book['last_used']).days > self.PURGE_CACHE_ENTRIES_DAYS: purged += 1 @@ -956,7 +956,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): from calibre.customize.ui import quick_metadata from calibre.ebooks.metadata.meta import get_metadata ext = temp_file_name.rpartition('.')[-1].lower() - with lopen(temp_file_name, 'rb') as stream: + with open(temp_file_name, 'rb') as stream: with quick_metadata: return get_metadata(stream, stream_type=ext, force_read_metadata=True, diff --git a/src/calibre/devices/usbms/cli.py b/src/calibre/devices/usbms/cli.py index 364ad80c9b..6ad4ce6592 100644 --- a/src/calibre/devices/usbms/cli.py +++ b/src/calibre/devices/usbms/cli.py @@ -34,14 +34,14 @@ class CLI: def get_file(self, path, outfile, end_session=True): path = self.munge_path(path) - with lopen(path, 'rb') as src: + with open(path, 'rb') as src: shutil.copyfileobj(src, outfile) def put_file(self, infile, path, replace_file=False, end_session=True): path = self.munge_path(path) close = False if not hasattr(infile, 'read'): - infile, close = lopen(infile, 'rb'), True + infile, close = open(infile, 'rb'), True infile.seek(0) if os.path.isdir(path): path = os.path.join(path, infile.name) @@ -99,6 +99,6 @@ class CLI: def touch(self, path, end_session=True): path = self.munge_path(path) if not os.path.exists(path): - lopen(path, 'wb').close() + open(path, 'wb').close() if not os.path.isdir(path): os.utime(path, None) diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index a7b26b36cc..3285cb5195 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -615,7 +615,7 @@ class Device(DeviceConfig, DevicePlugin): path = os.path.join(mp, 'calibre_readonly_test') ro = True try: - with lopen(path, 'wb'): + with open(path, 'wb'): ro = False except: pass diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index d281439f8d..0fc1e9ebd7 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -118,7 +118,7 @@ class USBMS(CLI, Device): def _update_driveinfo_file(self, prefix, location_code, name=None): from calibre.utils.config import from_json, to_json if os.path.exists(os.path.join(prefix, self.DRIVEINFO)): - with lopen(os.path.join(prefix, self.DRIVEINFO), 'rb') as f: + with open(os.path.join(prefix, self.DRIVEINFO), 'rb') as f: try: driveinfo = json.loads(f.read(), object_hook=from_json) except: @@ -128,7 +128,7 @@ class USBMS(CLI, Device): data = json.dumps(driveinfo, default=to_json) if not isinstance(data, bytes): data = data.encode('utf-8') - with lopen(os.path.join(prefix, self.DRIVEINFO), 'wb') as f: + with open(os.path.join(prefix, self.DRIVEINFO), 'wb') as f: f.write(data) fsync(f) else: @@ -136,7 +136,7 @@ class USBMS(CLI, Device): data = json.dumps(driveinfo, default=to_json) if not isinstance(data, bytes): data = data.encode('utf-8') - with lopen(os.path.join(prefix, self.DRIVEINFO), 'wb') as f: + with open(os.path.join(prefix, self.DRIVEINFO), 'wb') as f: f.write(data) fsync(f) return driveinfo @@ -460,7 +460,7 @@ class USBMS(CLI, Device): isinstance(booklists[listid], self.booklist_class)): if not os.path.exists(prefix): os.makedirs(self.normalize_path(prefix)) - with lopen(self.normalize_path(os.path.join(prefix, self.METADATA_CACHE)), 'wb') as f: + with open(self.normalize_path(os.path.join(prefix, self.METADATA_CACHE)), 'wb') as f: json_codec.encode_to_file(f, booklists[listid]) fsync(f) write_prefix(self._main_prefix, 0) @@ -506,7 +506,7 @@ class USBMS(CLI, Device): cache_file = cls.normalize_path(os.path.join(prefix, name)) if os.access(cache_file, os.R_OK): try: - with lopen(cache_file, 'rb') as f: + with open(cache_file, 'rb') as f: json_codec.decode_from_file(f, bl, cls.book_class, prefix) except: import traceback diff --git a/src/calibre/ebooks/chm/reader.py b/src/calibre/ebooks/chm/reader.py index 2feb362a5a..102f439857 100644 --- a/src/calibre/ebooks/chm/reader.py +++ b/src/calibre/ebooks/chm/reader.py @@ -189,7 +189,7 @@ class CHMReader(CHMFile): import shutil shutil.copytree(output_dir, os.path.join(debug_dump, 'debug_dump')) for lpath in html_files: - with lopen(lpath, 'r+b') as f: + with open(lpath, 'r+b') as f: data = f.read() data = self._reformat(data, lpath) if isinstance(data, str): diff --git a/src/calibre/ebooks/comic/input.py b/src/calibre/ebooks/comic/input.py index 5728f6699b..a0424e9f92 100644 --- a/src/calibre/ebooks/comic/input.py +++ b/src/calibre/ebooks/comic/input.py @@ -94,11 +94,11 @@ class PageProcessor(list): # {{{ def render(self): from calibre.utils.img import image_from_data, scale_image, crop_image - with lopen(self.path_to_page, 'rb') as f: + with open(self.path_to_page, 'rb') as f: img = image_from_data(f.read()) width, height = img.width(), img.height() if self.num == 0: # First image so create a thumbnail from it - with lopen(os.path.join(self.dest, 'thumbnail.png'), 'wb') as f: + with open(os.path.join(self.dest, 'thumbnail.png'), 'wb') as f: f.write(scale_image(img, as_png=True)[-1]) self.pages = [img] if width > height: @@ -196,7 +196,7 @@ class PageProcessor(list): # {{{ img = quantize_image(img, max_colors=min(256, self.opts.colors)) dest = '%d_%d.%s'%(self.num, i, self.opts.output_format) dest = os.path.join(self.dest, dest) - with lopen(dest, 'wb') as f: + with open(dest, 'wb') as f: f.write(image_to_data(img, fmt=self.opts.output_format)) self.append(dest) # }}} diff --git a/src/calibre/ebooks/conversion/config.py b/src/calibre/ebooks/conversion/config.py index 8d0aba9aea..db30f2859b 100644 --- a/src/calibre/ebooks/conversion/config.py +++ b/src/calibre/ebooks/conversion/config.py @@ -27,7 +27,7 @@ def save_defaults(name, recs): os.makedirs(config_dir, exist_ok=True) - with lopen(path, 'wb'): + with open(path, 'wb'): pass with ExclusiveFile(path) as f: f.write(raw) diff --git a/src/calibre/ebooks/conversion/plugins/chm_input.py b/src/calibre/ebooks/conversion/plugins/chm_input.py index 058f4b96aa..8a7d8da83c 100644 --- a/src/calibre/ebooks/conversion/plugins/chm_input.py +++ b/src/calibre/ebooks/conversion/plugins/chm_input.py @@ -172,7 +172,7 @@ class CHMInput(InputFormatPlugin): return htmlpath, toc def _read_file(self, name): - with lopen(name, 'rb') as f: + with open(name, 'rb') as f: data = f.read() return data diff --git a/src/calibre/ebooks/conversion/plugins/epub_input.py b/src/calibre/ebooks/conversion/plugins/epub_input.py index 6b880cd601..9915c2b169 100644 --- a/src/calibre/ebooks/conversion/plugins/epub_input.py +++ b/src/calibre/ebooks/conversion/plugins/epub_input.py @@ -21,7 +21,7 @@ def decrypt_font_data(key, data, algorithm): def decrypt_font(key, path, algorithm): - with lopen(path, 'r+b') as f: + with open(path, 'r+b') as f: data = decrypt_font_data(key, f.read(), algorithm) f.seek(0), f.truncate(), f.write(data) @@ -220,7 +220,7 @@ class EPUBInput(InputFormatPlugin): if os.path.exists(guide_cover): renderer = render_html_svg_workaround(guide_cover, log, root=os.getcwd()) if renderer is not None: - with lopen('calibre_raster_cover.jpg', 'wb') as f: + with open('calibre_raster_cover.jpg', 'wb') as f: f.write(renderer) # Set the titlepage guide entry @@ -235,7 +235,7 @@ class EPUBInput(InputFormatPlugin): if k.endswith(attr): return v try: - with lopen('META-INF/container.xml', 'rb') as f: + with open('META-INF/container.xml', 'rb') as f: root = safe_xml_fromstring(f.read()) for r in root.xpath('//*[local-name()="rootfile"]'): if attr(r, 'media-type') != "application/oebps-package+xml": @@ -342,7 +342,7 @@ class EPUBInput(InputFormatPlugin): if len(list(opf.iterspine())) == 0: raise ValueError('No valid entries in the spine of this EPUB') - with lopen('content.opf', 'wb') as nopf: + with open('content.opf', 'wb') as nopf: nopf.write(opf.render()) return os.path.abspath('content.opf') @@ -355,7 +355,7 @@ class EPUBInput(InputFormatPlugin): from calibre.ebooks.oeb.polish.toc import first_child from calibre.utils.xml_parse import safe_xml_fromstring from tempfile import NamedTemporaryFile - with lopen(nav_path, 'rb') as f: + with open(nav_path, 'rb') as f: raw = f.read() raw = xml_to_unicode(raw, strip_encoding_pats=True, assume_utf8=True)[0] root = parse(raw, log=log) @@ -419,7 +419,7 @@ class EPUBInput(InputFormatPlugin): changed = True elem.set('data-calibre-removed-titlepage', '1') if changed: - with lopen(nav_path, 'wb') as f: + with open(nav_path, 'wb') as f: f.write(serialize(root, 'application/xhtml+xml')) def postprocess_book(self, oeb, opts, log): diff --git a/src/calibre/ebooks/conversion/plugins/epub_output.py b/src/calibre/ebooks/conversion/plugins/epub_output.py index 6b1c55cca4..012eb512b0 100644 --- a/src/calibre/ebooks/conversion/plugins/epub_output.py +++ b/src/calibre/ebooks/conversion/plugins/epub_output.py @@ -334,7 +334,7 @@ class EPUBOutput(OutputFormatPlugin): uris.pop(uri) continue self.log.debug('Encrypting font:', uri) - with lopen(path, 'r+b') as f: + with open(path, 'r+b') as f: data = f.read(1024) if len(data) >= 1024: data = bytearray(data) diff --git a/src/calibre/ebooks/conversion/plugins/fb2_output.py b/src/calibre/ebooks/conversion/plugins/fb2_output.py index eb9bf110dd..f4e052ba67 100644 --- a/src/calibre/ebooks/conversion/plugins/fb2_output.py +++ b/src/calibre/ebooks/conversion/plugins/fb2_output.py @@ -188,7 +188,7 @@ class FB2Output(OutputFormatPlugin): close = True if not os.path.exists(os.path.dirname(output_path)) and os.path.dirname(output_path) != '': os.makedirs(os.path.dirname(output_path)) - out_stream = lopen(output_path, 'wb') + out_stream = open(output_path, 'wb') else: out_stream = output_path diff --git a/src/calibre/ebooks/conversion/plugins/htmlz_output.py b/src/calibre/ebooks/conversion/plugins/htmlz_output.py index 024d1cc135..bb11d1ec6f 100644 --- a/src/calibre/ebooks/conversion/plugins/htmlz_output.py +++ b/src/calibre/ebooks/conversion/plugins/htmlz_output.py @@ -112,7 +112,7 @@ class HTMLZOutput(OutputFormatPlugin): if cover_data: from calibre.utils.img import save_cover_data_to cover_path = os.path.join(tdir, 'cover.jpg') - with lopen(cover_path, 'w') as cf: + with open(cover_path, 'w') as cf: cf.write('') save_cover_data_to(cover_data, cover_path) except: diff --git a/src/calibre/ebooks/conversion/plugins/mobi_input.py b/src/calibre/ebooks/conversion/plugins/mobi_input.py index 7d515bd015..5af56b6591 100644 --- a/src/calibre/ebooks/conversion/plugins/mobi_input.py +++ b/src/calibre/ebooks/conversion/plugins/mobi_input.py @@ -50,14 +50,14 @@ class MOBIInput(InputFormatPlugin): if raw: if isinstance(raw, str): raw = raw.encode('utf-8') - with lopen('debug-raw.html', 'wb') as f: + with open('debug-raw.html', 'wb') as f: f.write(raw) from calibre.ebooks.oeb.base import close_self_closing_tags for f, root in parse_cache.items(): raw = html.tostring(root, encoding='utf-8', method='xml', include_meta_content_type=False) raw = close_self_closing_tags(raw) - with lopen(f, 'wb') as q: + with open(f, 'wb') as q: q.write(raw) accelerators['pagebreaks'] = '//h:div[@class="mbp_pagebreak"]' return mr.created_opf_path diff --git a/src/calibre/ebooks/conversion/plugins/oeb_output.py b/src/calibre/ebooks/conversion/plugins/oeb_output.py index ed91bfdcfb..2e74faca36 100644 --- a/src/calibre/ebooks/conversion/plugins/oeb_output.py +++ b/src/calibre/ebooks/conversion/plugins/oeb_output.py @@ -52,7 +52,7 @@ class OEBOutput(OutputFormatPlugin): # Needed as I can't get lxml to output opf:role and # not output as well raw = re.sub(br'(<[/]{0,1})opf:', br'\1', raw) - with lopen(href, 'wb') as f: + with open(href, 'wb') as f: f.write(raw) for item in oeb_book.manifest: @@ -64,7 +64,7 @@ class OEBOutput(OutputFormatPlugin): dir = os.path.dirname(path) if not os.path.exists(dir): os.makedirs(dir) - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(item.bytes_representation) item.unload_data_from_memory(memory=path) diff --git a/src/calibre/ebooks/conversion/plugins/pdb_output.py b/src/calibre/ebooks/conversion/plugins/pdb_output.py index ceb6c8fc60..724092eb18 100644 --- a/src/calibre/ebooks/conversion/plugins/pdb_output.py +++ b/src/calibre/ebooks/conversion/plugins/pdb_output.py @@ -38,7 +38,7 @@ class PDBOutput(OutputFormatPlugin): close = True if not os.path.exists(os.path.dirname(output_path)) and os.path.dirname(output_path): os.makedirs(os.path.dirname(output_path)) - out_stream = lopen(output_path, 'wb') + out_stream = open(output_path, 'wb') else: out_stream = output_path diff --git a/src/calibre/ebooks/conversion/plugins/pdf_input.py b/src/calibre/ebooks/conversion/plugins/pdf_input.py index cce1dd21ac..bd934869e8 100644 --- a/src/calibre/ebooks/conversion/plugins/pdf_input.py +++ b/src/calibre/ebooks/conversion/plugins/pdf_input.py @@ -33,7 +33,7 @@ class PDFInput(InputFormatPlugin): from calibre.ebooks.pdf.reflow import PDFDocument pdftohtml(os.getcwd(), stream.name, self.opts.no_images, as_xml=True) - with lopen('index.xml', 'rb') as f: + with open('index.xml', 'rb') as f: xml = clean_ascii_chars(f.read()) PDFDocument(xml, self.opts, self.log) return os.path.join(os.getcwd(), 'metadata.opf') @@ -66,12 +66,12 @@ class PDFInput(InputFormatPlugin): opf.create_spine(['index.html']) log.debug('Rendering manifest...') - with lopen('metadata.opf', 'wb') as opffile: + with open('metadata.opf', 'wb') as opffile: opf.render(opffile) if os.path.exists('toc.ncx'): ncxid = opf.manifest.id_for_path('toc.ncx') if ncxid: - with lopen('metadata.opf', 'r+b') as f: + with open('metadata.opf', 'r+b') as f: raw = f.read().replace(b' 1: # Decouple this file from its links os.unlink(dest) - with lopen(dest, 'wb') as f: + with open(dest, 'wb') as f: f.write(data) def filesize(self, name): @@ -1061,7 +1061,7 @@ class Container(ContainerBase): # {{{ this will commit the file if it is dirtied and remove it from the parse cache. You must finish with this file before accessing the parsed version of it again, or bad things will happen. ''' - return lopen(self.get_file_path_for_processing(name, mode not in {'r', 'rb'}), mode) + return open(self.get_file_path_for_processing(name, mode not in {'r', 'rb'}), mode) def commit(self, outpath=None, keep_parsed=False): ''' @@ -1079,7 +1079,7 @@ class Container(ContainerBase): # {{{ mismatches = [] for name, path in iteritems(self.name_path_map): opath = other.name_path_map[name] - with lopen(path, 'rb') as f1, lopen(opath, 'rb') as f2: + with open(path, 'rb') as f1, open(opath, 'rb') as f2: if f1.read() != f2.read(): mismatches.append('The file %s is not the same'%name) return '\n'.join(mismatches) @@ -1172,7 +1172,7 @@ class EpubContainer(Container): if fname is not None: shutil.copy(os.path.join(dirpath, fname), os.path.join(base, fname)) else: - with lopen(self.pathtoepub, 'rb') as stream: + with open(self.pathtoepub, 'rb') as stream: try: zf = ZipFile(stream) zf.extractall(tdir) @@ -1399,12 +1399,12 @@ class EpubContainer(Container): if err.errno != errno.EEXIST: raise for fname in filenames: - with lopen(os.path.join(dirpath, fname), 'rb') as src, lopen(os.path.join(base, fname), 'wb') as dest: + with open(os.path.join(dirpath, fname), 'rb') as src, open(os.path.join(base, fname), 'wb') as dest: shutil.copyfileobj(src, dest) else: from calibre.ebooks.tweak import zip_rebuilder - with lopen(join(self.root, 'mimetype'), 'wb') as f: + with open(join(self.root, 'mimetype'), 'wb') as f: et = guess_type('a.epub') if not isinstance(et, bytes): et = et.encode('ascii') @@ -1434,7 +1434,7 @@ class InvalidMobi(InvalidBook): def do_explode(path, dest): from calibre.ebooks.mobi.reader.mobi6 import MobiReader from calibre.ebooks.mobi.reader.mobi8 import Mobi8Reader - with lopen(path, 'rb') as stream: + with open(path, 'rb') as stream: mr = MobiReader(stream, default_log, None, None) with CurrentDir(dest): @@ -1494,7 +1494,7 @@ class AZW3Container(Container): tdir = PersistentTemporaryDirectory('_azw3_container') tdir = os.path.abspath(os.path.realpath(tdir)) self.root = tdir - with lopen(pathtoazw3, 'rb') as stream: + with open(pathtoazw3, 'rb') as stream: raw = stream.read(3) if raw == b'TPZ': raise InvalidMobi(_('This is not a MOBI file. It is a Topaz file.')) diff --git a/src/calibre/ebooks/oeb/polish/cover.py b/src/calibre/ebooks/oeb/polish/cover.py index 46dc869e30..3b9c94f969 100644 --- a/src/calibre/ebooks/oeb/polish/cover.py +++ b/src/calibre/ebooks/oeb/polish/cover.py @@ -31,7 +31,7 @@ def set_azw3_cover(container, cover_path, report, options=None): container.insert_into_xml(guide, guide.makeelement( OPF('reference'), href=href, type='cover')) if not existing_image: - with lopen(cover_path, 'rb') as src, container.open(name, 'wb') as dest: + with open(cover_path, 'rb') as src, container.open(name, 'wb') as dest: shutil.copyfileobj(src, dest) container.dirty(container.opf_name) report(_('Cover updated') if found else _('Cover inserted')) @@ -350,7 +350,7 @@ def create_epub_cover(container, cover_path, existing_image, options=None): if callable(cover_path): cover_path('write_image', dest) else: - with lopen(cover_path, 'rb') as src: + with open(cover_path, 'rb') as src: shutil.copyfileobj(src, dest) if options is None: opts = load_defaults('epub_output') @@ -374,7 +374,7 @@ def create_epub_cover(container, cover_path, existing_image, options=None): if existing_image: width, height = identify(container.raw_data(existing_image, decode=False))[1:] else: - with lopen(cover_path, 'rb') as csrc: + with open(cover_path, 'rb') as csrc: width, height = identify(csrc)[1:] except: container.log.exception("Failed to get width and height of cover") diff --git a/src/calibre/ebooks/oeb/polish/download.py b/src/calibre/ebooks/oeb/polish/download.py index 1ee8e42098..ec409e96d1 100644 --- a/src/calibre/ebooks/oeb/polish/download.py +++ b/src/calibre/ebooks/oeb/polish/download.py @@ -111,7 +111,7 @@ def download_one(tdir, timeout, progress_report, data_uri_map, url): path = unquote(purl.path) if iswindows and path.startswith('/'): path = path[1:] - src = lopen(path, 'rb') + src = open(path, 'rb') filename = os.path.basename(path) sz = (src.seek(0, os.SEEK_END), src.tell(), src.seek(0))[1] elif purl.scheme == 'data': @@ -167,7 +167,7 @@ def download_external_resources(container, urls, timeout=60, progress_report=lam for ok, result in pool.imap_unordered(partial(download_one, tdir, timeout, progress_report, data_uri_map), urls): if ok: url, suggested_filename, downloaded_file, mt = result - with lopen(downloaded_file, 'rb') as src: + with open(downloaded_file, 'rb') as src: name = container.add_file(suggested_filename, src, mt, modify_name_if_needed=True) replacements[url] = name else: diff --git a/src/calibre/ebooks/oeb/polish/images.py b/src/calibre/ebooks/oeb/polish/images.py index f92a82e3be..93b76ec80d 100644 --- a/src/calibre/ebooks/oeb/polish/images.py +++ b/src/calibre/ebooks/oeb/polish/images.py @@ -51,12 +51,12 @@ class Worker(Thread): else: func = partial(encode_jpeg, quality=self.jpeg_quality) before = os.path.getsize(path) - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: old_data = f.read() func(path) after = os.path.getsize(path) if after >= before: - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(old_data) after = before self.results[name] = (True, (before, after)) diff --git a/src/calibre/ebooks/oeb/polish/tests/base.py b/src/calibre/ebooks/oeb/polish/tests/base.py index 8cffaddd3b..6684d33627 100644 --- a/src/calibre/ebooks/oeb/polish/tests/base.py +++ b/src/calibre/ebooks/oeb/polish/tests/base.py @@ -55,7 +55,7 @@ def get_simple_book(fmt='epub'): if needs_recompile(ans, src): with TemporaryDirectory('bpt') as tdir: with CurrentDir(tdir): - with lopen(src, 'rb') as sf: + with open(src, 'rb') as sf: raw = sf.read().decode('utf-8') raw = add_resources(raw, { 'LMONOI': P('fonts/liberation/LiberationMono-Italic.ttf'), @@ -65,7 +65,7 @@ def get_simple_book(fmt='epub'): }) shutil.copy2(I('lt.png'), '.') x = 'index.html' - with lopen(x, 'wb') as f: + with open(x, 'wb') as f: f.write(raw.encode('utf-8')) build_book(x, ans, args=[ '--level1-toc=//h:h2', '--language=en', '--authors=Kovid Goyal', '--cover=lt.png']) @@ -78,9 +78,9 @@ def get_split_book(fmt='epub'): src = os.path.join(os.path.dirname(__file__), 'split.html') if needs_recompile(ans, src): x = src.replace('split.html', 'index.html') - raw = lopen(src, 'rb').read().decode('utf-8') + raw = open(src, 'rb').read().decode('utf-8') try: - with lopen(x, 'wb') as f: + with open(x, 'wb') as f: f.write(raw.encode('utf-8')) build_book(x, ans, args=['--level1-toc=//h:h2', '--language=en', '--authors=Kovid Goyal', '--cover=' + I('lt.png')]) diff --git a/src/calibre/ebooks/oeb/polish/tests/parsing.py b/src/calibre/ebooks/oeb/polish/tests/parsing.py index 25a8e65430..8f04f8a7d3 100644 --- a/src/calibre/ebooks/oeb/polish/tests/parsing.py +++ b/src/calibre/ebooks/oeb/polish/tests/parsing.py @@ -214,7 +214,7 @@ def timing(): from calibre.utils.monotonic import monotonic from html5lib import parse as vanilla filename = sys.argv[-1] - with lopen(filename, 'rb') as f: + with open(filename, 'rb') as f: raw = f.read() raw = xml_to_unicode(raw)[0] diff --git a/src/calibre/ebooks/pdb/plucker/reader.py b/src/calibre/ebooks/pdb/plucker/reader.py index 35a45c8a5c..4a9ac3313e 100644 --- a/src/calibre/ebooks/pdb/plucker/reader.py +++ b/src/calibre/ebooks/pdb/plucker/reader.py @@ -412,7 +412,7 @@ class Reader(FormatReader): for col in row: if col not in images: raise Exception('Image with uid: %s missing.' % col) - w, h = identify(lopen('%s.jpg' % col, 'rb'))[1:] + w, h = identify(open('%s.jpg' % col, 'rb'))[1:] row_width += w if col_height < h: col_height = h @@ -427,14 +427,14 @@ class Reader(FormatReader): x_off = 0 largest_height = 0 for col in row: - im = image_from_data(lopen('%s.jpg' % col, 'rb').read()) + im = image_from_data(open('%s.jpg' % col, 'rb').read()) canvas.compose(im, x_off, y_off) w, h = im.width(), im.height() x_off += w if largest_height < h: largest_height = h y_off += largest_height - with lopen('%s.jpg' % uid) as out: + with open('%s.jpg' % uid) as out: out.write(canvas.export(compression_quality=70)) self.log.debug(f'Wrote composite image with uid {uid} to images/{uid}.jpg') except Exception as e: diff --git a/src/calibre/ebooks/pdf/pdftohtml.py b/src/calibre/ebooks/pdf/pdftohtml.py index 3e8145b977..40eac81a63 100644 --- a/src/calibre/ebooks/pdf/pdftohtml.py +++ b/src/calibre/ebooks/pdf/pdftohtml.py @@ -46,7 +46,7 @@ def pdftohtml(output_dir, pdf_path, no_images, as_xml=False): pdfsrc = os.path.join(output_dir, 'src.pdf') index = os.path.join(output_dir, 'index.'+('xml' if as_xml else 'html')) - with lopen(pdf_path, 'rb') as src, lopen(pdfsrc, 'wb') as dest: + with open(pdf_path, 'rb') as src, open(pdfsrc, 'wb') as dest: shutil.copyfileobj(src, dest) with CurrentDir(output_dir): @@ -78,7 +78,7 @@ def pdftohtml(output_dir, pdf_path, no_images, as_xml=False): ret = eintr_retry_call(p.wait) logf.flush() logf.close() - out = lopen(logf.name, 'rb').read().decode('utf-8', 'replace').strip() + out = open(logf.name, 'rb').read().decode('utf-8', 'replace').strip() if ret != 0: raise ConversionError('pdftohtml failed with return code: %d\n%s' % (ret, out)) if out: @@ -88,7 +88,7 @@ def pdftohtml(output_dir, pdf_path, no_images, as_xml=False): raise DRMError() if not as_xml: - with lopen(index, 'r+b') as i: + with open(index, 'r+b') as i: raw = i.read().decode('utf-8', 'replace') raw = flip_images(raw) raw = raw.replace('\n ' - with lopen(attachment, 'rb') as f: + with open(attachment, 'rb') as f: msg = compose_mail(from_, to, text, subject, f, aname) efrom = extract_email_address(from_) eto = [] diff --git a/src/calibre/gui2/icon_theme.py b/src/calibre/gui2/icon_theme.py index ff2fca785c..15a7b3e0a7 100644 --- a/src/calibre/gui2/icon_theme.py +++ b/src/calibre/gui2/icon_theme.py @@ -187,7 +187,7 @@ def create_cover(report=None, icons=(), cols=5, size=120, padding=16, darkbg=Fal ipath = os.path.join(report.path, report.name_map[icon]) else: ipath = I(icon, allow_user_override=False) - with lopen(ipath, 'rb') as f: + with open(ipath, 'rb') as f: img = image_from_data(f.read()) scaled, nwidth, nheight = fit_image(img.width(), img.height(), size, size) img = img.scaled(int(nwidth), int(nheight), Qt.AspectRatioMode.IgnoreAspectRatio, Qt.TransformationMode.SmoothTransformation) @@ -403,7 +403,7 @@ def create_themeball(report, theme_metadata, progress=None, abort=None): with ZipFile(buf, 'w') as zf: for name in report.name_map: srcpath = os.path.join(report.path, name) - with lopen(srcpath, 'rb') as f: + with open(srcpath, 'rb') as f: zf.writestr(name, f.read(), compression=ZIP_STORED) buf.seek(0) icon_zip_data = buf @@ -418,7 +418,7 @@ def create_themeball(report, theme_metadata, progress=None, abort=None): if abort is not None and abort.is_set(): return None, None, None with ZipFile(buf, 'w') as zf: - with lopen(os.path.join(report.path, THEME_METADATA), 'rb') as f: + with open(os.path.join(report.path, THEME_METADATA), 'rb') as f: zf.writestr(prefix + '/' + THEME_METADATA, f.read()) zf.writestr(prefix + '/' + THEME_COVER, create_cover(report, darkbg=theme_metadata.get('color_palette') == 'dark')) zf.writestr(prefix + '/' + 'icons.zip.xz', compressed, compression=ZIP_STORED) @@ -450,7 +450,7 @@ def create_theme(folder=None, parent=None): [(_('ZIP files'), ['zip'])], initial_filename=prefix + '.zip') if not dest: return - with lopen(dest, 'wb') as f: + with open(dest, 'wb') as f: f.write(raw) if use_in_calibre: diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 22e0f1f252..7e573023a5 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -385,8 +385,8 @@ def run_in_debug_mode(): fd, logpath = tempfile.mkstemp('.txt') os.close(fd) run_calibre_debug( - '--gui-debug', logpath, stdout=lopen(logpath, 'wb'), - stderr=subprocess.STDOUT, stdin=lopen(os.devnull, 'rb')) + '--gui-debug', logpath, stdout=open(logpath, 'wb'), + stderr=subprocess.STDOUT, stdin=open(os.devnull, 'rb')) def run_gui(opts, args, app, gui_debug=None): diff --git a/src/calibre/gui2/preferences/coloring.py b/src/calibre/gui2/preferences/coloring.py index ed79a9e498..a1df13c6c0 100644 --- a/src/calibre/gui2/preferences/coloring.py +++ b/src/calibre/gui2/preferences/coloring.py @@ -1268,14 +1268,14 @@ class EditRules(QWidget): # {{{ data = json.dumps(rules, indent=2) if not isinstance(data, bytes): data = data.encode('utf-8') - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(data) def import_rules(self): files = choose_files(self, 'import-coloring-rules', _('Choose file to import from'), filters=[(_('Rules'), ['rules'])], all_files=False, select_only_single_file=True) if files: - with lopen(files[0], 'rb') as f: + with open(files[0], 'rb') as f: raw = f.read() try: rules = json.loads(raw) diff --git a/src/calibre/gui2/preferences/server.py b/src/calibre/gui2/preferences/server.py index 5323c4ccec..a49a9848fc 100644 --- a/src/calibre/gui2/preferences/server.py +++ b/src/calibre/gui2/preferences/server.py @@ -940,7 +940,7 @@ class CustomList(QWidget): # {{{ paths = choose_files(self, 'custom-list-template', _('Choose template file'), filters=[(_('Template files'), ['json'])], all_files=False, select_only_single_file=True) if paths: - with lopen(paths[0], 'rb') as f: + with open(paths[0], 'rb') as f: raw = f.read() self.current_template = self.deserialize(raw) @@ -950,7 +950,7 @@ class CustomList(QWidget): # {{{ filters=[(_('Template files'), ['json'])], initial_filename='custom-list-template.json') if path: raw = self.serialize(self.current_template) - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(as_bytes(raw)) def thumbnail_state_changed(self): @@ -1003,7 +1003,7 @@ class CustomList(QWidget): # {{{ raise else: raw = self.serialize(template) - with lopen(custom_list_template.path, 'wb') as f: + with open(custom_list_template.path, 'wb') as f: f.write(as_bytes(raw)) return True @@ -1165,7 +1165,7 @@ class SearchTheInternet(QWidget): return False cu = self.current_urls if cu: - with lopen(search_the_net_urls.path, 'wb') as f: + with open(search_the_net_urls.path, 'wb') as f: f.write(self.serialized_urls.encode('utf-8')) else: try: @@ -1180,14 +1180,14 @@ class SearchTheInternet(QWidget): self, 'search-net-urls', _('Choose URLs file'), filters=[(_('URL files'), ['json'])], initial_filename='search-urls.json') if path: - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(self.serialized_urls.encode('utf-8')) def import_urls(self): paths = choose_files(self, 'search-net-urls', _('Choose URLs file'), filters=[(_('URL files'), ['json'])], all_files=False, select_only_single_file=True) if paths: - with lopen(paths[0], 'rb') as f: + with open(paths[0], 'rb') as f: items = json.loads(f.read()) [self.append_item(x) for x in items] self.changed_signal.emit() diff --git a/src/calibre/gui2/save.py b/src/calibre/gui2/save.py index a59e33ddde..9c49a897df 100644 --- a/src/calibre/gui2/save.py +++ b/src/calibre/gui2/save.py @@ -233,7 +233,7 @@ class Saver(QObject): fname = os.path.join(self.tdir, '%d.jpg' % book_id) if fname: - with lopen(fname, 'wb') as f: + with open(fname, 'wb') as f: f.write(cdata) if self.opts.update_metadata: d['cover'] = fname @@ -245,7 +245,7 @@ class Saver(QObject): fname = os.path.join(self.tdir, '%d.opf' % book_id) if fname: opf = metadata_to_opf(mi) - with lopen(fname, 'wb') as f: + with open(fname, 'wb') as f: f.write(opf) if self.opts.update_metadata: d['opf'] = fname @@ -275,7 +275,7 @@ class Saver(QObject): def write_fmt(self, book_id, fmt, base_path): fmtpath = base_path + os.extsep + fmt written = False - with lopen(fmtpath, 'w+b') as f: + with open(fmtpath, 'w+b') as f: try: self.db.copy_format_to(book_id, fmt, f) written = True diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index 23ce37a42d..3785c83ec3 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -1572,7 +1572,7 @@ class Boss(QObject): name_map = json.loads(bytes(md.data(FILE_COPY_MIME))) container = current_container() for name, (path, mt) in iteritems(name_map): - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: container.add_file(name, f.read(), media_type=mt, modify_name_if_needed=True) self.apply_container_update_to_gui() diff --git a/src/calibre/gui2/tweak_book/editor/smarts/html.py b/src/calibre/gui2/tweak_book/editor/smarts/html.py index 733fc66e95..310bfd2ee7 100644 --- a/src/calibre/gui2/tweak_book/editor/smarts/html.py +++ b/src/calibre/gui2/tweak_book/editor/smarts/html.py @@ -876,7 +876,7 @@ class Smarts(NullSmarts): if __name__ == '__main__': # {{{ from calibre.gui2.tweak_book.editor.widget import launch_editor if sys.argv[-1].endswith('.html'): - raw = lopen(sys.argv[-1], 'rb').read().decode('utf-8') + raw = open(sys.argv[-1], 'rb').read().decode('utf-8') else: raw = '''\ diff --git a/src/calibre/gui2/tweak_book/editor/text.py b/src/calibre/gui2/tweak_book/editor/text.py index 74584c139d..7c51e19bc7 100644 --- a/src/calibre/gui2/tweak_book/editor/text.py +++ b/src/calibre/gui2/tweak_book/editor/text.py @@ -180,7 +180,7 @@ class TextEdit(PlainTextEdit): name = path else: name = get_name(os.path.basename(path)) - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: name = add_file(name, f.read(), mt) href = get_href(name) if mt.startswith('image/'): diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index afdcdfa8e7..a9f98a0aee 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -77,12 +77,12 @@ def add_quick_start_guide(library_view, refresh_cover_browser=None): gprefs['quick_start_guide_added'] = True imgbuf = BytesIO(calibre_cover2(_('Quick Start Guide'), '')) try: - with lopen(P('quick_start/%s.epub' % l), 'rb') as src: + with open(P('quick_start/%s.epub' % l), 'rb') as src: buf = BytesIO(src.read()) except OSError as err: if err.errno != errno.ENOENT: raise - with lopen(P('quick_start/eng.epub'), 'rb') as src: + with open(P('quick_start/eng.epub'), 'rb') as src: buf = BytesIO(src.read()) safe_replace(buf, 'images/cover.jpg', imgbuf) buf.seek(0) diff --git a/src/calibre/gui2/viewer/bookmarks.py b/src/calibre/gui2/viewer/bookmarks.py index 16483bb4ff..73e988a359 100644 --- a/src/calibre/gui2/viewer/bookmarks.py +++ b/src/calibre/gui2/viewer/bookmarks.py @@ -300,7 +300,7 @@ class BookmarkManager(QWidget): data = json.dumps({'type': 'bookmarks', 'entries': bm}, indent=True) if not isinstance(data, bytes): data = data.encode('utf-8') - with lopen(filename, 'wb') as fileobj: + with open(filename, 'wb') as fileobj: fileobj.write(data) def import_bookmarks(self): @@ -311,7 +311,7 @@ class BookmarkManager(QWidget): filename = files[0] imported = None - with lopen(filename, 'rb') as fileobj: + with open(filename, 'rb') as fileobj: imported = json.load(fileobj) def import_old_bookmarks(imported): diff --git a/src/calibre/gui2/viewer/convert_book.py b/src/calibre/gui2/viewer/convert_book.py index bae919fc39..fc9601f81c 100644 --- a/src/calibre/gui2/viewer/convert_book.py +++ b/src/calibre/gui2/viewer/convert_book.py @@ -186,7 +186,7 @@ def do_convert(path, temp_path, key, instance): ))) p.stdin.close() if p.wait() != 0: - with lopen(logpath, 'rb') as logf: + with open(logpath, 'rb') as logf: worker_output = logf.read().decode('utf-8', 'replace') raise ConversionFailure(path, worker_output) finally: diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index f9cc69cdc2..3a9dcb45f4 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -182,7 +182,7 @@ def main(args=sys.argv): processed_args.append(arg) if internal_book_data_path: try: - with lopen(internal_book_data_path, 'rb') as f: + with open(internal_book_data_path, 'rb') as f: internal_book_data = json.load(f) finally: try: diff --git a/src/calibre/gui2/viewer/web_view.py b/src/calibre/gui2/viewer/web_view.py index 9ec216ed3c..a75a3db420 100644 --- a/src/calibre/gui2/viewer/web_view.py +++ b/src/calibre/gui2/viewer/web_view.py @@ -103,7 +103,7 @@ def handle_mathjax_request(rq, name): if path.startswith(mathjax_dir): mt = guess_type(name) try: - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: raw = f.read() except OSError as err: prints(f"Failed to get mathjax file: {name} with error: {err}", file=sys.stderr) diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index 2daed5bc0e..ae00f6feb1 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -254,7 +254,7 @@ class ImageDropMixin: # {{{ d.start_download() if d.err is None: pmap = QPixmap() - with lopen(d.fpath, 'rb') as f: + with open(d.fpath, 'rb') as f: data = f.read() pmap.loadFromData(data) if not pmap.isNull(): diff --git a/src/calibre/gui2/widgets2.py b/src/calibre/gui2/widgets2.py index 95203ed1c6..7a57ba31a2 100644 --- a/src/calibre/gui2/widgets2.py +++ b/src/calibre/gui2/widgets2.py @@ -569,7 +569,7 @@ class HTMLDisplay(QTextBrowser): if qurl.isLocalFile(): path = qurl.toLocalFile() try: - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: data = f.read() except OSError: if path.rpartition('.')[-1].lower() in {'jpg', 'jpeg', 'gif', 'png', 'bmp', 'webp'}: diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index fc4434b799..197cc736f8 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -114,7 +114,7 @@ class MetadataBackup(Thread): # {{{ self.break_cycles() def write(self, path, raw): - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(raw) diff --git a/src/calibre/library/catalogs/epub_mobi_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py index 18c706dde9..42fa7f40dd 100644 --- a/src/calibre/library/catalogs/epub_mobi_builder.py +++ b/src/calibre/library/catalogs/epub_mobi_builder.py @@ -3709,7 +3709,7 @@ class CatalogBuilder: # Write the OPF file pretty_opf(root), pretty_xml_tree(root) output = etree.tostring(root, encoding='utf-8') - with lopen(f"{self.catalog_path}/{self.opts.basename}.opf", 'wb') as outfile: + with open(f"{self.catalog_path}/{self.opts.basename}.opf", 'wb') as outfile: outfile.write(output.strip()) def generate_rating_string(self, book): @@ -3885,7 +3885,7 @@ class CatalogBuilder: pass # Generate crc for current cover - with lopen(title['cover'], 'rb') as f: + with open(title['cover'], 'rb') as f: data = f.read() cover_crc = hex(zlib.crc32(data)) @@ -3909,7 +3909,7 @@ class CatalogBuilder: # Save thumb for catalog. If invalid data, error returns to generate_thumbnails() thumb_data = scale_image(data, width=self.thumb_width, height=self.thumb_height)[-1] - with lopen(os.path.join(image_dir, thumb_file), 'wb') as f: + with open(os.path.join(image_dir, thumb_file), 'wb') as f: f.write(thumb_data) # Save thumb to archive @@ -4378,5 +4378,5 @@ class CatalogBuilder: self.update_progress_full_step(_("Saving NCX")) pretty_xml_tree(self.ncx_root) ncx = etree.tostring(self.ncx_root, encoding='utf-8') - with lopen(f"{self.catalog_path}/{self.opts.basename}.ncx", 'wb') as outfile: + with open(f"{self.catalog_path}/{self.opts.basename}.ncx", 'wb') as outfile: outfile.write(ncx) diff --git a/src/calibre/library/database.py b/src/calibre/library/database.py index f3c0b7dd4c..16757b20ac 100644 --- a/src/calibre/library/database.py +++ b/src/calibre/library/database.py @@ -1355,7 +1355,7 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; id = obj.lastrowid self.conn.commit() self.set_metadata(id, mi) - stream = path if hasattr(path, 'read') else lopen(path, 'rb') + stream = path if hasattr(path, 'read') else open(path, 'rb') stream.seek(0, 2) usize = stream.tell() stream.seek(0) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 1632921287..5b186aa0ea 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -756,10 +756,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg') if os.access(path, os.R_OK): try: - f = lopen(path, 'rb') + f = open(path, 'rb') except OSError: time.sleep(0.2) - f = lopen(path, 'rb') + f = open(path, 'rb') with f: if as_path: pt = PersistentTemporaryFile('_dbcover.jpg') @@ -873,7 +873,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): continue try: raw = metadata_to_opf(mi) - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(raw) if remove_from_dirtied: self.clear_dirtied(book_id, sequence) @@ -1312,7 +1312,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if path is None: raise NoSuchFormat('Record %d has no fmt: %s'%(id_, fmt)) sha = hashlib.sha256() - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: while True: raw = f.read(SPOOL_SIZE) sha.update(raw) @@ -1408,7 +1408,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): windows_atomic_move.copy_path_to(path, dest) else: if hasattr(dest, 'write'): - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: shutil.copyfileobj(f, dest) if hasattr(dest, 'flush'): dest.flush() @@ -1427,7 +1427,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return except: pass - with lopen(path, 'rb') as f, lopen(dest, 'wb') as d: + with open(path, 'rb') as f, open(dest, 'wb') as d: shutil.copyfileobj(f, d) def copy_cover_to(self, index, dest, index_is_id=False, @@ -1458,10 +1458,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): else: if os.access(path, os.R_OK): try: - f = lopen(path, 'rb') + f = open(path, 'rb') except OSError: time.sleep(0.2) - f = lopen(path, 'rb') + f = open(path, 'rb') with f: if hasattr(dest, 'write'): shutil.copyfileobj(f, dest) @@ -1475,7 +1475,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return True except: pass - with lopen(dest, 'wb') as d: + with open(dest, 'wb') as d: shutil.copyfileobj(f, d) return True return False @@ -1500,7 +1500,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): ''' path = self.format_abspath(index, format, index_is_id=index_is_id) if path is not None: - with lopen(path, mode) as f: + with open(path, mode) as f: if as_path: if preserve_filename: bd = base_dir() @@ -1511,7 +1511,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): pass fname = os.path.basename(path) ret = os.path.join(d, fname) - with lopen(ret, 'wb') as f2: + with open(ret, 'wb') as f2: shutil.copyfileobj(f, f2) else: with PersistentTemporaryFile('.'+format.lower()) as pt: @@ -1532,7 +1532,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): path=None, notify=True, replace=True): npath = self.run_import_plugins(fpath, format) format = os.path.splitext(npath)[-1].lower().replace('.', '').upper() - stream = lopen(npath, 'rb') + stream = open(npath, 'rb') format = check_ebook_format(stream, format) id = index if index_is_id else self.id(index) retval = self.add_format(id, format, stream, replace=replace, @@ -1564,7 +1564,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): else: if (not getattr(stream, 'name', False) or not samefile(dest, stream.name)): - with lopen(dest, 'wb') as f: + with open(dest, 'wb') as f: shutil.copyfileobj(stream, f) size = f.tell() elif os.path.exists(dest): @@ -1587,7 +1587,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if opath is None: return False nfmt = 'ORIGINAL_'+fmt - with lopen(opath, 'rb') as f: + with open(opath, 'rb') as f: return self.add_format(book_id, nfmt, f, index_is_id=True, notify=notify) def original_fmt(self, book_id, fmt): @@ -1600,7 +1600,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): opath = self.format_abspath(book_id, original_fmt, index_is_id=True) if opath is not None: fmt = original_fmt.partition('_')[2] - with lopen(opath, 'rb') as f: + with open(opath, 'rb') as f: self.add_format(book_id, fmt, f, index_is_id=True, notify=False) self.remove_format(book_id, original_fmt, index_is_id=True, notify=notify) return True @@ -2336,7 +2336,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): doit(self.set_cover, id, mi.cover_data[1], commit=False) elif isinstance(mi.cover, string_or_bytes) and mi.cover: if os.access(mi.cover, os.R_OK): - with lopen(mi.cover, 'rb') as f: + with open(mi.cover, 'rb') as f: raw = f.read() if raw: doit(self.set_cover, id, raw, commit=False) @@ -3358,7 +3358,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): from calibre.ebooks.metadata.meta import get_metadata format = os.path.splitext(path)[1][1:].lower() - with lopen(path, 'rb') as stream: + with open(path, 'rb') as stream: matches = self.data.get_matches('title', '='+title) if matches: tag_matches = self.data.get_matches('tags', '='+_('Catalog')) @@ -3394,7 +3394,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): from calibre.ebooks.metadata.meta import get_metadata format = os.path.splitext(path)[1][1:].lower() - stream = path if hasattr(path, 'read') else lopen(path, 'rb') + stream = path if hasattr(path, 'read') else open(path, 'rb') stream.seek(0) mi = get_metadata(stream, format, use_libprs_metadata=False, force_read_metadata=True) @@ -3525,7 +3525,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.set_metadata(id, mi, commit=True, ignore_errors=True) npath = self.run_import_plugins(path, format) format = os.path.splitext(npath)[-1].lower().replace('.', '').upper() - with lopen(npath, 'rb') as stream: + with open(npath, 'rb') as stream: format = check_ebook_format(stream, format) self.add_format(id, format, stream, index_is_id=True) postimport.append((id, format)) @@ -3574,7 +3574,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if import_hooks: self.add_format_with_hooks(id, ext, path, index_is_id=True) else: - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: self.add_format(id, ext, f, index_is_id=True) # Mark the book dirty, It probably already has been done by # set_metadata, but probably isn't good enough diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py index 24a6625c11..e6d19dca71 100644 --- a/src/calibre/library/save_to_disk.py +++ b/src/calibre/library/save_to_disk.py @@ -341,13 +341,13 @@ def do_save_book_to_disk(db, book_id, mi, plugboards, cdata = db.cover(book_id) if cdata: cpath = base_path + '.jpg' - with lopen(cpath, 'wb') as f: + with open(cpath, 'wb') as f: f.write(cdata) mi.cover = base_name+'.jpg' if opts.write_opf: from calibre.ebooks.metadata.opf2 import metadata_to_opf opf = metadata_to_opf(mi) - with lopen(base_path+'.opf', 'wb') as f: + with open(base_path+'.opf', 'wb') as f: f.write(opf) finally: mi.cover, mi.pubdate, mi.timestamp = originals @@ -363,7 +363,7 @@ def do_save_book_to_disk(db, book_id, mi, plugboards, except NoSuchFormat: continue if opts.update_metadata: - with lopen(fmt_path, 'r+b') as stream: + with open(fmt_path, 'r+b') as stream: update_metadata(mi, fmt, stream, plugboards, cdata) return not formats_written, book_id, mi.title @@ -423,7 +423,7 @@ def read_serialized_metadata(data): mi.cover, mi.cover_data = None, (None, None) cdata = None if 'cover' in data: - with lopen(data['cover'], 'rb') as f: + with open(data['cover'], 'rb') as f: cdata = f.read() return mi, cdata @@ -441,7 +441,7 @@ def update_serialized_metadata(book, common_data=None): for fmt, fmtpath in zip(fmts, book['fmts']): try: - with lopen(fmtpath, 'r+b') as stream: + with open(fmtpath, 'r+b') as stream: update_metadata(mi, fmt, stream, (), cdata, error_report=report_error, plugboard_cache=plugboard_cache) except Exception: report_error(fmt, traceback.format_exc()) diff --git a/src/calibre/srv/books.py b/src/calibre/srv/books.py index 515d296b7c..463016281b 100644 --- a/src/calibre/srv/books.py +++ b/src/calibre/srv/books.py @@ -148,7 +148,7 @@ def book_manifest(ctx, rd, book_id, fmt): safe_remove(mpath, True) try: os.utime(mpath, None) - with lopen(mpath, 'rb') as f: + with open(mpath, 'rb') as f: ans = jsonlib.load(f) ans['metadata'] = book_as_json(db, book_id) user = rd.username or None @@ -179,7 +179,7 @@ def book_file(ctx, rd, book_id, fmt, size, mtime, name): if not mpath.startswith(base): raise HTTPNotFound(f'No book file with hash: {bhash} and name: {name}') try: - return rd.filesystem_file_with_custom_etag(lopen(mpath, 'rb'), bhash, name) + return rd.filesystem_file_with_custom_etag(open(mpath, 'rb'), bhash, name) except OSError as e: if e.errno != errno.ENOENT: raise @@ -304,4 +304,4 @@ def mathjax(ctx, rd, which): path = os.path.abspath(P('mathjax/' + which, allow_user_override=False)) if not path.startswith(P('mathjax', allow_user_override=False)): raise HTTPNotFound('No MathJax file named: %s' % which) - return rd.filesystem_file_with_constant_etag(lopen(path, 'rb'), manifest['etag']) + return rd.filesystem_file_with_constant_etag(open(path, 'rb'), manifest['etag']) diff --git a/src/calibre/srv/code.py b/src/calibre/srv/code.py index 142c5f232e..73e1d3c754 100644 --- a/src/calibre/srv/code.py +++ b/src/calibre/srv/code.py @@ -46,7 +46,7 @@ def index(ctx, rd): if not p.path.endswith(b'/'): p = p._replace(path=p.path + b'/') raise HTTPRedirect(urlunparse(p).decode('utf-8')) - ans_file = lopen(P('content-server/index-generated.html'), 'rb') + ans_file = open(P('content-server/index-generated.html'), 'rb') if not in_develop_mode: return ans_file return ans_file.read().replace(b'__IN_DEVELOP_MODE__', b'1') diff --git a/src/calibre/srv/embedded.py b/src/calibre/srv/embedded.py index 818a798183..7a0a08a547 100644 --- a/src/calibre/srv/embedded.py +++ b/src/calibre/srv/embedded.py @@ -26,7 +26,7 @@ def log_paths(): def read_json(path): try: - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: raw = f.read() except OSError as err: if err.errno != errno.ENOENT: diff --git a/src/calibre/srv/jobs.py b/src/calibre/srv/jobs.py index 89ae01d22d..79f1cab431 100644 --- a/src/calibre/srv/jobs.py +++ b/src/calibre/srv/jobs.py @@ -72,7 +72,7 @@ class Job(Thread): ans = '' if self.log_path is not None: try: - with lopen(self.log_path, 'rb') as f: + with open(self.log_path, 'rb') as f: ans = f.read() except OSError: pass diff --git a/src/calibre/srv/render_book.py b/src/calibre/srv/render_book.py index 1bf0ecad9a..4512373f52 100644 --- a/src/calibre/srv/render_book.py +++ b/src/calibre/srv/render_book.py @@ -416,8 +416,8 @@ class RenderManager: self.max_workers = max_workers def launch_worker(self): - with lopen(os.path.join(self.tdir, f'{len(self.workers)}.json'), 'wb') as output: - error = lopen(os.path.join(self.tdir, f'{len(self.workers)}.error'), 'wb') + with open(os.path.join(self.tdir, f'{len(self.workers)}.json'), 'wb') as output: + error = open(os.path.join(self.tdir, f'{len(self.workers)}.error'), 'wb') p = start_pipe_worker('from calibre.srv.render_book import worker_main; worker_main()', stdout=error, stderr=error) p.output_path = output.name p.error_path = error.name @@ -485,10 +485,10 @@ class RenderManager: worker.wait() continue if worker.wait() != 0: - with lopen(worker.error_path, 'rb') as f: + with open(worker.error_path, 'rb') as f: error = f.read().decode('utf-8', 'replace') else: - with lopen(worker.output_path, 'rb') as f: + with open(worker.output_path, 'rb') as f: results.append(msgpack_loads(f.read())) if error is not None: raise Exception('Render worker failed with error:\n' + error) @@ -698,7 +698,7 @@ def process_exploded_book( amap[k] = tuple(v) # needed for JSON serialization data = as_bytes(json.dumps(book_render_data, ensure_ascii=False)) - with lopen(os.path.join(container.root, 'calibre-book-manifest.json'), 'wb') as f: + with open(os.path.join(container.root, 'calibre-book-manifest.json'), 'wb') as f: f.write(data) return container, bookmark_data @@ -784,7 +784,7 @@ def render(pathtoebook, output_dir, book_hash=None, serialize_metadata=False, ex if serialize_metadata: from calibre.customize.ui import quick_metadata from calibre.ebooks.metadata.meta import get_metadata - with lopen(pathtoebook, 'rb') as f, quick_metadata: + with open(pathtoebook, 'rb') as f, quick_metadata: mi = get_metadata(f, os.path.splitext(pathtoebook)[1][1:].lower()) book_fmt, opfpath, input_fmt = extract_book(pathtoebook, output_dir, log=default_log) container, bookmark_data = process_exploded_book( @@ -797,14 +797,14 @@ def render(pathtoebook, output_dir, book_hash=None, serialize_metadata=False, ex d = metadata_as_dict(mi) d.pop('cover_data', None) serialize_datetimes(d), serialize_datetimes(d.get('user_metadata', {})) - with lopen(os.path.join(output_dir, 'calibre-book-metadata.json'), 'wb') as f: + with open(os.path.join(output_dir, 'calibre-book-metadata.json'), 'wb') as f: f.write(json_dumps(d)) if extract_annotations: annotations = None if bookmark_data: annotations = json_dumps(tuple(get_stored_annotations(container, bookmark_data))) if annotations: - with lopen(os.path.join(output_dir, 'calibre-book-annotations.json'), 'wb') as f: + with open(os.path.join(output_dir, 'calibre-book-annotations.json'), 'wb') as f: f.write(annotations) diff --git a/src/calibre/srv/standalone.py b/src/calibre/srv/standalone.py index c477a3a294..6bed06f780 100644 --- a/src/calibre/srv/standalone.py +++ b/src/calibre/srv/standalone.py @@ -68,10 +68,10 @@ class Server: access_log = RotatingLog(opts.access_log, max_size=log_size) self.handler = Handler(libraries, opts) if opts.custom_list_template: - with lopen(os.path.expanduser(opts.custom_list_template), 'rb') as f: + with open(os.path.expanduser(opts.custom_list_template), 'rb') as f: self.handler.router.ctx.custom_list_template = json.load(f) if opts.search_the_net_urls: - with lopen(os.path.expanduser(opts.search_the_net_urls), 'rb') as f: + with open(os.path.expanduser(opts.search_the_net_urls), 'rb') as f: self.handler.router.ctx.search_the_net_urls = json.load(f) plugins = [] if opts.use_bonjour: @@ -234,7 +234,7 @@ def main(args=sys.argv): ) daemonize() if opts.pidfile: - with lopen(opts.pidfile, 'wb') as f: + with open(opts.pidfile, 'wb') as f: f.write(str(os.getpid()).encode('ascii')) signal.signal(signal.SIGTERM, lambda s, f: server.stop()) if not getattr(opts, 'daemonize', False) and not iswindows: diff --git a/src/calibre/utils/exim.py b/src/calibre/utils/exim.py index 3d5880a267..dfc8865de4 100644 --- a/src/calibre/utils/exim.py +++ b/src/calibre/utils/exim.py @@ -139,13 +139,13 @@ class Exporter: rpath = os.path.relpath(fpath, path).replace(os.sep, '/') key = f'{pkey}:{rpath}' try: - with lopen(fpath, 'rb') as f: + with open(fpath, 'rb') as f: self.add_file(f, key) except OSError: if not iswindows: raise time.sleep(1) - with lopen(fpath, 'rb') as f: + with open(fpath, 'rb') as f: self.add_file(f, key) files.append((key, rpath)) @@ -274,7 +274,7 @@ class Importer: self.file_metadata = self.metadata['file_metadata'] def part(self, num): - return lopen(self.part_map[num], 'rb') + return open(self.part_map[num], 'rb') def start_file(self, key, description): partnum, pos, size, digest, mtime = self.file_metadata[key] @@ -287,16 +287,16 @@ class Importer: f = self.start_file(key, relpath) path = os.path.join(base_dir, relpath.replace('/', os.sep)) try: - with lopen(path, 'wb') as dest: + with open(path, 'wb') as dest: shutil.copyfileobj(f, dest) except OSError: os.makedirs(os.path.dirname(path)) - with lopen(path, 'wb') as dest: + with open(path, 'wb') as dest: shutil.copyfileobj(f, dest) f.close() gpath = os.path.join(base_dir, 'global.py') try: - with lopen(gpath, 'rb') as f: + with open(gpath, 'rb') as f: raw = f.read() except OSError: raw = b'' @@ -310,7 +310,7 @@ class Importer: raw = c.src if not isinstance(raw, bytes): raw = raw.encode('utf-8') - with lopen(gpath, 'wb') as f: + with open(gpath, 'wb') as f: f.write(raw) gprefs = JSONConfig('gui', base_path=base_dir) gprefs['library_usage_stats'] = dict(library_usage_stats) diff --git a/src/calibre/utils/filenames.py b/src/calibre/utils/filenames.py index 2fe951e9e3..0e6fd58635 100644 --- a/src/calibre/utils/filenames.py +++ b/src/calibre/utils/filenames.py @@ -197,7 +197,7 @@ def case_preserving_open_file(path, mode='wb', mkdir_mode=0o777): ans = fpath = cpath else: fname = components[-1] - ans = lopen(os.path.join(cpath, fname), mode) + ans = open(os.path.join(cpath, fname), mode) # Ensure file and all its metadata is written to disk so that subsequent # listdir() has file name in it. I don't know if this is actually # necessary, but given the diversity of platforms, best to be safe. @@ -407,7 +407,7 @@ class WindowsAtomicFolderMove: return winutil.set_file_pointer(handle, 0, winutil.FILE_BEGIN) - with lopen(dest, 'wb') as f: + with open(dest, 'wb') as f: sz = 1024 * 1024 while True: raw = winutil.read_file(handle, sz) diff --git a/src/calibre/utils/fonts/scanner.py b/src/calibre/utils/fonts/scanner.py index 17fff5d484..ca24757f40 100644 --- a/src/calibre/utils/fonts/scanner.py +++ b/src/calibre/utils/fonts/scanner.py @@ -253,7 +253,7 @@ class FontScanner(Thread): path = font_or_path if isinstance(font_or_path, dict): path = font_or_path['path'] - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: return f.read() def find_font_for_text(self, text, allowed_families={'serif', 'sans-serif'}, @@ -374,7 +374,7 @@ class FontScanner(Thread): self.write_cache() def read_font_metadata(self, path, fileid): - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: try: fm = FontMetadata(f) except UnsupportedFont: diff --git a/src/calibre/utils/fonts/utils.py b/src/calibre/utils/fonts/utils.py index 48f7d7f344..3c589c8479 100644 --- a/src/calibre/utils/fonts/utils.py +++ b/src/calibre/utils/fonts/utils.py @@ -442,7 +442,7 @@ def get_font_for_text(text, candidate_font_data=None): from calibre.utils.fonts.scanner import font_scanner family, faces = font_scanner.find_font_for_text(text) if faces: - with lopen(faces[0]['path'], 'rb') as f: + with open(faces[0]['path'], 'rb') as f: candidate_font_data = f.read() return candidate_font_data diff --git a/src/calibre/utils/hyphenation/dictionaries.py b/src/calibre/utils/hyphenation/dictionaries.py index 7abc5d2ee0..381c80a7a3 100644 --- a/src/calibre/utils/hyphenation/dictionaries.py +++ b/src/calibre/utils/hyphenation/dictionaries.py @@ -65,7 +65,7 @@ def extract_dicts(cache_path): tf = tarfile.open(dict_tarball) else: buf = BytesIO() - with lopen(dict_tarball, 'rb') as f: + with open(dict_tarball, 'rb') as f: data = f.read() decompress(data, outfile=buf) buf.seek(0) diff --git a/src/calibre/utils/img.py b/src/calibre/utils/img.py index 7dd1850ad9..3952f4bbb7 100644 --- a/src/calibre/utils/img.py +++ b/src/calibre/utils/img.py @@ -52,11 +52,11 @@ def load_jxr_data(data): with TemporaryDirectory() as tdir: if isinstance(tdir, bytes): tdir = os.fsdecode(tdir) - with lopen(os.path.join(tdir, 'input.jxr'), 'wb') as f: + with open(os.path.join(tdir, 'input.jxr'), 'wb') as f: f.write(data) cmd = [get_exe_path('JxrDecApp'), '-i', 'input.jxr', '-o', 'output.tif'] creationflags = subprocess.DETACHED_PROCESS if iswindows else 0 - subprocess.Popen(cmd, cwd=tdir, stdout=lopen(os.devnull, 'wb'), stderr=subprocess.STDOUT, creationflags=creationflags).wait() + subprocess.Popen(cmd, cwd=tdir, stdout=open(os.devnull, 'wb'), stderr=subprocess.STDOUT, creationflags=creationflags).wait() i = QImage() if not i.load(os.path.join(tdir, 'output.tif')): raise NotImage('Failed to convert JPEG-XR image') @@ -136,7 +136,7 @@ def image_from_data(data): def image_from_path(path): ' Load an image from the specified path. ' - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: return image_from_data(f.read()) @@ -212,7 +212,7 @@ def save_image(img, path, **kw): `image_to_data()` function. ''' fmt = path.rpartition('.')[-1] kw['fmt'] = kw.get('fmt', fmt) - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(image_to_data(image_from_data(img), **kw)) @@ -293,7 +293,7 @@ def save_cover_data_to( changed = True if path is None: return image_to_data(img, compression_quality, fmt, compression_quality // 10) if changed else data - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(image_to_data(img, compression_quality, fmt, compression_quality // 10) if changed else data) # }}} @@ -666,7 +666,7 @@ def test(): # {{{ if __name__ == '__main__': # {{{ args = sys.argv[1:] infile = args.pop(0) - img = image_from_data(lopen(infile, 'rb').read()) + img = image_from_data(open(infile, 'rb').read()) func = globals()[args[0]] kw = {} args.pop(0) @@ -691,6 +691,6 @@ if __name__ == '__main__': # {{{ bn = os.path.basename(infile) outf = bn.rpartition('.')[0] + '.' + '-output' + bn.rpartition('.')[-1] img = func(img, **kw) - with lopen(outf, 'wb') as f: + with open(outf, 'wb') as f: f.write(image_to_data(img, fmt=outf.rpartition('.')[-1])) # }}} diff --git a/src/calibre/utils/imghdr.py b/src/calibre/utils/imghdr.py index 22e1e28990..e23c4f8d17 100644 --- a/src/calibre/utils/imghdr.py +++ b/src/calibre/utils/imghdr.py @@ -16,7 +16,7 @@ def what(file, h=None): ' Recognize image headers ' if h is None: if isinstance(file, string_or_bytes): - with lopen(file, 'rb') as f: + with open(file, 'rb') as f: h = f.read(HSIZE) else: location = file.tell() @@ -42,7 +42,7 @@ def identify(src): needs_close = False if isinstance(src, str): - stream = lopen(src, 'rb') + stream = open(src, 'rb') needs_close = True elif isinstance(src, bytes): stream = ReadOnlyFileBuffer(src) diff --git a/src/calibre/utils/ipc/pool.py b/src/calibre/utils/ipc/pool.py index 24de5ef0f9..a94b3ac106 100644 --- a/src/calibre/utils/ipc/pool.py +++ b/src/calibre/utils/ipc/pool.py @@ -352,7 +352,7 @@ def worker_main(conn): break if not isinstance(job, Job): if isinstance(job, File): - with lopen(job.name, 'rb') as f: + with open(job.name, 'rb') as f: common_data = f.read() common_data = pickle_loads(common_data) else: diff --git a/src/calibre/utils/ipc/server.py b/src/calibre/utils/ipc/server.py index 07e2a25447..29766cfdae 100644 --- a/src/calibre/utils/ipc/server.py +++ b/src/calibre/utils/ipc/server.py @@ -193,7 +193,7 @@ class Server(Thread): job.returncode = worker.returncode elif os.path.exists(worker.rfile): try: - with lopen(worker.rfile, 'rb') as f: + with open(worker.rfile, 'rb') as f: job.result = pickle_loads(f.read()) os.remove(worker.rfile) except: diff --git a/src/calibre/utils/ipc/worker.py b/src/calibre/utils/ipc/worker.py index cdf3090af7..4afa8b652d 100644 --- a/src/calibre/utils/ipc/worker.py +++ b/src/calibre/utils/ipc/worker.py @@ -215,7 +215,7 @@ def main(): result = func(*args, **kwargs) if result is not None: os.makedirs(os.path.dirname(resultf), exist_ok=True) - with lopen(resultf, 'wb') as f: + with open(resultf, 'wb') as f: f.write(pickle_dumps(result)) notifier.queue.put(None) diff --git a/src/calibre/utils/lock.py b/src/calibre/utils/lock.py index 71ba23ffd2..c9ee5fb608 100644 --- a/src/calibre/utils/lock.py +++ b/src/calibre/utils/lock.py @@ -176,7 +176,7 @@ else: def create_single_instance_mutex(name, per_user=True): from calibre.utils.ipc import eintr_retry_call path = singleinstance_path(name, per_user) - f = lopen(path, 'w') + f = open(path, 'w') try: eintr_retry_call(fcntl.lockf, f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) return partial(_clean_lock_file, f) diff --git a/src/calibre/utils/magick/draw.py b/src/calibre/utils/magick/draw.py index 402d6b66b8..3794dfe9e0 100644 --- a/src/calibre/utils/magick/draw.py +++ b/src/calibre/utils/magick/draw.py @@ -112,7 +112,7 @@ def identify(path): (width, height, format) or raises an Exception. ''' - with lopen(path, 'rb') as f: + with open(path, 'rb') as f: fmt, width, height = _identify(f) return width, height, fmt diff --git a/src/calibre/utils/magick/legacy.py b/src/calibre/utils/magick/legacy.py index cf1544f339..651a0bd56e 100644 --- a/src/calibre/utils/magick/legacy.py +++ b/src/calibre/utils/magick/legacy.py @@ -40,7 +40,7 @@ class Image: if hasattr(path_or_file, 'read'): self.load(path_or_file.read()) else: - with lopen(path_or_file, 'rb') as f: + with open(path_or_file, 'rb') as f: self.load(f.read()) def load(self, data): @@ -101,7 +101,7 @@ class Image: format = ext[1:] format = format.upper() - with lopen(path, 'wb') as f: + with open(path, 'wb') as f: f.write(self.export(format)) def compose(self, img, left=0, top=0, operation='OverCompositeOp'): diff --git a/src/calibre/utils/rapydscript.py b/src/calibre/utils/rapydscript.py index cb7ede3919..d228363058 100644 --- a/src/calibre/utils/rapydscript.py +++ b/src/calibre/utils/rapydscript.py @@ -328,7 +328,7 @@ def base_dir(): def atomic_write(base, name, content): name = os.path.join(base, name) tname = name + '.tmp' - with lopen(tname, 'wb') as f: + with open(tname, 'wb') as f: f.write(as_bytes(content)) atomic_rename(tname, name) @@ -355,7 +355,7 @@ def run_rapydscript_tests(): base = base_dir() rapydscript_dir = os.path.join(base, 'src', 'pyj') fname = os.path.join(rapydscript_dir, 'test.pyj') - with lopen(fname, 'rb') as f: + with open(fname, 'rb') as f: js = compile_fast(f.read(), fname) class UrlSchemeHandler(QWebEngineUrlSchemeHandler): @@ -443,7 +443,7 @@ def compile_editor(): base = base_dir() rapydscript_dir = os.path.join(base, 'src', 'pyj') fname = os.path.join(rapydscript_dir, 'editor.pyj') - with lopen(fname, 'rb') as f: + with open(fname, 'rb') as f: js = set_data(compile_fast(f.read(), fname)) base = os.path.join(base, 'resources') atomic_write(base, 'editor.js', js) @@ -455,14 +455,14 @@ def compile_viewer(): g = {'__file__': iconf} exec_path(iconf, g) icons = g['merge']() - with lopen(os.path.join(base, 'resources', 'content-server', 'reset.css'), 'rb') as f: + with open(os.path.join(base, 'resources', 'content-server', 'reset.css'), 'rb') as f: reset = f.read().decode('utf-8') html = '\n{icons}'.format( icons=icons, reset=reset) rapydscript_dir = os.path.join(base, 'src', 'pyj') fname = os.path.join(rapydscript_dir, 'viewer-main.pyj') - with lopen(fname, 'rb') as f: + with open(fname, 'rb') as f: js = set_data(compile_fast(f.read(), fname)) base = os.path.join(base, 'resources') atomic_write(base, 'viewer.js', js) @@ -475,22 +475,22 @@ def compile_srv(): g = {'__file__': iconf} exec_path(iconf, g) icons = g['merge']().encode('utf-8') - with lopen(os.path.join(base, 'resources', 'content-server', 'reset.css'), 'rb') as f: + with open(os.path.join(base, 'resources', 'content-server', 'reset.css'), 'rb') as f: reset = f.read() rapydscript_dir = os.path.join(base, 'src', 'pyj') rb = os.path.join(base, 'src', 'calibre', 'srv', 'render_book.py') - with lopen(rb, 'rb') as f: + with open(rb, 'rb') as f: rv = str(int(re.search(br'^RENDER_VERSION\s+=\s+(\d+)', f.read(), re.M).group(1))) mathjax_version = json.loads(P('mathjax/manifest.json', data=True, allow_user_override=False))['etag'] base = os.path.join(base, 'resources', 'content-server') fname = os.path.join(rapydscript_dir, 'srv.pyj') - with lopen(fname, 'rb') as f: + with open(fname, 'rb') as f: js = set_data( compile_fast(f.read(), fname), __RENDER_VERSION__=rv, __MATHJAX_VERSION__=mathjax_version ).encode('utf-8') - with lopen(os.path.join(base, 'index.html'), 'rb') as f: + with open(os.path.join(base, 'index.html'), 'rb') as f: html = f.read().replace(b'RESET_STYLES', reset, 1).replace(b'ICONS', icons, 1).replace(b'MAIN_JS', js, 1) atomic_write(base, 'index-generated.html', html) diff --git a/src/calibre/utils/tdir_in_cache.py b/src/calibre/utils/tdir_in_cache.py index b65c0292f0..9672c36449 100644 --- a/src/calibre/utils/tdir_in_cache.py +++ b/src/calibre/utils/tdir_in_cache.py @@ -40,7 +40,7 @@ else: def lock_tdir(path): lf = os.path.join(path, TDIR_LOCK) - f = lopen(lf, 'w') + f = open(lf, 'w') eintr_retry_call(fcntl.lockf, f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) return f @@ -55,7 +55,7 @@ else: def is_tdir_locked(path): lf = os.path.join(path, TDIR_LOCK) - f = lopen(lf, 'w') + f = open(lf, 'w') try: eintr_retry_call(fcntl.lockf, f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) eintr_retry_call(fcntl.lockf, f.fileno(), fcntl.LOCK_UN) diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 35cab8ae43..9d71aa8393 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -727,7 +727,7 @@ class BasicNewsRecipe(Recipe): _raw = xml_to_unicode(_raw, strip_encoding_pats=True, resolve_entities=True)[0] _raw = clean_xml_chars(_raw) if save_raw: - with lopen(save_raw, 'wb') as f: + with open(save_raw, 'wb') as f: f.write(_raw.encode('utf-8')) if as_tree: from html5_parser import parse diff --git a/src/calibre/web/fetch/utils.py b/src/calibre/web/fetch/utils.py index 58eb41599c..4dab35c2ad 100644 --- a/src/calibre/web/fetch/utils.py +++ b/src/calibre/web/fetch/utils.py @@ -39,10 +39,10 @@ def rescale_image(data, scale_news_images, compress_news_images_max_size, compre def prepare_masthead_image(path_to_image, out_path, mi_width, mi_height): - with lopen(path_to_image, 'rb') as f: + with open(path_to_image, 'rb') as f: img = image_from_data(f.read()) img = blend_on_canvas(img, mi_width, mi_height) - with lopen(out_path, 'wb') as f: + with open(out_path, 'wb') as f: f.write(image_to_data(img)) From fb262b7b57391ff5190862abd13172e839986dc5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 9 Jan 2023 20:26:03 +0530 Subject: [PATCH 0095/2055] Remove use of custom globals --- src/calibre/__init__.py | 1 + src/calibre/srv/opts.py | 11 +++++--- src/calibre/utils/formatter.py | 9 +++++-- src/calibre/utils/formatter_functions.py | 15 ++++++----- src/calibre/utils/localization.py | 1 + src/calibre/utils/search_query_parser.py | 1 + src/calibre/utils/smtp.py | 1 + src/calibre/utils/winreg/default_programs.py | 16 +++++++----- src/calibre/web/feeds/__init__.py | 13 +++++++--- src/calibre/web/feeds/news.py | 10 +++----- src/calibre/web/feeds/recipes/collection.py | 27 +++++++++++--------- src/calibre/web/feeds/recipes/model.py | 9 ++++--- src/calibre/web/feeds/templates.py | 1 + src/calibre/web/fetch/simple.py | 1 + src/odf/odf2xhtml.py | 1 + 15 files changed, 73 insertions(+), 44 deletions(-) diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index 8be1057beb..a19abf77d0 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -77,6 +77,7 @@ def to_unicode(raw, encoding='utf-8', errors='strict'): def patheq(p1, p2): p = os.path + def d(x): return p.normcase(p.normpath(p.realpath(p.normpath(x)))) if not p1 or not p2: diff --git a/src/calibre/srv/opts.py b/src/calibre/srv/opts.py index 1537209fd9..b9ebffe6e7 100644 --- a/src/calibre/srv/opts.py +++ b/src/calibre/srv/opts.py @@ -4,15 +4,18 @@ __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' -import errno, os, numbers -from collections import namedtuple, OrderedDict -from operator import attrgetter +import errno +import numbers +import os +from collections import OrderedDict, namedtuple from functools import partial +from itertools import zip_longest +from operator import attrgetter from calibre.constants import config_dir +from calibre.utils.localization import _ from calibre.utils.lock import ExclusiveFile from polyglot.builtins import itervalues -from itertools import zip_longest Option = namedtuple('Option', 'name default longdoc shortdoc choices') diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index dad2816e20..7d8d3e6bb0 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -7,7 +7,10 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import re, string, traceback, numbers +import numbers +import re +import string +import traceback from collections import OrderedDict from functools import partial from math import modf @@ -18,8 +21,10 @@ from calibre.constants import DEBUG from calibre.ebooks.metadata.book.base import field_metadata from calibre.utils.config import tweaks from calibre.utils.formatter_functions import ( - formatter_functions, get_database, function_object_type, StoredObjectType) + StoredObjectType, formatter_functions, function_object_type, get_database, +) from calibre.utils.icu import strcmp +from calibre.utils.localization import _ from polyglot.builtins import error_message diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 64e77abb65..ed7c86b086 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -11,21 +11,24 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import inspect, re, traceback, numbers +import inspect +import numbers +import re +import traceback from contextlib import suppress from datetime import datetime, timedelta from enum import Enum, auto from functools import partial -from math import trunc, floor, ceil, modf +from math import ceil, floor, modf, trunc -from calibre import human_readable, prints, prepare_string_for_xml +from calibre import human_readable, prepare_string_for_xml, prints from calibre.constants import DEBUG from calibre.ebooks.metadata import title_sort from calibre.utils.config import tweaks +from calibre.utils.date import UNDEFINED_DATE, format_date, now, parse_date +from calibre.utils.icu import capitalize, sort_key, strcmp +from calibre.utils.localization import _, calibre_langcode_to_name, canonicalize_lang from calibre.utils.titlecase import titlecase -from calibre.utils.icu import capitalize, strcmp, sort_key -from calibre.utils.date import parse_date, format_date, now, UNDEFINED_DATE -from calibre.utils.localization import calibre_langcode_to_name, canonicalize_lang from polyglot.builtins import iteritems, itervalues diff --git a/src/calibre/utils/localization.py b/src/calibre/utils/localization.py index 0eec843531..428dc89540 100644 --- a/src/calibre/utils/localization.py +++ b/src/calibre/utils/localization.py @@ -7,6 +7,7 @@ __docformat__ = 'restructuredtext en' import os, locale, re, io from gettext import GNUTranslations, NullTranslations +from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems diff --git a/src/calibre/utils/search_query_parser.py b/src/calibre/utils/search_query_parser.py index e72ee4c72c..149943cf41 100644 --- a/src/calibre/utils/search_query_parser.py +++ b/src/calibre/utils/search_query_parser.py @@ -21,6 +21,7 @@ import weakref, re from calibre.constants import preferred_encoding from calibre.utils.icu import sort_key +from calibre.utils.localization import _ from calibre import prints from polyglot.binary import as_hex_unicode, from_hex_unicode from polyglot.builtins import codepoint_to_chr diff --git a/src/calibre/utils/smtp.py b/src/calibre/utils/smtp.py index 2d488baab2..213ddbae47 100644 --- a/src/calibre/utils/smtp.py +++ b/src/calibre/utils/smtp.py @@ -11,6 +11,7 @@ This module implements a simple commandline SMTP client that supports: import sys, traceback, os, socket, encodings.idna as idna from calibre import isbytestring from calibre.constants import iswindows +from calibre.utils.localization import _ from polyglot.builtins import as_unicode, native_string_type diff --git a/src/calibre/utils/winreg/default_programs.py b/src/calibre/utils/winreg/default_programs.py index 8ca9e57834..52f8596b55 100644 --- a/src/calibre/utils/winreg/default_programs.py +++ b/src/calibre/utils/winreg/default_programs.py @@ -4,16 +4,19 @@ __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' -import os, sys, time, traceback +import os +import sys +import time +import traceback from threading import Thread - from calibre import guess_type, prints -from calibre.constants import isportable, isfrozen, __version__, DEBUG -from calibre.utils.winreg.lib import Key, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE +from calibre.constants import DEBUG, __version__, isfrozen, isportable +from calibre.utils.localization import _ from calibre.utils.lock import singleinstance -from polyglot.builtins import iteritems, itervalues +from calibre.utils.winreg.lib import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, Key from calibre_extensions import winutil +from polyglot.builtins import iteritems, itervalues # See https://msdn.microsoft.com/en-us/library/windows/desktop/cc144154(v=vs.85).aspx @@ -49,6 +52,7 @@ def default_programs(): def extensions(basename): if basename == 'calibre.exe': from calibre.ebooks import BOOK_EXTENSIONS + # We remove rar and zip as they interfere with 7-zip associations # https://www.mobileread.com/forums/showthread.php?t=256459 return set(BOOK_EXTENSIONS) - {'rar', 'zip'} @@ -56,8 +60,8 @@ def extensions(basename): from calibre.customize.ui import all_input_formats return set(all_input_formats()) if basename == 'ebook-edit.exe': - from calibre.ebooks.oeb.polish.main import SUPPORTED from calibre.ebooks.oeb.polish.import_book import IMPORTABLE + from calibre.ebooks.oeb.polish.main import SUPPORTED return SUPPORTED | IMPORTABLE diff --git a/src/calibre/web/feeds/__init__.py b/src/calibre/web/feeds/__init__.py index 5f93d9c2df..adb7d69c3c 100644 --- a/src/calibre/web/feeds/__init__.py +++ b/src/calibre/web/feeds/__init__.py @@ -6,12 +6,16 @@ __copyright__ = '2008, Kovid Goyal ' ''' Contains the logic for parsing feeds. ''' -import time, traceback, copy, re +import copy +import re +import time +import traceback +from builtins import _ -from calibre.utils.logging import default_log -from calibre import entity_to_unicode, strftime, force_unicode -from calibre.utils.date import dt_factory, utcnow, local_tz +from calibre import entity_to_unicode, force_unicode, strftime from calibre.utils.cleantext import clean_ascii_chars, clean_xml_chars +from calibre.utils.date import dt_factory, local_tz, utcnow +from calibre.utils.logging import default_log from polyglot.builtins import string_or_bytes @@ -341,6 +345,7 @@ def feed_from_xml(raw_xml, title=None, oldest_article=7, get_article_url=lambda item: item.get('link', None), log=default_log): from calibre.web.feeds.feedparser import parse + # Handle unclosed escaped entities. They trip up feedparser and HBR for one # generates them raw_xml = re.sub(br'(&#\d+)([^0-9;])', br'\1;\2', raw_xml) diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 9d71aa8393..19ed7e04fc 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -18,7 +18,7 @@ from urllib.parse import urlparse, urlsplit from calibre import ( __appname__, as_unicode, browser, force_unicode, iswindows, preferred_encoding, - random_user_agent, strftime + random_user_agent, strftime, ) from calibre.ebooks.BeautifulSoup import BeautifulSoup, CData, NavigableString, Tag from calibre.ebooks.metadata import MetaInformation @@ -28,13 +28,13 @@ from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.date import now as nowf from calibre.utils.icu import numeric_sort_key from calibre.utils.img import add_borders_to_image, image_to_data, save_cover_data_to -from calibre.utils.localization import canonicalize_lang +from calibre.utils.localization import _, canonicalize_lang from calibre.utils.logging import ThreadSafeWrapper from calibre.utils.threadpool import NoResultsPending, ThreadPool, WorkRequest from calibre.web import Recipe from calibre.web.feeds import Feed, feed_from_xml, feeds_from_index, templates from calibre.web.fetch.simple import ( - AbortArticle, RecursiveFetcher, option_parser as web2disk_option_parser + AbortArticle, RecursiveFetcher, option_parser as web2disk_option_parser, ) from calibre.web.fetch.utils import prepare_masthead_image from polyglot.builtins import string_or_bytes @@ -717,9 +717,7 @@ class BasicNewsRecipe(Recipe): _raw = self.encoding(_raw) else: _raw = _raw.decode(self.encoding, 'replace') - from calibre.ebooks.chardet import ( - strip_encoding_declarations, xml_to_unicode - ) + from calibre.ebooks.chardet import strip_encoding_declarations, xml_to_unicode from calibre.utils.cleantext import clean_xml_chars if isinstance(_raw, str): _raw = strip_encoding_declarations(_raw) diff --git a/src/calibre/web/feeds/recipes/collection.py b/src/calibre/web/feeds/recipes/collection.py index f6c018254d..e064a43cd7 100644 --- a/src/calibre/web/feeds/recipes/collection.py +++ b/src/calibre/web/feeds/recipes/collection.py @@ -5,19 +5,24 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, calendar, zipfile -from threading import RLock +import calendar +import os +import zipfile from datetime import timedelta - from lxml import etree from lxml.builder import ElementMaker +from threading import RLock from calibre import force_unicode -from calibre.utils.xml_parse import safe_xml_fromstring from calibre.constants import numeric_version +from calibre.utils.date import ( + EPOCH, UNDEFINED_DATE, isoformat, local_tz, now as nowf, utcnow, +) from calibre.utils.iso8601 import parse_iso8601 -from calibre.utils.date import now as nowf, utcnow, local_tz, isoformat, EPOCH, UNDEFINED_DATE +from calibre.utils.resources import get_path as P +from calibre.utils.localization import _ from calibre.utils.recycle_bin import delete_file +from calibre.utils.xml_parse import safe_xml_fromstring from polyglot.builtins import iteritems NS = 'http://calibre-ebook.com/recipe_collection' @@ -106,8 +111,7 @@ def get_builtin_recipe_collection(): def get_custom_recipe_collection(*args): - from calibre.web.feeds.recipes import compile_recipe, \ - custom_recipes + from calibre.web.feeds.recipes import compile_recipe, custom_recipes bdir = os.path.dirname(custom_recipes.file_path) rmap = {} for id_, x in iteritems(custom_recipes): @@ -132,8 +136,7 @@ def update_custom_recipe(id_, title, script): def update_custom_recipes(script_ids): - from calibre.web.feeds.recipes import custom_recipes, \ - custom_recipe_filename + from calibre.web.feeds.recipes import custom_recipe_filename, custom_recipes bdir = os.path.dirname(custom_recipes.file_path) for id_, title, script in script_ids: @@ -162,8 +165,7 @@ def add_custom_recipe(title, script): def add_custom_recipes(script_map): - from calibre.web.feeds.recipes import custom_recipes, \ - custom_recipe_filename + from calibre.web.feeds.recipes import custom_recipe_filename, custom_recipes id_ = 1000 keys = tuple(map(int, custom_recipes)) if keys: @@ -217,9 +219,10 @@ def get_builtin_recipe_titles(): def download_builtin_recipe(urn): + import bz2 + from calibre.utils.config_base import prefs from calibre.utils.https import get_https_resource_securely - import bz2 recipe_source = bz2.decompress(get_https_resource_securely( 'https://code.calibre-ebook.com/recipe-compressed/'+urn, headers={'CALIBRE-INSTALL-UUID':prefs['installation_uuid']})) recipe_source = recipe_source.decode('utf-8') diff --git a/src/calibre/web/feeds/recipes/model.py b/src/calibre/web/feeds/recipes/model.py index cd343416a0..7774d4958a 100644 --- a/src/calibre/web/feeds/recipes/model.py +++ b/src/calibre/web/feeds/recipes/model.py @@ -5,19 +5,20 @@ import copy import zipfile from functools import total_ordering from qt.core import ( - QAbstractItemModel, QApplication, QFont, QIcon, QModelIndex, QPalette, QPixmap, - Qt, pyqtSignal + QAbstractItemModel, QApplication, QFont, QIcon, QModelIndex, QPalette, QPixmap, Qt, + pyqtSignal, ) from calibre import force_unicode from calibre.utils.icu import primary_sort_key -from calibre.utils.localization import get_language +from calibre.utils.localization import _, get_language +from calibre.utils.resources import get_path as P from calibre.utils.search_query_parser import ParseException, SearchQueryParser from calibre.web.feeds.recipes.collection import ( SchedulerConfig, add_custom_recipe, add_custom_recipes, download_builtin_recipe, get_builtin_recipe, get_builtin_recipe_collection, get_custom_recipe, get_custom_recipe_collection, remove_custom_recipe, update_custom_recipe, - update_custom_recipes + update_custom_recipes, ) from polyglot.builtins import iteritems diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py index afcdda48cc..6c7f9daa00 100644 --- a/src/calibre/web/feeds/templates.py +++ b/src/calibre/web/feeds/templates.py @@ -13,6 +13,7 @@ from lxml.html.builder import HTML, HEAD, TITLE, STYLE, DIV, BODY, \ TABLE, TD, TR from calibre import strftime, isbytestring +from calibre.utils.localization import _ def attrs(*args, **kw): diff --git a/src/calibre/web/fetch/simple.py b/src/calibre/web/fetch/simple.py index fd4a01bed9..7c0effa251 100644 --- a/src/calibre/web/fetch/simple.py +++ b/src/calibre/web/fetch/simple.py @@ -26,6 +26,7 @@ from calibre.ebooks.chardet import xml_to_unicode from calibre.utils.config import OptionParser from calibre.utils.filenames import ascii_filename from calibre.utils.imghdr import what +from calibre.utils.localization import _ from calibre.utils.logging import Log from calibre.web.fetch.utils import rescale_image from polyglot.http_client import responses diff --git a/src/odf/odf2xhtml.py b/src/odf/odf2xhtml.py index a7a3c56d06..92f0066664 100644 --- a/src/odf/odf2xhtml.py +++ b/src/odf/odf2xhtml.py @@ -913,6 +913,7 @@ dl.notes dd:last-of-type { page-break-after: avoid } if self.currentnote == 0: return # Changed by Kovid to improve endnote functionality + from builtins import _ self.opentag('h1', {'class':'notes-header'}) self.writeout(_('Notes')) self.closetag('h1') From 0cf25eb724d8ceb3523922baf6c2d2f2aacae20e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 9 Jan 2023 20:45:33 +0530 Subject: [PATCH 0096/2055] Remove use of global P() --- src/calibre/__init__.py | 1 + src/calibre/db/backend.py | 13 +-- src/calibre/db/fts/schema_upgrade.py | 2 + .../ebooks/conversion/plugins/fb2_input.py | 20 ++-- .../ebooks/conversion/plugins/html_output.py | 19 ++-- .../ebooks/conversion/plugins/lrf_input.py | 1 + .../ebooks/conversion/plugins/rtf_input.py | 12 ++- src/calibre/ebooks/lrf/fonts.py | 1 + src/calibre/ebooks/lrf/input.py | 6 +- src/calibre/ebooks/oeb/polish/cascade.py | 14 +-- src/calibre/ebooks/oeb/polish/check/css.py | 6 +- src/calibre/ebooks/oeb/polish/create.py | 19 ++-- src/calibre/ebooks/oeb/polish/tests/base.py | 12 ++- .../ebooks/oeb/polish/tests/container.py | 22 +++-- src/calibre/ebooks/oeb/polish/toc.py | 14 +-- src/calibre/ebooks/oeb/stylizer.py | 26 ++++-- src/calibre/ebooks/oeb/transforms/jacket.py | 3 +- src/calibre/ebooks/pdf/html_writer.py | 16 ++-- .../ebooks/unihandecode/pykakasi/jisyo.py | 1 + src/calibre/gui2/__init__.py | 22 ++--- src/calibre/gui2/book_details.py | 14 +-- src/calibre/gui2/dialogs/template_dialog.py | 32 ++++--- src/calibre/gui2/icon_theme.py | 12 ++- src/calibre/gui2/library/models.py | 15 +-- src/calibre/gui2/lrf_renderer/document.py | 16 ++-- src/calibre/gui2/preferences/look_feel.py | 39 ++++---- .../gui2/preferences/template_functions.py | 7 +- src/calibre/gui2/toc/location.py | 5 +- src/calibre/gui2/tweak_book/editor/help.py | 1 + .../tweak_book/editor/syntax/javascript.py | 14 ++- .../gui2/tweak_book/function_replace.py | 22 +++-- src/calibre/gui2/tweak_book/preview.py | 16 ++-- src/calibre/gui2/tweak_book/spell.py | 21 +++-- src/calibre/gui2/ui.py | 11 ++- src/calibre/gui2/viewer/lookup.py | 7 +- src/calibre/gui2/viewer/web_view.py | 12 +-- .../library/catalogs/epub_mobi_builder.py | 16 ++-- src/calibre/library/database2.py | 91 +++++++++++-------- src/calibre/linux.py | 55 ++++++----- src/calibre/scraper/simple.py | 1 + src/calibre/scraper/simple_backend.py | 1 + src/calibre/spell/__init__.py | 1 + src/calibre/spell/dictionary.py | 1 + src/calibre/spell/import_from.py | 4 + src/calibre/srv/books.py | 1 + src/calibre/srv/code.py | 1 + src/calibre/srv/content.py | 20 ++-- src/calibre/srv/tests/base.py | 21 +++-- src/calibre/srv/tests/content.py | 10 +- src/calibre/srv/tests/http.py | 9 +- src/calibre/test_build.py | 4 +- src/calibre/utils/config_base.py | 3 +- src/calibre/utils/fonts/scanner.py | 13 ++- src/calibre/utils/fonts/sfnt/container.py | 5 +- src/calibre/utils/fonts/sfnt/subset.py | 15 ++- src/calibre/utils/fonts/utils.py | 8 +- src/calibre/utils/fonts/win_fonts.py | 1 + src/calibre/utils/https.py | 1 + src/calibre/utils/hyphenation/dictionaries.py | 1 + src/calibre/utils/random_ua.py | 1 + src/calibre/utils/rapydscript.py | 1 + src/calibre/utils/search_query_parser.py | 2 +- src/calibre/utils/titlecase.py | 4 +- src/calibre/web/feeds/news.py | 2 +- 64 files changed, 455 insertions(+), 312 deletions(-) diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index a19abf77d0..638eac538b 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -22,6 +22,7 @@ from calibre.startup import initialize_calibre initialize_calibre() from calibre.utils.icu import safe_chr from calibre.prints import prints +from calibre.utils.resources import get_path as P if False: # Prevent pyflakes from complaining diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 19b941dd1d..f8d2b7ab09 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -20,7 +20,7 @@ from functools import partial from calibre import as_unicode, force_unicode, isbytestring, prints from calibre.constants import ( - filesystem_encoding, iswindows, plugins, preferred_encoding + filesystem_encoding, iswindows, plugins, preferred_encoding, ) from calibre.db import SPOOL_SIZE, FTSQueryError from calibre.db.annotations import annot_db_data, unicode_normalize @@ -29,7 +29,7 @@ from calibre.db.errors import NoSuchFormat from calibre.db.schema_upgrades import SchemaUpgrade from calibre.db.tables import ( AuthorsTable, CompositeTable, FormatsTable, IdentifiersTable, ManyToManyTable, - ManyToOneTable, OneToOneTable, PathTable, RatingTable, SizeTable, UUIDTable + ManyToOneTable, OneToOneTable, PathTable, RatingTable, SizeTable, UUIDTable, ) from calibre.ebooks.metadata import author_to_author_sort, title_sort from calibre.library.field_metadata import FieldMetadata @@ -40,15 +40,16 @@ from calibre.utils.date import EPOCH, parse_date, utcfromtimestamp, utcnow from calibre.utils.filenames import ( WindowsAtomicFolderMove, ascii_filename, atomic_rename, copyfile_using_links, copytree_using_links, hardlink_file, is_case_sensitive, is_fat_filesystem, - remove_dir_if_empty, samefile + remove_dir_if_empty, samefile, ) from calibre.utils.formatter_functions import ( - compile_user_template_functions, formatter_functions, - load_user_template_functions, unload_user_template_functions + compile_user_template_functions, formatter_functions, load_user_template_functions, + unload_user_template_functions, ) from calibre.utils.icu import sort_key +from calibre.utils.resources import get_path as P from polyglot.builtins import ( - cmp, iteritems, itervalues, native_string_type, reraise, string_or_bytes + cmp, iteritems, itervalues, native_string_type, reraise, string_or_bytes, ) # }}} diff --git a/src/calibre/db/fts/schema_upgrade.py b/src/calibre/db/fts/schema_upgrade.py index 3ed9fd7837..5a31156cc2 100644 --- a/src/calibre/db/fts/schema_upgrade.py +++ b/src/calibre/db/fts/schema_upgrade.py @@ -2,6 +2,8 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2022, Kovid Goyal +from calibre.utils.resources import get_path as P + class SchemaUpgrade: diff --git a/src/calibre/ebooks/conversion/plugins/fb2_input.py b/src/calibre/ebooks/conversion/plugins/fb2_input.py index cccd6fa4a7..dae3137270 100644 --- a/src/calibre/ebooks/conversion/plugins/fb2_input.py +++ b/src/calibre/ebooks/conversion/plugins/fb2_input.py @@ -3,10 +3,12 @@ __copyright__ = '2008, Anatoly Shipitsin ' """ Convert .fb2 files to .lrf """ -import os, re +import os +import re -from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation from calibre import guess_type +from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation +from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems FB2NS = 'http://www.gribuser.ru/xml/fictionbook/2.0' @@ -37,12 +39,13 @@ class FB2Input(InputFormatPlugin): def convert(self, stream, options, file_ext, log, accelerators): from lxml import etree - from calibre.utils.xml_parse import safe_xml_fromstring - from calibre.ebooks.metadata.fb2 import ensure_namespace, get_fb2_data - from calibre.ebooks.metadata.opf2 import OPFCreator - from calibre.ebooks.metadata.meta import get_metadata - from calibre.ebooks.oeb.base import XLINK_NS, XHTML_NS + from calibre.ebooks.chardet import xml_to_unicode + from calibre.ebooks.metadata.fb2 import ensure_namespace, get_fb2_data + from calibre.ebooks.metadata.meta import get_metadata + from calibre.ebooks.metadata.opf2 import OPFCreator + from calibre.ebooks.oeb.base import XHTML_NS, XLINK_NS + from calibre.utils.xml_parse import safe_xml_fromstring self.log = log log.debug('Parsing XML...') raw = get_fb2_data(stream)[0] @@ -68,7 +71,8 @@ class FB2Input(InputFormatPlugin): css += etree.tostring(s, encoding='unicode', method='text', with_tail=False) + '\n\n' if css: - import css_parser, logging + import css_parser + import logging parser = css_parser.CSSParser(fetcher=None, log=logging.getLogger('calibre.css')) diff --git a/src/calibre/ebooks/conversion/plugins/html_output.py b/src/calibre/ebooks/conversion/plugins/html_output.py index 1694ef29ef..d8e2f1880e 100644 --- a/src/calibre/ebooks/conversion/plugins/html_output.py +++ b/src/calibre/ebooks/conversion/plugins/html_output.py @@ -2,12 +2,15 @@ __license__ = 'GPL 3' __copyright__ = '2010, Fabian Grassl ' __docformat__ = 'restructuredtext en' -import os, re, shutil -from os.path import dirname, abspath, relpath as _relpath, exists, basename +import os +import re +import shutil +from os.path import abspath, basename, dirname, exists, relpath as _relpath -from calibre.customize.conversion import OutputFormatPlugin, OptionRecommendation from calibre import CurrentDir +from calibre.customize.conversion import OptionRecommendation, OutputFormatPlugin from calibre.ptempfile import PersistentTemporaryDirectory +from calibre.utils.resources import get_path as P def relpath(*args): @@ -45,10 +48,10 @@ class HTMLOutput(OutputFormatPlugin): Generate table of contents ''' from lxml import etree - from polyglot.urllib import unquote from calibre.ebooks.oeb.base import element from calibre.utils.cleantext import clean_xml_chars + from polyglot.urllib import unquote with CurrentDir(output_dir): def build_node(current_node, parent=None): if parent is None: @@ -82,10 +85,11 @@ class HTMLOutput(OutputFormatPlugin): def convert(self, oeb_book, output_path, input_plugin, opts, log): from lxml import etree - from calibre.utils import zipfile - from templite import Templite - from polyglot.urllib import unquote + from calibre.ebooks.html.meta import EasyMeta + from calibre.utils import zipfile + from polyglot.urllib import unquote + from templite import Templite # read template files if opts.template_html_index is not None: @@ -193,6 +197,7 @@ class HTMLOutput(OutputFormatPlugin): # render template templite = Templite(template_html_data) + def toc(): return self.generate_html_toc(oeb_book, path, output_dir) t = templite.render(ebookContent=ebook_content, diff --git a/src/calibre/ebooks/conversion/plugins/lrf_input.py b/src/calibre/ebooks/conversion/plugins/lrf_input.py index bd2ab4ecd7..650a105171 100644 --- a/src/calibre/ebooks/conversion/plugins/lrf_input.py +++ b/src/calibre/ebooks/conversion/plugins/lrf_input.py @@ -7,6 +7,7 @@ __docformat__ = 'restructuredtext en' import os, sys from calibre.customize.conversion import InputFormatPlugin +from calibre.utils.resources import get_path as P class LRFInput(InputFormatPlugin): diff --git a/src/calibre/ebooks/conversion/plugins/rtf_input.py b/src/calibre/ebooks/conversion/plugins/rtf_input.py index 3db7518ab2..4651bfe89a 100644 --- a/src/calibre/ebooks/conversion/plugins/rtf_input.py +++ b/src/calibre/ebooks/conversion/plugins/rtf_input.py @@ -1,10 +1,14 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import os, glob, re, textwrap +import glob +import os +import re +import textwrap from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation -from polyglot.builtins import iteritems, as_bytes +from calibre.utils.resources import get_path as P +from polyglot.builtins import as_bytes, iteritems border_style_map = { 'single' : 'solid', @@ -116,8 +120,9 @@ class RTFInput(InputFormatPlugin): return f.read() def extract_images(self, picts): - from calibre.utils.imghdr import what from binascii import unhexlify + + from calibre.utils.imghdr import what self.log('Extracting images...') with open(picts, 'rb') as f: @@ -246,6 +251,7 @@ class RTFInput(InputFormatPlugin): def convert(self, stream, options, file_ext, log, accelerators): from lxml import etree + from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.metadata.opf2 import OPFCreator from calibre.ebooks.rtf2xml.ParseRtf import RtfInvalidCodeException diff --git a/src/calibre/ebooks/lrf/fonts.py b/src/calibre/ebooks/lrf/fonts.py index 7da59cae0b..eafc65b95f 100644 --- a/src/calibre/ebooks/lrf/fonts.py +++ b/src/calibre/ebooks/lrf/fonts.py @@ -25,6 +25,7 @@ def get_font(name, size, encoding='unic'): @param encoding: Font encoding to use. E.g. 'unic', 'symbol', 'ADOB', 'ADBE', 'aprm' @param manager: A dict that will store the PersistentTemporary ''' + from calibre.utils.resources import get_path as P if name in LIBERATION_FONT_MAP: return ImageFont.truetype(P('fonts/liberation/%s.ttf' % LIBERATION_FONT_MAP[name]), size, encoding=encoding) elif name in FONT_FILE_MAP: diff --git a/src/calibre/ebooks/lrf/input.py b/src/calibre/ebooks/lrf/input.py index 08224595f0..d14def7d79 100644 --- a/src/calibre/ebooks/lrf/input.py +++ b/src/calibre/ebooks/lrf/input.py @@ -5,9 +5,9 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import textwrap, operator -from copy import deepcopy, copy - +import operator +import textwrap +from copy import copy, deepcopy from lxml import etree from calibre import guess_type diff --git a/src/calibre/ebooks/oeb/polish/cascade.py b/src/calibre/ebooks/oeb/polish/cascade.py index 6a18120338..5f71b31074 100644 --- a/src/calibre/ebooks/oeb/polish/cascade.py +++ b/src/calibre/ebooks/oeb/polish/cascade.py @@ -2,22 +2,22 @@ # License: GPLv3 Copyright: 2016, Kovid Goyal +import re from collections import defaultdict, namedtuple +from css_parser.css import CSSRule, CSSStyleSheet, Property from functools import partial from itertools import count from operator import itemgetter -import re -from css_parser.css import CSSStyleSheet, CSSRule, Property - -from css_selectors import Select, INAPPROPRIATE_PSEUDO_CLASSES, SelectorError from calibre import as_unicode from calibre.ebooks.css_transform_rules import all_properties from calibre.ebooks.oeb.base import OEB_STYLES, XHTML, css_text -from calibre.ebooks.oeb.normalize_css import normalizers, DEFAULTS -from calibre.ebooks.oeb.stylizer import media_ok, INHERITED -from tinycss.fonts3 import serialize_font_family, parse_font_family +from calibre.ebooks.oeb.normalize_css import DEFAULTS, normalizers +from calibre.ebooks.oeb.stylizer import INHERITED, media_ok +from calibre.utils.resources import get_path as P +from css_selectors import INAPPROPRIATE_PSEUDO_CLASSES, Select, SelectorError from polyglot.builtins import iteritems, itervalues +from tinycss.fonts3 import parse_font_family, serialize_font_family _html_css_stylesheet = None diff --git a/src/calibre/ebooks/oeb/polish/check/css.py b/src/calibre/ebooks/oeb/polish/check/css.py index 7aa3aac121..f910c47e92 100644 --- a/src/calibre/ebooks/oeb/polish/check/css.py +++ b/src/calibre/ebooks/oeb/polish/check/css.py @@ -7,15 +7,13 @@ import numbers import sys from collections import namedtuple from itertools import repeat - from qt.core import QApplication, QEventLoop, pyqtSignal, sip -from qt.webengine import ( - QWebEnginePage, QWebEngineProfile, QWebEngineScript -) +from qt.webengine import QWebEnginePage, QWebEngineProfile, QWebEngineScript from calibre import detect_ncpus as cpu_count, prints from calibre.ebooks.oeb.polish.check.base import ERROR, WARN, BaseError from calibre.gui2 import must_use_qt +from calibre.utils.resources import get_path as P from calibre.utils.webengine import secure_webengine, setup_profile diff --git a/src/calibre/ebooks/oeb/polish/create.py b/src/calibre/ebooks/oeb/polish/create.py index 470bd8f7b0..9645aa71b9 100644 --- a/src/calibre/ebooks/oeb/polish/create.py +++ b/src/calibre/ebooks/oeb/polish/create.py @@ -4,23 +4,24 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' -import sys, os - +import os +import sys from lxml import etree -from calibre import prepare_string_for_xml, CurrentDir -from calibre.ptempfile import TemporaryDirectory -from calibre.ebooks.oeb.base import serialize +from calibre import CurrentDir, prepare_string_for_xml from calibre.ebooks.metadata import authors_to_string from calibre.ebooks.metadata.opf2 import metadata_to_opf +from calibre.ebooks.oeb.base import serialize +from calibre.ebooks.oeb.polish.container import OPF_NAMESPACES, Container, opf_to_azw3 from calibre.ebooks.oeb.polish.parsing import parse -from calibre.ebooks.oeb.polish.container import OPF_NAMESPACES, opf_to_azw3, Container -from calibre.ebooks.oeb.polish.utils import guess_type -from calibre.ebooks.oeb.polish.pretty import pretty_xml_tree, pretty_html_tree +from calibre.ebooks.oeb.polish.pretty import pretty_html_tree, pretty_xml_tree from calibre.ebooks.oeb.polish.toc import TOC, create_ncx +from calibre.ebooks.oeb.polish.utils import guess_type +from calibre.ptempfile import TemporaryDirectory from calibre.utils.localization import lang_as_iso639_1 from calibre.utils.logging import DevNull -from calibre.utils.zipfile import ZipFile, ZIP_STORED +from calibre.utils.resources import get_path as P +from calibre.utils.zipfile import ZIP_STORED, ZipFile from polyglot.builtins import as_bytes valid_empty_formats = {'epub', 'txt', 'docx', 'azw3', 'md'} diff --git a/src/calibre/ebooks/oeb/polish/tests/base.py b/src/calibre/ebooks/oeb/polish/tests/base.py index 6684d33627..fb68c038ac 100644 --- a/src/calibre/ebooks/oeb/polish/tests/base.py +++ b/src/calibre/ebooks/oeb/polish/tests/base.py @@ -4,13 +4,15 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' -import os, unittest, shutil +import os +import shutil +import unittest -from calibre import CurrentDir -from calibre.ptempfile import TemporaryDirectory -from calibre.ptempfile import PersistentTemporaryDirectory -from calibre.utils.logging import DevNull import calibre.ebooks.oeb.polish.container as pc +from calibre import CurrentDir +from calibre.ptempfile import PersistentTemporaryDirectory, TemporaryDirectory +from calibre.utils.logging import DevNull +from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems diff --git a/src/calibre/ebooks/oeb/polish/tests/container.py b/src/calibre/ebooks/oeb/polish/tests/container.py index b84f9973ac..597ff2c297 100644 --- a/src/calibre/ebooks/oeb/polish/tests/container.py +++ b/src/calibre/ebooks/oeb/polish/tests/container.py @@ -4,16 +4,22 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' -import os, subprocess +import os +import subprocess from zipfile import ZipFile from calibre import CurrentDir -from calibre.ebooks.oeb.polish.tests.base import BaseTest, get_simple_book, get_split_book -from calibre.ebooks.oeb.polish.container import get_container as _gc, clone_container, OCF_NS -from calibre.ebooks.oeb.polish.replace import rename_files, rationalize_folders -from calibre.ebooks.oeb.polish.split import split, merge +from calibre.ebooks.oeb.polish.container import ( + OCF_NS, clone_container, get_container as _gc, +) +from calibre.ebooks.oeb.polish.replace import rationalize_folders, rename_files +from calibre.ebooks.oeb.polish.split import merge, split +from calibre.ebooks.oeb.polish.tests.base import ( + BaseTest, get_simple_book, get_split_book, +) +from calibre.ptempfile import TemporaryDirectory, TemporaryFile from calibre.utils.filenames import nlinks_file -from calibre.ptempfile import TemporaryFile, TemporaryDirectory +from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems, itervalues @@ -193,7 +199,9 @@ class ContainerTests(BaseTest): def test_actual_case(self): ' Test getting the actual case for files from names on case insensitive filesystems ' - from calibre.ebooks.oeb.polish.utils import actual_case_for_name, corrected_case_for_name + from calibre.ebooks.oeb.polish.utils import ( + actual_case_for_name, corrected_case_for_name, + ) book = get_simple_book() c = get_container(book) name = 'f1/f2/added file.html' diff --git a/src/calibre/ebooks/oeb/polish/toc.py b/src/calibre/ebooks/oeb/polish/toc.py index 8dbe2cebc6..a6f51ea1b0 100644 --- a/src/calibre/ebooks/oeb/polish/toc.py +++ b/src/calibre/ebooks/oeb/polish/toc.py @@ -8,20 +8,22 @@ __docformat__ = 'restructuredtext en' import re from collections import Counter, OrderedDict from functools import partial -from operator import itemgetter - from lxml import etree from lxml.builder import ElementMaker +from operator import itemgetter from calibre import __version__ from calibre.ebooks.oeb.base import ( - XPath, uuid_id, xml2text, NCX, NCX_NS, XML, XHTML, XHTML_NS, serialize, EPUB_NS, XML_NS, OEB_DOCS) + EPUB_NS, NCX, NCX_NS, OEB_DOCS, XHTML, XHTML_NS, XML, XML_NS, XPath, serialize, + uuid_id, xml2text, +) from calibre.ebooks.oeb.polish.errors import MalformedMarkup -from calibre.ebooks.oeb.polish.utils import guess_type, extract -from calibre.ebooks.oeb.polish.opf import set_guide_item, get_book_language +from calibre.ebooks.oeb.polish.opf import get_book_language, set_guide_item from calibre.ebooks.oeb.polish.pretty import pretty_html_tree, pretty_xml_tree +from calibre.ebooks.oeb.polish.utils import extract, guess_type from calibre.translations.dynamic import translate -from calibre.utils.localization import get_lang, canonicalize_lang, lang_as_iso639_1 +from calibre.utils.localization import canonicalize_lang, get_lang, lang_as_iso639_1 +from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems from polyglot.urllib import urlparse diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py index 4bbd182503..ce08fb6962 100644 --- a/src/calibre/ebooks/oeb/stylizer.py +++ b/src/calibre/ebooks/oeb/stylizer.py @@ -5,19 +5,29 @@ CSS property propagation class. __license__ = 'GPL v3' __copyright__ = '2008, Marshall T. Vandegrift ' -import os, re, logging, copy, unicodedata, numbers +import copy +import logging +import numbers +import os +import re +import unicodedata +from css_parser import ( + CSSParser, log as css_parser_log, parseString, parseStyle, profile as cssprofiles, + profiles, replaceUrls, +) +from css_parser.css import CSSFontFaceRule, CSSPageRule, CSSStyleRule, cssproperties from operator import itemgetter from weakref import WeakKeyDictionary from xml.dom import SyntaxErr as CSSSyntaxError -from css_parser.css import (CSSStyleRule, CSSPageRule, CSSFontFaceRule, - cssproperties) -from css_parser import (profile as cssprofiles, parseString, parseStyle, log as - css_parser_log, CSSParser, profiles, replaceUrls) -from calibre import force_unicode, as_unicode + +from calibre import as_unicode, force_unicode from calibre.ebooks import unit_convert -from calibre.ebooks.oeb.base import XHTML, XHTML_NS, CSS_MIME, OEB_STYLES, xpath, urlnormalize +from calibre.ebooks.oeb.base import ( + CSS_MIME, OEB_STYLES, XHTML, XHTML_NS, urlnormalize, xpath, +) from calibre.ebooks.oeb.normalize_css import DEFAULTS, normalizers -from css_selectors import Select, SelectorError, INAPPROPRIATE_PSEUDO_CLASSES +from calibre.utils.resources import get_path as P +from css_selectors import INAPPROPRIATE_PSEUDO_CLASSES, Select, SelectorError from polyglot.builtins import iteritems from tinycss.media3 import CSSMedia3Parser diff --git a/src/calibre/ebooks/oeb/transforms/jacket.py b/src/calibre/ebooks/oeb/transforms/jacket.py index 70ce8b3820..9b1b0fb27a 100644 --- a/src/calibre/ebooks/oeb/transforms/jacket.py +++ b/src/calibre/ebooks/oeb/transforms/jacket.py @@ -18,12 +18,13 @@ from calibre.ebooks.chardet import strip_encoding_declarations from calibre.ebooks.metadata import fmt_sidx, rating_to_stars from calibre.ebooks.metadata.sources.identify import urls_from_identifiers from calibre.ebooks.oeb.base import ( - XHTML, XHTML_NS, XPath, urldefrag, urlnormalize, xml2text + XHTML, XHTML_NS, XPath, urldefrag, urlnormalize, xml2text, ) from calibre.library.comments import comments_to_html, markdown from calibre.utils.config import tweaks from calibre.utils.date import as_local_time, format_date, is_date_undefined from calibre.utils.icu import sort_key +from calibre.utils.resources import get_path as P JACKET_XPATH = '//h:meta[@name="calibre-content" and @content="jacket"]' diff --git a/src/calibre/ebooks/pdf/html_writer.py b/src/calibre/ebooks/pdf/html_writer.py index b1d16f0db2..6da4ef1d29 100644 --- a/src/calibre/ebooks/pdf/html_writer.py +++ b/src/calibre/ebooks/pdf/html_writer.py @@ -2,8 +2,6 @@ # License: GPL v3 Copyright: 2019, Kovid Goyal # Imports {{{ - - import copy import json import os @@ -16,25 +14,23 @@ from io import BytesIO from itertools import count, repeat from qt.core import ( QApplication, QByteArray, QMarginsF, QObject, QPageLayout, Qt, QTimer, QUrl, - pyqtSignal, sip + pyqtSignal, sip, ) from qt.webengine import ( QWebEnginePage, QWebEngineProfile, QWebEngineSettings, QWebEngineUrlRequestInterceptor, QWebEngineUrlRequestJob, - QWebEngineUrlSchemeHandler + QWebEngineUrlSchemeHandler, ) from calibre import detect_ncpus, human_readable, prepare_string_for_xml -from calibre.constants import ( - FAKE_HOST, FAKE_PROTOCOL, __version__, ismacos, iswindows -) +from calibre.constants import FAKE_HOST, FAKE_PROTOCOL, __version__, ismacos, iswindows from calibre.ebooks.metadata.xmp import metadata_to_xmp_packet from calibre.ebooks.oeb.base import XHTML, XPath from calibre.ebooks.oeb.polish.container import Container as ContainerBase from calibre.ebooks.oeb.polish.toc import get_toc from calibre.ebooks.oeb.polish.utils import guess_type from calibre.ebooks.pdf.image_writer import ( - Image, PDFMetadata, draw_image_page, get_page_layout + Image, PDFMetadata, draw_image_page, get_page_layout, ) from calibre.ebooks.pdf.render.serialize import PDFStream from calibre.gui2 import setup_unix_signals @@ -46,8 +42,10 @@ from calibre.utils.fonts.sfnt.subset import pdf_subset from calibre.utils.logging import default_log from calibre.utils.monotonic import monotonic from calibre.utils.podofo import ( - dedup_type3_fonts, get_podofo, remove_unused_fonts, set_metadata_implementation + dedup_type3_fonts, get_podofo, remove_unused_fonts, set_metadata_implementation, ) + +from calibre.utils.resources import get_path as P from calibre.utils.short_uuid import uuid4 from calibre.utils.webengine import secure_webengine, send_reply, setup_profile from polyglot.builtins import as_bytes, iteritems diff --git a/src/calibre/ebooks/unihandecode/pykakasi/jisyo.py b/src/calibre/ebooks/unihandecode/pykakasi/jisyo.py index a08c2f586a..749ba30971 100644 --- a/src/calibre/ebooks/unihandecode/pykakasi/jisyo.py +++ b/src/calibre/ebooks/unihandecode/pykakasi/jisyo.py @@ -22,6 +22,7 @@ class jisyo : def __init__(self): from calibre.utils.serialize import msgpack_loads + from calibre.utils.resources import get_path as P if self.kanwadict is None: self.kanwadict = msgpack_loads( P('localization/pykakasi/kanwadict2.calibre_msgpack', data=True)) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 151f5b59ab..bdf28807d8 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -12,11 +12,11 @@ from contextlib import contextmanager, suppress from functools import lru_cache from qt.core import ( QApplication, QBuffer, QByteArray, QColor, QDateTime, QDesktopServices, QDialog, - QDialogButtonBox, QEvent, QFile, QFileDialog, QFileIconProvider, QFileInfo, - QFont, QFontDatabase, QFontInfo, QFontMetrics, QGuiApplication, QIcon, - QImageReader, QImageWriter, QIODevice, QLocale, QNetworkProxyFactory, QObject, - QPalette, QResource, QSettings, QSocketNotifier, QStringListModel, Qt, QThread, - QTimer, QTranslator, QUrl, pyqtSignal, pyqtSlot + QDialogButtonBox, QEvent, QFile, QFileDialog, QFileIconProvider, QFileInfo, QFont, + QFontDatabase, QFontInfo, QFontMetrics, QGuiApplication, QIcon, QImageReader, + QImageWriter, QIODevice, QLocale, QNetworkProxyFactory, QObject, QPalette, + QResource, QSettings, QSocketNotifier, QStringListModel, Qt, QThread, QTimer, + QTranslator, QUrl, pyqtSignal, pyqtSlot, ) from threading import Lock, RLock @@ -24,13 +24,13 @@ import calibre.gui2.pyqt6_compat as pqc from calibre import as_unicode, prints from calibre.constants import ( DEBUG, __appname__ as APP_UID, __version__, builtin_colors_dark, - builtin_colors_light, config_dir, is_running_from_develop, isbsd, isfrozen, - islinux, ismacos, iswindows, isxp, numeric_version, plugins_loc + builtin_colors_light, config_dir, is_running_from_develop, isbsd, isfrozen, islinux, + ismacos, iswindows, isxp, numeric_version, plugins_loc, ) from calibre.ebooks.metadata import MetaInformation from calibre.gui2.geometry import geometry_for_restore_as_dict from calibre.gui2.linux_file_dialogs import ( - check_for_linux_native_dialogs, linux_native_dialog + check_for_linux_native_dialogs, linux_native_dialog, ) from calibre.gui2.palette import PaletteManager from calibre.gui2.qt_file_dialogs import FileDialog @@ -41,7 +41,7 @@ from calibre.utils.date import UNDEFINED_DATE from calibre.utils.file_type_icons import EXT_MAP from calibre.utils.img import set_image_allocation_limit from calibre.utils.localization import get_lang -from calibre.utils.resources import user_dir +from calibre.utils.resources import get_path as P, user_dir from polyglot import queue from polyglot.builtins import iteritems, string_or_bytes @@ -888,14 +888,14 @@ if not iswindows and not ismacos and 'CALIBRE_NO_NATIVE_FILEDIALOGS' not in os.e if has_windows_file_dialog_helper: from calibre.gui2.win_file_dialogs import ( - choose_dir, choose_files, choose_images, choose_save_file + choose_dir, choose_files, choose_images, choose_save_file, ) elif has_linux_file_dialog_helper: choose_dir, choose_files, choose_save_file, choose_images = map( linux_native_dialog, 'dir files save_file images'.split()) else: from calibre.gui2.qt_file_dialogs import ( - choose_dir, choose_files, choose_images, choose_save_file + choose_dir, choose_files, choose_images, choose_save_file, ) choose_files, choose_images, choose_dir, choose_save_file diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 8e6628af25..b085f4f9dd 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -5,12 +5,11 @@ import os import re from collections import namedtuple -from functools import partial, lru_cache +from functools import lru_cache, partial from qt.core import ( QAction, QApplication, QClipboard, QColor, QDialog, QEasingCurve, QIcon, - QKeySequence, QMenu, QMimeData, QPainter, QPen, QPixmap, QSplitter, - QPropertyAnimation, QRect, QSize, QSizePolicy, Qt, QUrl, QWidget, pyqtProperty, - QTimer, pyqtSignal + QKeySequence, QMenu, QMimeData, QPainter, QPen, QPixmap, QPropertyAnimation, QRect, + QSize, QSizePolicy, QSplitter, Qt, QTimer, QUrl, QWidget, pyqtProperty, pyqtSignal, ) from calibre import fit_image, sanitize_file_name @@ -20,20 +19,21 @@ from calibre.ebooks.metadata.book.base import Metadata, field_metadata from calibre.ebooks.metadata.book.render import mi_to_html from calibre.ebooks.metadata.search_internet import ( all_author_searches, all_book_searches, name_for, url_for_author_search, - url_for_book_search + url_for_book_search, ) from calibre.gui2 import ( NO_URL_FORMATTING, choose_save_file, config, default_author_link, gprefs, - pixmap_to_data, rating_font, safe_open_url + pixmap_to_data, rating_font, safe_open_url, ) from calibre.gui2.dialogs.confirm_delete import confirm, confirm as confirm_delete from calibre.gui2.dnd import ( - dnd_get_files, dnd_get_image, dnd_has_extension, dnd_has_image, image_extensions + dnd_get_files, dnd_get_image, dnd_has_extension, dnd_has_image, image_extensions, ) from calibre.gui2.widgets2 import HTMLDisplay from calibre.utils.config import tweaks from calibre.utils.img import blend_image, image_from_x from calibre.utils.localization import is_rtl, langnames_to_langcodes +from calibre.utils.resources import get_path as P from calibre.utils.serialize import json_loads from polyglot.binary import from_hex_bytes diff --git a/src/calibre/gui2/dialogs/template_dialog.py b/src/calibre/gui2/dialogs/template_dialog.py index 1444870cd5..22086c705f 100644 --- a/src/calibre/gui2/dialogs/template_dialog.py +++ b/src/calibre/gui2/dialogs/template_dialog.py @@ -5,30 +5,36 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' __license__ = 'GPL v3' -import json, os, traceback, re -from functools import partial +import json +import os +import re import sys - -from qt.core import (Qt, QDialog, QDialogButtonBox, QSyntaxHighlighter, QFont, - QApplication, QTextCharFormat, QColor, QCursor, - QIcon, QSize, QPalette, QLineEdit, QFontInfo, - QFontDatabase, QVBoxLayout, QTableWidget, QTableWidgetItem, - QComboBox, QAbstractItemView, QTextOption, QFontMetrics) +import traceback +from functools import partial +from qt.core import ( + QAbstractItemView, QApplication, QColor, QComboBox, QCursor, QDialog, + QDialogButtonBox, QFont, QFontDatabase, QFontInfo, QFontMetrics, QIcon, QLineEdit, + QPalette, QSize, QSyntaxHighlighter, Qt, QTableWidget, QTableWidgetItem, + QTextCharFormat, QTextOption, QVBoxLayout, +) from calibre import sanitize_file_name from calibre.constants import config_dir from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.formatter import SafeFormat -from calibre.gui2 import (gprefs, error_dialog, choose_files, choose_save_file, - pixmap_to_data, question_dialog) +from calibre.gui2 import ( + choose_files, choose_save_file, error_dialog, gprefs, pixmap_to_data, + question_dialog, +) from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog -from calibre.library.coloring import (displayable_columns, color_row_key) +from calibre.library.coloring import color_row_key, displayable_columns from calibre.utils.config_base import tweaks from calibre.utils.date import DEFAULT_DATE -from calibre.utils.formatter_functions import formatter_functions, StoredObjectType -from calibre.utils.formatter import StopException, PythonTemplateContext +from calibre.utils.formatter import PythonTemplateContext, StopException +from calibre.utils.formatter_functions import StoredObjectType, formatter_functions from calibre.utils.icu import sort_key from calibre.utils.localization import localize_user_manual_link +from calibre.utils.resources import get_path as P class ParenPosition: diff --git a/src/calibre/gui2/icon_theme.py b/src/calibre/gui2/icon_theme.py index 15a7b3e0a7..0268ccb2ed 100644 --- a/src/calibre/gui2/icon_theme.py +++ b/src/calibre/gui2/icon_theme.py @@ -20,10 +20,10 @@ from multiprocessing.pool import ThreadPool from qt.core import ( QAbstractItemView, QApplication, QCheckBox, QComboBox, QDialog, QDialogButtonBox, QFormLayout, QGroupBox, QHBoxLayout, QIcon, QImage, QImageReader, - QItemSelectionModel, QLabel, QLineEdit, QListWidget, QListWidgetItem, QPen, - QPixmap, QProgressDialog, QSize, QSpinBox, QSplitter, QStackedLayout, - QStaticText, QStyle, QStyledItemDelegate, Qt, QTabWidget, QTextEdit, QVBoxLayout, - QWidget, pyqtSignal, sip + QItemSelectionModel, QLabel, QLineEdit, QListWidget, QListWidgetItem, QPen, QPixmap, + QProgressDialog, QSize, QSpinBox, QSplitter, QStackedLayout, QStaticText, QStyle, + QStyledItemDelegate, Qt, QTabWidget, QTextEdit, QVBoxLayout, QWidget, pyqtSignal, + sip, ) from threading import Event, Thread @@ -32,7 +32,7 @@ from calibre.constants import cache_dir from calibre.customize.ui import interface_actions from calibre.gui2 import ( choose_dir, choose_save_file, empty_index, error_dialog, gprefs, - icon_resource_manager, must_use_qt, safe_open_url + icon_resource_manager, must_use_qt, safe_open_url, ) from calibre.gui2.dialogs.progress import ProgressDialog from calibre.gui2.progress_indicator import ProgressIndicator @@ -42,6 +42,7 @@ from calibre.utils.filenames import ascii_filename, atomic_rename from calibre.utils.https import HTTPError, get_https_resource_securely from calibre.utils.icu import numeric_sort_key as sort_key from calibre.utils.img import Canvas, image_from_data, optimize_jpeg, optimize_png +from calibre.utils.resources import get_path as P from calibre.utils.zipfile import ZIP_STORED, ZipFile from polyglot import http_client from polyglot.builtins import as_bytes, iteritems, reraise @@ -127,6 +128,7 @@ def read_theme_from_folder(path): return int(x) except Exception: return -1 + def g(x, defval=''): return metadata.get(x, defval) theme = Theme(g('title'), g('author'), safe_int(g('version', -1)), g('description'), g('license', 'Unknown'), g('url', None)) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 8069af295e..30e9c1755b 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -14,10 +14,14 @@ import time import traceback from collections import defaultdict, namedtuple from itertools import groupby +from qt.core import ( + QAbstractTableModel, QApplication, QColor, QDateTime, QFont, QFontMetrics, QIcon, + QImage, QModelIndex, QPainter, QPixmap, Qt, pyqtSignal, +) from calibre import ( fit_image, force_unicode, human_readable, isbytestring, prepare_string_for_xml, - strftime + strftime, ) from calibre.constants import DEBUG, config_dir, dark_link_color, filesystem_encoding from calibre.db.search import CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH, _match @@ -30,15 +34,14 @@ from calibre.library.coloring import color_row_key from calibre.library.save_to_disk import find_plugboard from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.config import device_prefs, prefs, tweaks -from calibre.utils.date import (UNDEFINED_DATE, as_local_time, dt_factory, - is_date_undefined, qt_to_dt) +from calibre.utils.date import ( + UNDEFINED_DATE, as_local_time, dt_factory, is_date_undefined, qt_to_dt, +) from calibre.utils.icu import sort_key from calibre.utils.localization import calibre_langcode_to_name +from calibre.utils.resources import get_path as P from calibre.utils.search_query_parser import ParseException, SearchQueryParser from polyglot.builtins import iteritems, itervalues, string_or_bytes -from qt.core import (QAbstractTableModel, QApplication, QColor, QDateTime, - QFont, QFontMetrics, QIcon, QImage, QModelIndex, QPainter, - QPixmap, Qt, pyqtSignal) Counts = namedtuple('Counts', 'library_total total current') diff --git a/src/calibre/gui2/lrf_renderer/document.py b/src/calibre/gui2/lrf_renderer/document.py index b2f062ff82..54a80a59ac 100644 --- a/src/calibre/gui2/lrf_renderer/document.py +++ b/src/calibre/gui2/lrf_renderer/document.py @@ -1,15 +1,17 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import collections, itertools, glob - +import collections +import glob +import itertools from qt.core import ( - Qt, QByteArray, pyqtSignal, QGraphicsRectItem, QGraphicsScene, QPen, - QBrush, QColor, QFontDatabase, QGraphicsItem, QGraphicsLineItem) + QBrush, QByteArray, QColor, QFontDatabase, QGraphicsItem, QGraphicsLineItem, + QGraphicsRectItem, QGraphicsScene, QPen, Qt, pyqtSignal, +) -from calibre.gui2.lrf_renderer.text import TextBlock, FontLoader, COLOR, PixmapItem -from calibre.ebooks.lrf.objects import RuledLine as _RuledLine -from calibre.ebooks.lrf.objects import Canvas as __Canvas +from calibre.ebooks.lrf.objects import Canvas as __Canvas, RuledLine as _RuledLine +from calibre.gui2.lrf_renderer.text import COLOR, FontLoader, PixmapItem, TextBlock +from calibre.utils.resources import get_path as P class Color(QColor): diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 71437c65fa..ae951cc5e8 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -7,39 +7,40 @@ __docformat__ = 'restructuredtext en' import json from collections import defaultdict -from threading import Thread from functools import partial - from qt.core import ( - QApplication, QFont, QFontInfo, QFontDialog, QColorDialog, QPainter, QDialog, - QAbstractListModel, Qt, QIcon, QKeySequence, QColor, pyqtSignal, QHeaderView, QListWidgetItem, - QWidget, QSizePolicy, QBrush, QPixmap, QSize, QPushButton, QVBoxLayout, QItemSelectionModel, - QTableWidget, QTableWidgetItem, QLabel, QFormLayout, QLineEdit, QComboBox, QDialogButtonBox + QAbstractListModel, QApplication, QBrush, QColor, QColorDialog, QComboBox, QDialog, + QDialogButtonBox, QFont, QFontDialog, QFontInfo, QFormLayout, QHeaderView, QIcon, + QItemSelectionModel, QKeySequence, QLabel, QLineEdit, QListWidgetItem, QPainter, + QPixmap, QPushButton, QSize, QSizePolicy, Qt, QTableWidget, QTableWidgetItem, + QVBoxLayout, QWidget, pyqtSignal, ) +from threading import Thread from calibre import human_readable from calibre.constants import ismacos, iswindows from calibre.db.categories import is_standard_category from calibre.ebooks.metadata.book.render import DEFAULT_AUTHOR_LINK from calibre.ebooks.metadata.sources.prefs import msprefs -from calibre.gui2.custom_column_widgets import get_field_list as em_get_field_list -from calibre.gui2 import default_author_link, icon_resource_manager, choose_save_file, choose_files -from calibre.gui2.dialogs.template_dialog import TemplateDialog -from calibre.gui2.preferences import ConfigWidgetBase, test_widget -from calibre.gui2.preferences.look_feel_ui import Ui_Form -from calibre.gui2 import config, gprefs, qt_app, open_local_file, question_dialog, error_dialog -from calibre.utils.localization import (available_translations, - get_language, get_lang) -from calibre.utils.config import prefs -from calibre.utils.icu import sort_key +from calibre.gui2 import ( + choose_files, choose_save_file, config, default_author_link, error_dialog, gprefs, + icon_resource_manager, open_local_file, qt_app, question_dialog, +) +from calibre.gui2.actions.show_quickview import get_quickview_action_plugin from calibre.gui2.book_details import get_field_list +from calibre.gui2.custom_column_widgets import get_field_list as em_get_field_list from calibre.gui2.dialogs.quickview import get_qv_field_list +from calibre.gui2.dialogs.template_dialog import TemplateDialog +from calibre.gui2.library.alternate_views import CM_TO_INCH, auto_height +from calibre.gui2.preferences import ConfigWidgetBase, test_widget from calibre.gui2.preferences.coloring import EditRules -from calibre.gui2.library.alternate_views import auto_height, CM_TO_INCH +from calibre.gui2.preferences.look_feel_ui import Ui_Form from calibre.gui2.widgets import BusyCursor from calibre.gui2.widgets2 import Dialog -from calibre.gui2.actions.show_quickview import get_quickview_action_plugin -from calibre.utils.resources import set_data +from calibre.utils.config import prefs +from calibre.utils.icu import sort_key +from calibre.utils.localization import available_translations, get_lang, get_language +from calibre.utils.resources import get_path as P, set_data from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/preferences/template_functions.py b/src/calibre/gui2/preferences/template_functions.py index e83504c992..a373b64b53 100644 --- a/src/calibre/gui2/preferences/template_functions.py +++ b/src/calibre/gui2/preferences/template_functions.py @@ -12,10 +12,11 @@ from calibre.gui2.preferences import AbortInitialize, ConfigWidgetBase, test_wid from calibre.gui2.preferences.template_functions_ui import Ui_Form from calibre.gui2.widgets import PythonHighlighter from calibre.utils.formatter_functions import ( - compile_user_function, compile_user_template_functions, formatter_functions, - function_object_type, function_pref_name, load_user_template_functions, - StoredObjectType + StoredObjectType, compile_user_function, compile_user_template_functions, + formatter_functions, function_object_type, function_pref_name, + load_user_template_functions, ) +from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/toc/location.py b/src/calibre/gui2/toc/location.py index fa825e6416..e67eec1527 100644 --- a/src/calibre/gui2/toc/location.py +++ b/src/calibre/gui2/toc/location.py @@ -10,12 +10,12 @@ from functools import lru_cache from qt.core import ( QApplication, QByteArray, QFrame, QGridLayout, QIcon, QLabel, QLineEdit, QListWidget, QPushButton, QSize, QSplitter, Qt, QUrl, QVBoxLayout, QWidget, - pyqtSignal + pyqtSignal, ) from qt.webengine import ( QWebEnginePage, QWebEngineProfile, QWebEngineScript, QWebEngineUrlRequestInterceptor, QWebEngineUrlRequestJob, - QWebEngineUrlSchemeHandler, QWebEngineView + QWebEngineUrlSchemeHandler, QWebEngineView, ) from calibre.constants import FAKE_HOST, FAKE_PROTOCOL @@ -23,6 +23,7 @@ from calibre.ebooks.oeb.polish.utils import guess_type from calibre.gui2 import error_dialog, gprefs, is_dark_theme, question_dialog from calibre.gui2.palette import dark_color, dark_link_color, dark_text_color from calibre.utils.logging import default_log +from calibre.utils.resources import get_path as P from calibre.utils.short_uuid import uuid4 from calibre.utils.webengine import secure_webengine, send_reply, setup_profile from polyglot.builtins import as_bytes diff --git a/src/calibre/gui2/tweak_book/editor/help.py b/src/calibre/gui2/tweak_book/editor/help.py index 2a35f4dc20..b1853aef21 100644 --- a/src/calibre/gui2/tweak_book/editor/help.py +++ b/src/calibre/gui2/tweak_book/editor/help.py @@ -12,6 +12,7 @@ from lxml import html from calibre import browser from calibre.ebooks.oeb.polish.container import OEB_DOCS from calibre.ebooks.oeb.polish.utils import guess_type +from calibre.utils.resources import get_path as P class URLMap: diff --git a/src/calibre/gui2/tweak_book/editor/syntax/javascript.py b/src/calibre/gui2/tweak_book/editor/syntax/javascript.py index a00d65c1dc..2eb9a7c712 100644 --- a/src/calibre/gui2/tweak_book/editor/syntax/javascript.py +++ b/src/calibre/gui2/tweak_book/editor/syntax/javascript.py @@ -4,13 +4,17 @@ __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' -import re - -from pygments.lexer import RegexLexer, default, include -from pygments.token import Comment, Punctuation, Number, Keyword, Text, String, Operator, Name import pygments.unistring as uni +import re +from pygments.lexer import RegexLexer, default, include +from pygments.token import ( + Comment, Keyword, Name, Number, Operator, Punctuation, String, Text, +) -from calibre.gui2.tweak_book.editor.syntax.pygments_highlighter import create_highlighter +from calibre.gui2.tweak_book.editor.syntax.pygments_highlighter import ( + create_highlighter, +) +from calibre.utils.resources import get_path as P from polyglot.builtins import native_string_type JS_IDENT_START = ('(?:[$_' + uni.combine('Lu', 'Ll', 'Lt', 'Lm', 'Lo', 'Nl') + diff --git a/src/calibre/gui2/tweak_book/function_replace.py b/src/calibre/gui2/tweak_book/function_replace.py index d6c6cf2778..f8a89f829a 100644 --- a/src/calibre/gui2/tweak_book/function_replace.py +++ b/src/calibre/gui2/tweak_book/function_replace.py @@ -4,22 +4,28 @@ __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' -import re, io, weakref, sys - +import io +import re +import sys +import weakref from qt.core import ( - pyqtSignal, QVBoxLayout, QHBoxLayout, QPlainTextEdit, QLabel, QFontMetrics, - QSize, Qt, QApplication, QIcon, QDialogButtonBox) + QApplication, QDialogButtonBox, QFontMetrics, QHBoxLayout, QIcon, QLabel, + QPlainTextEdit, QSize, Qt, QVBoxLayout, pyqtSignal, +) -from calibre.ebooks.oeb.polish.utils import apply_func_to_match_groups, apply_func_to_html_text +from calibre.ebooks.oeb.polish.utils import ( + apply_func_to_html_text, apply_func_to_match_groups, +) from calibre.gui2 import error_dialog from calibre.gui2.complete2 import EditWithComplete from calibre.gui2.tweak_book import dictionaries -from calibre.gui2.tweak_book.widgets import Dialog from calibre.gui2.tweak_book.editor.text import TextEdit +from calibre.gui2.tweak_book.widgets import Dialog from calibre.utils.config import JSONConfig -from calibre.utils.icu import capitalize, upper, lower, swapcase -from calibre.utils.titlecase import titlecase +from calibre.utils.icu import capitalize, lower, swapcase, upper from calibre.utils.localization import localize_user_manual_link +from calibre.utils.resources import get_path as P +from calibre.utils.titlecase import titlecase from polyglot.builtins import iteritems from polyglot.io import PolyglotStringIO diff --git a/src/calibre/gui2/tweak_book/preview.py b/src/calibre/gui2/tweak_book/preview.py index 8cd226ef5a..16c9b37cd4 100644 --- a/src/calibre/gui2/tweak_book/preview.py +++ b/src/calibre/gui2/tweak_book/preview.py @@ -9,25 +9,24 @@ from functools import partial from qt.core import ( QAction, QApplication, QByteArray, QHBoxLayout, QIcon, QLabel, QMenu, QSize, QSizePolicy, QStackedLayout, Qt, QTimer, QToolBar, QUrl, QVBoxLayout, QWidget, - pyqtSignal + pyqtSignal, ) from qt.webengine import ( - QWebEngineContextMenuRequest, QWebEnginePage, QWebEngineProfile, - QWebEngineScript, QWebEngineSettings, QWebEngineUrlRequestJob, - QWebEngineUrlSchemeHandler, QWebEngineView + QWebEngineContextMenuRequest, QWebEnginePage, QWebEngineProfile, QWebEngineScript, + QWebEngineSettings, QWebEngineUrlRequestJob, QWebEngineUrlSchemeHandler, + QWebEngineView, ) from threading import Thread from calibre import prints from calibre.constants import ( - FAKE_HOST, FAKE_PROTOCOL, __version__, is_running_from_develop, ismacos, - iswindows + FAKE_HOST, FAKE_PROTOCOL, __version__, is_running_from_develop, ismacos, iswindows, ) from calibre.ebooks.oeb.base import OEB_DOCS, XHTML_MIME, serialize from calibre.ebooks.oeb.polish.parsing import parse from calibre.gui2 import ( NO_URL_FORMATTING, QT_HIDDEN_CLEAR_ACTION, error_dialog, is_dark_theme, - safe_open_url + safe_open_url, ) from calibre.gui2.palette import dark_color, dark_link_color, dark_text_color from calibre.gui2.tweak_book import TOP, actions, current_container, editors, tprefs @@ -36,9 +35,10 @@ from calibre.gui2.viewer.web_view import handle_mathjax_request, send_reply from calibre.gui2.webengine import RestartingWebEngineView from calibre.gui2.widgets2 import HistoryLineEdit2 from calibre.utils.ipc.simple_worker import offload_worker +from calibre.utils.resources import get_path as P from calibre.utils.webengine import ( Bridge, create_script, from_js, insert_scripts, secure_webengine, setup_profile, - to_js + to_js, ) from polyglot.builtins import iteritems from polyglot.queue import Empty, Queue diff --git a/src/calibre/gui2/tweak_book/spell.py b/src/calibre/gui2/tweak_book/spell.py index d0238905ac..3ab38c25bf 100644 --- a/src/calibre/gui2/tweak_book/spell.py +++ b/src/calibre/gui2/tweak_book/spell.py @@ -10,27 +10,27 @@ from collections import OrderedDict, defaultdict from functools import partial from itertools import chain from qt.core import ( - QT_VERSION_STR, QAbstractTableModel, QApplication, QCheckBox, QComboBox, QDialog, - QDialogButtonBox, QFont, QFormLayout, QGridLayout, QHBoxLayout, QIcon, - QInputDialog, QKeySequence, QLabel, QLineEdit, QListWidget, QListWidgetItem, + QT_VERSION_STR, QAbstractItemView, QAbstractTableModel, QApplication, QCheckBox, + QComboBox, QDialog, QDialogButtonBox, QFont, QFormLayout, QGridLayout, QHBoxLayout, + QIcon, QInputDialog, QKeySequence, QLabel, QLineEdit, QListWidget, QListWidgetItem, QMenu, QModelIndex, QPlainTextEdit, QPushButton, QSize, QStackedLayout, Qt, - QTableView, QTimer, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, - QWidget, pyqtSignal, QAbstractItemView + QTableView, QTimer, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, + pyqtSignal, ) from threading import Thread from calibre.constants import __appname__ -from calibre.ebooks.oeb.base import OEB_DOCS, NCX_MIME, OPF_MIME +from calibre.ebooks.oeb.base import NCX_MIME, OEB_DOCS, OPF_MIME from calibre.ebooks.oeb.polish.spell import ( get_all_words, get_checkable_file_names, merge_locations, replace_word, - undo_replace_word + undo_replace_word, ) from calibre.gui2 import choose_files, error_dialog from calibre.gui2.complete2 import LineEdit from calibre.gui2.languages import LanguagesEdit from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.tweak_book import ( - current_container, dictionaries, editors, set_book_locale, tprefs + current_container, dictionaries, editors, set_book_locale, tprefs, ) from calibre.gui2.tweak_book.widgets import Dialog from calibre.gui2.widgets2 import FlowLayout @@ -38,13 +38,14 @@ from calibre.spell import DictionaryLocale from calibre.spell.break_iterator import split_into_words from calibre.spell.dictionary import ( best_locale_for_language, builtin_dictionaries, custom_dictionaries, dprefs, - get_dictionary, remove_dictionary, rename_dictionary + get_dictionary, remove_dictionary, rename_dictionary, ) from calibre.spell.import_from import import_from_oxt from calibre.utils.icu import contains, primary_contains, primary_sort_key, sort_key from calibre.utils.localization import ( - calibre_langcode_to_name, canonicalize_lang, get_lang, get_language + calibre_langcode_to_name, canonicalize_lang, get_lang, get_language, ) +from calibre.utils.resources import get_path as P from calibre_extensions.progress_indicator import set_no_activate_on_click from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index a9f98a0aee..c2caf99f7d 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -20,19 +20,19 @@ from collections import OrderedDict, deque from io import BytesIO from qt.core import ( QAction, QApplication, QDialog, QFont, QIcon, QMenu, QSystemTrayIcon, Qt, QTimer, - QUrl, pyqtSignal + QUrl, pyqtSignal, ) from calibre import detect_ncpus, force_unicode, prints from calibre.constants import ( - DEBUG, __appname__, config_dir, filesystem_encoding, ismacos, iswindows + DEBUG, __appname__, config_dir, filesystem_encoding, ismacos, iswindows, ) from calibre.customize import PluginInstallationType from calibre.customize.ui import available_store_plugins, interface_actions from calibre.db.legacy import LibraryDatabase from calibre.gui2 import ( Dispatcher, GetMetadata, config, error_dialog, gprefs, info_dialog, - max_available_height, open_url, question_dialog, warning_dialog + max_available_height, open_url, question_dialog, warning_dialog, ) from calibre.gui2.auto_add import AutoAdder from calibre.gui2.changes import handle_changes @@ -59,6 +59,7 @@ from calibre.library import current_library_name from calibre.srv.library_broker import GuiLibraryBroker, db_matches from calibre.utils.config import dynamic, prefs from calibre.utils.ipc.pool import Pool +from calibre.utils.resources import get_path as P from polyglot.builtins import string_or_bytes from polyglot.queue import Empty, Queue @@ -625,7 +626,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.tags_view.recount() def handle_cli_args(self, args): - from urllib.parse import unquote, urlparse, parse_qs + from urllib.parse import parse_qs, unquote, urlparse if isinstance(args, string_or_bytes): args = [args] files, urls = [], [] @@ -870,7 +871,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ ) if repair: from calibre.gui2.dialogs.restore_library import ( - repair_library_at + repair_library_at, ) if repair_library_at(newloc, parent=self): db = LibraryDatabase(newloc, default_prefs=default_prefs) diff --git a/src/calibre/gui2/viewer/lookup.py b/src/calibre/gui2/viewer/lookup.py index 51d4f7ee33..f1e74c1584 100644 --- a/src/calibre/gui2/viewer/lookup.py +++ b/src/calibre/gui2/viewer/lookup.py @@ -8,10 +8,10 @@ from functools import lru_cache from qt.core import ( QAbstractItemView, QApplication, QCheckBox, QComboBox, QDialog, QDialogButtonBox, QFormLayout, QHBoxLayout, QIcon, QLabel, QLineEdit, QListWidget, QListWidgetItem, - QPalette, QPushButton, QSize, Qt, QTimer, QUrl, QVBoxLayout, QWidget, pyqtSignal + QPalette, QPushButton, QSize, Qt, QTimer, QUrl, QVBoxLayout, QWidget, pyqtSignal, ) from qt.webengine import ( - QWebEnginePage, QWebEngineProfile, QWebEngineScript, QWebEngineView + QWebEnginePage, QWebEngineProfile, QWebEngineScript, QWebEngineView, ) from calibre import prints, random_user_agent @@ -19,8 +19,9 @@ from calibre.gui2 import error_dialog from calibre.gui2.viewer.web_view import apply_font_settings, vprefs from calibre.gui2.widgets2 import Dialog from calibre.utils.localization import canonicalize_lang, get_lang, lang_as_iso639_1 +from calibre.utils.resources import get_path as P from calibre.utils.webengine import ( - create_script, insert_scripts, secure_webengine, setup_profile + create_script, insert_scripts, secure_webengine, setup_profile, ) diff --git a/src/calibre/gui2/viewer/web_view.py b/src/calibre/gui2/viewer/web_view.py index a75a3db420..40470c3ec0 100644 --- a/src/calibre/gui2/viewer/web_view.py +++ b/src/calibre/gui2/viewer/web_view.py @@ -7,19 +7,18 @@ import shutil import sys from itertools import count from qt.core import ( - QT_VERSION, QApplication, QByteArray, QEvent, QFontDatabase, QFontInfo, - QHBoxLayout, QLocale, QMimeData, QPalette, QSize, Qt, QTimer, QUrl, QWidget, - pyqtSignal, sip + QT_VERSION, QApplication, QByteArray, QEvent, QFontDatabase, QFontInfo, QHBoxLayout, + QLocale, QMimeData, QPalette, QSize, Qt, QTimer, QUrl, QWidget, pyqtSignal, sip, ) from qt.webengine import ( QWebEnginePage, QWebEngineProfile, QWebEngineScript, QWebEngineSettings, - QWebEngineUrlRequestJob, QWebEngineUrlSchemeHandler, QWebEngineView + QWebEngineUrlRequestJob, QWebEngineUrlSchemeHandler, QWebEngineView, ) from calibre import as_unicode, prints from calibre.constants import ( FAKE_HOST, FAKE_PROTOCOL, __version__, in_develop_mode, is_running_from_develop, - ismacos, iswindows + ismacos, iswindows, ) from calibre.ebooks.metadata.book.base import field_metadata from calibre.ebooks.oeb.polish.utils import guess_type @@ -30,11 +29,12 @@ from calibre.gui2.viewer.tts import TTS from calibre.gui2.webengine import RestartingWebEngineView from calibre.srv.code import get_translations_data from calibre.utils.localization import localize_user_manual_link +from calibre.utils.resources import get_path as P from calibre.utils.serialize import json_loads from calibre.utils.shared_file import share_open from calibre.utils.webengine import ( Bridge, create_script, from_js, insert_scripts, secure_webengine, send_reply, - to_js, setup_profile + setup_profile, to_js, ) from polyglot.builtins import as_bytes, iteritems from polyglot.functools import lru_cache diff --git a/src/calibre/library/catalogs/epub_mobi_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py index 42fa7f40dd..ebfc2594e4 100644 --- a/src/calibre/library/catalogs/epub_mobi_builder.py +++ b/src/calibre/library/catalogs/epub_mobi_builder.py @@ -11,16 +11,14 @@ import time import unicodedata import zlib from copy import deepcopy +from lxml import etree from xml.sax.saxutils import escape -from lxml import etree - from calibre import ( - as_unicode, force_unicode, isbytestring, prepare_string_for_xml, - replace_entities, strftime, xml_replace_entities + as_unicode, force_unicode, isbytestring, prepare_string_for_xml, replace_entities, + strftime, xml_replace_entities, ) from calibre.constants import cache_dir, ismacos -from calibre.utils.xml_parse import safe_xml_fromstring from calibre.customize.conversion import DummyReporter from calibre.customize.ui import output_profiles from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, prettify @@ -29,17 +27,19 @@ from calibre.ebooks.metadata import author_to_author_sort from calibre.ebooks.oeb.polish.pretty import pretty_opf, pretty_xml_tree from calibre.library.catalogs import ( AuthorSortMismatchException, EmptyCatalogException, - InvalidGenresSourceFieldException + InvalidGenresSourceFieldException, ) from calibre.library.comments import comments_to_html from calibre.ptempfile import PersistentTemporaryDirectory from calibre.utils.date import ( - as_local_time, format_date, is_date_undefined, now as nowf + as_local_time, format_date, is_date_undefined, now as nowf, ) from calibre.utils.filenames import ascii_text, shorten_components_to from calibre.utils.formatter import TemplateFormatter from calibre.utils.icu import capitalize, collation_order, sort_key from calibre.utils.localization import get_lang, lang_as_iso639_1 +from calibre.utils.resources import get_path as P +from calibre.utils.xml_parse import safe_xml_fromstring from calibre.utils.zipfile import ZipFile from polyglot.builtins import iteritems @@ -1096,8 +1096,8 @@ class CatalogBuilder: bookmarked_books (dict): dict of Bookmarks """ - from calibre.devices.usbms.device import Device from calibre.devices.kindle.bookmark import Bookmark + from calibre.devices.usbms.device import Device from calibre.ebooks.metadata import MetaInformation MBP_FORMATS = ['azw', 'mobi', 'prc', 'txt'] diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 5b186aa0ea..268258a21a 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -6,47 +6,64 @@ __docformat__ = 'restructuredtext en' The database used to store ebook metadata ''' -import os, sys, shutil, glob, time, functools, traceback, re, \ - json, uuid, hashlib, copy, numbers +import copy +import functools +import glob +import hashlib +import json +import numbers +import os +import random +import re +import shutil +import sys +import threading +import time +import traceback +import uuid from collections import defaultdict, namedtuple -import threading, random -from calibre import prints, force_unicode -from calibre.ebooks.metadata import (title_sort, author_to_author_sort, - string_to_authors, get_title_sort_pat) -from calibre.ebooks.metadata.opf2 import metadata_to_opf -from calibre.library.database import LibraryDatabase -from calibre.library.field_metadata import FieldMetadata -from calibre.library.schema_upgrades import SchemaUpgrade -from calibre.library.caches import ResultCache -from calibre.library.custom_columns import CustomColumns -from calibre.library.sqlite import connect, IntegrityError -from calibre.library.prefs import DBPrefs -from calibre.ebooks.metadata.book.base import Metadata -from calibre.constants import preferred_encoding, iswindows, filesystem_encoding -from calibre.ptempfile import (PersistentTemporaryFile, - base_dir, SpooledTemporaryFile) -from calibre.customize.ui import (run_plugins_on_import, - run_plugins_on_postimport) -from calibre import isbytestring -from calibre.utils.filenames import (ascii_filename, samefile, - WindowsAtomicFolderMove, hardlink_file) -from calibre.utils.date import (utcnow, now as nowf, utcfromtimestamp, - parse_only_date, UNDEFINED_DATE, parse_date) -from calibre.utils.config import prefs, tweaks, from_json, to_json -from calibre.utils.icu import sort_key, strcmp, lower -from calibre.utils.search_query_parser import saved_searches, set_saved_searches -from calibre.ebooks import check_ebook_format -from calibre.utils.img import save_cover_data_to -from calibre.utils.recycle_bin import delete_file, delete_tree -from calibre.utils.formatter_functions import load_user_template_functions -from calibre.db import _get_next_series_num_for_list, _get_series_values, get_data_as_dict -from calibre.db.adding import find_books_in_directory, import_book_directory_multiple, import_book_directory, recursive_import +from calibre import force_unicode, isbytestring, prints +from calibre.constants import filesystem_encoding, iswindows, preferred_encoding +from calibre.customize.ui import run_plugins_on_import, run_plugins_on_postimport +from calibre.db import ( + _get_next_series_num_for_list, _get_series_values, get_data_as_dict, +) +from calibre.db.adding import ( + find_books_in_directory, import_book_directory, import_book_directory_multiple, + recursive_import, +) +from calibre.db.categories import CATEGORY_SORTS, Tag from calibre.db.errors import NoSuchFormat from calibre.db.lazy import FormatMetadata, FormatsList -from calibre.db.categories import Tag, CATEGORY_SORTS -from calibre.utils.localization import (canonicalize_lang, - calibre_langcode_to_name) +from calibre.ebooks import check_ebook_format +from calibre.ebooks.metadata import ( + author_to_author_sort, get_title_sort_pat, string_to_authors, title_sort, +) +from calibre.ebooks.metadata.book.base import Metadata +from calibre.ebooks.metadata.opf2 import metadata_to_opf +from calibre.library.caches import ResultCache +from calibre.library.custom_columns import CustomColumns +from calibre.library.database import LibraryDatabase +from calibre.library.field_metadata import FieldMetadata +from calibre.library.prefs import DBPrefs +from calibre.library.schema_upgrades import SchemaUpgrade +from calibre.library.sqlite import IntegrityError, connect +from calibre.ptempfile import PersistentTemporaryFile, SpooledTemporaryFile, base_dir +from calibre.utils.config import from_json, prefs, to_json, tweaks +from calibre.utils.date import ( + UNDEFINED_DATE, now as nowf, parse_date, parse_only_date, utcfromtimestamp, utcnow, +) +from calibre.utils.filenames import ( + WindowsAtomicFolderMove, ascii_filename, hardlink_file, samefile, +) +from calibre.utils.formatter_functions import load_user_template_functions +from calibre.utils.icu import lower, sort_key, strcmp +from calibre.utils.img import save_cover_data_to +from calibre.utils.localization import calibre_langcode_to_name, canonicalize_lang +from calibre.utils.recycle_bin import delete_file, delete_tree +from calibre.utils.resources import get_path as P +from calibre.utils.search_query_parser import saved_searches, set_saved_searches from polyglot.builtins import iteritems, string_or_bytes copyfile = os.link if hasattr(os, 'link') else shutil.copyfile diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 0843c0ea08..c979b39fe2 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -3,18 +3,21 @@ ''' Post installation script for linux ''' -import sys, os, textwrap, stat, errno -from subprocess import check_call, check_output +import errno +import os +import stat +import sys +import textwrap from functools import partial +from subprocess import check_call, check_output -from calibre import __appname__, prints, guess_type -from calibre.constants import islinux, isbsd +from calibre import CurrentDir, __appname__, guess_type, prints +from calibre.constants import isbsd, islinux from calibre.customize.ui import all_input_formats from calibre.ptempfile import TemporaryDirectory -from calibre import CurrentDir +from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems - entry_points = { 'console_scripts': [ 'ebook-device = calibre.devices.cli:main', @@ -322,11 +325,11 @@ class ZshCompleter: # {{{ self.commands[name] = txt def do_ebook_convert(self, f): - from calibre.ebooks.conversion.plumber import supported_input_formats - from calibre.web.feeds.recipes.collection import get_builtin_recipe_titles from calibre.customize.ui import available_output_formats from calibre.ebooks.conversion.cli import create_option_parser, group_titles + from calibre.ebooks.conversion.plumber import supported_input_formats from calibre.utils.logging import DevNull + from calibre.web.feeds.recipes.collection import get_builtin_recipe_titles input_fmts = set(supported_input_formats()) output_fmts = set(available_output_formats()) iexts = {x.upper() for x in input_fmts}.union(input_fmts) @@ -417,8 +420,8 @@ class ZshCompleter: # {{{ w('\n}\n') def do_ebook_edit(self, f): - from calibre.ebooks.oeb.polish.main import SUPPORTED from calibre.ebooks.oeb.polish.import_book import IMPORTABLE + from calibre.ebooks.oeb.polish.main import SUPPORTED from calibre.gui2.tweak_book.main import option_parser tweakable_fmts = SUPPORTED | IMPORTABLE parser = option_parser() @@ -468,8 +471,8 @@ _ebook_edit() {{ '''.format(opt_lines, '|'.join(tweakable_fmts)) + '\n\n') def do_calibredb(self, f): - from calibre.db.cli.main import COMMANDS, option_parser_for from calibre.customize.ui import available_catalog_formats + from calibre.db.cli.main import COMMANDS, option_parser_for parsers, descs = {}, {} for command in COMMANDS: p = option_parser_for(command)() @@ -568,20 +571,22 @@ def get_bash_completion_path(root, share, info): def write_completion(self, bash_comp_dest, zsh): - from calibre.ebooks.metadata.cli import option_parser as metaop, filetypes as meta_filetypes - from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop - from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop - from calibre.gui2.viewer.main import option_parser as viewer_op - from calibre.gui2.tweak_book.main import option_parser as tweak_op - from calibre.ebooks.metadata.sources.cli import option_parser as fem_op - from calibre.gui2.main import option_parser as guiop - from calibre.utils.smtp import option_parser as smtp_op - from calibre.srv.standalone import create_option_parser as serv_op - from calibre.ebooks.oeb.polish.main import option_parser as polish_op, SUPPORTED - from calibre.ebooks.oeb.polish.import_book import IMPORTABLE + from calibre.customize.ui import available_input_formats from calibre.debug import option_parser as debug_op from calibre.ebooks import BOOK_EXTENSIONS - from calibre.customize.ui import available_input_formats + from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop + from calibre.ebooks.metadata.cli import ( + filetypes as meta_filetypes, option_parser as metaop, + ) + from calibre.ebooks.metadata.sources.cli import option_parser as fem_op + from calibre.ebooks.oeb.polish.import_book import IMPORTABLE + from calibre.ebooks.oeb.polish.main import SUPPORTED, option_parser as polish_op + from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop + from calibre.gui2.main import option_parser as guiop + from calibre.gui2.tweak_book.main import option_parser as tweak_op + from calibre.gui2.viewer.main import option_parser as viewer_op + from calibre.srv.standalone import create_option_parser as serv_op + from calibre.utils.smtp import option_parser as smtp_op input_formats = sorted(all_input_formats()) tweak_formats = sorted(x.lower() for x in SUPPORTED|IMPORTABLE) @@ -917,8 +922,8 @@ class PostInstall: line += extra + ';' f.write(line.encode('utf-8') + b'\n') - from calibre.ebooks.oeb.polish.main import SUPPORTED from calibre.ebooks.oeb.polish.import_book import IMPORTABLE + from calibre.ebooks.oeb.polish.main import SUPPORTED with open('calibre-lrfviewer.desktop', 'wb') as f: f.write(VIEWER.encode('utf-8')) with open('calibre-ebook-viewer.desktop', 'wb') as f: @@ -1208,8 +1213,8 @@ def changelog_bullet_to_text(bullet): def make_appdata_releases(): - from lxml.builder import E import json + from lxml.builder import E changelog = json.loads(P('changelog.json', data=True)) releases = E.releases() @@ -1247,8 +1252,8 @@ def make_appdata_releases(): def write_appdata(key, entry, base, translators): - from lxml.etree import tostring from lxml.builder import E + from lxml.etree import tostring fpath = os.path.join(base, '%s.metainfo.xml' % key) screenshots = E.screenshots() for w, h, url in entry['screenshots']: diff --git a/src/calibre/scraper/simple.py b/src/calibre/scraper/simple.py index bb34526425..72cfbcf068 100644 --- a/src/calibre/scraper/simple.py +++ b/src/calibre/scraper/simple.py @@ -13,6 +13,7 @@ from calibre.constants import iswindows from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.filenames import retry_on_fail from calibre.utils.ipc.simple_worker import start_pipe_worker +from calibre.utils.resources import get_path as P def worker_main(source): diff --git a/src/calibre/scraper/simple_backend.py b/src/calibre/scraper/simple_backend.py index 025f6571b9..5e1da83d10 100644 --- a/src/calibre/scraper/simple_backend.py +++ b/src/calibre/scraper/simple_backend.py @@ -10,6 +10,7 @@ from functools import lru_cache from qt.core import QApplication, QEventLoop, QUrl from qt.webengine import QWebEnginePage, QWebEngineProfile, QWebEngineSettings +from calibre.utils.resources import get_path as P from calibre.utils.webengine import create_script, insert_scripts, setup_profile diff --git a/src/calibre/spell/__init__.py b/src/calibre/spell/__init__.py index c63ad4e799..253dcbda4d 100644 --- a/src/calibre/spell/__init__.py +++ b/src/calibre/spell/__init__.py @@ -7,6 +7,7 @@ __copyright__ = '2014, Kovid Goyal ' from collections import namedtuple from calibre.utils.localization import canonicalize_lang +from calibre.utils.resources import get_path as P DictionaryLocale = namedtuple('DictionaryLocale', 'langcode countrycode') diff --git a/src/calibre/spell/dictionary.py b/src/calibre/spell/dictionary.py index 3aae5a9d81..89042bcf63 100644 --- a/src/calibre/spell/dictionary.py +++ b/src/calibre/spell/dictionary.py @@ -17,6 +17,7 @@ from calibre.spell import parse_lang_code from calibre.utils.config import JSONConfig from calibre.utils.icu import capitalize from calibre.utils.localization import get_lang, get_system_locale +from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems, itervalues Dictionary = namedtuple('Dictionary', 'primary_locale locales dicpath affpath builtin name id') diff --git a/src/calibre/spell/import_from.py b/src/calibre/spell/import_from.py index 90d3fb10fe..5fa7b36f17 100644 --- a/src/calibre/spell/import_from.py +++ b/src/calibre/spell/import_from.py @@ -11,6 +11,7 @@ from lxml import etree from calibre.constants import config_dir from calibre.utils.xml_parse import safe_xml_fromstring from calibre.utils.zipfile import ZipFile +from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems NS_MAP = { @@ -19,8 +20,11 @@ NS_MAP = { 'manifest': 'http://openoffice.org/2001/manifest', } + def XPath(x): return etree.XPath(x, namespaces=NS_MAP) + + BUILTIN_LOCALES = {'en-US', 'en-GB', 'es-ES'} diff --git a/src/calibre/srv/books.py b/src/calibre/srv/books.py index 463016281b..3af369b0d8 100644 --- a/src/calibre/srv/books.py +++ b/src/calibre/srv/books.py @@ -21,6 +21,7 @@ from calibre.srv.render_book import RENDER_VERSION from calibre.srv.routes import endpoint, json from calibre.srv.utils import get_db, get_library_data from calibre.utils.filenames import rmtree +from calibre.utils.resources import get_path as P from calibre.utils.serialize import json_dumps from polyglot.builtins import as_unicode, itervalues diff --git a/src/calibre/srv/code.py b/src/calibre/srv/code.py index 73e1d3c754..4fa3a4d2ab 100644 --- a/src/calibre/srv/code.py +++ b/src/calibre/srv/code.py @@ -29,6 +29,7 @@ from calibre.utils.icu import numeric_sort_key, sort_key from calibre.utils.localization import ( get_lang, lang_code_for_user_manual, lang_map_for_ui, localize_website_link, ) +from calibre.utils.resources import get_path as P from calibre.utils.search_query_parser import ParseException from calibre.utils.serialize import json_dumps from polyglot.builtins import iteritems, itervalues diff --git a/src/calibre/srv/content.py b/src/calibre/srv/content.py index 57729f10fc..17fe89b863 100644 --- a/src/calibre/srv/content.py +++ b/src/calibre/srv/content.py @@ -4,30 +4,34 @@ __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' -import os, errno -from io import BytesIO -from threading import Lock +import errno +import os from contextlib import suppress from functools import partial +from io import BytesIO +from threading import Lock from calibre import fit_image, sanitize_file_name from calibre.constants import config_dir, iswindows from calibre.db.errors import NoSuchFormat -from calibre.ebooks.covers import cprefs, override_prefs, scale_cover, generate_cover, set_use_roman +from calibre.ebooks.covers import ( + cprefs, generate_cover, override_prefs, scale_cover, set_use_roman, +) from calibre.ebooks.metadata import authors_to_string from calibre.ebooks.metadata.meta import set_metadata from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.library.save_to_disk import find_plugboard -from calibre.srv.errors import HTTPNotFound, BookNotFound +from calibre.srv.errors import BookNotFound, HTTPNotFound from calibre.srv.routes import endpoint, json -from calibre.srv.utils import http_date, get_db, get_use_roman +from calibre.srv.utils import get_db, get_use_roman, http_date from calibre.utils.config_base import tweaks from calibre.utils.date import timestampfromdt -from calibre.utils.img import scale_image, image_from_data from calibre.utils.filenames import ascii_filename, atomic_rename +from calibre.utils.img import image_from_data, scale_image +from calibre.utils.resources import get_path as P from calibre.utils.shared_file import share_open -from polyglot.urllib import quote from polyglot.binary import as_hex_unicode +from polyglot.urllib import quote plugboard_content_server_value = 'content_server' plugboard_content_server_formats = ['epub', 'mobi', 'azw3'] diff --git a/src/calibre/srv/tests/base.py b/src/calibre/srv/tests/base.py index dd3efcf1b8..5ab2ded116 100644 --- a/src/calibre/srv/tests/base.py +++ b/src/calibre/srv/tests/base.py @@ -5,12 +5,19 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import unittest, time, shutil, gc, tempfile, atexit, os -from io import BytesIO +import atexit +import gc +import os +import shutil +import tempfile +import time +import unittest from functools import partial +from io import BytesIO from threading import Thread from calibre.srv.utils import ServerLog +from calibre.utils.resources import get_path as P from polyglot import http_client rmtree = partial(shutil.rmtree, ignore_errors=True) @@ -70,8 +77,8 @@ class LibraryBaseTest(BaseTest): return ans def create_db(self, library_path): - from calibre.db.legacy import create_backend from calibre.db.cache import Cache + from calibre.db.legacy import create_backend d = os.path.dirname src = os.path.join(d(d(d(os.path.abspath(__file__)))), 'db', 'tests', 'metadata.db') dest = os.path.join(library_path, 'metadata.db') @@ -98,9 +105,9 @@ class TestServer(Thread): def __init__(self, handler, plugins=(), **kwargs): Thread.__init__(self, name='ServerMain') - from calibre.srv.opts import Options - from calibre.srv.loop import ServerLoop from calibre.srv.http_response import create_http_handler + from calibre.srv.loop import ServerLoop + from calibre.srv.opts import Options self.setup_defaults(kwargs) self.loop = ServerLoop( create_http_handler(handler), @@ -155,10 +162,10 @@ class LibraryServer(TestServer): def __init__(self, library_path, libraries=(), plugins=(), **kwargs): Thread.__init__(self, name='ServerMain') - from calibre.srv.opts import Options - from calibre.srv.loop import ServerLoop from calibre.srv.handler import Handler from calibre.srv.http_response import create_http_handler + from calibre.srv.loop import ServerLoop + from calibre.srv.opts import Options self.setup_defaults(kwargs) opts = Options(**kwargs) self.libraries = libraries or (library_path,) diff --git a/src/calibre/srv/tests/content.py b/src/calibre/srv/tests/content.py index ac5aa1a3a0..aa76b94ebf 100644 --- a/src/calibre/srv/tests/content.py +++ b/src/calibre/srv/tests/content.py @@ -4,13 +4,17 @@ __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' -import zlib, json, time, os +import json +import os +import time +import zlib from io import BytesIO from calibre.ebooks.metadata.epub import get_metadata from calibre.ebooks.metadata.opf2 import OPF from calibre.srv.tests.base import LibraryBaseTest from calibre.utils.imghdr import identify +from calibre.utils.resources import get_path as P from calibre.utils.shared_file import share_open from polyglot import http_client from polyglot.binary import from_hex_unicode @@ -226,8 +230,8 @@ class ContentTest(LibraryBaseTest): # }}} def test_char_count(self): # {{{ - from calibre.srv.render_book import get_length from calibre.ebooks.oeb.parse_utils import html5_parse + from calibre.srv.render_book import get_length root = html5_parse('

a b\nc\td\re') self.ae(get_length(root), 5) @@ -238,8 +242,8 @@ class ContentTest(LibraryBaseTest): # }}} def test_html_as_json(self): # {{{ - from calibre.srv.render_book import html_as_json from calibre.ebooks.oeb.parse_utils import html5_parse + from calibre.srv.render_book import html_as_json def t(html, body_children, nsmap=('http://www.w3.org/1999/xhtml',)): root = html5_parse(html) diff --git a/src/calibre/srv/tests/http.py b/src/calibre/srv/tests/http.py index 2970cfa31a..5f217d5806 100644 --- a/src/calibre/srv/tests/http.py +++ b/src/calibre/srv/tests/http.py @@ -4,7 +4,11 @@ __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' -import hashlib, zlib, string, time, os +import hashlib +import os +import string +import time +import zlib from io import BytesIO from tempfile import NamedTemporaryFile @@ -12,8 +16,9 @@ from calibre import guess_type from calibre.srv.tests.base import BaseTest, TestServer from calibre.srv.utils import eintr_retry_call from calibre.utils.monotonic import monotonic -from polyglot.builtins import iteritems +from calibre.utils.resources import get_path as P from polyglot import http_client +from polyglot.builtins import iteritems is_ci = os.environ.get('CI', '').lower() == 'true' diff --git a/src/calibre/test_build.py b/src/calibre/test_build.py index 5b3bd63f12..cad191b616 100644 --- a/src/calibre/test_build.py +++ b/src/calibre/test_build.py @@ -18,6 +18,7 @@ import time import unittest from calibre.constants import islinux, ismacos, iswindows, plugins_loc +from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems is_ci = os.environ.get('CI', '').lower() == 'true' @@ -304,7 +305,8 @@ class BuildTest(unittest.TestCase): if is_sanitized: raise unittest.SkipTest('Skipping Qt build test as sanitizer is enabled') from qt.core import ( - QApplication, QFontDatabase, QImageReader, QNetworkAccessManager, QTimer, QSslSocket + QApplication, QFontDatabase, QImageReader, QNetworkAccessManager, + QSslSocket, QTimer, ) from qt.webengine import QWebEnginePage diff --git a/src/calibre/utils/config_base.py b/src/calibre/utils/config_base.py index c89fecbfc6..2caeaea00a 100644 --- a/src/calibre/utils/config_base.py +++ b/src/calibre/utils/config_base.py @@ -15,8 +15,9 @@ from functools import partial from calibre.constants import ( CONFIG_DIR_MODE, config_dir, filesystem_encoding, get_umask, iswindows, - preferred_encoding + preferred_encoding, ) +from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems plugin_dir = os.path.join(config_dir, 'plugins') diff --git a/src/calibre/utils/fonts/scanner.py b/src/calibre/utils/fonts/scanner.py index ca24757f40..d3b2983308 100644 --- a/src/calibre/utils/fonts/scanner.py +++ b/src/calibre/utils/fonts/scanner.py @@ -9,11 +9,13 @@ import os from collections import defaultdict from threading import Thread -from calibre import walk, prints, as_unicode -from calibre.constants import (config_dir, iswindows, ismacos, DEBUG, - isworker, filesystem_encoding) +from calibre import as_unicode, prints, walk +from calibre.constants import ( + DEBUG, config_dir, filesystem_encoding, ismacos, iswindows, isworker, +) from calibre.utils.fonts.metadata import FontMetadata, UnsupportedFont from calibre.utils.icu import sort_key +from calibre.utils.resources import get_path as P from polyglot.builtins import itervalues @@ -267,8 +269,9 @@ class FontScanner(Thread): :return: (family name, faces) or None, None ''' - from calibre.utils.fonts.utils import (supports_text, - panose_to_css_generic_family, get_printable_characters) + from calibre.utils.fonts.utils import ( + get_printable_characters, panose_to_css_generic_family, supports_text, + ) if not isinstance(text, str): raise TypeError('%r is not unicode'%text) text = get_printable_characters(text) diff --git a/src/calibre/utils/fonts/sfnt/container.py b/src/calibre/utils/fonts/sfnt/container.py index da7a341277..93cf47e08a 100644 --- a/src/calibre/utils/fonts/sfnt/container.py +++ b/src/calibre/utils/fonts/sfnt/container.py @@ -13,12 +13,13 @@ from calibre.utils.fonts.sfnt.errors import UnsupportedFont from calibre.utils.fonts.sfnt.glyf import GlyfTable from calibre.utils.fonts.sfnt.gsub import GSUBTable from calibre.utils.fonts.sfnt.head import ( - HeadTable, HorizontalHeader, OS2Table, PostTable, VerticalHeader + HeadTable, HorizontalHeader, OS2Table, PostTable, VerticalHeader, ) from calibre.utils.fonts.sfnt.kern import KernTable from calibre.utils.fonts.sfnt.loca import LocaTable from calibre.utils.fonts.sfnt.maxp import MaxpTable from calibre.utils.fonts.utils import checksum_of_block, get_tables, verify_checksums +from calibre.utils.resources import get_path as P # OpenType spec: http://www.microsoft.com/typography/otspec/otff.htm @@ -105,7 +106,7 @@ class Sfnt: return ans def get_all_font_names(self): - from calibre.utils.fonts.metadata import get_font_names2, FontNames + from calibre.utils.fonts.metadata import FontNames, get_font_names2 name_table = self.get(b'name') if name_table is not None: return FontNames(*get_font_names2(name_table.raw, raw_is_table=True)) diff --git a/src/calibre/utils/fonts/sfnt/subset.py b/src/calibre/utils/fonts/sfnt/subset.py index c90118b2c8..2377467ba5 100644 --- a/src/calibre/utils/fonts/sfnt/subset.py +++ b/src/calibre/utils/fonts/sfnt/subset.py @@ -7,12 +7,13 @@ __docformat__ = 'restructuredtext en' import traceback from collections import OrderedDict -from operator import itemgetter from functools import partial +from operator import itemgetter -from calibre.utils.icu import safe_chr, ord_string from calibre.utils.fonts.sfnt.container import Sfnt -from calibre.utils.fonts.sfnt.errors import UnsupportedFont, NoGlyphs +from calibre.utils.fonts.sfnt.errors import NoGlyphs, UnsupportedFont +from calibre.utils.icu import ord_string, safe_chr +from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems, itervalues # TrueType outlines {{{ @@ -197,6 +198,7 @@ def subset(raw, individual_chars, ranges=(), warnings=None): def option_parser(): import textwrap + from calibre.utils.config import OptionParser parser = OptionParser(usage=textwrap.dedent('''\ %prog [options] input_font_file output_font_file characters_to_keep @@ -239,7 +241,9 @@ def print_stats(old_stats, new_stats): def main(args): - import sys, time + import sys + import time + from calibre import prints parser = option_parser() opts, args = parser.parse_args(args) @@ -308,8 +312,9 @@ if __name__ == '__main__': def test_mem(): - from calibre.utils.mem import memory import gc + + from calibre.utils.mem import memory gc.collect() start_mem = memory() raw = P('fonts/liberation/LiberationSerif-Regular.ttf', data=True) diff --git a/src/calibre/utils/fonts/utils.py b/src/calibre/utils/fonts/utils.py index 3c589c8479..9450a17699 100644 --- a/src/calibre/utils/fonts/utils.py +++ b/src/calibre/utils/fonts/utils.py @@ -6,10 +6,11 @@ __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' import struct -from io import BytesIO from collections import defaultdict +from io import BytesIO -from polyglot.builtins import iteritems, itervalues, as_bytes +from calibre.utils.resources import get_path as P +from polyglot.builtins import as_bytes, iteritems, itervalues class UnsupportedFont(ValueError): @@ -484,7 +485,8 @@ def test(): def main(): - import sys, os + import os + import sys for arg in sys.argv[1:]: print(os.path.basename(arg)) with open(arg, 'rb') as f: diff --git a/src/calibre/utils/fonts/win_fonts.py b/src/calibre/utils/fonts/win_fonts.py index f3c981cf4a..3411a73ce2 100644 --- a/src/calibre/utils/fonts/win_fonts.py +++ b/src/calibre/utils/fonts/win_fonts.py @@ -9,6 +9,7 @@ import os, sys, atexit from itertools import product from calibre import prints, isbytestring +from calibre.utils.resources import get_path as P from calibre.constants import filesystem_encoding from calibre.utils.fonts.utils import (is_truetype_font, get_font_names, get_font_characteristics) diff --git a/src/calibre/utils/https.py b/src/calibre/utils/https.py index 94a8648989..84395de07f 100644 --- a/src/calibre/utils/https.py +++ b/src/calibre/utils/https.py @@ -8,6 +8,7 @@ import ssl, socket, re from contextlib import closing from calibre import get_proxies +from calibre.utils.resources import get_path as P from polyglot import http_client from polyglot.urllib import urlsplit has_ssl_verify = hasattr(ssl, 'create_default_context') and hasattr(ssl, '_create_unverified_context') diff --git a/src/calibre/utils/hyphenation/dictionaries.py b/src/calibre/utils/hyphenation/dictionaries.py index 381c80a7a3..1d562f27ee 100644 --- a/src/calibre/utils/hyphenation/dictionaries.py +++ b/src/calibre/utils/hyphenation/dictionaries.py @@ -10,6 +10,7 @@ from io import BytesIO from calibre.constants import cache_dir from calibre.ptempfile import TemporaryDirectory +from calibre.utils.resources import get_path as P from calibre.utils.localization import lang_as_iso639_1 from calibre.utils.lock import ExclusiveFile from polyglot.builtins import iteritems diff --git a/src/calibre/utils/random_ua.py b/src/calibre/utils/random_ua.py index be487947d6..4e69bc622b 100644 --- a/src/calibre/utils/random_ua.py +++ b/src/calibre/utils/random_ua.py @@ -4,6 +4,7 @@ import json import random +from calibre.utils.resources import get_path as P def user_agent_data(): diff --git a/src/calibre/utils/rapydscript.py b/src/calibre/utils/rapydscript.py index d228363058..01fdce65a9 100644 --- a/src/calibre/utils/rapydscript.py +++ b/src/calibre/utils/rapydscript.py @@ -17,6 +17,7 @@ from calibre.constants import ( ) from calibre.ptempfile import TemporaryDirectory from calibre.utils.filenames import atomic_rename +from calibre.utils.resources import get_path as P from polyglot.builtins import as_bytes, as_unicode, exec_path COMPILER_PATH = 'rapydscript/compiler.js.xz' diff --git a/src/calibre/utils/search_query_parser.py b/src/calibre/utils/search_query_parser.py index 149943cf41..c72615595f 100644 --- a/src/calibre/utils/search_query_parser.py +++ b/src/calibre/utils/search_query_parser.py @@ -20,7 +20,7 @@ If this module is run, it will perform a series of unit tests. import weakref, re from calibre.constants import preferred_encoding -from calibre.utils.icu import sort_key +from calibre.utils.icu import sort_key, lower as icu_lower from calibre.utils.localization import _ from calibre import prints from polyglot.binary import as_hex_unicode, from_hex_unicode diff --git a/src/calibre/utils/titlecase.py b/src/calibre/utils/titlecase.py index 61857a6329..f5d5163e04 100644 --- a/src/calibre/utils/titlecase.py +++ b/src/calibre/utils/titlecase.py @@ -10,7 +10,7 @@ License: http://www.opensource.org/licenses/mit-license.php import re -from calibre.utils.icu import capitalize, upper +from calibre.utils.icu import capitalize, lower as icu_lower, upper as icu_upper __all__ = ['titlecase'] __version__ = '0.5' @@ -52,7 +52,7 @@ def titlecase(text): """ - all_caps = upper(text) == text + all_caps = icu_upper(text) == text pat = re.compile(r'(\s+)') line = [] diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 19ed7e04fc..52a6555f7a 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -28,7 +28,7 @@ from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.date import now as nowf from calibre.utils.icu import numeric_sort_key from calibre.utils.img import add_borders_to_image, image_to_data, save_cover_data_to -from calibre.utils.localization import _, canonicalize_lang +from calibre.utils.localization import _, canonicalize_lang, ngettext from calibre.utils.logging import ThreadSafeWrapper from calibre.utils.threadpool import NoResultsPending, ThreadPool, WorkRequest from calibre.web import Recipe From 45244e2a3f304668bbb0eee016728c4e1dbf45d8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 9 Jan 2023 20:53:27 +0530 Subject: [PATCH 0097/2055] Get rid of global I() --- src/calibre/db/tests/base.py | 10 ++++- src/calibre/devices/android/driver.py | 1 + src/calibre/devices/nook/driver.py | 4 +- src/calibre/ebooks/covers.py | 24 +++++++----- src/calibre/ebooks/docx/writer/images.py | 6 +-- src/calibre/ebooks/oeb/polish/tests/base.py | 2 +- src/calibre/gui2/__init__.py | 2 +- src/calibre/gui2/email.py | 7 ++-- src/calibre/gui2/icon_theme.py | 2 +- src/calibre/gui2/main.py | 15 +++++--- src/calibre/gui2/metadata/single_download.py | 38 ++++++++++--------- src/calibre/gui2/notify.py | 1 + src/calibre/gui2/open_with.py | 21 +++++----- .../gui2/preferences/texture_chooser.py | 10 +++-- src/calibre/gui2/splash_screen.py | 5 ++- src/calibre/gui2/ui.py | 2 +- .../library/catalogs/epub_mobi_builder.py | 3 +- src/calibre/linux.py | 2 +- src/calibre/srv/content.py | 2 +- src/calibre/srv/tests/base.py | 2 +- src/calibre/srv/tests/content.py | 2 +- src/calibre/test_build.py | 2 +- src/calibre/utils/img.py | 7 ++-- 23 files changed, 98 insertions(+), 72 deletions(-) diff --git a/src/calibre/db/tests/base.py b/src/calibre/db/tests/base.py index 6a9d6eca3f..3ccdf5b643 100644 --- a/src/calibre/db/tests/base.py +++ b/src/calibre/db/tests/base.py @@ -5,10 +5,18 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import unittest, os, shutil, tempfile, atexit, gc, time +import atexit +import gc +import os +import shutil +import tempfile +import time +import unittest from functools import partial from io import BytesIO +from calibre.utils.resources import get_image_path as I + rmtree = partial(shutil.rmtree, ignore_errors=True) IMG = b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00\xff\xe1\x00\x16Exif\x00\x00II*\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xdb\x00C\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xff\xdb\x00C\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\xff\xc0\x00\x11\x08\x00\x01\x00\x01\x03\x01"\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\n\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\xbf\x80\x01\xff\xd9' # noqa {{{ }}} diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 1f965f2581..e43005344f 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -7,6 +7,7 @@ import os from calibre import fsync from calibre.devices.usbms.driver import USBMS +from calibre.utils.resources import get_image_path as I from polyglot.builtins import string_or_bytes HTC_BCDS = [0x100, 0x0222, 0x0224, 0x0226, 0x227, 0x228, 0x229, 0x0231, 0x9999] diff --git a/src/calibre/devices/nook/driver.py b/src/calibre/devices/nook/driver.py index ad43a2a153..fe50e03580 100644 --- a/src/calibre/devices/nook/driver.py +++ b/src/calibre/devices/nook/driver.py @@ -6,11 +6,13 @@ __docformat__ = 'restructuredtext en' Device driver for Barns and Nobel's Nook ''' -import io, os +import io +import os from calibre import fsync, prints from calibre.constants import DEBUG from calibre.devices.usbms.driver import USBMS +from calibre.utils.resources import get_image_path as I class NOOK(USBMS): diff --git a/src/calibre/ebooks/covers.py b/src/calibre/ebooks/covers.py index af9ff00054..9f99489373 100644 --- a/src/calibre/ebooks/covers.py +++ b/src/calibre/ebooks/covers.py @@ -4,27 +4,30 @@ __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' -import re, random, unicodedata, numbers +import numbers +import random +import re +import unicodedata from collections import namedtuple from contextlib import contextmanager -from math import ceil, sqrt, cos, sin, atan2 -from polyglot.builtins import iteritems, itervalues, string_or_bytes from itertools import chain - +from math import atan2, ceil, cos, sin, sqrt from qt.core import ( - QImage, Qt, QFont, QPainter, QPointF, QTextLayout, QTextOption, - QFontMetrics, QTextCharFormat, QColor, QRect, QBrush, QLinearGradient, - QPainterPath, QPen, QRectF, QTransform, QRadialGradient + QBrush, QColor, QFont, QFontMetrics, QImage, QLinearGradient, QPainter, + QPainterPath, QPen, QPointF, QRadialGradient, QRect, QRectF, Qt, QTextCharFormat, + QTextLayout, QTextOption, QTransform, ) -from calibre import force_unicode, fit_image +from calibre import fit_image, force_unicode from calibre.constants import __appname__, __version__ from calibre.ebooks.metadata import fmt_sidx from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.formatter import SafeFormat -from calibre.gui2 import ensure_app, config, load_builtin_fonts, pixmap_to_data +from calibre.gui2 import config, ensure_app, load_builtin_fonts, pixmap_to_data from calibre.utils.cleantext import clean_ascii_chars, clean_xml_chars from calibre.utils.config import JSONConfig +from calibre.utils.resources import get_image_path as I +from polyglot.builtins import iteritems, itervalues, string_or_bytes # Default settings {{{ cprefs = JSONConfig('cover_generation') @@ -726,7 +729,8 @@ def generate_masthead(title, output_path=None, width=600, height=60, as_qimage=F def test(scale=0.25): - from qt.core import QLabel, QPixmap, QMainWindow, QWidget, QScrollArea, QGridLayout + from qt.core import QGridLayout, QLabel, QMainWindow, QPixmap, QScrollArea, QWidget + from calibre.gui2 import Application app = Application([]) mi = Metadata('Unknown', ['Kovid Goyal', 'John & Doe', 'Author']) diff --git a/src/calibre/ebooks/docx/writer/images.py b/src/calibre/ebooks/docx/writer/images.py index e05dc1e6ea..18bdf6142a 100644 --- a/src/calibre/ebooks/docx/writer/images.py +++ b/src/calibre/ebooks/docx/writer/images.py @@ -8,15 +8,15 @@ import os import posixpath from collections import namedtuple from functools import partial -from polyglot.builtins import iteritems, itervalues - from lxml import etree from calibre import fit_image -from calibre.ebooks.oeb.base import urlunquote, urlquote from calibre.ebooks.docx.images import pt_to_emu +from calibre.ebooks.oeb.base import urlquote, urlunquote from calibre.utils.filenames import ascii_filename from calibre.utils.imghdr import identify +from calibre.utils.resources import get_image_path as I +from polyglot.builtins import iteritems, itervalues Image = namedtuple('Image', 'rid fname width height fmt item') diff --git a/src/calibre/ebooks/oeb/polish/tests/base.py b/src/calibre/ebooks/oeb/polish/tests/base.py index fb68c038ac..db8396076b 100644 --- a/src/calibre/ebooks/oeb/polish/tests/base.py +++ b/src/calibre/ebooks/oeb/polish/tests/base.py @@ -12,7 +12,7 @@ import calibre.ebooks.oeb.polish.container as pc from calibre import CurrentDir from calibre.ptempfile import PersistentTemporaryDirectory, TemporaryDirectory from calibre.utils.logging import DevNull -from calibre.utils.resources import get_path as P +from calibre.utils.resources import get_image_path as I, get_path as P from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index bdf28807d8..1e397c42ff 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -41,7 +41,7 @@ from calibre.utils.date import UNDEFINED_DATE from calibre.utils.file_type_icons import EXT_MAP from calibre.utils.img import set_image_allocation_limit from calibre.utils.localization import get_lang -from calibre.utils.resources import get_path as P, user_dir +from calibre.utils.resources import get_image_path as I, get_path as P, user_dir from polyglot import queue from polyglot.builtins import iteritems, string_or_bytes diff --git a/src/calibre/gui2/email.py b/src/calibre/gui2/email.py index a2cd817268..77e36bcb74 100644 --- a/src/calibre/gui2/email.py +++ b/src/calibre/gui2/email.py @@ -14,7 +14,7 @@ from functools import partial from itertools import repeat from qt.core import ( QDialog, QDialogButtonBox, QGridLayout, QIcon, QLabel, QLineEdit, QListWidget, - QListWidgetItem, QPushButton, Qt + QListWidgetItem, QPushButton, Qt, ) from threading import Thread @@ -26,8 +26,9 @@ from calibre.gui2.threaded_jobs import ThreadedJob from calibre.library.save_to_disk import get_components from calibre.utils.config import prefs, tweaks from calibre.utils.icu import primary_sort_key +from calibre.utils.resources import get_image_path as I from calibre.utils.smtp import ( - compose_mail, config as email_config, extract_email_address, sendmail + compose_mail, config as email_config, extract_email_address, sendmail, ) from polyglot.binary import from_hex_unicode from polyglot.builtins import iteritems, itervalues @@ -439,8 +440,8 @@ class EmailMixin: # {{{ _('in the %s format.') % os.path.splitext(f)[1][1:].upper()) if mi.comments and gprefs['add_comments_to_email']: - from calibre.utils.html2text import html2text from calibre.ebooks.metadata import fmt_sidx + from calibre.utils.html2text import html2text if mi.series: sidx=fmt_sidx(1.0 if mi.series_index is None else mi.series_index, use_roman=config['use_roman_numerals_for_series_number']) texts[-1] += '\n\n' + _('{series_index} of {series}').format(series_index=sidx, series=mi.series) diff --git a/src/calibre/gui2/icon_theme.py b/src/calibre/gui2/icon_theme.py index 0268ccb2ed..e33b583354 100644 --- a/src/calibre/gui2/icon_theme.py +++ b/src/calibre/gui2/icon_theme.py @@ -42,7 +42,7 @@ from calibre.utils.filenames import ascii_filename, atomic_rename from calibre.utils.https import HTTPError, get_https_resource_securely from calibre.utils.icu import numeric_sort_key as sort_key from calibre.utils.img import Canvas, image_from_data, optimize_jpeg, optimize_png -from calibre.utils.resources import get_path as P +from calibre.utils.resources import get_image_path as I, get_path as P from calibre.utils.zipfile import ZIP_STORED, ZipFile from polyglot import http_client from polyglot.builtins import as_bytes, iteritems, reraise diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 7e573023a5..495fe0d2fe 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -2,23 +2,22 @@ # License: GPLv3 Copyright: 2015, Kovid Goyal +import apsw import os import re import sys import time import traceback - -import apsw from qt.core import QCoreApplication, QIcon, QObject, QTimer from calibre import force_unicode, prints from calibre.constants import ( - DEBUG, MAIN_APP_UID, __appname__, filesystem_encoding, get_portable_base, - islinux, ismacos, iswindows + DEBUG, MAIN_APP_UID, __appname__, filesystem_encoding, get_portable_base, islinux, + ismacos, iswindows, ) from calibre.gui2 import ( Application, choose_dir, error_dialog, gprefs, initialize_file_icon_provider, - question_dialog, setup_gui_option_parser + question_dialog, setup_gui_option_parser, ) from calibre.gui2.listener import send_message_in_process from calibre.gui2.main_window import option_parser as _option_parser @@ -26,6 +25,7 @@ from calibre.gui2.splash_screen import SplashScreen from calibre.utils.config import dynamic, prefs from calibre.utils.lock import SingleInstance from calibre.utils.monotonic import monotonic +from calibre.utils.resources import get_image_path as I from polyglot.builtins import as_bytes, environ_item after_quit_actions = {'debug_on_restart': False, 'restart_after_quit': False, 'no_plugins_on_restart': False} @@ -197,6 +197,7 @@ def repair_library(library_path): def windows_repair(library_path=None): import subprocess + from calibre.utils.serialize import json_dumps, json_loads from polyglot.binary import as_hex_unicode, from_hex_bytes if library_path: @@ -380,8 +381,10 @@ class GuiRunner(QObject): def run_in_debug_mode(): + import subprocess + import tempfile + from calibre.debug import run_calibre_debug - import tempfile, subprocess fd, logpath = tempfile.mkstemp('.txt') os.close(fd) run_calibre_debug( diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index e31f42e62b..ee98e224cc 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -8,36 +8,38 @@ __docformat__ = 'restructuredtext en' DEBUG_DIALOG = False # Imports {{{ -import os, time -from threading import Thread, Event -from operator import attrgetter +import os +import time from io import BytesIO - +from operator import attrgetter from qt.core import ( - QStyledItemDelegate, QTextDocument, QRectF, QIcon, Qt, QApplication, - QDialog, QVBoxLayout, QLabel, QDialogButtonBox, QStyle, QStackedWidget, - QWidget, QTableView, QGridLayout, QPalette, QTimer, pyqtSignal, - QAbstractTableModel, QSize, QListView, QPixmap, QModelIndex, QSplitter, - QAbstractListModel, QRect, QTextBrowser, QStringListModel, QMenu, QItemSelectionModel, - QCursor, QHBoxLayout, QPushButton, QSizePolicy, QAbstractItemView) + QAbstractItemView, QAbstractListModel, QAbstractTableModel, QApplication, QCursor, + QDialog, QDialogButtonBox, QGridLayout, QHBoxLayout, QIcon, QItemSelectionModel, + QLabel, QListView, QMenu, QModelIndex, QPalette, QPixmap, QPushButton, QRect, + QRectF, QSize, QSizePolicy, QSplitter, QStackedWidget, QStringListModel, QStyle, + QStyledItemDelegate, Qt, QTableView, QTextBrowser, QTextDocument, QTimer, + QVBoxLayout, QWidget, pyqtSignal, +) +from threading import Event, Thread +from calibre import force_unicode from calibre.customize.ui import metadata_plugins from calibre.ebooks.metadata import authors_to_string, rating_to_stars -from calibre.utils.logging import GUILog as Log -from calibre.ebooks.metadata.sources.identify import urls_from_identifiers from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.opf2 import OPF -from calibre.gui2 import error_dialog, rating_font, gprefs +from calibre.ebooks.metadata.sources.identify import urls_from_identifiers +from calibre.gui2 import error_dialog, gprefs, rating_font from calibre.gui2.progress_indicator import SpinAnimator from calibre.gui2.widgets2 import HTMLDisplay -from calibre.utils.date import (utcnow, fromordinal, format_date, - UNDEFINED_DATE, as_utc) from calibre.library.comments import comments_to_html -from calibre import force_unicode -from calibre.utils.ipc.simple_worker import fork_job, WorkerError from calibre.ptempfile import TemporaryDirectory +from calibre.utils.date import UNDEFINED_DATE, as_utc, format_date, fromordinal, utcnow +from calibre.utils.ipc.simple_worker import WorkerError, fork_job +from calibre.utils.logging import GUILog as Log +from calibre.utils.resources import get_image_path as I from polyglot.builtins import iteritems, itervalues -from polyglot.queue import Queue, Empty +from polyglot.queue import Empty, Queue + # }}} diff --git a/src/calibre/gui2/notify.py b/src/calibre/gui2/notify.py index e8610e3699..f2fff217de 100644 --- a/src/calibre/gui2/notify.py +++ b/src/calibre/gui2/notify.py @@ -11,6 +11,7 @@ from contextlib import suppress from functools import lru_cache from calibre.constants import DEBUG, __appname__, get_osx_version, islinux, ismacos +from calibre.utils.resources import get_image_path as I class Notifier: diff --git a/src/calibre/gui2/open_with.py b/src/calibre/gui2/open_with.py index bcd96ad1f5..971da327cf 100644 --- a/src/calibre/gui2/open_with.py +++ b/src/calibre/gui2/open_with.py @@ -9,22 +9,23 @@ import uuid from contextlib import suppress from functools import partial from qt.core import ( - QAction, QBuffer, QByteArray, QIcon, QInputDialog, QKeySequence, QLabel, - QListWidget, QListWidgetItem, QPixmap, QSize, QStackedLayout, Qt, QVBoxLayout, - QWidget, pyqtSignal, QIODevice, QDialogButtonBox + QAction, QBuffer, QByteArray, QDialogButtonBox, QIcon, QInputDialog, QIODevice, + QKeySequence, QLabel, QListWidget, QListWidgetItem, QPixmap, QSize, QStackedLayout, + Qt, QVBoxLayout, QWidget, pyqtSignal, ) from threading import Thread from calibre import as_unicode from calibre.constants import ismacos, iswindows from calibre.gui2 import ( - Application, choose_files, choose_images, choose_osx_app, elided_text, - error_dialog, sanitize_env_vars + Application, choose_files, choose_images, choose_osx_app, elided_text, error_dialog, + sanitize_env_vars, ) from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.widgets2 import Dialog from calibre.utils.config import JSONConfig from calibre.utils.icu import numeric_sort_key as sort_key +from calibre.utils.resources import get_image_path as I from polyglot.builtins import iteritems, string_or_bytes ENTRY_ROLE = Qt.ItemDataRole.UserRole @@ -80,11 +81,9 @@ if iswindows: import subprocess from calibre.utils.open_with.windows import ( - load_icon_for_cmdline, load_icon_resource - ) - from calibre.utils.winreg.default_programs import ( - find_programs, friendly_app_name + load_icon_for_cmdline, load_icon_resource, ) + from calibre.utils.winreg.default_programs import find_programs, friendly_app_name from calibre_extensions import winutil oprefs = JSONConfig('windows_open_with') @@ -176,7 +175,7 @@ elif ismacos: # macOS {{{ oprefs = JSONConfig('osx_open_with') from calibre.utils.open_with.osx import ( - entry_to_cmdline, find_programs, get_bundle_data, get_icon + entry_to_cmdline, find_programs, get_bundle_data, get_icon, ) def entry_sort_key(entry): @@ -226,7 +225,7 @@ else: # XDG {{{ oprefs = JSONConfig('xdg_open_with') from calibre.utils.open_with.linux import ( - entry_sort_key, entry_to_cmdline, find_programs + entry_sort_key, entry_to_cmdline, find_programs, ) def change_name_in_entry(entry, newname): diff --git a/src/calibre/gui2/preferences/texture_chooser.py b/src/calibre/gui2/preferences/texture_chooser.py index 4415deb2ad..08ac8ab07d 100644 --- a/src/calibre/gui2/preferences/texture_chooser.py +++ b/src/calibre/gui2/preferences/texture_chooser.py @@ -4,15 +4,19 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' -import glob, os, shutil +import glob +import os +import shutil from functools import partial from qt.core import ( - QDialog, QVBoxLayout, QListWidget, QListWidgetItem, Qt, QIcon, - QApplication, QSize, QDialogButtonBox, QTimer, QLabel, QAbstractItemView, QListView) + QAbstractItemView, QApplication, QDialog, QDialogButtonBox, QIcon, QLabel, + QListView, QListWidget, QListWidgetItem, QSize, Qt, QTimer, QVBoxLayout, +) from calibre.constants import config_dir from calibre.gui2 import choose_files, error_dialog from calibre.utils.icu import sort_key +from calibre.utils.resources import get_image_path as I def texture_dir(): diff --git a/src/calibre/gui2/splash_screen.py b/src/calibre/gui2/splash_screen.py index 5554820439..165f25873b 100644 --- a/src/calibre/gui2/splash_screen.py +++ b/src/calibre/gui2/splash_screen.py @@ -3,12 +3,13 @@ from qt.core import ( - QApplication, QBrush, QColor, QFont, QFontMetrics, QPen, QPixmap, QSplashScreen, - Qt, QPainter + QApplication, QBrush, QColor, QFont, QFontMetrics, QPainter, QPen, QPixmap, + QSplashScreen, Qt, ) from calibre.constants import __appname__, numeric_version from calibre.utils.monotonic import monotonic +from calibre.utils.resources import get_image_path as I class SplashScreen(QSplashScreen): diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index c2caf99f7d..c545397146 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -59,7 +59,7 @@ from calibre.library import current_library_name from calibre.srv.library_broker import GuiLibraryBroker, db_matches from calibre.utils.config import dynamic, prefs from calibre.utils.ipc.pool import Pool -from calibre.utils.resources import get_path as P +from calibre.utils.resources import get_image_path as I, get_path as P from polyglot.builtins import string_or_bytes from polyglot.queue import Empty, Queue diff --git a/src/calibre/library/catalogs/epub_mobi_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py index ebfc2594e4..cabe14deab 100644 --- a/src/calibre/library/catalogs/epub_mobi_builder.py +++ b/src/calibre/library/catalogs/epub_mobi_builder.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2010, Greg Riker - import datetime import os import platform @@ -38,7 +37,7 @@ from calibre.utils.filenames import ascii_text, shorten_components_to from calibre.utils.formatter import TemplateFormatter from calibre.utils.icu import capitalize, collation_order, sort_key from calibre.utils.localization import get_lang, lang_as_iso639_1 -from calibre.utils.resources import get_path as P +from calibre.utils.resources import get_image_path as I, get_path as P from calibre.utils.xml_parse import safe_xml_fromstring from calibre.utils.zipfile import ZipFile from polyglot.builtins import iteritems diff --git a/src/calibre/linux.py b/src/calibre/linux.py index c979b39fe2..f81de08db5 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -15,7 +15,7 @@ from calibre import CurrentDir, __appname__, guess_type, prints from calibre.constants import isbsd, islinux from calibre.customize.ui import all_input_formats from calibre.ptempfile import TemporaryDirectory -from calibre.utils.resources import get_path as P +from calibre.utils.resources import get_image_path as I, get_path as P from polyglot.builtins import iteritems entry_points = { diff --git a/src/calibre/srv/content.py b/src/calibre/srv/content.py index 17fe89b863..91274eeb33 100644 --- a/src/calibre/srv/content.py +++ b/src/calibre/srv/content.py @@ -28,7 +28,7 @@ from calibre.utils.config_base import tweaks from calibre.utils.date import timestampfromdt from calibre.utils.filenames import ascii_filename, atomic_rename from calibre.utils.img import image_from_data, scale_image -from calibre.utils.resources import get_path as P +from calibre.utils.resources import get_image_path as I, get_path as P from calibre.utils.shared_file import share_open from polyglot.binary import as_hex_unicode from polyglot.urllib import quote diff --git a/src/calibre/srv/tests/base.py b/src/calibre/srv/tests/base.py index 5ab2ded116..cf2ce9cd04 100644 --- a/src/calibre/srv/tests/base.py +++ b/src/calibre/srv/tests/base.py @@ -17,7 +17,7 @@ from io import BytesIO from threading import Thread from calibre.srv.utils import ServerLog -from calibre.utils.resources import get_path as P +from calibre.utils.resources import get_image_path as I, get_path as P from polyglot import http_client rmtree = partial(shutil.rmtree, ignore_errors=True) diff --git a/src/calibre/srv/tests/content.py b/src/calibre/srv/tests/content.py index aa76b94ebf..da8c183bff 100644 --- a/src/calibre/srv/tests/content.py +++ b/src/calibre/srv/tests/content.py @@ -14,7 +14,7 @@ from calibre.ebooks.metadata.epub import get_metadata from calibre.ebooks.metadata.opf2 import OPF from calibre.srv.tests.base import LibraryBaseTest from calibre.utils.imghdr import identify -from calibre.utils.resources import get_path as P +from calibre.utils.resources import get_image_path as I, get_path as P from calibre.utils.shared_file import share_open from polyglot import http_client from polyglot.binary import from_hex_unicode diff --git a/src/calibre/test_build.py b/src/calibre/test_build.py index cad191b616..ad1e223347 100644 --- a/src/calibre/test_build.py +++ b/src/calibre/test_build.py @@ -18,7 +18,7 @@ import time import unittest from calibre.constants import islinux, ismacos, iswindows, plugins_loc -from calibre.utils.resources import get_path as P +from calibre.utils.resources import get_image_path as I, get_path as P from polyglot.builtins import iteritems is_ci = os.environ.get('CI', '').lower() == 'true' diff --git a/src/calibre/utils/img.py b/src/calibre/utils/img.py index 3952f4bbb7..e07cdac79e 100644 --- a/src/calibre/utils/img.py +++ b/src/calibre/utils/img.py @@ -11,8 +11,8 @@ import tempfile from contextlib import suppress from io import BytesIO from qt.core import ( - QBuffer, QByteArray, QColor, QImage, QImageReader, QImageWriter, QIODevice, - QPixmap, Qt, QTransform + QBuffer, QByteArray, QColor, QImage, QImageReader, QImageWriter, QIODevice, QPixmap, + Qt, QTransform, ) from threading import Thread @@ -22,6 +22,7 @@ from calibre.ptempfile import TemporaryDirectory from calibre.utils.config_base import tweaks from calibre.utils.filenames import atomic_rename from calibre.utils.imghdr import what +from calibre.utils.resources import get_image_path as I from calibre_extensions import imageops from polyglot.builtins import string_or_bytes @@ -110,7 +111,7 @@ def gif_data_to_png_data(data, discard_animation=False): def set_image_allocation_limit(size_in_mb=1024): with suppress(ImportError): # for people running form source from calibre_extensions.progress_indicator import ( - set_image_allocation_limit as impl + set_image_allocation_limit as impl, ) impl(size_in_mb) From 779e69a38cf4bbe1c32037dc9e84628f761bab42 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 9 Jan 2023 21:38:45 +0530 Subject: [PATCH 0098/2055] Remove use of global icu_upper/lower --- src/calibre/db/adding.py | 3 ++ src/calibre/db/backend.py | 2 +- src/calibre/db/cache.py | 6 +-- src/calibre/db/categories.py | 8 ++-- src/calibre/db/legacy.py | 19 ++++++--- src/calibre/db/search.py | 20 ++++++---- src/calibre/db/tables.py | 7 ++-- src/calibre/db/utils.py | 15 ++++--- src/calibre/db/write.py | 9 +++-- src/calibre/devices/mtp/books.py | 2 +- src/calibre/devices/mtp/driver.py | 21 ++++++---- src/calibre/ebooks/metadata/author_mapper.py | 4 +- src/calibre/ebooks/metadata/book/base.py | 16 +++++--- src/calibre/ebooks/metadata/opf2.py | 40 ++++++++++++------- src/calibre/ebooks/metadata/sources/amazon.py | 26 +++++++----- src/calibre/ebooks/metadata/tag_mapper.py | 2 + src/calibre/ebooks/metadata/worker.py | 1 + src/calibre/ebooks/oeb/polish/css.py | 14 +++---- src/calibre/ebooks/oeb/polish/embed.py | 4 +- src/calibre/ebooks/oeb/polish/stats.py | 9 +++-- .../ebooks/oeb/polish/tests/cascade.py | 14 ++++--- src/calibre/ebooks/oeb/polish/utils.py | 2 + .../ebooks/oeb/transforms/manglecase.py | 6 +-- src/calibre/gui2/actions/similar_books.py | 1 + src/calibre/gui2/add.py | 1 + src/calibre/gui2/author_mapper.py | 2 +- src/calibre/gui2/custom_column_widgets.py | 28 +++++++------ .../gui2/device_drivers/mtp_folder_browser.py | 8 ++-- src/calibre/gui2/dialogs/authors_edit.py | 11 ++--- .../gui2/dialogs/edit_authors_dialog.py | 15 ++++--- src/calibre/gui2/dialogs/metadata_bulk.py | 14 ++++--- src/calibre/gui2/dialogs/plugin_updater.py | 11 ++--- .../gui2/dialogs/saved_search_editor.py | 6 +-- src/calibre/gui2/dialogs/tag_categories.py | 7 ++-- src/calibre/gui2/dialogs/tag_list_editor.py | 18 +++++---- src/calibre/gui2/dialogs/template_dialog.py | 2 +- src/calibre/gui2/font_family_chooser.py | 9 +++-- src/calibre/gui2/metadata/diff.py | 4 +- src/calibre/gui2/preferences/search.py | 11 ++--- src/calibre/gui2/store/search/models.py | 15 +++---- src/calibre/gui2/tag_browser/model.py | 11 ++--- src/calibre/gui2/tag_mapper.py | 6 +-- src/calibre/gui2/toc/main.py | 17 ++++---- src/calibre/gui2/tweak_book/manage_fonts.py | 17 ++++---- src/calibre/library/caches.py | 28 +++++++------ .../library/catalogs/epub_mobi_builder.py | 2 +- src/calibre/library/database2.py | 2 +- src/calibre/library/field_metadata.py | 1 + src/calibre/srv/metadata.py | 14 +++---- src/calibre/utils/fonts/scanner.py | 2 +- src/calibre/utils/formatter_functions.py | 2 +- src/calibre/utils/icu.py | 2 +- src/calibre/utils/matcher.py | 20 ++++++---- 53 files changed, 317 insertions(+), 220 deletions(-) diff --git a/src/calibre/db/adding.py b/src/calibre/db/adding.py index 58452b32a0..b78e6fdadb 100644 --- a/src/calibre/db/adding.py +++ b/src/calibre/db/adding.py @@ -15,6 +15,7 @@ from calibre import prints from calibre.constants import filesystem_encoding, ismacos, iswindows from calibre.ebooks import BOOK_EXTENSIONS from calibre.utils.filenames import make_long_path_useable +from calibre.utils.icu import lower as icu_lower from polyglot.builtins import itervalues @@ -48,10 +49,12 @@ def compile_rule(rule): return icu_lower(filename).endswith(q) elif 'glob' in mt: q = compile_glob(rule['query']) + def func(filename): return (q.match(filename) is not None) else: q = re.compile(rule['query']) + def func(filename): return (q.match(filename) is not None) ans = func diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index f8d2b7ab09..614abdbcdc 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -46,7 +46,7 @@ from calibre.utils.formatter_functions import ( compile_user_template_functions, formatter_functions, load_user_template_functions, unload_user_template_functions, ) -from calibre.utils.icu import sort_key +from calibre.utils.icu import lower as icu_lower, sort_key from calibre.utils.resources import get_path as P from polyglot.builtins import ( cmp, iteritems, itervalues, native_string_type, reraise, string_or_bytes, diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index c7508026bc..cab398e9fa 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -24,7 +24,7 @@ from time import monotonic, sleep, time from calibre import as_unicode, detect_ncpus, isbytestring from calibre.constants import iswindows, preferred_encoding from calibre.customize.ui import ( - run_plugins_on_import, run_plugins_on_postadd, run_plugins_on_postimport + run_plugins_on_import, run_plugins_on_postadd, run_plugins_on_postimport, ) from calibre.db import SPOOL_SIZE, _get_next_series_num_for_list from calibre.db.annotations import merge_annotations @@ -34,7 +34,7 @@ from calibre.db.fields import IDENTITY, InvalidLinkTable, create_field from calibre.db.lazy import FormatMetadata, FormatsList, ProxyMetadata from calibre.db.listeners import EventDispatcher, EventType from calibre.db.locking import ( - DowngradeLockError, LockingError, SafeReadLock, create_locks, try_lock + DowngradeLockError, LockingError, SafeReadLock, create_locks, try_lock, ) from calibre.db.search import Search from calibre.db.tables import VirtualTable @@ -47,7 +47,7 @@ from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ptempfile import PersistentTemporaryFile, SpooledTemporaryFile, base_dir from calibre.utils.config import prefs, tweaks from calibre.utils.date import UNDEFINED_DATE, now as nowf, utcnow -from calibre.utils.icu import sort_key +from calibre.utils.icu import lower as icu_lower, sort_key from calibre.utils.localization import canonicalize_lang from polyglot.builtins import cmp, iteritems, itervalues, string_or_bytes diff --git a/src/calibre/db/categories.py b/src/calibre/db/categories.py index 07b7c50cd5..f0c8636348 100644 --- a/src/calibre/db/categories.py +++ b/src/calibre/db/categories.py @@ -8,11 +8,13 @@ __docformat__ = 'restructuredtext en' import copy from collections import OrderedDict from functools import partial -from polyglot.builtins import iteritems, native_string_type from calibre.ebooks.metadata import author_to_author_sort -from calibre.utils.config_base import tweaks, prefs -from calibre.utils.icu import sort_key, collation_order +from calibre.utils.config_base import prefs, tweaks +from calibre.utils.icu import ( + collation_order, lower as icu_lower, sort_key, upper as icu_upper, +) +from polyglot.builtins import iteritems, native_string_type CATEGORY_SORTS = ('name', 'popularity', 'rating') # This has to be a tuple not a set diff --git a/src/calibre/db/legacy.py b/src/calibre/db/legacy.py index 7567050030..a333bb3d6c 100644 --- a/src/calibre/db/legacy.py +++ b/src/calibre/db/legacy.py @@ -4,24 +4,30 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' -import os, traceback, weakref -from polyglot.builtins import iteritems +import os +import traceback +import weakref from collections.abc import MutableMapping from calibre import force_unicode, isbytestring from calibre.constants import preferred_encoding -from calibre.db import _get_next_series_num_for_list, _get_series_values, get_data_as_dict +from calibre.db import ( + _get_next_series_num_for_list, _get_series_values, get_data_as_dict, +) from calibre.db.adding import ( - find_books_in_directory, import_book_directory_multiple, - import_book_directory, recursive_import, add_catalog, add_news) + add_catalog, add_news, find_books_in_directory, import_book_directory, + import_book_directory_multiple, recursive_import, +) from calibre.db.backend import DB, set_global_state as backend_set_global_state from calibre.db.cache import Cache -from calibre.db.errors import NoSuchFormat from calibre.db.categories import CATEGORY_SORTS +from calibre.db.errors import NoSuchFormat from calibre.db.view import View from calibre.db.write import clean_identifier, get_series_values from calibre.utils.date import utcnow +from calibre.utils.icu import lower as icu_lower from calibre.utils.search_query_parser import set_saved_searches +from polyglot.builtins import iteritems def cleanup_tags(tags): @@ -156,6 +162,7 @@ class ThreadSafePrefs(MutableMapping): if isinstance(raw, bytes): raw = raw.decode(preferred_encoding) import json + from calibre.utils.config import from_json return json.loads(raw, object_hook=from_json) diff --git a/src/calibre/db/search.py b/src/calibre/db/search.py index 60d48cee3f..b4de2f014b 100644 --- a/src/calibre/db/search.py +++ b/src/calibre/db/search.py @@ -5,18 +5,22 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import regex, weakref, operator -from functools import partial +import operator +import regex +import weakref +from collections import OrderedDict, deque from datetime import timedelta -from collections import deque, OrderedDict +from functools import partial -from calibre.constants import preferred_encoding, DEBUG +from calibre.constants import DEBUG, preferred_encoding from calibre.db.utils import force_to_bool from calibre.utils.config_base import prefs -from calibre.utils.date import parse_date, UNDEFINED_DATE, now, dt_as_local -from calibre.utils.icu import primary_no_punc_contains, primary_contains, sort_key -from calibre.utils.localization import lang_map, canonicalize_lang -from calibre.utils.search_query_parser import SearchQueryParser, ParseException +from calibre.utils.date import UNDEFINED_DATE, dt_as_local, now, parse_date +from calibre.utils.icu import ( + lower as icu_lower, primary_contains, primary_no_punc_contains, sort_key, +) +from calibre.utils.localization import canonicalize_lang, lang_map +from calibre.utils.search_query_parser import ParseException, SearchQueryParser from polyglot.builtins import iteritems, string_or_bytes CONTAINS_MATCH = 0 diff --git a/src/calibre/db/tables.py b/src/calibre/db/tables.py index cff608b312..4a2a4da706 100644 --- a/src/calibre/db/tables.py +++ b/src/calibre/db/tables.py @@ -6,13 +6,14 @@ __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' import numbers -from datetime import datetime, timedelta from collections import defaultdict +from datetime import datetime, timedelta -from calibre.utils.date import parse_date, UNDEFINED_DATE, utc_tz from calibre.ebooks.metadata import author_to_author_sort -from polyglot.builtins import iteritems, itervalues +from calibre.utils.date import UNDEFINED_DATE, parse_date, utc_tz +from calibre.utils.icu import lower as icu_lower from calibre_extensions.speedup import parse_date as _c_speedup +from polyglot.builtins import iteritems, itervalues def c_parse(val): diff --git a/src/calibre/db/utils.py b/src/calibre/db/utils.py index 2d3fc1deae..8c8a449284 100644 --- a/src/calibre/db/utils.py +++ b/src/calibre/db/utils.py @@ -4,16 +4,21 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' -import os, errno, sys, re -from locale import localeconv +import errno +import os +import re +import sys from collections import OrderedDict, namedtuple -from polyglot.builtins import iteritems, itervalues, string_or_bytes +from locale import localeconv from threading import Lock from calibre import as_unicode, prints -from calibre.constants import cache_dir, get_windows_number_formats, iswindows, preferred_encoding - +from calibre.constants import ( + cache_dir, get_windows_number_formats, iswindows, preferred_encoding, +) +from calibre.utils.icu import lower as icu_lower from calibre.utils.localization import canonicalize_lang +from polyglot.builtins import iteritems, itervalues, string_or_bytes def force_to_bool(val): diff --git a/src/calibre/db/write.py b/src/calibre/db/write.py index 5f97dd80ce..8b240316ff 100644 --- a/src/calibre/db/write.py +++ b/src/calibre/db/write.py @@ -6,16 +6,17 @@ __copyright__ = '2013, Kovid Goyal ' __docformat__ = 'restructuredtext en' import re -from functools import partial from datetime import datetime -from polyglot.builtins import iteritems, itervalues +from functools import partial from calibre.constants import preferred_encoding from calibre.ebooks.metadata import author_to_author_sort, title_sort from calibre.utils.date import ( - parse_only_date, parse_date, UNDEFINED_DATE, isoformat, is_date_undefined) + UNDEFINED_DATE, is_date_undefined, isoformat, parse_date, parse_only_date, +) +from calibre.utils.icu import lower as icu_lower, strcmp from calibre.utils.localization import canonicalize_lang -from calibre.utils.icu import strcmp +from polyglot.builtins import iteritems, itervalues missing = object() diff --git a/src/calibre/devices/mtp/books.py b/src/calibre/devices/mtp/books.py index 3384a830d5..44a6ca0495 100644 --- a/src/calibre/devices/mtp/books.py +++ b/src/calibre/devices/mtp/books.py @@ -12,6 +12,7 @@ from calibre.ebooks.metadata import title_sort from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.json_codec import JsonCodec from calibre.utils.date import utcnow +from calibre.utils.icu import lower as icu_lower class BookList(BL): @@ -73,4 +74,3 @@ class Book(Metadata): class JSONCodec(JsonCodec): pass - diff --git a/src/calibre/devices/mtp/driver.py b/src/calibre/devices/mtp/driver.py index 6d12c601e0..5d0e53d8de 100644 --- a/src/calibre/devices/mtp/driver.py +++ b/src/calibre/devices/mtp/driver.py @@ -5,7 +5,11 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import json, traceback, posixpath, importlib, os +import importlib +import json +import os +import posixpath +import traceback from io import BytesIO from calibre import prints @@ -13,9 +17,10 @@ from calibre.constants import iswindows, numeric_version from calibre.devices.errors import PathError from calibre.devices.mtp.base import debug from calibre.devices.mtp.defaults import DeviceDefaults -from calibre.ptempfile import SpooledTemporaryFile, PersistentTemporaryDirectory +from calibre.ptempfile import PersistentTemporaryDirectory, SpooledTemporaryFile from calibre.utils.filenames import shorten_components_to -from polyglot.builtins import iteritems, itervalues, as_bytes +from calibre.utils.icu import lower as icu_lower +from polyglot.builtins import as_bytes, iteritems, itervalues BASE = importlib.import_module('calibre.devices.mtp.%s.driver'%( 'windows' if iswindows else 'unix')).MTP_DEVICE @@ -153,9 +158,10 @@ class MTP_DEVICE(BASE): # Device information {{{ def _update_drive_info(self, storage, location_code, name=None): - from calibre.utils.date import isoformat, now - from calibre.utils.config import from_json, to_json import uuid + + from calibre.utils.config import from_json, to_json + from calibre.utils.date import isoformat, now f = storage.find_path(self.calibre_file_paths['driveinfo'].split('/')) dinfo = {} if f is not None: @@ -213,8 +219,7 @@ class MTP_DEVICE(BASE): self.report_progress(0, msg) def books(self, oncard=None, end_session=True): - from calibre.devices.mtp.books import JSONCodec - from calibre.devices.mtp.books import BookList, Book + from calibre.devices.mtp.books import Book, BookList, JSONCodec self.report_progress(0, _('Listing files, this can take a while')) self.get_driveinfo() # Ensure driveinfo is loaded sid = {'carda':self._carda_id, 'cardb':self._cardb_id}.get(oncard, @@ -288,8 +293,8 @@ class MTP_DEVICE(BASE): return bl def read_file_metadata(self, mtp_file): - from calibre.ebooks.metadata.meta import get_metadata from calibre.customize.ui import quick_metadata + from calibre.ebooks.metadata.meta import get_metadata ext = mtp_file.name.rpartition('.')[-1].lower() stream = self.get_mtp_file(mtp_file) with quick_metadata: diff --git a/src/calibre/ebooks/metadata/author_mapper.py b/src/calibre/ebooks/metadata/author_mapper.py index e7705b539a..9286f160f7 100644 --- a/src/calibre/ebooks/metadata/author_mapper.py +++ b/src/calibre/ebooks/metadata/author_mapper.py @@ -5,7 +5,9 @@ import re from collections import deque -from calibre.utils.icu import capitalize, lower, upper +from calibre.utils.icu import ( + capitalize, lower, lower as icu_lower, upper, upper as icu_upper, +) def cap_author_token(token): diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index bc17b256f0..ed6b15e1e0 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -5,15 +5,17 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import copy, traceback +import copy +import traceback from calibre import prints from calibre.constants import DEBUG -from calibre.ebooks.metadata.book import (SC_COPYABLE_FIELDS, - SC_FIELDS_COPY_NOT_NULL, STANDARD_METADATA_FIELDS, - TOP_LEVEL_IDENTIFIERS, ALL_METADATA_FIELDS) +from calibre.ebooks.metadata.book import ( + ALL_METADATA_FIELDS, SC_COPYABLE_FIELDS, SC_FIELDS_COPY_NOT_NULL, + STANDARD_METADATA_FIELDS, TOP_LEVEL_IDENTIFIERS, +) from calibre.library.field_metadata import FieldMetadata -from calibre.utils.icu import sort_key +from calibre.utils.icu import lower as icu_lower, sort_key from polyglot.builtins import iteritems, string_or_bytes # Special sets used to optimize the performance of getting and setting @@ -56,6 +58,8 @@ def reset_field_metadata(): def ck(typ): return icu_lower(typ).strip().replace(':', '').replace(',', '') + + def cv(val): return val.strip().replace(',', '|') @@ -734,8 +738,8 @@ class Metadata: A string representation of this object, suitable for printing to console ''' - from calibre.utils.date import isoformat from calibre.ebooks.metadata import authors_to_string + from calibre.utils.date import isoformat ans = [] def fmt(x, y): diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index ce3eb80a4c..d3a7a6744a 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -8,21 +8,29 @@ __docformat__ = 'restructuredtext en' lxml based OPF parser. ''' -import re, sys, functools, os, uuid, glob, io, json, copy - +import copy +import functools +import glob +import io +import json +import os +import re +import sys +import uuid from lxml import etree -from calibre.ebooks import escape_xpath_attr +from calibre import guess_type, prints from calibre.constants import __appname__, __version__, filesystem_encoding +from calibre.ebooks import escape_xpath_attr +from calibre.ebooks.metadata import MetaInformation, check_isbn, string_to_authors +from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.toc import TOC from calibre.ebooks.metadata.utils import parse_opf, pretty_print_opf as _pretty_print -from calibre.ebooks.metadata import string_to_authors, MetaInformation, check_isbn -from calibre.ebooks.metadata.book.base import Metadata -from calibre.utils.date import parse_date, isoformat -from calibre.utils.localization import get_lang, canonicalize_lang -from calibre import prints, guess_type from calibre.utils.cleantext import clean_ascii_chars, clean_xml_chars from calibre.utils.config import tweaks +from calibre.utils.date import isoformat, parse_date +from calibre.utils.icu import lower as icu_lower, upper as icu_upper +from calibre.utils.localization import canonicalize_lang, get_lang from calibre.utils.xml_parse import safe_xml_fromstring from polyglot.builtins import iteritems from polyglot.urllib import unquote, urlparse @@ -480,9 +488,10 @@ class TitleSortField(MetadataField): def serialize_user_metadata(metadata_elem, all_user_metadata, tail='\n'+(' '*8)): + from calibre.ebooks.metadata.book.json_codec import ( + encode_is_multiple, object_to_unicode, + ) from calibre.utils.config import to_json - from calibre.ebooks.metadata.book.json_codec import (object_to_unicode, - encode_is_multiple) for name, fm in all_user_metadata.items(): try: @@ -626,8 +635,8 @@ class OPF: # {{{ def read_user_metadata(self): self._user_metadata_ = {} temp = Metadata('x', ['x']) - from calibre.utils.config import from_json from calibre.ebooks.metadata.book.json_codec import decode_is_multiple + from calibre.utils.config import from_json elems = self.root.xpath('//*[name() = "meta" and starts-with(@name,' '"calibre:user_metadata:") and @content]') for elem in elems: @@ -1448,7 +1457,8 @@ class OPFCreator(Metadata): # Actual rendering from lxml.builder import ElementMaker - from calibre.ebooks.oeb.base import OPF2_NS, DC11_NS, CALIBRE_NS + + from calibre.ebooks.oeb.base import CALIBRE_NS, DC11_NS, OPF2_NS DNS = OPF2_NS+'___xx___' E = ElementMaker(namespace=DNS, nsmap={None:DNS}) M = ElementMaker(namespace=DNS, @@ -1571,9 +1581,10 @@ class OPFCreator(Metadata): def metadata_to_opf(mi, as_string=True, default_lang=None): - from lxml import etree import textwrap - from calibre.ebooks.oeb.base import OPF, DC + from lxml import etree + + from calibre.ebooks.oeb.base import DC, OPF if not mi.application_id: mi.application_id = str(uuid.uuid4()) @@ -1652,6 +1663,7 @@ def metadata_to_opf(mi, as_string=True, default_lang=None): if mi.tags: for tag in mi.tags: factory(DC('subject'), tag) + def meta(n, c): return factory('meta', name='calibre:' + n, content=c) if getattr(mi, 'author_link_map', None) is not None: diff --git a/src/calibre/ebooks/metadata/sources/amazon.py b/src/calibre/ebooks/metadata/sources/amazon.py index 3594495184..78a268d784 100644 --- a/src/calibre/ebooks/metadata/sources/amazon.py +++ b/src/calibre/ebooks/metadata/sources/amazon.py @@ -4,15 +4,18 @@ from __future__ import absolute_import, division, print_function, unicode_literals import re -import string import socket +import string import time from functools import partial + try: from queue import Empty, Queue except ImportError: from Queue import Empty, Queue + from threading import Thread + try: from urllib.parse import urlparse except ImportError: @@ -24,9 +27,10 @@ from calibre import as_unicode, browser, random_user_agent, xml_replace_entities from calibre.ebooks.metadata import check_isbn from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.sources.base import Option, Source, fixauthors, fixcase +from calibre.ebooks.oeb.base import urlquote +from calibre.utils.icu import lower as icu_lower from calibre.utils.localization import canonicalize_lang from calibre.utils.random_ua import accept_header_for_ua -from calibre.ebooks.oeb.base import urlquote def sort_matches_preferring_kindle_editions(matches): @@ -89,9 +93,10 @@ def parse_html(raw): def parse_details_page(url, log, timeout, browser, domain): - from calibre.utils.cleantext import clean_ascii_chars - from calibre.ebooks.chardet import xml_to_unicode from lxml.html import tostring + + from calibre.ebooks.chardet import xml_to_unicode + from calibre.utils.cleantext import clean_ascii_chars try: from calibre.ebooks.metadata.sources.update import search_engines_module get_data_for_cached_url = search_engines_module().get_data_for_cached_url @@ -726,6 +731,7 @@ class Worker(Thread): # Get details {{{ ns = ns[0] if len(ns) == 0 and ns.text: import html5lib + # html5lib parsed noscript as CDATA ns = html5lib.parseFragment( '

%s
' % (ns.text), treebuilder='lxml', namespaceHTMLElements=False)[0] @@ -1271,9 +1277,9 @@ class Amazon(Source): def create_query(self, log, title=None, authors=None, identifiers={}, # {{{ domain=None, for_amazon=True): try: - from urllib.parse import urlencode, unquote_plus + from urllib.parse import unquote_plus, urlencode except ImportError: - from urllib import urlencode, unquote_plus + from urllib import unquote_plus, urlencode if domain is None: domain = self.domain @@ -1442,8 +1448,8 @@ class Amazon(Source): # }}} def search_amazon(self, br, testing, log, abort, title, authors, identifiers, timeout): # {{{ - from calibre.utils.cleantext import clean_ascii_chars from calibre.ebooks.chardet import xml_to_unicode + from calibre.utils.cleantext import clean_ascii_chars matches = [] query, domain = self.create_query(log, title=title, authors=authors, identifiers=identifiers) @@ -1703,8 +1709,10 @@ class Amazon(Source): def manual_tests(domain, **kw): # {{{ # To run these test use: # calibre-debug -c "from calibre.ebooks.metadata.sources.amazon import *; manual_tests('com')" - from calibre.ebooks.metadata.sources.test import (test_identify_plugin, - isbn_test, title_test, authors_test, comments_test, series_test) + from calibre.ebooks.metadata.sources.test import ( + authors_test, comments_test, isbn_test, series_test, test_identify_plugin, + title_test, + ) all_tests = {} all_tests['com'] = [ # {{{ ( # Paperback with series diff --git a/src/calibre/ebooks/metadata/tag_mapper.py b/src/calibre/ebooks/metadata/tag_mapper.py index 198e20ae2b..4d41f1e616 100644 --- a/src/calibre/ebooks/metadata/tag_mapper.py +++ b/src/calibre/ebooks/metadata/tag_mapper.py @@ -4,6 +4,8 @@ from collections import deque +from calibre.utils.icu import lower as icu_lower, upper as icu_upper + def compile_pat(pat): import regex diff --git a/src/calibre/ebooks/metadata/worker.py b/src/calibre/ebooks/metadata/worker.py index 2e3de51942..4c9b939949 100644 --- a/src/calibre/ebooks/metadata/worker.py +++ b/src/calibre/ebooks/metadata/worker.py @@ -8,6 +8,7 @@ from calibre.customize.ui import run_plugins_on_import from calibre.ebooks.metadata.meta import metadata_from_formats from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.utils.filenames import samefile +from calibre.utils.icu import lower as icu_lower def serialize_metadata_for(paths, tdir, group_id): diff --git a/src/calibre/ebooks/oeb/polish/css.py b/src/calibre/ebooks/oeb/polish/css.py index 386f962b23..8155eaeee5 100644 --- a/src/calibre/ebooks/oeb/polish/css.py +++ b/src/calibre/ebooks/oeb/polish/css.py @@ -6,18 +6,18 @@ __copyright__ = '2014, Kovid Goyal ' import re from collections import defaultdict +from css_parser.css import CSSRule, CSSStyleDeclaration from functools import partial from operator import itemgetter -from css_parser.css import CSSRule, CSSStyleDeclaration -from css_selectors import parse, SelectorSyntaxError - from calibre import force_unicode -from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS, XHTML, css_text +from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES, XHTML, css_text from calibre.ebooks.oeb.normalize_css import normalize_filter_css, normalizers -from calibre.ebooks.oeb.polish.pretty import pretty_script_or_style, pretty_xml_tree, serialize -from calibre.utils.icu import numeric_sort_key -from css_selectors import Select, SelectorError +from calibre.ebooks.oeb.polish.pretty import ( + pretty_script_or_style, pretty_xml_tree, serialize, +) +from calibre.utils.icu import lower as icu_lower, numeric_sort_key +from css_selectors import Select, SelectorError, SelectorSyntaxError, parse from polyglot.builtins import iteritems, itervalues from polyglot.functools import lru_cache diff --git a/src/calibre/ebooks/oeb/polish/embed.py b/src/calibre/ebooks/oeb/polish/embed.py index 443933bf7c..3904bd18d3 100644 --- a/src/calibre/ebooks/oeb/polish/embed.py +++ b/src/calibre/ebooks/oeb/polish/embed.py @@ -6,12 +6,12 @@ __copyright__ = '2013, Kovid Goyal ' __docformat__ = 'restructuredtext en' import sys - from lxml import etree from calibre import prints from calibre.ebooks.oeb.base import XHTML from calibre.utils.filenames import ascii_filename +from calibre.utils.icu import lower as icu_lower from polyglot.builtins import iteritems, itervalues, string_or_bytes props = {'font-family':None, 'font-weight':'normal', 'font-style':'normal', 'font-stretch':'normal'} @@ -163,7 +163,7 @@ def embed_font(container, font, all_font_rules, report, warned): if not isinstance(ff, string_or_bytes): ff = ff[0] if rule is None: - from calibre.utils.fonts.scanner import font_scanner, NoFonts + from calibre.utils.fonts.scanner import NoFonts, font_scanner if ff in warned: return try: diff --git a/src/calibre/ebooks/oeb/polish/stats.py b/src/calibre/ebooks/oeb/polish/stats.py index 67feeb3b65..91b302ecb2 100644 --- a/src/calibre/ebooks/oeb/polish/stats.py +++ b/src/calibre/ebooks/oeb/polish/stats.py @@ -5,15 +5,16 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' __docformat__ = 'restructuredtext en' +import regex import sys from functools import partial - from lxml.etree import tostring -import regex from calibre.ebooks.oeb.base import XHTML, css_text -from calibre.ebooks.oeb.polish.cascade import iterrules, resolve_styles, iterdeclaration -from calibre.utils.icu import ord_string, safe_chr +from calibre.ebooks.oeb.polish.cascade import iterdeclaration, iterrules, resolve_styles +from calibre.utils.icu import ( + lower as icu_lower, ord_string, safe_chr, upper as icu_upper, +) from polyglot.builtins import iteritems, itervalues from tinycss.fonts3 import parse_font_family diff --git a/src/calibre/ebooks/oeb/polish/tests/cascade.py b/src/calibre/ebooks/oeb/polish/tests/cascade.py index fd2e07c104..ec47d9911b 100644 --- a/src/calibre/ebooks/oeb/polish/tests/cascade.py +++ b/src/calibre/ebooks/oeb/polish/tests/cascade.py @@ -4,18 +4,20 @@ __license__ = 'GPL v3' __copyright__ = '2016, Kovid Goyal ' +from css_parser import parseStyle from functools import partial -from css_parser import parseStyle - from calibre.constants import iswindows -from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS -from calibre.ebooks.oeb.polish.cascade import iterrules, resolve_styles, DEFAULTS +from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES +from calibre.ebooks.oeb.polish.cascade import DEFAULTS, iterrules, resolve_styles +from calibre.ebooks.oeb.polish.container import ContainerBase, href_to_name from calibre.ebooks.oeb.polish.css import remove_property_value from calibre.ebooks.oeb.polish.embed import find_matching_font -from calibre.ebooks.oeb.polish.container import ContainerBase, href_to_name -from calibre.ebooks.oeb.polish.stats import StatsCollector, font_keys, normalize_font_properties, prepare_font_rule +from calibre.ebooks.oeb.polish.stats import ( + StatsCollector, font_keys, normalize_font_properties, prepare_font_rule, +) from calibre.ebooks.oeb.polish.tests.base import BaseTest +from calibre.utils.icu import lower as icu_lower from calibre.utils.logging import Log, Stream from polyglot.builtins import iteritems diff --git a/src/calibre/ebooks/oeb/polish/utils.py b/src/calibre/ebooks/oeb/polish/utils.py index 955237ce07..afd5e6b6cf 100644 --- a/src/calibre/ebooks/oeb/polish/utils.py +++ b/src/calibre/ebooks/oeb/polish/utils.py @@ -8,6 +8,7 @@ import re, os from bisect import bisect from calibre import guess_type as _guess_type, replace_entities +from calibre.utils.icu import upper as icu_upper BLOCK_TAG_NAMES = frozenset(( @@ -248,6 +249,7 @@ def apply_func_to_match_groups(match, func=icu_upper, handle_entities=handle_ent found_groups = False i = 0 parts, pos = [], match.start() + def f(text): return handle_entities(text, func) while True: diff --git a/src/calibre/ebooks/oeb/transforms/manglecase.py b/src/calibre/ebooks/oeb/transforms/manglecase.py index 4b955663c5..835c1939d3 100644 --- a/src/calibre/ebooks/oeb/transforms/manglecase.py +++ b/src/calibre/ebooks/oeb/transforms/manglecase.py @@ -6,10 +6,10 @@ __license__ = 'GPL v3' __copyright__ = '2008, Marshall T. Vandegrift ' from lxml import etree -from calibre.ebooks.oeb.base import XHTML, XHTML_NS -from calibre.ebooks.oeb.base import CSS_MIME -from calibre.ebooks.oeb.base import namespace + +from calibre.ebooks.oeb.base import CSS_MIME, XHTML, XHTML_NS, namespace from calibre.ebooks.oeb.stylizer import Stylizer +from calibre.utils.icu import lower as icu_lower, upper as icu_upper from polyglot.builtins import string_or_bytes CASE_MANGLER_CSS = """ diff --git a/src/calibre/gui2/actions/similar_books.py b/src/calibre/gui2/actions/similar_books.py index cc248ba20e..cac3aa2fe6 100644 --- a/src/calibre/gui2/actions/similar_books.py +++ b/src/calibre/gui2/actions/similar_books.py @@ -9,6 +9,7 @@ __docformat__ = 'restructuredtext en' from qt.core import QToolButton from calibre.gui2.actions import InterfaceAction +from calibre.utils.icu import lower as icu_lower from polyglot.builtins import string_or_bytes diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py index 9f0f1bbe4d..b91351c02b 100644 --- a/src/calibre/gui2/add.py +++ b/src/calibre/gui2/add.py @@ -31,6 +31,7 @@ from calibre.ptempfile import PersistentTemporaryDirectory from calibre.utils import join_with_timeout from calibre.utils.config import prefs from calibre.utils.filenames import make_long_path_useable +from calibre.utils.icu import lower as icu_lower from calibre.utils.ipc.pool import Failure, Pool from polyglot.builtins import iteritems, string_or_bytes from polyglot.queue import Empty diff --git a/src/calibre/gui2/author_mapper.py b/src/calibre/gui2/author_mapper.py index 1605793d4b..5d26d08a9c 100644 --- a/src/calibre/gui2/author_mapper.py +++ b/src/calibre/gui2/author_mapper.py @@ -10,7 +10,7 @@ from calibre.gui2 import Application, elided_text from calibre.gui2.tag_mapper import ( RuleEdit as RuleEditBase, RuleEditDialog as RuleEditDialogBase, RuleItem as RuleItemBase, Rules as RulesBase, RulesDialog as RulesDialogBase, - Tester as TesterBase + Tester as TesterBase, ) from calibre.utils.config import JSONConfig diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 7969dbfe9d..243f30331e 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -8,24 +8,26 @@ __docformat__ = 'restructuredtext en' import os from collections import OrderedDict from functools import partial - -from qt.core import (Qt, QComboBox, QLabel, QSpinBox, QDoubleSpinBox, - QDateTime, QGroupBox, QVBoxLayout, QSizePolicy, QGridLayout, QUrl, - QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, QLineEdit, - QMessageBox, QToolButton, QPlainTextEdit, QApplication, QStyle, QDialog) +from qt.core import ( + QApplication, QCheckBox, QComboBox, QDateTime, QDialog, QDoubleSpinBox, QGridLayout, + QGroupBox, QHBoxLayout, QIcon, QLabel, QLineEdit, QMessageBox, QPlainTextEdit, + QSizePolicy, QSpacerItem, QSpinBox, QStyle, Qt, QToolButton, QUrl, QVBoxLayout, + QWidget, +) from calibre.ebooks.metadata import title_sort -from calibre.utils.date import (qt_to_dt, now, as_local_time, as_utc, - internal_iso_format_string, is_date_undefined) -from calibre.gui2.complete2 import EditWithComplete as EWC +from calibre.gui2 import UNDEFINED_QDATETIME, elided_text, error_dialog, gprefs from calibre.gui2.comments_editor import Editor as CommentsEditor -from calibre.gui2 import UNDEFINED_QDATETIME, error_dialog, elided_text, gprefs +from calibre.gui2.complete2 import EditWithComplete as EWC from calibre.gui2.dialogs.tag_editor import TagEditor -from calibre.utils.config import tweaks -from calibre.utils.icu import sort_key -from calibre.library.comments import comments_to_html from calibre.gui2.library.delegates import ClearingDoubleSpinBox, ClearingSpinBox -from calibre.gui2.widgets2 import RatingEditor, DateTimeEdit as DateTimeEditBase +from calibre.gui2.widgets2 import DateTimeEdit as DateTimeEditBase, RatingEditor +from calibre.library.comments import comments_to_html +from calibre.utils.config import tweaks +from calibre.utils.date import ( + as_local_time, as_utc, internal_iso_format_string, is_date_undefined, now, qt_to_dt, +) +from calibre.utils.icu import lower as icu_lower, sort_key class EditWithComplete(EWC): diff --git a/src/calibre/gui2/device_drivers/mtp_folder_browser.py b/src/calibre/gui2/device_drivers/mtp_folder_browser.py index 3855b4f736..a852b6b83e 100644 --- a/src/calibre/gui2/device_drivers/mtp_folder_browser.py +++ b/src/calibre/gui2/device_drivers/mtp_folder_browser.py @@ -6,11 +6,13 @@ __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' from operator import attrgetter - -from qt.core import (QTabWidget, QTreeWidget, QTreeWidgetItem, Qt, QDialog, - QDialogButtonBox, QVBoxLayout, QSize, pyqtSignal, QIcon, QLabel) +from qt.core import ( + QDialog, QDialogButtonBox, QIcon, QLabel, QSize, Qt, QTabWidget, QTreeWidget, + QTreeWidgetItem, QVBoxLayout, pyqtSignal, +) from calibre.gui2 import file_icon_provider +from calibre.utils.icu import lower as icu_lower def browser_item(f, parent): diff --git a/src/calibre/gui2/dialogs/authors_edit.py b/src/calibre/gui2/dialogs/authors_edit.py index 5777013d9a..2de105021c 100644 --- a/src/calibre/gui2/dialogs/authors_edit.py +++ b/src/calibre/gui2/dialogs/authors_edit.py @@ -5,15 +5,16 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' from collections import OrderedDict - from qt.core import ( - QDialog, QGridLayout, QDialogButtonBox, QListWidget, QApplication, Qt, - pyqtSignal, QSize, QPushButton, QIcon, QStyledItemDelegate, QLabel, QAbstractItemView) + QAbstractItemView, QApplication, QDialog, QDialogButtonBox, QGridLayout, QIcon, + QLabel, QListWidget, QPushButton, QSize, QStyledItemDelegate, Qt, pyqtSignal, +) -from calibre.utils.config_base import tweaks +from calibre.ebooks.metadata import string_to_authors from calibre.gui2 import gprefs from calibre.gui2.complete2 import EditWithComplete -from calibre.ebooks.metadata import string_to_authors +from calibre.utils.config_base import tweaks +from calibre.utils.icu import lower as icu_lower class ItemDelegate(QStyledItemDelegate): diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.py b/src/calibre/gui2/dialogs/edit_authors_dialog.py index 69a0907150..7f3dfb657b 100644 --- a/src/calibre/gui2/dialogs/edit_authors_dialog.py +++ b/src/calibre/gui2/dialogs/edit_authors_dialog.py @@ -5,19 +5,22 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' __license__ = 'GPL v3' -from functools import partial from contextlib import contextmanager - -from qt.core import (Qt, QDialog, QTableWidgetItem, QAbstractItemView, QIcon, - QDialogButtonBox, QFrame, QLabel, QTimer, QMenu, QApplication, - QItemDelegate, QAction) +from functools import partial +from qt.core import ( + QAbstractItemView, QAction, QApplication, QDialog, QDialogButtonBox, QFrame, QIcon, + QItemDelegate, QLabel, QMenu, Qt, QTableWidgetItem, QTimer, +) from calibre.ebooks.metadata import author_to_author_sort, string_to_authors from calibre.gui2 import error_dialog, gprefs from calibre.gui2.dialogs.edit_authors_dialog_ui import Ui_EditAuthorsDialog from calibre.utils.config import prefs from calibre.utils.config_base import tweaks -from calibre.utils.icu import sort_key, primary_contains, contains, primary_startswith +from calibre.utils.icu import ( + contains, lower as icu_lower, primary_contains, primary_startswith, sort_key, + upper as icu_upper, +) QT_HIDDEN_CLEAR_ACTION = '_q_qlineeditclearaction' diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 2706c34906..1ff57fbd65 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -7,9 +7,9 @@ import regex from collections import defaultdict, namedtuple from io import BytesIO from qt.core import ( - QApplication, QComboBox, QCompleter, QDateTime, QDialog, - QDialogButtonBox, QFont, QGridLayout, QInputDialog, QLabel, QLineEdit, - QProgressBar, QSize, Qt, QVBoxLayout, pyqtSignal + QApplication, QComboBox, QCompleter, QDateTime, QDialog, QDialogButtonBox, QFont, + QGridLayout, QInputDialog, QLabel, QLineEdit, QProgressBar, QSize, Qt, QVBoxLayout, + pyqtSignal, ) from threading import Thread @@ -21,7 +21,7 @@ from calibre.ebooks.metadata.book.formatter import SafeFormat from calibre.ebooks.metadata.opf2 import OPF from calibre.gui2 import ( UNDEFINED_QDATETIME, FunctionDispatcher, error_dialog, gprefs, info_dialog, - question_dialog + question_dialog, ) from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog @@ -30,7 +30,9 @@ from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor from calibre.gui2.widgets import LineEditECM from calibre.utils.config import JSONConfig, dynamic, prefs, tweaks from calibre.utils.date import internal_iso_format_string, qt_to_dt -from calibre.utils.icu import capitalize, sort_key +from calibre.utils.icu import ( + capitalize, lower as icu_lower, sort_key, upper as icu_upper, +) from calibre.utils.titlecase import titlecase from polyglot.builtins import error_message, iteritems, itervalues, native_string_type @@ -317,7 +319,7 @@ class MyBlockingBusy(QDialog): # {{{ elif args.cover_action == 'trim': self.progress_next_step_range.emit(len(self.ids)) from calibre.utils.img import ( - image_from_data, image_to_data, remove_borders_from_image + image_from_data, image_to_data, remove_borders_from_image, ) for book_id in self.ids: cdata = cache.cover(book_id) diff --git a/src/calibre/gui2/dialogs/plugin_updater.py b/src/calibre/gui2/dialogs/plugin_updater.py index 7bd8c75fad..0fd4658189 100644 --- a/src/calibre/gui2/dialogs/plugin_updater.py +++ b/src/calibre/gui2/dialogs/plugin_updater.py @@ -9,24 +9,25 @@ import datetime import re import traceback from qt.core import ( - QAbstractItemView, QAbstractTableModel, QAction, QBrush, QComboBox, - QDialog, QDialogButtonBox, QFont, QFrame, QHBoxLayout, QIcon, QLabel, QLineEdit, - QModelIndex, QSize, QSortFilterProxyModel, Qt, QTableView, QUrl, QVBoxLayout + QAbstractItemView, QAbstractTableModel, QAction, QBrush, QComboBox, QDialog, + QDialogButtonBox, QFont, QFrame, QHBoxLayout, QIcon, QLabel, QLineEdit, QModelIndex, + QSize, QSortFilterProxyModel, Qt, QTableView, QUrl, QVBoxLayout, ) from calibre import prints from calibre.constants import ( - DEBUG, __appname__, __version__, ismacos, iswindows, numeric_version + DEBUG, __appname__, __version__, ismacos, iswindows, numeric_version, ) from calibre.customize import PluginInstallationType from calibre.customize.ui import ( NameConflict, add_plugin, disable_plugin, enable_plugin, has_external_plugins, - initialized_plugins, is_disabled, remove_plugin + initialized_plugins, is_disabled, remove_plugin, ) from calibre.gui2 import error_dialog, gprefs, info_dialog, open_url, question_dialog from calibre.gui2.preferences.plugins import ConfigWidget from calibre.utils.date import UNDEFINED_DATE, format_date from calibre.utils.https import get_https_resource_securely +from calibre.utils.icu import lower as icu_lower from polyglot.builtins import itervalues SERVER = 'https://code.calibre-ebook.com/plugins/' diff --git a/src/calibre/gui2/dialogs/saved_search_editor.py b/src/calibre/gui2/dialogs/saved_search_editor.py index 20deb7072c..1a8b83274c 100644 --- a/src/calibre/gui2/dialogs/saved_search_editor.py +++ b/src/calibre/gui2/dialogs/saved_search_editor.py @@ -3,15 +3,15 @@ from qt.core import ( - QFormLayout, QIcon, QLabel, QLineEdit, QListWidget, Qt, QVBoxLayout, QDialog, - QDialogButtonBox, QPlainTextEdit + QDialog, QDialogButtonBox, QFormLayout, QIcon, QLabel, QLineEdit, QListWidget, + QPlainTextEdit, Qt, QVBoxLayout, ) from calibre import prepare_string_for_xml from calibre.gui2 import error_dialog from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.widgets2 import Dialog -from calibre.utils.icu import sort_key +from calibre.utils.icu import lower as icu_lower, sort_key def commit_searches(searches): diff --git a/src/calibre/gui2/dialogs/tag_categories.py b/src/calibre/gui2/dialogs/tag_categories.py index 090421f94d..9df414d4c8 100644 --- a/src/calibre/gui2/dialogs/tag_categories.py +++ b/src/calibre/gui2/dialogs/tag_categories.py @@ -1,15 +1,16 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -from qt.core import (Qt, QApplication, QDialog, QIcon, QListWidgetItem) - from collections import namedtuple +from qt.core import QApplication, QDialog, QIcon, QListWidgetItem, Qt from calibre.constants import islinux from calibre.gui2 import error_dialog, warning_dialog from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.tag_categories_ui import Ui_TagCategories -from calibre.utils.icu import primary_sort_key, strcmp, primary_contains +from calibre.utils.icu import ( + lower as icu_lower, primary_contains, primary_sort_key, strcmp, +) class TagCategories(QDialog, Ui_TagCategories): diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index be790b809b..a452b57391 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -3,19 +3,23 @@ from functools import partial +from qt.core import ( + QAbstractItemView, QAction, QApplication, QColor, QDialog, QDialogButtonBox, QFrame, + QIcon, QItemDelegate, QLabel, QMenu, QSize, Qt, QTableWidgetItem, QTimer, + pyqtSignal, +) -from qt.core import (Qt, QDialog, QTableWidgetItem, QIcon, QSize, QAbstractItemView, - QDialogButtonBox, QItemDelegate, QApplication, - pyqtSignal, QAction, QFrame, QLabel, QTimer, QMenu, QColor) - +from calibre.gui2 import error_dialog, gprefs, question_dialog from calibre.gui2.actions.show_quickview import get_quickview_action_plugin from calibre.gui2.complete2 import EditWithComplete -from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor from calibre.gui2.dialogs.confirm_delete import confirm +from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor from calibre.gui2.widgets import EnLineEdit -from calibre.gui2 import question_dialog, error_dialog, gprefs from calibre.utils.config import prefs -from calibre.utils.icu import contains, primary_contains, primary_startswith, capitalize +from calibre.utils.icu import ( + capitalize, contains, lower as icu_lower, primary_contains, primary_startswith, + upper as icu_upper, +) from calibre.utils.titlecase import titlecase QT_HIDDEN_CLEAR_ACTION = '_q_qlineeditclearaction' diff --git a/src/calibre/gui2/dialogs/template_dialog.py b/src/calibre/gui2/dialogs/template_dialog.py index 22086c705f..5077bb980f 100644 --- a/src/calibre/gui2/dialogs/template_dialog.py +++ b/src/calibre/gui2/dialogs/template_dialog.py @@ -32,7 +32,7 @@ from calibre.utils.config_base import tweaks from calibre.utils.date import DEFAULT_DATE from calibre.utils.formatter import PythonTemplateContext, StopException from calibre.utils.formatter_functions import StoredObjectType, formatter_functions -from calibre.utils.icu import sort_key +from calibre.utils.icu import lower as icu_lower, sort_key from calibre.utils.localization import localize_user_manual_link from calibre.utils.resources import get_path as P diff --git a/src/calibre/gui2/font_family_chooser.py b/src/calibre/gui2/font_family_chooser.py index 5de96c6e6a..338828d9b7 100644 --- a/src/calibre/gui2/font_family_chooser.py +++ b/src/calibre/gui2/font_family_chooser.py @@ -8,14 +8,15 @@ __docformat__ = 'restructuredtext en' import os import shutil from qt.core import ( - QAbstractItemView, QDialog, QDialogButtonBox, QFont, QFontComboBox, - QFontDatabase, QFontInfo, QFontMetrics, QGridLayout, QHBoxLayout, QIcon, QLabel, - QLineEdit, QListView, QPen, QPushButton, QSize, QSizePolicy, QStringListModel, - QStyle, QStyledItemDelegate, Qt, QToolButton, QVBoxLayout, QWidget, pyqtSignal + QAbstractItemView, QDialog, QDialogButtonBox, QFont, QFontComboBox, QFontDatabase, + QFontInfo, QFontMetrics, QGridLayout, QHBoxLayout, QIcon, QLabel, QLineEdit, + QListView, QPen, QPushButton, QSize, QSizePolicy, QStringListModel, QStyle, + QStyledItemDelegate, Qt, QToolButton, QVBoxLayout, QWidget, pyqtSignal, ) from calibre.constants import config_dir from calibre.gui2 import choose_files, empty_index, error_dialog, info_dialog +from calibre.utils.icu import lower as icu_lower def add_fonts(parent): diff --git a/src/calibre/gui2/metadata/diff.py b/src/calibre/gui2/metadata/diff.py index c59ddeeee4..e12e283b5e 100644 --- a/src/calibre/gui2/metadata/diff.py +++ b/src/calibre/gui2/metadata/diff.py @@ -12,7 +12,7 @@ from qt.core import ( QAction, QApplication, QCheckBox, QColor, QDialog, QDialogButtonBox, QFont, QGridLayout, QHBoxLayout, QIcon, QKeySequence, QLabel, QMenu, QPainter, QPen, QPixmap, QScrollArea, QSize, QSizePolicy, QStackedLayout, Qt, QToolButton, - QVBoxLayout, QWidget, pyqtSignal + QVBoxLayout, QWidget, pyqtSignal, ) from calibre import fit_image @@ -26,6 +26,7 @@ from calibre.gui2.metadata.basic_widgets import PubdateEdit, RatingEdit from calibre.gui2.widgets2 import RightClickButton from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.date import UNDEFINED_DATE +from calibre.utils.icu import lower as icu_lower from polyglot.builtins import iteritems, itervalues Widgets = namedtuple('Widgets', 'new old label button') @@ -783,6 +784,7 @@ if __name__ == '__main__': ids = sorted(db.all_ids(), reverse=True) ids = tuple(zip(ids[0::2], ids[1::2])) gm = partial(db.get_metadata, index_is_id=True, get_cover=True, cover_as_data=True) + def get_metadata(x): return list(map(gm, ids[x])) d = CompareMany(list(range(len(ids))), get_metadata, db.field_metadata, db=db) diff --git a/src/calibre/gui2/preferences/search.py b/src/calibre/gui2/preferences/search.py index 13faccb23f..d0264dfd2c 100644 --- a/src/calibre/gui2/preferences/search.py +++ b/src/calibre/gui2/preferences/search.py @@ -8,13 +8,14 @@ __docformat__ = 'restructuredtext en' from qt.core import QApplication, QTimer from calibre.db.categories import find_categories -from calibre.gui2.preferences import ConfigWidgetBase, test_widget, \ - CommaSeparatedList, AbortCommit -from calibre.gui2.preferences.search_ui import Ui_Form from calibre.gui2 import config, error_dialog, gprefs -from calibre.utils.config import prefs -from calibre.utils.icu import sort_key +from calibre.gui2.preferences import ( + AbortCommit, CommaSeparatedList, ConfigWidgetBase, test_widget, +) +from calibre.gui2.preferences.search_ui import Ui_Form from calibre.library.caches import set_use_primary_find_in_search +from calibre.utils.config import prefs +from calibre.utils.icu import lower as icu_lower, sort_key from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/store/search/models.py b/src/calibre/gui2/store/search/models.py index ed63c66e27..9e14375134 100644 --- a/src/calibre/gui2/store/search/models.py +++ b/src/calibre/gui2/store/search/models.py @@ -2,18 +2,19 @@ __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' __docformat__ = 'restructuredtext en' -import re, string +import re +import string from operator import attrgetter - -from qt.core import (Qt, QAbstractItemModel, QPixmap, QModelIndex, QSize, - pyqtSignal, QIcon, QApplication) +from qt.core import ( + QAbstractItemModel, QApplication, QIcon, QModelIndex, QPixmap, QSize, Qt, + pyqtSignal, +) from calibre import force_unicode from calibre.gui2 import FunctionDispatcher +from calibre.gui2.store.search.download_thread import CoverThreadPool, DetailsThreadPool from calibre.gui2.store.search_result import SearchResult -from calibre.gui2.store.search.download_thread import DetailsThreadPool, \ - CoverThreadPool -from calibre.utils.icu import sort_key +from calibre.utils.icu import lower as icu_lower, sort_key from calibre.utils.localization import pgettext from calibre.utils.search_query_parser import SearchQueryParser diff --git a/src/calibre/gui2/tag_browser/model.py b/src/calibre/gui2/tag_browser/model.py index c16cb0988d..34b20627af 100644 --- a/src/calibre/gui2/tag_browser/model.py +++ b/src/calibre/gui2/tag_browser/model.py @@ -10,21 +10,22 @@ import os import traceback from collections import OrderedDict, namedtuple from qt.core import ( - QAbstractItemModel, QFont, QIcon, QMimeData, QModelIndex, QObject, Qt, - pyqtSignal + QAbstractItemModel, QFont, QIcon, QMimeData, QModelIndex, QObject, Qt, pyqtSignal, ) from calibre.constants import config_dir from calibre.db.categories import Tag, category_display_order from calibre.ebooks.metadata import rating_to_stars -from calibre.gui2 import config, error_dialog, file_icon_provider, gprefs, question_dialog +from calibre.gui2 import ( + config, error_dialog, file_icon_provider, gprefs, question_dialog, +) from calibre.gui2.dialogs.confirm_delete import confirm from calibre.library.field_metadata import category_icon_map from calibre.utils.config import prefs, tweaks from calibre.utils.formatter import EvalFormatter from calibre.utils.icu import ( - contains, lower, primary_contains, primary_strcmp, sort_key, - strcmp, collation_order_for_partitioning + collation_order_for_partitioning, contains, lower, lower as icu_lower, + primary_contains, primary_strcmp, sort_key, strcmp, upper as icu_upper, ) from calibre.utils.serialize import json_dumps, json_loads from polyglot.builtins import iteritems, itervalues diff --git a/src/calibre/gui2/tag_mapper.py b/src/calibre/gui2/tag_mapper.py index a4266e93fa..0f11a3fd03 100644 --- a/src/calibre/gui2/tag_mapper.py +++ b/src/calibre/gui2/tag_mapper.py @@ -6,9 +6,9 @@ import textwrap from collections import OrderedDict from qt.core import ( QAbstractItemView, QComboBox, QDialog, QDialogButtonBox, QHBoxLayout, QIcon, - QInputDialog, QItemSelectionModel, QLabel, QLineEdit, QListWidget, - QListWidgetItem, QMenu, QPalette, QPushButton, QSize, QStaticText, QStyle, - QStyledItemDelegate, Qt, QToolButton, QVBoxLayout, QWidget, pyqtSignal + QInputDialog, QItemSelectionModel, QLabel, QLineEdit, QListWidget, QListWidgetItem, + QMenu, QPalette, QPushButton, QSize, QStaticText, QStyle, QStyledItemDelegate, Qt, + QToolButton, QVBoxLayout, QWidget, pyqtSignal, ) from calibre.ebooks.metadata.tag_mapper import compile_pat, map_tags diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py index 5111a36399..50f1e6565b 100644 --- a/src/calibre/gui2/toc/main.py +++ b/src/calibre/gui2/toc/main.py @@ -8,11 +8,10 @@ import tempfile import textwrap from functools import partial from qt.core import ( - QAbstractItemView, QCheckBox, QCursor, QDialog, QDialogButtonBox, - QEvent, QFrame, QGridLayout, QIcon, QInputDialog, QItemSelectionModel, - QKeySequence, QLabel, QMenu, QPushButton, QScrollArea, QSize, QSizePolicy, - QStackedWidget, Qt, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, - QWidget, pyqtSignal + QAbstractItemView, QCheckBox, QCursor, QDialog, QDialogButtonBox, QEvent, QFrame, + QGridLayout, QIcon, QInputDialog, QItemSelectionModel, QKeySequence, QLabel, QMenu, + QPushButton, QScrollArea, QSize, QSizePolicy, QStackedWidget, Qt, QToolButton, + QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, pyqtSignal, ) from threading import Thread from time import monotonic @@ -20,17 +19,16 @@ from time import monotonic from calibre.constants import TOC_DIALOG_APP_UID, islinux, iswindows from calibre.ebooks.oeb.polish.container import AZW3Container, get_container from calibre.ebooks.oeb.polish.toc import ( - TOC, add_id, commit_toc, from_files, from_links, from_xpaths, get_toc -) -from calibre.gui2 import ( - Application, error_dialog, info_dialog, set_app_uid + TOC, add_id, commit_toc, from_files, from_links, from_xpaths, get_toc, ) +from calibre.gui2 import Application, error_dialog, info_dialog, set_app_uid from calibre.gui2.convert.xpath_wizard import XPathEdit from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.toc.location import ItemEdit from calibre.ptempfile import reset_base_dir from calibre.utils.config import JSONConfig from calibre.utils.filenames import atomic_rename +from calibre.utils.icu import lower as icu_lower, upper as icu_upper from calibre.utils.logging import GUILog ICON_SIZE = 24 @@ -1166,6 +1164,7 @@ class TOCEditor(QDialog): # {{{ def main(shm_name=None): import json import struct + from calibre.utils.shm import SharedMemory # Ensure we can continue to function if GUI is closed diff --git a/src/calibre/gui2/tweak_book/manage_fonts.py b/src/calibre/gui2/tweak_book/manage_fonts.py index a6ee741f84..14cfaaa032 100644 --- a/src/calibre/gui2/tweak_book/manage_fonts.py +++ b/src/calibre/gui2/tweak_book/manage_fonts.py @@ -4,23 +4,24 @@ __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' -import sys, textwrap +import sys +import textwrap from io import BytesIO - from qt.core import ( - QSplitter, QVBoxLayout, QTableView, QWidget, QLabel, QAbstractTableModel, - Qt, QTimer, QPushButton, pyqtSignal, QFormLayout, QLineEdit, QIcon, QSize, - QHBoxLayout, QTextEdit, QApplication, QMessageBox, QAbstractItemView, QDialog, QDialogButtonBox) + QAbstractItemView, QAbstractTableModel, QApplication, QDialog, QDialogButtonBox, + QFormLayout, QHBoxLayout, QIcon, QLabel, QLineEdit, QMessageBox, QPushButton, QSize, + QSplitter, Qt, QTableView, QTextEdit, QTimer, QVBoxLayout, QWidget, pyqtSignal, +) from calibre.ebooks.oeb.polish.container import get_container -from calibre.ebooks.oeb.polish.fonts import font_family_data, change_font +from calibre.ebooks.oeb.polish.fonts import change_font, font_family_data from calibre.gui2 import error_dialog, info_dialog from calibre.gui2.tweak_book import current_container, set_current_container from calibre.gui2.tweak_book.widgets import Dialog from calibre.gui2.widgets import BusyCursor -from calibre.utils.icu import primary_sort_key as sort_key -from calibre.utils.fonts.scanner import font_scanner, NoFonts from calibre.utils.fonts.metadata import FontMetadata, UnsupportedFont +from calibre.utils.fonts.scanner import NoFonts, font_scanner +from calibre.utils.icu import lower as icu_lower, primary_sort_key as sort_key from polyglot.builtins import iteritems diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 197cc736f8..efc3994a1d 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -5,22 +5,24 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import time, traceback, locale -from itertools import repeat -from datetime import timedelta -from threading import Thread +import locale +import time +import traceback from contextlib import suppress +from datetime import timedelta +from itertools import repeat +from threading import Thread -from calibre.utils.config import tweaks, prefs -from calibre.utils.date import parse_date, now, UNDEFINED_DATE, clean_date_for_sort -from calibre.utils.search_query_parser import SearchQueryParser -from calibre.utils.search_query_parser import ParseException -from calibre.utils.localization import (canonicalize_lang, lang_map, get_udc) +from calibre import force_unicode, prints from calibre.db.search import CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH, _match -from calibre.ebooks.metadata import title_sort, author_to_author_sort +from calibre.ebooks.metadata import author_to_author_sort, title_sort from calibre.ebooks.metadata.opf2 import metadata_to_opf -from calibre import prints, force_unicode -from polyglot.builtins import iteritems, itervalues, string_or_bytes, cmp +from calibre.utils.config import prefs, tweaks +from calibre.utils.date import UNDEFINED_DATE, clean_date_for_sort, now, parse_date +from calibre.utils.icu import lower as icu_lower +from calibre.utils.localization import canonicalize_lang, get_udc, lang_map +from calibre.utils.search_query_parser import ParseException, SearchQueryParser +from polyglot.builtins import cmp, iteritems, itervalues, string_or_bytes class MetadataBackup(Thread): # {{{ @@ -437,6 +439,7 @@ class ResultCache(SearchQueryParser): # {{{ if val_func is None: loc = self.field_metadata[location]['rec_index'] + def val_func(item, loc=loc): return item[loc] q = '' @@ -472,6 +475,7 @@ class ResultCache(SearchQueryParser): # {{{ elif dt == 'rating': def cast(x): return (0 if x is None else int(x)) + def adjust(x): return (x // 2) elif dt in ('float', 'composite'): diff --git a/src/calibre/library/catalogs/epub_mobi_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py index cabe14deab..79a6964821 100644 --- a/src/calibre/library/catalogs/epub_mobi_builder.py +++ b/src/calibre/library/catalogs/epub_mobi_builder.py @@ -35,7 +35,7 @@ from calibre.utils.date import ( ) from calibre.utils.filenames import ascii_text, shorten_components_to from calibre.utils.formatter import TemplateFormatter -from calibre.utils.icu import capitalize, collation_order, sort_key +from calibre.utils.icu import capitalize, collation_order, sort_key, upper as icu_upper from calibre.utils.localization import get_lang, lang_as_iso639_1 from calibre.utils.resources import get_image_path as I, get_path as P from calibre.utils.xml_parse import safe_xml_fromstring diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 268258a21a..3ea0ecb429 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -58,7 +58,7 @@ from calibre.utils.filenames import ( WindowsAtomicFolderMove, ascii_filename, hardlink_file, samefile, ) from calibre.utils.formatter_functions import load_user_template_functions -from calibre.utils.icu import lower, sort_key, strcmp +from calibre.utils.icu import lower, lower as icu_lower, sort_key, strcmp from calibre.utils.img import save_cover_data_to from calibre.utils.localization import calibre_langcode_to_name, canonicalize_lang from calibre.utils.recycle_bin import delete_file, delete_tree diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index be54e3f652..f1efea9a9f 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -8,6 +8,7 @@ import traceback from collections import OrderedDict from calibre.utils.config_base import tweaks +from calibre.utils.icu import lower as icu_lower from polyglot.builtins import iteritems, itervalues category_icon_map = { diff --git a/src/calibre/srv/metadata.py b/src/calibre/srv/metadata.py index 6960b89d8f..8f3dabb16a 100644 --- a/src/calibre/srv/metadata.py +++ b/src/calibre/srv/metadata.py @@ -3,8 +3,8 @@ import os -from copy import copy from collections import namedtuple +from copy import copy from datetime import datetime, time from functools import partial from threading import Lock @@ -12,14 +12,14 @@ from threading import Lock from calibre.constants import config_dir from calibre.db.categories import Tag, category_display_order from calibre.ebooks.metadata.sources.identify import urls_from_identifiers -from calibre.utils.date import isoformat, UNDEFINED_DATE, local_tz -from calibre.utils.config import tweaks -from calibre.utils.formatter import EvalFormatter -from calibre.utils.file_type_icons import EXT_MAP -from calibre.utils.icu import collation_order_for_partitioning -from calibre.utils.localization import calibre_langcode_to_name from calibre.library.comments import comments_to_html, markdown from calibre.library.field_metadata import category_icon_map +from calibre.utils.config import tweaks +from calibre.utils.date import UNDEFINED_DATE, isoformat, local_tz +from calibre.utils.file_type_icons import EXT_MAP +from calibre.utils.formatter import EvalFormatter +from calibre.utils.icu import collation_order_for_partitioning, upper as icu_upper +from calibre.utils.localization import calibre_langcode_to_name from polyglot.builtins import iteritems, itervalues from polyglot.urllib import quote diff --git a/src/calibre/utils/fonts/scanner.py b/src/calibre/utils/fonts/scanner.py index d3b2983308..c1985250e2 100644 --- a/src/calibre/utils/fonts/scanner.py +++ b/src/calibre/utils/fonts/scanner.py @@ -14,7 +14,7 @@ from calibre.constants import ( DEBUG, config_dir, filesystem_encoding, ismacos, iswindows, isworker, ) from calibre.utils.fonts.metadata import FontMetadata, UnsupportedFont -from calibre.utils.icu import sort_key +from calibre.utils.icu import lower as icu_lower, sort_key from calibre.utils.resources import get_path as P from polyglot.builtins import itervalues diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index ed7c86b086..30540ca2d4 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -26,7 +26,7 @@ from calibre.constants import DEBUG from calibre.ebooks.metadata import title_sort from calibre.utils.config import tweaks from calibre.utils.date import UNDEFINED_DATE, format_date, now, parse_date -from calibre.utils.icu import capitalize, sort_key, strcmp +from calibre.utils.icu import capitalize, lower as icu_lower, sort_key, strcmp from calibre.utils.localization import _, calibre_langcode_to_name, canonicalize_lang from calibre.utils.titlecase import titlecase from polyglot.builtins import iteritems, itervalues diff --git a/src/calibre/utils/icu.py b/src/calibre/utils/icu.py index 6ee1c4ed8a..596adfee46 100644 --- a/src/calibre/utils/icu.py +++ b/src/calibre/utils/icu.py @@ -287,7 +287,7 @@ def partition_by_first_letter(items, reverse=False, key=lambda x:x): ans = OrderedDict() last_c, last_ordnum = ' ', 0 for item in items: - c = icu_upper(key(item) or ' ') + c = upper(key(item) or ' ') ordnum, ordlen = collation_order(c) if last_ordnum != ordnum: last_c = c[0:1] diff --git a/src/calibre/utils/matcher.py b/src/calibre/utils/matcher.py index 07f1c799d0..aac31436e0 100644 --- a/src/calibre/utils/matcher.py +++ b/src/calibre/utils/matcher.py @@ -4,17 +4,22 @@ __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' -import atexit, os, sys -from math import ceil -from unicodedata import normalize -from threading import Thread, Lock -from operator import itemgetter +import atexit +import os +import sys from collections import OrderedDict from itertools import islice +from math import ceil +from operator import itemgetter +from threading import Lock, Thread +from unicodedata import normalize -from calibre import detect_ncpus as cpu_count, as_unicode +from calibre import as_unicode, detect_ncpus as cpu_count from calibre.constants import filesystem_encoding -from calibre.utils.icu import primary_sort_key, primary_find, primary_collator +from calibre.utils.icu import ( + lower as icu_lower, primary_collator, primary_find, primary_sort_key, + upper as icu_upper, +) from polyglot.builtins import iteritems, itervalues from polyglot.queue import Queue @@ -275,6 +280,7 @@ def test(return_tests=False): @unittest.skipIf(is_sanitized, 'Sanitizer enabled can\'t check for leaks') def test_mem_leaks(self): import gc + from calibre.utils.mem import get_memory as memory m = Matcher(['a'], scorer=CScorer) m('a') From 56c18998686e9af289f3de14cc6324aecbe664e3 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 9 Jan 2023 16:29:13 +0000 Subject: [PATCH 0099/2055] Enhancement #2002257: multiple template tester dialogs. Also changed the quickview dialog to be parentless. --- .../gui2/actions/show_template_tester.py | 41 +++++++++++++--- src/calibre/gui2/dialogs/quickview.py | 5 +- src/calibre/gui2/dialogs/template_dialog.py | 49 ++++++++++++++----- 3 files changed, 73 insertions(+), 22 deletions(-) diff --git a/src/calibre/gui2/actions/show_template_tester.py b/src/calibre/gui2/actions/show_template_tester.py index 5b5ae8208d..be163e8639 100644 --- a/src/calibre/gui2/actions/show_template_tester.py +++ b/src/calibre/gui2/actions/show_template_tester.py @@ -5,8 +5,6 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' - -from qt.core import QDialog from calibre.gui2.actions import InterfaceAction from calibre.gui2.dialogs.template_dialog import TemplateDialog from calibre.gui2 import error_dialog @@ -23,6 +21,17 @@ class ShowTemplateTesterAction(InterfaceAction): self.previous_text = _('Enter a template to test using data from the selected book') self.first_time = True self.qaction.triggered.connect(self.show_template_editor) + self.window_title = self.action_spec[0] + + # self.hidden_menu = QMenu() + # self.non_modal_window_title = _('Template tester -- separate dialog') + # self.shortcut_action = self.create_menu_action( + # menu=self.hidden_menu, + # unique_name='Template tester', + # text=self.non_modal_window_title, + # icon='debug.png', + # triggered=partial(self.show_template_editor, modal=False)) + self.non_modal_dialogs = list() def last_template_text(self): return self.previous_text @@ -44,9 +53,27 @@ class ShowTemplateTesterAction(InterfaceAction): if row.isValid(): mi.append(db.new_api.get_proxy_metadata(db.data.index_to_id(row.row()))) if mi: + for dn in range(-1, len(self.non_modal_dialogs)): + if dn < 0: + continue + if self.non_modal_dialogs[dn] is None: + break + else: + dn = len(self.non_modal_dialogs) + if dn == len(self.non_modal_dialogs): + self.non_modal_dialogs.append(True) + else: + self.non_modal_dialogs[dn] = True t = TemplateDialog(self.gui, self.previous_text, - mi, text_is_placeholder=self.first_time) - t.setWindowTitle(_('Template tester')) - if t.exec() == QDialog.DialogCode.Accepted: - self.previous_text = t.rule[1] - self.first_time = False + mi, text_is_placeholder=self.first_time, + dialog_number=dn) + self.non_modal_dialogs[dn] = t + t.setWindowTitle(self.window_title, dialog_number=dn+1) + t.tester_closed.connect(self.save_template_text) + t.show() + + def save_template_text(self, txt, dialog_number): + if txt is not None: + self.previous_text = txt + self.first_time = False + self.non_modal_dialogs[dialog_number] = None diff --git a/src/calibre/gui2/dialogs/quickview.py b/src/calibre/gui2/dialogs/quickview.py index 3c77b86a12..98b033d1f5 100644 --- a/src/calibre/gui2/dialogs/quickview.py +++ b/src/calibre/gui2/dialogs/quickview.py @@ -154,11 +154,10 @@ class Quickview(QDialog, Ui_Quickview): def __init__(self, gui, row, toggle_shortcut): self.is_pane = gprefs.get('quickview_is_pane', False) - if not self.is_pane: - QDialog.__init__(self, gui, flags=Qt.WindowType.Widget) + QDialog.__init__(self, None, flags=Qt.WindowType.Window) else: - QDialog.__init__(self, gui) + QDialog.__init__(self, None, flags=Qt.WindowType.Dialog) Ui_Quickview.__init__(self) self.setupUi(self) self.isClosed = False diff --git a/src/calibre/gui2/dialogs/template_dialog.py b/src/calibre/gui2/dialogs/template_dialog.py index 5077bb980f..96e73e0be6 100644 --- a/src/calibre/gui2/dialogs/template_dialog.py +++ b/src/calibre/gui2/dialogs/template_dialog.py @@ -15,7 +15,7 @@ from qt.core import ( QAbstractItemView, QApplication, QColor, QComboBox, QCursor, QDialog, QDialogButtonBox, QFont, QFontDatabase, QFontInfo, QFontMetrics, QIcon, QLineEdit, QPalette, QSize, QSyntaxHighlighter, Qt, QTableWidget, QTableWidgetItem, - QTextCharFormat, QTextOption, QVBoxLayout, + QTextCharFormat, QTextOption, QVBoxLayout, pyqtSignal ) from calibre import sanitize_file_name @@ -316,15 +316,35 @@ translate_table = str.maketrans({ class TemplateDialog(QDialog, Ui_TemplateDialog): + tester_closed = pyqtSignal(object, object) + + def setWindowTitle(self, title, dialog_number=None): + if dialog_number is None: + title = _('{title} (only one template dialog allowed)').format(title=title) + else: + title = _('{title} dialog number {number} (multiple template dialogs allowed)').format( + title=title, number=dialog_number) + super().setWindowTitle(title) + def __init__(self, parent, text, mi=None, fm=None, color_field=None, icon_field_key=None, icon_rule_kind=None, doing_emblem=False, text_is_placeholder=False, dialog_is_st_editor=False, global_vars=None, all_functions=None, builtin_functions=None, - python_context_object=None): - QDialog.__init__(self, parent) + python_context_object=None, dialog_number=None): + # If dialog_number isn't None then we want separate non-modal windows + # that don't stay on top of the main dialog. This lets Alt-Tab work to + # switch between them. dialog_number must be set only by the template + # tester, not the rules dialogs etc that depend on modality. + if dialog_number is None: + QDialog.__init__(self, parent, flags=Qt.WindowType.Dialog) + else: + QDialog.__init__(self, None, flags=Qt.WindowType.Window) + self.raise_() # Not needed on windows but here just in case Ui_TemplateDialog.__init__(self) self.setupUi(self) + self.setWindowIcon(self.windowIcon()) + self.dialog_number = dialog_number self.coloring = color_field is not None self.iconing = icon_field_key is not None self.embleming = doing_emblem @@ -387,10 +407,6 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): self.icon_field.setCurrentIndex(self.icon_field.findData(icon_field_key)) self.setup_saved_template_editor(not dialog_is_st_editor, dialog_is_st_editor) - # Remove help icon on title bar - icon = self.windowIcon() - self.setWindowFlags(self.windowFlags()&(~Qt.WindowType.WindowContextHelpButtonHint)) - self.setWindowIcon(icon) self.all_functions = all_functions if all_functions else formatter_functions().get_functions() self.builtins = (builtin_functions if builtin_functions else @@ -411,8 +427,7 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): # Set up the display table self.table_column_widths = None try: - self.table_column_widths = \ - gprefs.get('template_editor_table_widths', None) + self.table_column_widths = gprefs.get(self.geometry_string('template_editor_table_widths'), None) except: pass self.set_mi(mi, fm) @@ -483,7 +498,12 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): self.textbox.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.textbox.customContextMenuRequested.connect(self.show_context_menu) # Now geometry - self.restore_geometry(gprefs, 'template_editor_dialog_geometry') + self.restore_geometry(gprefs, self.geometry_string('template_editor_dialog_geometry')) + + def geometry_string(self, txt): + if self.dialog_number is None or self.dialog_number == 0: + return txt + return txt + '_' + str(self.dialog_number) def setup_saved_template_editor(self, show_buttonbox, show_doc_and_name): self.buttonBox.setVisible(show_buttonbox) @@ -890,8 +910,8 @@ def evaluate(book, context): self.table_column_widths.append(self.template_value.columnWidth(c)) def save_geometry(self): - gprefs['template_editor_table_widths'] = self.table_column_widths - super().save_geometry(gprefs, 'template_editor_dialog_geometry') + gprefs[self.geometry_string('template_editor_table_widths')] = self.table_column_widths + super().save_geometry(gprefs, self.geometry_string('template_editor_dialog_geometry')) def keyPressEvent(self, ev): if ev.key() == Qt.Key.Key_Escape: @@ -932,8 +952,11 @@ def evaluate(book, context): self.rule = ('', txt) self.save_geometry() QDialog.accept(self) + if self.dialog_number is not None: + self.tester_closed.emit(txt, self.dialog_number) def reject(self): + self.save_geometry() QDialog.reject(self) if self.dialog_is_st_editor: parent = self.parent() @@ -944,6 +967,8 @@ def evaluate(book, context): parent = parent.parent() if parent is None: break + if self.dialog_number is not None: + self.tester_closed.emit(None, self.dialog_number) class BreakReporterItem(QTableWidgetItem): From 6014aec7ae4e69879ca2e95ffef98d479a03cdb1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 9 Jan 2023 22:16:29 +0530 Subject: [PATCH 0100/2055] Remove more global functions --- src/calibre/ebooks/metadata/book/base.py | 1 + src/calibre/ebooks/oeb/base.py | 31 +++++--- src/calibre/ebooks/oeb/polish/css.py | 1 + src/calibre/ebooks/oeb/transforms/jacket.py | 1 + .../ebooks/oeb/transforms/manglecase.py | 4 +- src/calibre/gui2/actions/add.py | 5 +- src/calibre/gui2/actions/author_mapper.py | 4 +- src/calibre/gui2/actions/choose_library.py | 11 +-- src/calibre/gui2/actions/convert.py | 10 +-- src/calibre/gui2/actions/copy_to_library.py | 9 ++- src/calibre/gui2/actions/delete.py | 3 +- src/calibre/gui2/actions/edit_metadata.py | 6 +- src/calibre/gui2/actions/embed.py | 4 +- src/calibre/gui2/actions/polish.py | 20 +++-- src/calibre/gui2/actions/tag_mapper.py | 4 +- src/calibre/gui2/add.py | 1 + src/calibre/gui2/convert/bulk.py | 18 +++-- src/calibre/gui2/device.py | 73 +++++++++++-------- src/calibre/gui2/dialogs/duplicates.py | 9 ++- src/calibre/gui2/dialogs/match_books.py | 8 +- src/calibre/gui2/dialogs/message_box.py | 5 +- src/calibre/gui2/dialogs/metadata_bulk.py | 1 + src/calibre/gui2/dialogs/template_dialog.py | 6 +- src/calibre/gui2/fts/search.py | 10 +-- src/calibre/gui2/init.py | 10 +-- src/calibre/gui2/jobs.py | 32 ++++---- src/calibre/gui2/library/annotations.py | 15 ++-- src/calibre/gui2/library/models.py | 2 +- src/calibre/gui2/metadata/basic_widgets.py | 21 +++--- src/calibre/gui2/metadata/bulk_download.py | 16 ++-- src/calibre/gui2/metadata/diff.py | 1 + src/calibre/gui2/metadata/single.py | 2 +- src/calibre/gui2/preferences/coloring.py | 19 +++-- .../gui2/preferences/create_custom_column.py | 13 ++-- .../gui2/preferences/metadata_sources.py | 7 +- src/calibre/gui2/preferences/server.py | 9 ++- src/calibre/gui2/search_restriction_mixin.py | 4 +- src/calibre/gui2/tag_browser/ui.py | 23 +++--- src/calibre/gui2/tweak_book/boss.py | 40 +++++----- src/calibre/gui2/tweak_book/check_links.py | 16 ++-- src/calibre/gui2/tweak_book/download.py | 18 +++-- src/calibre/gui2/tweak_book/file_list.py | 12 +-- src/calibre/gui2/tweak_book/manage_fonts.py | 1 + src/calibre/gui2/tweak_book/preferences.py | 39 +++++----- src/calibre/gui2/update.py | 12 +-- src/calibre/gui2/viewer/highlights.py | 11 +-- src/calibre/gui2/viewer/search.py | 4 +- .../library/catalogs/epub_mobi_builder.py | 6 +- src/calibre/library/field_metadata.py | 1 + src/calibre/srv/manage_users_cli.py | 3 +- src/calibre/srv/opds.py | 1 + 51 files changed, 327 insertions(+), 256 deletions(-) diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index ed6b15e1e0..a7afd67f46 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -16,6 +16,7 @@ from calibre.ebooks.metadata.book import ( ) from calibre.library.field_metadata import FieldMetadata from calibre.utils.icu import lower as icu_lower, sort_key +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems, string_or_bytes # Special sets used to optimize the performance of getting and setting diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index 746f7133ef..a0ef201374 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -6,25 +6,32 @@ __license__ = 'GPL v3' __copyright__ = '2008, Marshall T. Vandegrift ' __docformat__ = 'restructuredtext en' -import os, re, logging, sys, numbers +import logging +import numbers +import os +import re +import sys from collections import defaultdict from itertools import count +from lxml import etree, html from operator import attrgetter -from lxml import etree, html -from calibre import force_unicode -from calibre.constants import filesystem_encoding, __version__ -from calibre.translations.dynamic import translate -from calibre.utils.xml_parse import safe_xml_fromstring +from calibre import as_unicode, force_unicode, get_types_map, isbytestring +from calibre.constants import __version__, filesystem_encoding from calibre.ebooks.chardet import xml_to_unicode from calibre.ebooks.conversion.preprocess import CSSPreProcessor -from calibre import (isbytestring, as_unicode, get_types_map) -from calibre.ebooks.oeb.parse_utils import barename, XHTML_NS, namespace, XHTML, parse_html, NotHTML +from calibre.ebooks.oeb.parse_utils import ( + XHTML, XHTML_NS, NotHTML, barename, namespace, parse_html, +) +from calibre.translations.dynamic import translate from calibre.utils.cleantext import clean_xml_chars +from calibre.utils.icu import numeric_sort_key, title_case as icu_title from calibre.utils.short_uuid import uuid4 -from polyglot.builtins import iteritems, string_or_bytes, itervalues, codepoint_to_chr -from polyglot.urllib import unquote as urlunquote, urldefrag, urljoin, urlparse, urlunparse -from calibre.utils.icu import numeric_sort_key +from calibre.utils.xml_parse import safe_xml_fromstring +from polyglot.builtins import codepoint_to_chr, iteritems, itervalues, string_or_bytes +from polyglot.urllib import ( + unquote as urlunquote, urldefrag, urljoin, urlparse, urlunparse, +) XML_NS = 'http://www.w3.org/XML/1998/namespace' OEB_DOC_NS = 'http://openebook.org/namespaces/oeb-document/1.0/' @@ -249,7 +256,7 @@ def rewrite_links(root, link_repl_func, resolve_base_href=False): If the ``link_repl_func`` returns None, the attribute or tag text will be removed completely. ''' - from css_parser import replaceUrls, log, CSSParser + from css_parser import CSSParser, log, replaceUrls log.setLevel(logging.WARN) log.raiseExceptions = False diff --git a/src/calibre/ebooks/oeb/polish/css.py b/src/calibre/ebooks/oeb/polish/css.py index 8155eaeee5..37db8222f5 100644 --- a/src/calibre/ebooks/oeb/polish/css.py +++ b/src/calibre/ebooks/oeb/polish/css.py @@ -17,6 +17,7 @@ from calibre.ebooks.oeb.polish.pretty import ( pretty_script_or_style, pretty_xml_tree, serialize, ) from calibre.utils.icu import lower as icu_lower, numeric_sort_key +from calibre.utils.localization import ngettext from css_selectors import Select, SelectorError, SelectorSyntaxError, parse from polyglot.builtins import iteritems, itervalues from polyglot.functools import lru_cache diff --git a/src/calibre/ebooks/oeb/transforms/jacket.py b/src/calibre/ebooks/oeb/transforms/jacket.py index 9b1b0fb27a..0add5bfb72 100644 --- a/src/calibre/ebooks/oeb/transforms/jacket.py +++ b/src/calibre/ebooks/oeb/transforms/jacket.py @@ -24,6 +24,7 @@ from calibre.library.comments import comments_to_html, markdown from calibre.utils.config import tweaks from calibre.utils.date import as_local_time, format_date, is_date_undefined from calibre.utils.icu import sort_key +from calibre.utils.localization import ngettext from calibre.utils.resources import get_path as P JACKET_XPATH = '//h:meta[@name="calibre-content" and @content="jacket"]' diff --git a/src/calibre/ebooks/oeb/transforms/manglecase.py b/src/calibre/ebooks/oeb/transforms/manglecase.py index 835c1939d3..c2f76239d2 100644 --- a/src/calibre/ebooks/oeb/transforms/manglecase.py +++ b/src/calibre/ebooks/oeb/transforms/manglecase.py @@ -9,7 +9,9 @@ from lxml import etree from calibre.ebooks.oeb.base import CSS_MIME, XHTML, XHTML_NS, namespace from calibre.ebooks.oeb.stylizer import Stylizer -from calibre.utils.icu import lower as icu_lower, upper as icu_upper +from calibre.utils.icu import ( + lower as icu_lower, title_case as icu_title, upper as icu_upper, +) from polyglot.builtins import string_or_bytes CASE_MANGLER_CSS = """ diff --git a/src/calibre/gui2/actions/add.py b/src/calibre/gui2/actions/add.py index e9b0b55fbc..d2bc9bcd53 100644 --- a/src/calibre/gui2/actions/add.py +++ b/src/calibre/gui2/actions/add.py @@ -15,8 +15,8 @@ from calibre.constants import iswindows from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks.metadata import MetaInformation, normalize_isbn from calibre.gui2 import ( - choose_dir, choose_files, choose_files_and_remember_all_files, error_dialog, - gprefs, info_dialog, question_dialog, warning_dialog + choose_dir, choose_files, choose_files_and_remember_all_files, error_dialog, gprefs, + info_dialog, question_dialog, warning_dialog, ) from calibre.gui2.actions import InterfaceAction from calibre.gui2.dialogs.add_empty_book import AddEmptyBookDialog @@ -26,6 +26,7 @@ from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.config_base import tweaks from calibre.utils.filenames import ascii_filename, make_long_path_useable from calibre.utils.icu import sort_key +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems, string_or_bytes diff --git a/src/calibre/gui2/actions/author_mapper.py b/src/calibre/gui2/actions/author_mapper.py index efa842b4a8..abc25fcb06 100644 --- a/src/calibre/gui2/actions/author_mapper.py +++ b/src/calibre/gui2/actions/author_mapper.py @@ -3,8 +3,10 @@ from qt.core import QDialog + from calibre.gui2 import gprefs from calibre.gui2.actions import InterfaceAction +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems @@ -27,7 +29,7 @@ class AuthorMapAction(InterfaceAction): self.do_map(ids, selected) def do_map(self, book_ids, selected): - from calibre.ebooks.metadata.author_mapper import map_authors, compile_rules + from calibre.ebooks.metadata.author_mapper import compile_rules, map_authors from calibre.gui2.author_mapper import RulesDialog from calibre.gui2.widgets import BusyCursor d = RulesDialog(self.gui) diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index a1e08f687e..e68fe447f9 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -9,25 +9,26 @@ import posixpath import sys import weakref from contextlib import suppress -from functools import partial, lru_cache +from functools import lru_cache, partial from qt.core import ( QAction, QCoreApplication, QDialog, QDialogButtonBox, QGridLayout, QIcon, - QInputDialog, QLabel, QLineEdit, QMenu, QSize, Qt, QTimer, QToolButton, - QVBoxLayout, pyqtSignal + QInputDialog, QLabel, QLineEdit, QMenu, QSize, Qt, QTimer, QToolButton, QVBoxLayout, + pyqtSignal, ) from calibre import isbytestring, sanitize_file_name from calibre.constants import ( - config_dir, filesystem_encoding, get_portable_base, isportable, iswindows + config_dir, filesystem_encoding, get_portable_base, isportable, iswindows, ) from calibre.gui2 import ( Dispatcher, choose_dir, choose_images, error_dialog, gprefs, info_dialog, - open_local_file, pixmap_to_data, question_dialog, warning_dialog + open_local_file, pixmap_to_data, question_dialog, warning_dialog, ) from calibre.gui2.actions import InterfaceAction from calibre.library import current_library_name from calibre.utils.config import prefs, tweaks from calibre.utils.icu import sort_key +from calibre.utils.localization import ngettext def db_class(): diff --git a/src/calibre/gui2/actions/convert.py b/src/calibre/gui2/actions/convert.py index bf52e9aa7d..0cfd8c39b1 100644 --- a/src/calibre/gui2/actions/convert.py +++ b/src/calibre/gui2/actions/convert.py @@ -7,14 +7,14 @@ __docformat__ = 'restructuredtext en' import os from functools import partial - from qt.core import QModelIndex, QTimer -from calibre.gui2 import error_dialog, Dispatcher, gprefs -from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebook -from calibre.utils.config import prefs, tweaks -from calibre.gui2.actions import InterfaceAction from calibre.customize.ui import plugin_for_input_format +from calibre.gui2 import Dispatcher, error_dialog, gprefs +from calibre.gui2.actions import InterfaceAction +from calibre.gui2.tools import convert_bulk_ebook, convert_single_ebook +from calibre.utils.config import prefs, tweaks +from calibre.utils.localization import ngettext class ConvertAction(InterfaceAction): diff --git a/src/calibre/gui2/actions/copy_to_library.py b/src/calibre/gui2/actions/copy_to_library.py index 02929721d6..fbbbf92ad8 100644 --- a/src/calibre/gui2/actions/copy_to_library.py +++ b/src/calibre/gui2/actions/copy_to_library.py @@ -10,9 +10,9 @@ from collections import defaultdict from contextlib import closing from functools import partial from qt.core import ( - QAbstractItemView, QApplication, QCheckBox, QDialog, QDialogButtonBox, - QFormLayout, QGridLayout, QHBoxLayout, QIcon, QLabel, QLineEdit, QListWidget, - QListWidgetItem, QScrollArea, QSize, Qt, QToolButton, QVBoxLayout, QWidget + QAbstractItemView, QApplication, QCheckBox, QDialog, QDialogButtonBox, QFormLayout, + QGridLayout, QHBoxLayout, QIcon, QLabel, QLineEdit, QListWidget, QListWidgetItem, + QScrollArea, QSize, Qt, QToolButton, QVBoxLayout, QWidget, ) from threading import Thread @@ -20,7 +20,7 @@ from calibre import as_unicode from calibre.constants import ismacos from calibre.db.copy_to_library import copy_one_book from calibre.gui2 import ( - Dispatcher, choose_dir, error_dialog, gprefs, info_dialog, warning_dialog + Dispatcher, choose_dir, error_dialog, gprefs, info_dialog, warning_dialog, ) from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions.choose_library import library_qicon @@ -28,6 +28,7 @@ from calibre.gui2.dialogs.progress import ProgressDialog from calibre.gui2.widgets2 import Dialog from calibre.utils.config import prefs from calibre.utils.icu import numeric_sort_key, sort_key +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems, itervalues diff --git a/src/calibre/gui2/actions/delete.py b/src/calibre/gui2/actions/delete.py index c5783742fe..0f996c3f70 100644 --- a/src/calibre/gui2/actions/delete.py +++ b/src/calibre/gui2/actions/delete.py @@ -17,8 +17,9 @@ from calibre.gui2.actions import InterfaceAction from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.confirm_delete_location import confirm_location from calibre.gui2.dialogs.delete_matching_from_device import ( - DeleteMatchingFromDeviceDialog + DeleteMatchingFromDeviceDialog, ) +from calibre.utils.localization import ngettext from calibre.utils.recycle_bin import can_recycle single_shot = partial(QTimer.singleShot, 10) diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index e0216c909b..a2bdd1fa69 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -8,12 +8,11 @@ __docformat__ = 'restructuredtext en' import copy import os import shutil -from functools import partial from contextlib import contextmanager +from functools import partial from io import BytesIO from qt.core import ( - QAction, QApplication, QDialog, QIcon, QMenu, QMimeData, QModelIndex, QTimer, - QUrl + QAction, QApplication, QDialog, QIcon, QMenu, QMimeData, QModelIndex, QTimer, QUrl, ) from calibre.db.errors import NoSuchFormat @@ -31,6 +30,7 @@ from calibre.library.comments import merge_comments from calibre.utils.config import tweaks from calibre.utils.date import is_date_undefined from calibre.utils.icu import sort_key +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/actions/embed.py b/src/calibre/gui2/actions/embed.py index c06e1a43d6..83beb2569c 100644 --- a/src/calibre/gui2/actions/embed.py +++ b/src/calibre/gui2/actions/embed.py @@ -5,12 +5,12 @@ __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' from functools import partial - -from qt.core import QTimer, QProgressDialog, Qt +from qt.core import QProgressDialog, Qt, QTimer from calibre import force_unicode from calibre.gui2 import gprefs from calibre.gui2.actions import InterfaceAction +from calibre.utils.localization import ngettext class EmbedAction(InterfaceAction): diff --git a/src/calibre/gui2/actions/polish.py b/src/calibre/gui2/actions/polish.py index c79c17a321..944978339f 100644 --- a/src/calibre/gui2/actions/polish.py +++ b/src/calibre/gui2/actions/polish.py @@ -5,22 +5,26 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, weakref, shutil, textwrap +import os +import shutil +import textwrap +import weakref from collections import OrderedDict from functools import partial -from polyglot.builtins import iteritems, itervalues +from qt.core import ( + QApplication, QCheckBox, QDialog, QDialogButtonBox, QFrame, QGridLayout, QIcon, + QInputDialog, QLabel, QMenu, QModelIndex, QSize, QSizePolicy, QSpacerItem, Qt, + QTextEdit, QTimer, +) -from qt.core import (QDialog, QGridLayout, QIcon, QCheckBox, QLabel, QFrame, - QApplication, QDialogButtonBox, Qt, QSize, QSpacerItem, - QSizePolicy, QTimer, QModelIndex, QTextEdit, - QInputDialog, QMenu) - -from calibre.gui2 import error_dialog, Dispatcher, gprefs, question_dialog +from calibre.gui2 import Dispatcher, error_dialog, gprefs, question_dialog from calibre.gui2.actions import InterfaceAction from calibre.gui2.convert.metadata import create_opf_file from calibre.gui2.dialogs.progress import ProgressDialog from calibre.ptempfile import PersistentTemporaryDirectory from calibre.utils.config_base import tweaks +from calibre.utils.localization import ngettext +from polyglot.builtins import iteritems, itervalues class Polish(QDialog): # {{{ diff --git a/src/calibre/gui2/actions/tag_mapper.py b/src/calibre/gui2/actions/tag_mapper.py index 83c9ccc402..4ad0b5bb90 100644 --- a/src/calibre/gui2/actions/tag_mapper.py +++ b/src/calibre/gui2/actions/tag_mapper.py @@ -1,12 +1,12 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2015, Kovid Goyal - from qt.core import QDialog -from polyglot.builtins import iteritems from calibre.gui2 import gprefs from calibre.gui2.actions import InterfaceAction +from calibre.utils.localization import ngettext +from polyglot.builtins import iteritems class TagMapAction(InterfaceAction): diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py index b91351c02b..dcac357d41 100644 --- a/src/calibre/gui2/add.py +++ b/src/calibre/gui2/add.py @@ -33,6 +33,7 @@ from calibre.utils.config import prefs from calibre.utils.filenames import make_long_path_useable from calibre.utils.icu import lower as icu_lower from calibre.utils.ipc.pool import Failure, Pool +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems, string_or_bytes from polyglot.queue import Empty diff --git a/src/calibre/gui2/convert/bulk.py b/src/calibre/gui2/convert/bulk.py index ab187c8805..cfd2c97405 100644 --- a/src/calibre/gui2/convert/bulk.py +++ b/src/calibre/gui2/convert/bulk.py @@ -3,20 +3,22 @@ __copyright__ = '2009, John Schember ' __docformat__ = 'restructuredtext en' import shutil +from qt.core import QDialog, QDialogButtonBox, QModelIndex -from qt.core import QModelIndex, QDialog, QDialogButtonBox - -from calibre.gui2.convert.single import Config, GroupModel, gprefs -from calibre.gui2.convert.look_and_feel import LookAndFeelWidget +from calibre.ebooks.conversion.config import ( + get_output_formats, sort_formats_by_preference, +) +from calibre.ebooks.conversion.plumber import Plumber +from calibre.gui2.convert import GuiRecommendations from calibre.gui2.convert.heuristics import HeuristicsWidget -from calibre.gui2.convert.search_and_replace import SearchAndReplaceWidget +from calibre.gui2.convert.look_and_feel import LookAndFeelWidget from calibre.gui2.convert.page_setup import PageSetupWidget +from calibre.gui2.convert.search_and_replace import SearchAndReplaceWidget +from calibre.gui2.convert.single import Config, GroupModel, gprefs from calibre.gui2.convert.structure_detection import StructureDetectionWidget from calibre.gui2.convert.toc import TOCWidget -from calibre.gui2.convert import GuiRecommendations -from calibre.ebooks.conversion.plumber import Plumber -from calibre.ebooks.conversion.config import sort_formats_by_preference, get_output_formats from calibre.utils.config import prefs +from calibre.utils.localization import ngettext from calibre.utils.logging import Log diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 09e7cce924..a9da487129 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -1,41 +1,56 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' +import io + # Imports {{{ -import os, traceback, time, io, re, sys, weakref -from threading import Thread, Event - +import os +import re +import sys +import time +import traceback +import weakref from qt.core import ( - QMenu, QAction, QActionGroup, QIcon, pyqtSignal, QDialog, - QObject, QVBoxLayout, QDialogButtonBox, QCoreApplication, - QEventLoop, QTimer) + QAction, QActionGroup, QCoreApplication, QDialog, QDialogButtonBox, QEventLoop, + QIcon, QMenu, QObject, QTimer, QVBoxLayout, pyqtSignal, +) +from threading import Event, Thread -from calibre.customize.ui import (available_input_formats, available_output_formats, - device_plugins, disabled_device_plugins) -from calibre.devices.interface import DevicePlugin, currently_connected_device -from calibre.devices.errors import (UserFeedback, OpenFeedback, OpenFailed, OpenActionNeeded, - InitialConnectionError) -from calibre.ebooks.covers import cprefs, override_prefs, scale_cover, generate_cover -from calibre.gui2.dialogs.choose_format_device import ChooseFormatDeviceDialog -from calibre.utils.ipc.job import BaseJob -from calibre.devices.scanner import DeviceScanner -from calibre.gui2 import (config, error_dialog, Dispatcher, dynamic, - warning_dialog, info_dialog, choose_dir, FunctionDispatcher, - show_restart_warning, gprefs, question_dialog) -from calibre.gui2.widgets import BusyCursor -from calibre.ebooks.metadata import authors_to_string -from calibre import preferred_encoding, prints, force_unicode, as_unicode, sanitize_file_name -from calibre.utils.filenames import ascii_filename -from calibre.devices.errors import (FreeSpaceError, WrongDestinationError, - BlacklistedDevice) -from calibre.devices.folder_device.driver import FOLDER_DEVICE +from calibre import ( + as_unicode, force_unicode, preferred_encoding, prints, sanitize_file_name, +) from calibre.constants import DEBUG -from calibre.utils.config import tweaks, device_prefs -from calibre.utils.img import scale_image +from calibre.customize.ui import ( + available_input_formats, available_output_formats, device_plugins, + disabled_device_plugins, +) +from calibre.devices.errors import ( + BlacklistedDevice, FreeSpaceError, InitialConnectionError, OpenActionNeeded, + OpenFailed, OpenFeedback, UserFeedback, WrongDestinationError, +) +from calibre.devices.folder_device.driver import FOLDER_DEVICE +from calibre.devices.interface import DevicePlugin, currently_connected_device +from calibre.devices.scanner import DeviceScanner +from calibre.ebooks.covers import cprefs, generate_cover, override_prefs, scale_cover +from calibre.ebooks.metadata import authors_to_string +from calibre.gui2 import ( + Dispatcher, FunctionDispatcher, choose_dir, config, dynamic, error_dialog, gprefs, + info_dialog, question_dialog, show_restart_warning, warning_dialog, +) +from calibre.gui2.dialogs.choose_format_device import ChooseFormatDeviceDialog +from calibre.gui2.widgets import BusyCursor from calibre.library.save_to_disk import find_plugboard -from calibre.ptempfile import PersistentTemporaryFile, force_unicode as filename_to_unicode -from polyglot.builtins import string_or_unicode +from calibre.ptempfile import ( + PersistentTemporaryFile, force_unicode as filename_to_unicode, +) +from calibre.utils.config import device_prefs, tweaks +from calibre.utils.filenames import ascii_filename +from calibre.utils.img import scale_image +from calibre.utils.ipc.job import BaseJob +from calibre.utils.localization import ngettext from polyglot import queue +from polyglot.builtins import string_or_unicode + # }}} diff --git a/src/calibre/gui2/dialogs/duplicates.py b/src/calibre/gui2/dialogs/duplicates.py index b7f460574c..74815ea069 100644 --- a/src/calibre/gui2/dialogs/duplicates.py +++ b/src/calibre/gui2/dialogs/duplicates.py @@ -6,14 +6,15 @@ __copyright__ = '2013, Kovid Goyal ' __docformat__ = 'restructuredtext en' import os.path - from qt.core import ( - QDialog, QGridLayout, QIcon, QLabel, QTreeWidget, QTreeWidgetItem, Qt, - QFont, QDialogButtonBox, QApplication) + QApplication, QDialog, QDialogButtonBox, QFont, QGridLayout, QIcon, QLabel, Qt, + QTreeWidget, QTreeWidgetItem, +) -from calibre.gui2 import gprefs from calibre.ebooks.metadata import authors_to_string +from calibre.gui2 import gprefs from calibre.utils.icu import primary_sort_key +from calibre.utils.localization import ngettext class DuplicatesQuestion(QDialog): diff --git a/src/calibre/gui2/dialogs/match_books.py b/src/calibre/gui2/dialogs/match_books.py index 7412b1f600..790bf50a15 100644 --- a/src/calibre/gui2/dialogs/match_books.py +++ b/src/calibre/gui2/dialogs/match_books.py @@ -6,12 +6,14 @@ __copyright__ = '2013, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' -from qt.core import (Qt, QDialog, QAbstractItemView, QTableWidgetItem, - QApplication, QCursor, QTimer) +from qt.core import ( + QAbstractItemView, QApplication, QCursor, QDialog, Qt, QTableWidgetItem, QTimer, +) -from calibre.gui2 import gprefs, error_dialog +from calibre.gui2 import error_dialog, gprefs from calibre.gui2.dialogs.match_books_ui import Ui_MatchBooks from calibre.utils.icu import sort_key +from calibre.utils.localization import ngettext class TableItem(QTableWidgetItem): diff --git a/src/calibre/gui2/dialogs/message_box.py b/src/calibre/gui2/dialogs/message_box.py index 0e7b1b7560..f286e257a7 100644 --- a/src/calibre/gui2/dialogs/message_box.py +++ b/src/calibre/gui2/dialogs/message_box.py @@ -9,11 +9,12 @@ import sys from qt.core import ( QAction, QApplication, QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QIcon, QKeySequence, QLabel, QPainter, QPlainTextEdit, QSize, QSizePolicy, Qt, - QTextBrowser, QTextDocument, QVBoxLayout, QWidget, pyqtSignal + QTextBrowser, QTextDocument, QVBoxLayout, QWidget, pyqtSignal, ) from calibre.constants import __version__, isfrozen from calibre.gui2 import gprefs +from calibre.utils.localization import ngettext class Icon(QWidget): @@ -491,8 +492,8 @@ class JobError(QDialog): # {{{ if __name__ == '__main__': - from calibre.gui2 import Application, question_dialog from calibre import prepare_string_for_xml + from calibre.gui2 import Application, question_dialog app = Application([]) merged = {'Kovid Goyal': ['Waterloo', 'Doomed'], 'Someone Else': ['Some other book ' * 1000]} lines = [] diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 1ff57fbd65..a5c975db55 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -33,6 +33,7 @@ from calibre.utils.date import internal_iso_format_string, qt_to_dt from calibre.utils.icu import ( capitalize, lower as icu_lower, sort_key, upper as icu_upper, ) +from calibre.utils.localization import ngettext from calibre.utils.titlecase import titlecase from polyglot.builtins import error_message, iteritems, itervalues, native_string_type diff --git a/src/calibre/gui2/dialogs/template_dialog.py b/src/calibre/gui2/dialogs/template_dialog.py index 96e73e0be6..e8bd1851c7 100644 --- a/src/calibre/gui2/dialogs/template_dialog.py +++ b/src/calibre/gui2/dialogs/template_dialog.py @@ -15,7 +15,7 @@ from qt.core import ( QAbstractItemView, QApplication, QColor, QComboBox, QCursor, QDialog, QDialogButtonBox, QFont, QFontDatabase, QFontInfo, QFontMetrics, QIcon, QLineEdit, QPalette, QSize, QSyntaxHighlighter, Qt, QTableWidget, QTableWidgetItem, - QTextCharFormat, QTextOption, QVBoxLayout, pyqtSignal + QTextCharFormat, QTextOption, QVBoxLayout, pyqtSignal, ) from calibre import sanitize_file_name @@ -33,7 +33,7 @@ from calibre.utils.date import DEFAULT_DATE from calibre.utils.formatter import PythonTemplateContext, StopException from calibre.utils.formatter_functions import StoredObjectType, formatter_functions from calibre.utils.icu import lower as icu_lower, sort_key -from calibre.utils.localization import localize_user_manual_link +from calibre.utils.localization import localize_user_manual_link, ngettext from calibre.utils.resources import get_path as P @@ -339,7 +339,7 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): QDialog.__init__(self, parent, flags=Qt.WindowType.Dialog) else: QDialog.__init__(self, None, flags=Qt.WindowType.Window) - self.raise_() # Not needed on windows but here just in case + self.raise_() # Not needed on windows but here just in case Ui_TemplateDialog.__init__(self) self.setupUi(self) self.setWindowIcon(self.windowIcon()) diff --git a/src/calibre/gui2/fts/search.py b/src/calibre/gui2/fts/search.py index 5b5160a3b6..88cc891a50 100644 --- a/src/calibre/gui2/fts/search.py +++ b/src/calibre/gui2/fts/search.py @@ -11,10 +11,9 @@ from contextlib import suppress from functools import partial from itertools import count from qt.core import ( - QAbstractItemModel, QAbstractItemView, QCheckBox, QDialog, QDialogButtonBox, - QFont, QHBoxLayout, QIcon, QLabel, QMenu, QModelIndex, QPixmap, QPushButton, - QRect, QSize, QSplitter, QStackedWidget, Qt, QTreeView, QVBoxLayout, QWidget, - pyqtSignal + QAbstractItemModel, QAbstractItemView, QCheckBox, QDialog, QDialogButtonBox, QFont, + QHBoxLayout, QIcon, QLabel, QMenu, QModelIndex, QPixmap, QPushButton, QRect, QSize, + QSplitter, QStackedWidget, Qt, QTreeView, QVBoxLayout, QWidget, pyqtSignal, ) from threading import Event, Thread @@ -22,7 +21,7 @@ from calibre import fit_image, prepare_string_for_xml from calibre.db import FTSQueryError from calibre.ebooks.metadata import authors_to_string, fmt_sidx from calibre.gui2 import ( - config, error_dialog, gprefs, info_dialog, question_dialog, safe_open_url + config, error_dialog, gprefs, info_dialog, question_dialog, safe_open_url, ) from calibre.gui2.fts.utils import get_db from calibre.gui2.library.models import render_pin @@ -31,6 +30,7 @@ from calibre.gui2.ui import get_gui from calibre.gui2.viewer.widgets import ResultsDelegate, SearchBox from calibre.gui2.widgets import BusyCursor from calibre.gui2.widgets2 import HTMLDisplay +from calibre.utils.localization import ngettext ROOT = QModelIndex() sanitize_text_pat = re.compile(r'\s+') diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index c1b8484f80..5b31623aea 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -7,15 +7,15 @@ __docformat__ = 'restructuredtext en' import functools from qt.core import ( - QAction, QApplication, QDialog, QEvent, QIcon, QLabel, QMenu, QPixmap, QUrl, - QSizePolicy, QStackedWidget, QStatusBar, QStyle, QStyleOption, QSplitter, - QStylePainter, Qt, QTabBar, QTimer, QToolButton, QVBoxLayout, QWidget + QAction, QApplication, QDialog, QEvent, QIcon, QLabel, QMenu, QPixmap, QSizePolicy, + QSplitter, QStackedWidget, QStatusBar, QStyle, QStyleOption, QStylePainter, Qt, + QTabBar, QTimer, QToolButton, QUrl, QVBoxLayout, QWidget, ) from calibre.constants import get_appname_for_display, get_version, ismacos from calibre.customize.ui import find_plugin from calibre.gui2 import ( - config, error_dialog, gprefs, is_widescreen, open_local_file, open_url + config, error_dialog, gprefs, is_widescreen, open_local_file, open_url, ) from calibre.gui2.book_details import BookDetails from calibre.gui2.layout_menu import LayoutMenu @@ -26,7 +26,7 @@ from calibre.gui2.tag_browser.ui import TagBrowserWidget from calibre.gui2.widgets import LayoutButton, Splitter from calibre.utils.config import prefs from calibre.utils.icu import sort_key -from calibre.utils.localization import localize_website_link +from calibre.utils.localization import localize_website_link, ngettext _keep_refs = [] diff --git a/src/calibre/gui2/jobs.py b/src/calibre/gui2/jobs.py index 52e1321632..700f24475c 100644 --- a/src/calibre/gui2/jobs.py +++ b/src/calibre/gui2/jobs.py @@ -10,28 +10,28 @@ Job management. ''' import time +from qt.core import ( + QAbstractItemDelegate, QAbstractTableModel, QAction, QApplication, QByteArray, + QCoreApplication, QDialog, QDialogButtonBox, QEvent, QHBoxLayout, QIcon, + QItemSelectionModel, QLabel, QModelIndex, QPlainTextEdit, QSize, QSizePolicy, + QSortFilterProxyModel, QStyle, QStyleOption, QStyleOptionProgressBar, QStylePainter, + Qt, QTextBrowser, QTimer, QToolTip, QVBoxLayout, QWidget, pyqtSignal, +) -from qt.core import (QAbstractTableModel, QModelIndex, Qt, QStylePainter, - QTimer, pyqtSignal, QIcon, QDialog, QAbstractItemDelegate, QApplication, QEvent, - QSize, QStyleOptionProgressBar, QStyle, QToolTip, QWidget, QStyleOption, - QHBoxLayout, QVBoxLayout, QSizePolicy, QLabel, QCoreApplication, QAction, QItemSelectionModel, - QByteArray, QSortFilterProxyModel, QTextBrowser, QPlainTextEdit, QDialogButtonBox) - -from calibre import strftime -from calibre.constants import islinux, isbsd -from calibre.utils.ipc.server import Server -from calibre.utils.ipc.job import ParallelJob -from calibre.gui2 import (Dispatcher, error_dialog, question_dialog, - config, gprefs) +from calibre import __appname__, as_unicode, strftime +from calibre.constants import isbsd, islinux +from calibre.db.utils import human_readable_interval +from calibre.gui2 import Dispatcher, config, error_dialog, gprefs, question_dialog from calibre.gui2.device import DeviceJob from calibre.gui2.dialogs.jobs_ui import Ui_JobsDialog -from calibre import __appname__, as_unicode from calibre.gui2.progress_indicator import ProgressIndicator -from calibre.gui2.threaded_jobs import ThreadedJobServer, ThreadedJob +from calibre.gui2.threaded_jobs import ThreadedJob, ThreadedJobServer from calibre.gui2.widgets2 import Dialog -from calibre.utils.search_query_parser import SearchQueryParser, ParseException -from calibre.db.utils import human_readable_interval from calibre.utils.icu import lower +from calibre.utils.ipc.job import ParallelJob +from calibre.utils.ipc.server import Server +from calibre.utils.localization import ngettext +from calibre.utils.search_query_parser import ParseException, SearchQueryParser from polyglot.queue import Empty, Queue diff --git a/src/calibre/gui2/library/annotations.py b/src/calibre/gui2/library/annotations.py index 17a2b7b776..60c36ab53d 100644 --- a/src/calibre/gui2/library/annotations.py +++ b/src/calibre/gui2/library/annotations.py @@ -7,28 +7,29 @@ import os import re from functools import lru_cache, partial from qt.core import ( - QAbstractItemView, QApplication, QCheckBox, QComboBox, QDateTime, - QDialog, QDialogButtonBox, QFont, QFormLayout, QFrame, QHBoxLayout, QIcon, - QKeySequence, QLabel, QLocale, QMenu, QPalette, QPlainTextEdit, QSize, QSplitter, - Qt, QTextBrowser, QTimer, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, - QWidget, pyqtSignal + QAbstractItemView, QApplication, QCheckBox, QComboBox, QDateTime, QDialog, + QDialogButtonBox, QFont, QFormLayout, QFrame, QHBoxLayout, QIcon, QKeySequence, + QLabel, QLocale, QMenu, QPalette, QPlainTextEdit, QSize, QSplitter, Qt, + QTextBrowser, QTimer, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, + QWidget, pyqtSignal, ) from urllib.parse import quote from calibre import prepare_string_for_xml from calibre.constants import ( - builtin_colors_dark, builtin_colors_light, builtin_decorations + builtin_colors_dark, builtin_colors_light, builtin_decorations, ) from calibre.db.backend import FTSQueryError from calibre.ebooks.metadata import authors_to_string, fmt_sidx from calibre.gui2 import ( Application, choose_save_file, config, error_dialog, gprefs, is_dark_theme, - safe_open_url + safe_open_url, ) from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.viewer.widgets import ResultsDelegate, SearchBox from calibre.gui2.widgets import BusyCursor from calibre.gui2.widgets2 import Dialog, RightClickButton +from calibre.utils.localization import ngettext def render_timestamp(ts): diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 30e9c1755b..96772474fc 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -38,7 +38,7 @@ from calibre.utils.date import ( UNDEFINED_DATE, as_local_time, dt_factory, is_date_undefined, qt_to_dt, ) from calibre.utils.icu import sort_key -from calibre.utils.localization import calibre_langcode_to_name +from calibre.utils.localization import calibre_langcode_to_name, ngettext from calibre.utils.resources import get_path as P from calibre.utils.search_query_parser import ParseException, SearchQueryParser from polyglot.builtins import iteritems, itervalues, string_or_bytes diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 16ba3210b6..8e83ee72ad 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -12,10 +12,11 @@ import textwrap import weakref from datetime import date, datetime from qt.core import ( - QAction, QApplication, QDateTime, QDialog, QDialogButtonBox, QDoubleSpinBox, QAbstractItemView, - QGridLayout, QIcon, QKeySequence, QLabel, QLineEdit, QListWidgetItem, QMenu, - QMessageBox, QPixmap, QPlainTextEdit, QSize, QSizePolicy, Qt, QToolButton, QComboBox, - QUndoCommand, QUndoStack, QUrl, QVBoxLayout, QWidget, pyqtSignal + QAbstractItemView, QAction, QApplication, QComboBox, QDateTime, QDialog, + QDialogButtonBox, QDoubleSpinBox, QGridLayout, QIcon, QKeySequence, QLabel, + QLineEdit, QListWidgetItem, QMenu, QMessageBox, QPixmap, QPlainTextEdit, QSize, + QSizePolicy, Qt, QToolButton, QUndoCommand, QUndoStack, QUrl, QVBoxLayout, QWidget, + pyqtSignal, ) from calibre import strftime @@ -24,12 +25,13 @@ from calibre.customize.ui import run_plugins_on_import from calibre.db import SPOOL_SIZE from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks.metadata import ( - authors_to_sort_string, check_isbn, string_to_authors, title_sort + authors_to_sort_string, check_isbn, string_to_authors, title_sort, ) from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.oeb.polish.main import SUPPORTED as EDIT_SUPPORTED from calibre.gui2 import ( - choose_files_and_remember_all_files, choose_images, error_dialog, file_icon_provider, gprefs + choose_files_and_remember_all_files, choose_images, error_dialog, + file_icon_provider, gprefs, ) from calibre.gui2.comments_editor import Editor from calibre.gui2.complete2 import EditWithComplete @@ -38,17 +40,18 @@ from calibre.gui2.languages import LanguagesEdit as LE from calibre.gui2.widgets import EnLineEdit, FormatList as _FormatList, ImageView from calibre.gui2.widgets2 import ( DateTimeEdit, Dialog, RatingEditor, RightClickButton, access_key, - populate_standard_spinbox_context_menu + populate_standard_spinbox_context_menu, ) from calibre.library.comments import comments_to_html from calibre.ptempfile import PersistentTemporaryFile, SpooledTemporaryFile from calibre.utils.config import prefs, tweaks from calibre.utils.date import ( UNDEFINED_DATE, as_local_time, internal_iso_format_string, is_date_undefined, - local_tz, parse_only_date, qt_to_dt, utcfromtimestamp + local_tz, parse_only_date, qt_to_dt, utcfromtimestamp, ) from calibre.utils.filenames import make_long_path_useable from calibre.utils.icu import sort_key, strcmp +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems @@ -1221,7 +1224,7 @@ class Cover(ImageView): # {{{ if not cdata: return from calibre.utils.img import ( - image_from_data, image_to_data, remove_borders_from_image + image_from_data, image_to_data, remove_borders_from_image, ) img = image_from_data(cdata) nimg = remove_borders_from_image(img) diff --git a/src/calibre/gui2/metadata/bulk_download.py b/src/calibre/gui2/metadata/bulk_download.py index 95296cb58a..03cbeed404 100644 --- a/src/calibre/gui2/metadata/bulk_download.py +++ b/src/calibre/gui2/metadata/bulk_download.py @@ -5,17 +5,17 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, time, shutil +import os +import shutil +import time +from qt.core import QDialog, QDialogButtonBox, QGridLayout, QIcon, QLabel, Qt from threading import Thread -from qt.core import (QIcon, QDialog, - QDialogButtonBox, QLabel, QGridLayout, Qt) - -from calibre.gui2.threaded_jobs import ThreadedJob from calibre.ebooks.metadata.opf2 import metadata_to_opf -from calibre.utils.ipc.simple_worker import fork_job, WorkerError -from calibre.ptempfile import (PersistentTemporaryDirectory, - PersistentTemporaryFile) +from calibre.gui2.threaded_jobs import ThreadedJob +from calibre.ptempfile import PersistentTemporaryDirectory, PersistentTemporaryFile +from calibre.utils.ipc.simple_worker import WorkerError, fork_job +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems # Start download {{{ diff --git a/src/calibre/gui2/metadata/diff.py b/src/calibre/gui2/metadata/diff.py index e12e283b5e..4266ba1239 100644 --- a/src/calibre/gui2/metadata/diff.py +++ b/src/calibre/gui2/metadata/diff.py @@ -27,6 +27,7 @@ from calibre.gui2.widgets2 import RightClickButton from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.date import UNDEFINED_DATE from calibre.utils.icu import lower as icu_lower +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems, itervalues Widgets = namedtuple('Widgets', 'new old label button') diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index 2dffa514f0..d8cb7334c6 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -32,7 +32,7 @@ from calibre.gui2.metadata.single_download import FullFetch from calibre.gui2.widgets2 import CenteredToolButton from calibre.library.comments import merge_comments as merge_two_comments from calibre.utils.date import local_tz -from calibre.utils.localization import canonicalize_lang +from calibre.utils.localization import canonicalize_lang, ngettext from polyglot.builtins import iteritems BASE_TITLE = _('Edit metadata') diff --git a/src/calibre/gui2/preferences/coloring.py b/src/calibre/gui2/preferences/coloring.py index a1df13c6c0..76544c7a35 100644 --- a/src/calibre/gui2/preferences/coloring.py +++ b/src/calibre/gui2/preferences/coloring.py @@ -10,30 +10,29 @@ import os import textwrap from functools import partial from qt.core import ( - QAbstractItemView, QAbstractListModel, QApplication, QCheckBox, QComboBox, - QDialog, QDialogButtonBox, QDoubleValidator, QFrame, QGridLayout, QIcon, - QIntValidator, QItemSelectionModel, QLabel, QLineEdit, QListView, - QPalette, QPushButton, QScrollArea, QSize, QSizePolicy, QSpacerItem, - QStandardItem, QStandardItemModel, Qt, QToolButton, QVBoxLayout, QWidget, - QItemSelection, QListWidget, QListWidgetItem, pyqtSignal + QAbstractItemView, QAbstractListModel, QApplication, QCheckBox, QComboBox, QDialog, + QDialogButtonBox, QDoubleValidator, QFrame, QGridLayout, QIcon, QIntValidator, + QItemSelection, QItemSelectionModel, QLabel, QLineEdit, QListView, QListWidget, + QListWidgetItem, QPalette, QPushButton, QScrollArea, QSize, QSizePolicy, + QSpacerItem, QStandardItem, QStandardItemModel, Qt, QToolButton, QVBoxLayout, + QWidget, pyqtSignal, ) from calibre import as_unicode, prepare_string_for_xml, sanitize_file_name from calibre.constants import config_dir from calibre.gui2 import ( choose_files, choose_save_file, error_dialog, gprefs, open_local_file, - pixmap_to_data, question_dialog + pixmap_to_data, question_dialog, ) from calibre.gui2.dialogs.template_dialog import TemplateDialog from calibre.gui2.metadata.single_download import RichTextDelegate from calibre.gui2.preferences import ListViewWithMoveByKeyPress from calibre.gui2.widgets2 import ColorButton, FlowLayout, Separator from calibre.library.coloring import ( - Rule, color_row_key, conditionable_columns, displayable_columns, - rule_from_template + Rule, color_row_key, conditionable_columns, displayable_columns, rule_from_template, ) from calibre.utils.icu import lower, sort_key -from calibre.utils.localization import lang_map +from calibre.utils.localization import lang_map, ngettext from polyglot.builtins import iteritems all_columns_string = _('All columns') diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index 9f6e4c6602..aa3a83ea50 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -6,19 +6,20 @@ __copyright__ = '2010, Kovid Goyal ' '''Dialog to create a new custom column''' -import copy, re +import copy +import re from enum import Enum from functools import partial - from qt.core import ( - QDialog, Qt, QColor, QIcon, QVBoxLayout, QLabel, QGridLayout, - QDialogButtonBox, QWidget, QLineEdit, QHBoxLayout, QComboBox, - QCheckBox, QSpinBox, QRadioButton, QGroupBox + QCheckBox, QColor, QComboBox, QDialog, QDialogButtonBox, QGridLayout, QGroupBox, + QHBoxLayout, QIcon, QLabel, QLineEdit, QRadioButton, QSpinBox, Qt, QVBoxLayout, + QWidget, ) from calibre.gui2 import error_dialog from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor -from calibre.utils.date import parse_date, UNDEFINED_DATE +from calibre.utils.date import UNDEFINED_DATE, parse_date +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/preferences/metadata_sources.py b/src/calibre/gui2/preferences/metadata_sources.py index 4bba974884..63f0354843 100644 --- a/src/calibre/gui2/preferences/metadata_sources.py +++ b/src/calibre/gui2/preferences/metadata_sources.py @@ -7,18 +7,19 @@ __docformat__ = 'restructuredtext en' from operator import attrgetter from qt.core import ( - QAbstractListModel, QAbstractTableModel, QDialogButtonBox, QFrame, QIcon, QLabel, - QScrollArea, Qt, QVBoxLayout, QWidget, pyqtSignal, QDialog, QMenu, QCursor + QAbstractListModel, QAbstractTableModel, QCursor, QDialog, QDialogButtonBox, QFrame, + QIcon, QLabel, QMenu, QScrollArea, Qt, QVBoxLayout, QWidget, pyqtSignal, ) from calibre.customize.ui import ( all_metadata_plugins, default_disabled_plugins, disable_plugin, enable_plugin, - is_disabled + is_disabled, ) from calibre.ebooks.metadata.sources.prefs import msprefs from calibre.gui2 import error_dialog, question_dialog from calibre.gui2.preferences import ConfigWidgetBase, test_widget from calibre.gui2.preferences.metadata_sources_ui import Ui_Form +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/preferences/server.py b/src/calibre/gui2/preferences/server.py index a49a9848fc..e7c10a8883 100644 --- a/src/calibre/gui2/preferences/server.py +++ b/src/calibre/gui2/preferences/server.py @@ -12,14 +12,14 @@ from qt.core import ( QApplication, QCheckBox, QComboBox, QDialog, QDialogButtonBox, QDoubleSpinBox, QFormLayout, QFrame, QHBoxLayout, QIcon, QLabel, QLayout, QLineEdit, QListWidget, QPlainTextEdit, QPushButton, QScrollArea, QSize, QSizePolicy, QSpinBox, Qt, - QTabWidget, QTimer, QToolButton, QUrl, QVBoxLayout, QWidget, pyqtSignal, sip + QTabWidget, QTimer, QToolButton, QUrl, QVBoxLayout, QWidget, pyqtSignal, sip, ) from calibre import as_unicode from calibre.constants import isportable, iswindows from calibre.gui2 import ( - choose_files, choose_save_file, config, error_dialog, gprefs, info_dialog, - open_url, warning_dialog + choose_files, choose_save_file, config, error_dialog, gprefs, info_dialog, open_url, + warning_dialog, ) from calibre.gui2.preferences import AbortCommit, ConfigWidgetBase, test_widget from calibre.gui2.widgets import HistoryLineEdit @@ -29,9 +29,10 @@ from calibre.srv.library_broker import load_gui_libraries from calibre.srv.loop import parse_trusted_ips from calibre.srv.opts import change_settings, options, server_config from calibre.srv.users import ( - UserManager, create_user_data, validate_password, validate_username + UserManager, create_user_data, validate_password, validate_username, ) from calibre.utils.icu import primary_sort_key +from calibre.utils.localization import ngettext from calibre.utils.shared_file import share_open from polyglot.builtins import as_bytes diff --git a/src/calibre/gui2/search_restriction_mixin.py b/src/calibre/gui2/search_restriction_mixin.py index f738601677..96f9f9eae7 100644 --- a/src/calibre/gui2/search_restriction_mixin.py +++ b/src/calibre/gui2/search_restriction_mixin.py @@ -8,14 +8,14 @@ from functools import partial from qt.core import ( QAbstractItemView, QAction, QComboBox, QDialog, QDialogButtonBox, QFrame, QGridLayout, QIcon, QLabel, QLineEdit, QListView, QMenu, QRadioButton, QSize, - QStringListModel, Qt, QTextBrowser, QVBoxLayout, QSortFilterProxyModel + QSortFilterProxyModel, QStringListModel, Qt, QTextBrowser, QVBoxLayout, ) from calibre.gui2 import error_dialog, gprefs, question_dialog from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.widgets import ComboBoxWithHelp from calibre.utils.icu import sort_key -from calibre.utils.localization import localize_user_manual_link, pgettext +from calibre.utils.localization import localize_user_manual_link, ngettext, pgettext from calibre.utils.search_query_parser import ParseException diff --git a/src/calibre/gui2/tag_browser/ui.py b/src/calibre/gui2/tag_browser/ui.py index bd7f07b26e..d4c67d2997 100644 --- a/src/calibre/gui2/tag_browser/ui.py +++ b/src/calibre/gui2/tag_browser/ui.py @@ -5,22 +5,25 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import copy, textwrap +import copy +import textwrap from functools import partial - from qt.core import ( - Qt, QIcon, QWidget, QHBoxLayout, QVBoxLayout, QToolButton, QLabel, QFrame, QDialog, QComboBox, QLineEdit, - QTimer, QMenu, QActionGroup, QAction, QSizePolicy, pyqtSignal) + QAction, QActionGroup, QComboBox, QDialog, QFrame, QHBoxLayout, QIcon, QLabel, + QLineEdit, QMenu, QSizePolicy, Qt, QTimer, QToolButton, QVBoxLayout, QWidget, + pyqtSignal, +) -from calibre.gui2 import error_dialog, question_dialog, gprefs, config +from calibre.ebooks.metadata import title_sort +from calibre.gui2 import config, error_dialog, gprefs, question_dialog +from calibre.gui2.dialogs.edit_authors_dialog import EditAuthorsDialog +from calibre.gui2.dialogs.tag_categories import TagCategories +from calibre.gui2.dialogs.tag_list_editor import TagListEditor +from calibre.gui2.tag_browser.view import TagsView from calibre.gui2.widgets import HistoryLineEdit from calibre.library.field_metadata import category_icon_map from calibre.utils.icu import sort_key -from calibre.gui2.tag_browser.view import TagsView -from calibre.ebooks.metadata import title_sort -from calibre.gui2.dialogs.tag_categories import TagCategories -from calibre.gui2.dialogs.tag_list_editor import TagListEditor -from calibre.gui2.dialogs.edit_authors_dialog import EditAuthorsDialog +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index 3785c83ec3..7958bef8eb 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2013, Kovid Goyal - import errno import os import shutil @@ -11,39 +10,37 @@ from functools import partial, wraps from qt.core import ( QApplication, QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QIcon, QInputDialog, QLabel, QMimeData, QObject, QSize, Qt, QTimer, QUrl, QVBoxLayout, - pyqtSignal + pyqtSignal, ) from calibre import isbytestring, prints from calibre.constants import cache_dir, iswindows from calibre.ebooks.oeb.base import urlnormalize from calibre.ebooks.oeb.polish.container import ( - OEB_DOCS, OEB_STYLES, clone_container, get_container as _gc, guess_type -) -from calibre.ebooks.oeb.polish.cover import ( - mark_as_cover, mark_as_titlepage, set_cover + OEB_DOCS, OEB_STYLES, clone_container, get_container as _gc, guess_type, ) +from calibre.ebooks.oeb.polish.cover import mark_as_cover, mark_as_titlepage, set_cover from calibre.ebooks.oeb.polish.css import filter_css, rename_class from calibre.ebooks.oeb.polish.main import SUPPORTED, tweak_polish from calibre.ebooks.oeb.polish.pretty import fix_all_html, pretty_all from calibre.ebooks.oeb.polish.replace import ( - get_recommended_folders, rationalize_folders, rename_files, replace_file + get_recommended_folders, rationalize_folders, rename_files, replace_file, ) from calibre.ebooks.oeb.polish.split import AbortError, merge, multisplit, split from calibre.ebooks.oeb.polish.toc import ( - create_inline_toc, mark_as_nav, remove_names_from_toc + create_inline_toc, mark_as_nav, remove_names_from_toc, ) from calibre.ebooks.oeb.polish.utils import ( - link_stylesheets, setup_css_parser_serialization as scs + link_stylesheets, setup_css_parser_serialization as scs, ) from calibre.gui2 import ( - add_to_recent_docs, choose_dir, choose_files, choose_save_file, error_dialog, warning_dialog, - info_dialog, open_url, question_dialog + add_to_recent_docs, choose_dir, choose_files, choose_save_file, error_dialog, + info_dialog, open_url, question_dialog, warning_dialog, ) from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.tweak_book import ( actions, current_container, dictionaries, editor_name, editors, set_book_locale, - set_current_container, tprefs + set_current_container, tprefs, ) from calibre.gui2.tweak_book.completion.worker import completion_worker from calibre.gui2.tweak_book.editor import editor_from_syntax, syntax_from_mime @@ -52,23 +49,22 @@ from calibre.gui2.tweak_book.file_list import FILE_COPY_MIME, NewFileDialog from calibre.gui2.tweak_book.preferences import Preferences from calibre.gui2.tweak_book.preview import parse_worker from calibre.gui2.tweak_book.save import ( - SaveManager, find_first_existing_ancestor, save_container + SaveManager, find_first_existing_ancestor, save_container, ) from calibre.gui2.tweak_book.search import run_search, validate_search_request -from calibre.gui2.tweak_book.spell import ( - find_next as find_next_word, find_next_error -) +from calibre.gui2.tweak_book.spell import find_next as find_next_word, find_next_error from calibre.gui2.tweak_book.toc import TOCEditor from calibre.gui2.tweak_book.undo import GlobalUndoHistory from calibre.gui2.tweak_book.widgets import ( AddCover, FilterCSS, ImportForeign, InsertLink, InsertSemantics, InsertTag, - MultiSplit, QuickOpen, RationalizeFolders + MultiSplit, QuickOpen, RationalizeFolders, ) from calibre.gui2.widgets import BusyCursor from calibre.ptempfile import PersistentTemporaryDirectory, TemporaryDirectory from calibre.utils.config import JSONConfig from calibre.utils.icu import numeric_sort_key from calibre.utils.imghdr import identify +from calibre.utils.localization import ngettext from calibre.utils.tdir_in_cache import tdir_in_cache from polyglot.builtins import as_bytes, iteritems, itervalues, string_or_bytes from polyglot.urllib import urlparse @@ -216,7 +212,7 @@ class Boss(QObject): ed.apply_settings(dictionaries_changed=p.dictionaries_changed) if orig_spell != tprefs['inline_spell_check']: from calibre.gui2.tweak_book.editor.syntax.html import ( - refresh_spell_check_status + refresh_spell_check_status, ) refresh_spell_check_status() for ed in itervalues(editors): @@ -502,7 +498,7 @@ class Boss(QObject): added_name = self.do_add_file(d.file_name, d.file_data, using_template=d.using_template, edit_file=True) if d.file_name.rpartition('.')[2].lower() in ('ttf', 'otf', 'woff'): from calibre.gui2.tweak_book.manage_fonts import ( - show_font_face_rule_for_font_file + show_font_face_rule_for_font_file, ) show_font_face_rule_for_font_file(d.file_data, added_name, self.gui) @@ -567,7 +563,7 @@ class Boss(QObject): completion_worker().clear_caches('names') if added_fonts: from calibre.gui2.tweak_book.manage_fonts import ( - show_font_face_rule_for_font_files + show_font_face_rule_for_font_files, ) show_font_face_rule_for_font_files(c, added_fonts, self.gui) @@ -1219,7 +1215,7 @@ class Boss(QObject): def editor_class_clicked(self, class_data): from calibre.gui2.tweak_book.jump_to_class import ( - NoMatchingRuleFound, NoMatchingTagFound, find_first_matching_rule + NoMatchingRuleFound, NoMatchingTagFound, find_first_matching_rule, ) ed = self.gui.central.current_editor name = editor_name(ed) @@ -1617,7 +1613,7 @@ class Boss(QObject): if not self.ensure_book(_('You must first open a book in order to compress images.')): return from calibre.gui2.tweak_book.polish import ( - CompressImages, CompressImagesProgress, show_report + CompressImages, CompressImagesProgress, show_report, ) d = CompressImages(self.gui) if d.exec() == QDialog.DialogCode.Accepted: diff --git a/src/calibre/gui2/tweak_book/check_links.py b/src/calibre/gui2/tweak_book/check_links.py index 09e08d4bc3..02dca7da12 100644 --- a/src/calibre/gui2/tweak_book/check_links.py +++ b/src/calibre/gui2/tweak_book/check_links.py @@ -1,19 +1,20 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2015, Kovid Goyal - from collections import defaultdict +from qt.core import ( + QCheckBox, QDialogButtonBox, QHBoxLayout, QIcon, QInputDialog, QLabel, QProgressBar, + QSizePolicy, QStackedWidget, Qt, QTextBrowser, QVBoxLayout, QWidget, pyqtSignal, +) from threading import Thread -from qt.core import ( - QCheckBox, QHBoxLayout, QIcon, QInputDialog, QLabel, QProgressBar, QSizePolicy, - QStackedWidget, Qt, QTextBrowser, QVBoxLayout, QWidget, pyqtSignal, QDialogButtonBox -) - from calibre.gui2 import error_dialog -from calibre.gui2.tweak_book import current_container, editors, set_current_container, tprefs +from calibre.gui2.tweak_book import ( + current_container, editors, set_current_container, tprefs, +) from calibre.gui2.tweak_book.boss import get_boss from calibre.gui2.tweak_book.widgets import Dialog +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems @@ -168,6 +169,7 @@ class CheckExternalLinks(Dialog): if __name__ == '__main__': import sys + from calibre.gui2 import Application from calibre.gui2.tweak_book.boss import get_container app = Application([]) diff --git a/src/calibre/gui2/tweak_book/download.py b/src/calibre/gui2/tweak_book/download.py index 46de1d060f..246c5b46c1 100644 --- a/src/calibre/gui2/tweak_book/download.py +++ b/src/calibre/gui2/tweak_book/download.py @@ -2,17 +2,20 @@ # License: GPLv3 Copyright: 2016, Kovid Goyal +from qt.core import ( + QDialogButtonBox, QGridLayout, QIcon, QLabel, QListWidget, QListWidgetItem, + QProgressBar, QScrollArea, QSize, Qt, QVBoxLayout, QWidget, pyqtSignal, +) from threading import Thread -from qt.core import ( - pyqtSignal, QWidget, QListWidget, QListWidgetItem, QLabel, Qt, - QVBoxLayout, QScrollArea, QProgressBar, QGridLayout, QSize, QIcon, QDialogButtonBox) - +from calibre.ebooks.oeb.polish.download import ( + download_external_resources, get_external_resources, replace_resources, +) from calibre.gui2 import error_dialog, info_dialog, warning_dialog +from calibre.gui2.progress_indicator import WaitStack from calibre.gui2.tweak_book import current_container from calibre.gui2.tweak_book.widgets import Dialog -from calibre.gui2.progress_indicator import WaitStack -from calibre.ebooks.oeb.polish.download import get_external_resources, download_external_resources, replace_resources +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems @@ -258,8 +261,9 @@ class DownloadResources(Dialog): if __name__ == '__main__': - from calibre.gui2 import Application import sys + + from calibre.gui2 import Application app = Application([]) from calibre.gui2.tweak_book import set_current_container from calibre.gui2.tweak_book.boss import get_container diff --git a/src/calibre/gui2/tweak_book/file_list.py b/src/calibre/gui2/tweak_book/file_list.py index 437058eb76..1eb9dfef9f 100644 --- a/src/calibre/gui2/tweak_book/file_list.py +++ b/src/calibre/gui2/tweak_book/file_list.py @@ -13,31 +13,31 @@ from qt.core import ( QFormLayout, QGridLayout, QIcon, QInputDialog, QItemSelectionModel, QLabel, QLineEdit, QListWidget, QListWidgetItem, QMenu, QPainter, QPixmap, QRadioButton, QScrollArea, QSize, QSpinBox, QStyle, QStyledItemDelegate, Qt, QTimer, QTreeView, - QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, pyqtSignal, sip + QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, pyqtSignal, sip, ) from calibre import human_readable, sanitize_file_name from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES from calibre.ebooks.oeb.polish.cover import ( - get_cover_page_name, get_raster_cover_name, is_raster_image + get_cover_page_name, get_raster_cover_name, is_raster_image, ) from calibre.ebooks.oeb.polish.css import add_stylesheet_links from calibre.ebooks.oeb.polish.replace import ( - get_recommended_folders, get_spine_order_for_all_files + get_recommended_folders, get_spine_order_for_all_files, ) from calibre.ebooks.oeb.polish.utils import OEB_FONTS, guess_type from calibre.gui2 import ( choose_dir, choose_files, choose_save_file, elided_text, error_dialog, - make_view_use_window_background, question_dialog + make_view_use_window_background, question_dialog, ) from calibre.gui2.tweak_book import ( - CONTAINER_DND_MIMETYPE, current_container, editors, tprefs + CONTAINER_DND_MIMETYPE, current_container, editors, tprefs, ) from calibre.gui2.tweak_book.editor import syntax_from_mime from calibre.gui2.tweak_book.templates import template_for from calibre.utils.fonts.utils import get_font_names -from calibre.utils.localization import pgettext from calibre.utils.icu import numeric_sort_key +from calibre.utils.localization import ngettext, pgettext from calibre_extensions.progress_indicator import set_no_activate_on_click from polyglot.binary import as_hex_unicode from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/tweak_book/manage_fonts.py b/src/calibre/gui2/tweak_book/manage_fonts.py index 14cfaaa032..8e28d8b8e6 100644 --- a/src/calibre/gui2/tweak_book/manage_fonts.py +++ b/src/calibre/gui2/tweak_book/manage_fonts.py @@ -22,6 +22,7 @@ from calibre.gui2.widgets import BusyCursor from calibre.utils.fonts.metadata import FontMetadata, UnsupportedFont from calibre.utils.fonts.scanner import NoFonts, font_scanner from calibre.utils.icu import lower as icu_lower, primary_sort_key as sort_key +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/tweak_book/preferences.py b/src/calibre/gui2/tweak_book/preferences.py index 5b4bcf5a0c..506d316538 100644 --- a/src/calibre/gui2/tweak_book/preferences.py +++ b/src/calibre/gui2/tweak_book/preferences.py @@ -5,30 +5,34 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' import numbers -from operator import attrgetter, methodcaller -from functools import partial from collections import namedtuple -from polyglot.builtins import iteritems, itervalues -from itertools import product from copy import copy, deepcopy - +from functools import partial +from itertools import product +from operator import attrgetter, methodcaller from qt.core import ( - QDialog, QGridLayout, QStackedWidget, QDialogButtonBox, QListWidget, - QListWidgetItem, QIcon, QWidget, QSize, QFormLayout, Qt, QSpinBox, QListView, - QCheckBox, pyqtSignal, QDoubleSpinBox, QComboBox, QLabel, QFont, - QFontComboBox, QPushButton, QSizePolicy, QHBoxLayout, QGroupBox, QAbstractItemView, - QToolButton, QVBoxLayout, QSpacerItem, QTimer, QRadioButton) + QAbstractItemView, QCheckBox, QComboBox, QDialog, QDialogButtonBox, QDoubleSpinBox, + QFont, QFontComboBox, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout, QIcon, + QLabel, QListView, QListWidget, QListWidgetItem, QPushButton, QRadioButton, QSize, + QSizePolicy, QSpacerItem, QSpinBox, QStackedWidget, Qt, QTimer, QToolButton, + QVBoxLayout, QWidget, pyqtSignal, +) from calibre import prepare_string_for_xml -from calibre.utils.localization import get_lang from calibre.gui2 import info_dialog -from calibre.gui2.keyboard import ShortcutConfig -from calibre.gui2.tweak_book import tprefs, toolbar_actions, editor_toolbar_actions, actions -from calibre.gui2.tweak_book.editor.themes import default_theme, all_theme_names, ThemeEditor -from calibre.gui2.tweak_book.spell import ManageDictionaries from calibre.gui2.font_family_chooser import FontFamilyChooser +from calibre.gui2.keyboard import ShortcutConfig +from calibre.gui2.tweak_book import ( + actions, editor_toolbar_actions, toolbar_actions, tprefs, +) +from calibre.gui2.tweak_book.editor.themes import ( + ThemeEditor, all_theme_names, default_theme, +) +from calibre.gui2.tweak_book.spell import ManageDictionaries from calibre.gui2.tweak_book.widgets import Dialog from calibre.gui2.widgets2 import ColorButton +from calibre.utils.localization import get_lang, ngettext +from polyglot.builtins import iteritems, itervalues class BasicSettings(QWidget): # {{{ @@ -348,7 +352,7 @@ class PreviewSettings(BasicSettings): # {{{ def default_font(which): if not self.default_font_settings: - from qt.webengine import QWebEngineSettings, QWebEnginePage + from qt.webengine import QWebEnginePage, QWebEngineSettings page = QWebEnginePage() s = page.settings() self.default_font_settings = { @@ -672,8 +676,9 @@ class TemplatesDialog(Dialog): # {{{ Dialog.__init__(self, _('Customize templates'), 'customize-templates', parent=parent) def setup_ui(self): - from calibre.gui2.tweak_book.templates import DEFAULT_TEMPLATES from calibre.gui2.tweak_book.editor.text import TextEdit + from calibre.gui2.tweak_book.templates import DEFAULT_TEMPLATES + # Cannot use QFormLayout as it does not play nice with TextEdit on windows self.l = l = QVBoxLayout(self) diff --git a/src/calibre/gui2/update.py b/src/calibre/gui2/update.py index 25770d7c17..237f363734 100644 --- a/src/calibre/gui2/update.py +++ b/src/calibre/gui2/update.py @@ -4,20 +4,20 @@ __copyright__ = '2008, Kovid Goyal ' import re import ssl from qt.core import ( - QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QIcon, QLabel, QObject, Qt, - QUrl, pyqtSignal + QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QIcon, QLabel, QObject, Qt, QUrl, + pyqtSignal, ) from threading import Event, Thread from calibre import as_unicode, prints from calibre.constants import ( - __appname__, __version__, ismacos, isportable, iswindows, numeric_version + __appname__, __version__, ismacos, isportable, iswindows, numeric_version, ) from calibre.gui2 import config, dynamic, icon_resource_manager, open_url from calibre.gui2.dialogs.plugin_updater import get_plugin_updates_available from calibre.utils.config import prefs from calibre.utils.https import get_https_resource_securely -from calibre.utils.localization import localize_website_link +from calibre.utils.localization import localize_website_link, ngettext from calibre.utils.serialize import msgpack_dumps, msgpack_loads from polyglot.binary import as_hex_unicode, from_hex_bytes @@ -168,7 +168,7 @@ class UpdateNotification(QDialog): def get_plugins(self): from calibre.gui2.dialogs.plugin_updater import ( - FILTER_UPDATE_AVAILABLE, PluginUpdaterDialog + FILTER_UPDATE_AVAILABLE, PluginUpdaterDialog, ) d = PluginUpdaterDialog(self.parent(), initial_filter=FILTER_UPDATE_AVAILABLE) @@ -238,7 +238,7 @@ class UpdateMixin: elif has_plugin_updates: if force: from calibre.gui2.dialogs.plugin_updater import ( - FILTER_UPDATE_AVAILABLE, PluginUpdaterDialog + FILTER_UPDATE_AVAILABLE, PluginUpdaterDialog, ) d = PluginUpdaterDialog(self, initial_filter=FILTER_UPDATE_AVAILABLE) diff --git a/src/calibre/gui2/viewer/highlights.py b/src/calibre/gui2/viewer/highlights.py index 3776885efa..e652764857 100644 --- a/src/calibre/gui2/viewer/highlights.py +++ b/src/calibre/gui2/viewer/highlights.py @@ -8,26 +8,27 @@ from functools import lru_cache from itertools import chain from qt.core import ( QAbstractItemView, QColor, QDialog, QFont, QHBoxLayout, QIcon, QImage, - QItemSelectionModel, QKeySequence, QLabel, QMenu, QPainter, QPainterPath, - QPalette, QPixmap, QPushButton, QRect, QSizePolicy, QStyle, Qt, QTextCursor, - QTextEdit, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, pyqtSignal + QItemSelectionModel, QKeySequence, QLabel, QMenu, QPainter, QPainterPath, QPalette, + QPixmap, QPushButton, QRect, QSizePolicy, QStyle, Qt, QTextCursor, QTextEdit, + QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, pyqtSignal, ) from calibre.constants import ( - builtin_colors_dark, builtin_colors_light, builtin_decorations + builtin_colors_dark, builtin_colors_light, builtin_decorations, ) from calibre.ebooks.epub.cfi.parse import cfi_sort_key from calibre.gui2 import error_dialog, is_dark_theme, safe_open_url from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.gestures import GestureManager from calibre.gui2.library.annotations import ( - Details, Export as ExportBase, render_highlight_as_text, render_notes + Details, Export as ExportBase, render_highlight_as_text, render_notes, ) from calibre.gui2.viewer import link_prefix_for_location_links from calibre.gui2.viewer.config import vprefs from calibre.gui2.viewer.search import SearchInput from calibre.gui2.viewer.shortcuts import get_shortcut_for, index_to_key_sequence from calibre.gui2.widgets2 import Dialog +from calibre.utils.localization import ngettext from calibre_extensions.progress_indicator import set_no_activate_on_click decoration_cache = {} diff --git a/src/calibre/gui2/viewer/search.py b/src/calibre/gui2/viewer/search.py index 112d381118..b59118b3ea 100644 --- a/src/calibre/gui2/viewer/search.py +++ b/src/calibre/gui2/viewer/search.py @@ -7,7 +7,7 @@ from collections import Counter, OrderedDict from html import escape from qt.core import ( QAbstractItemView, QCheckBox, QComboBox, QFont, QHBoxLayout, QIcon, QLabel, Qt, - QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, pyqtSignal + QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, pyqtSignal, ) from threading import Thread @@ -19,6 +19,7 @@ from calibre.gui2.viewer.config import vprefs from calibre.gui2.viewer.web_view import get_data, get_manifest from calibre.gui2.viewer.widgets import ResultsDelegate, SearchBox from calibre.utils.icu import primary_collator_without_punctuation +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems from polyglot.functools import lru_cache from polyglot.queue import Queue @@ -363,6 +364,7 @@ def search_in_name(name, search_query, ctx_size=75): else: spans = [] + def miter(): return spans if raw: diff --git a/src/calibre/library/catalogs/epub_mobi_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py index 79a6964821..cf6578c5b5 100644 --- a/src/calibre/library/catalogs/epub_mobi_builder.py +++ b/src/calibre/library/catalogs/epub_mobi_builder.py @@ -35,8 +35,10 @@ from calibre.utils.date import ( ) from calibre.utils.filenames import ascii_text, shorten_components_to from calibre.utils.formatter import TemplateFormatter -from calibre.utils.icu import capitalize, collation_order, sort_key, upper as icu_upper -from calibre.utils.localization import get_lang, lang_as_iso639_1 +from calibre.utils.icu import ( + capitalize, collation_order, sort_key, title_case as icu_title, upper as icu_upper, +) +from calibre.utils.localization import get_lang, lang_as_iso639_1, ngettext from calibre.utils.resources import get_image_path as I, get_path as P from calibre.utils.xml_parse import safe_xml_fromstring from calibre.utils.zipfile import ZipFile diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index f1efea9a9f..07f27a0935 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -9,6 +9,7 @@ from collections import OrderedDict from calibre.utils.config_base import tweaks from calibre.utils.icu import lower as icu_lower +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems, itervalues category_icon_map = { diff --git a/src/calibre/srv/manage_users_cli.py b/src/calibre/srv/manage_users_cli.py index f3082752b0..7544c9901d 100644 --- a/src/calibre/srv/manage_users_cli.py +++ b/src/calibre/srv/manage_users_cli.py @@ -5,8 +5,9 @@ import sys from functools import partial from calibre import prints -from calibre.constants import preferred_encoding, iswindows +from calibre.constants import iswindows, preferred_encoding from calibre.utils.config import OptionParser +from calibre.utils.localization import ngettext from polyglot.builtins import iteritems diff --git a/src/calibre/srv/opds.py b/src/calibre/srv/opds.py index 8e19a7b561..2ec3e0d242 100644 --- a/src/calibre/srv/opds.py +++ b/src/calibre/srv/opds.py @@ -24,6 +24,7 @@ from calibre.srv.utils import Offsets, get_library_data, http_date from calibre.utils.config import prefs from calibre.utils.date import as_utc, is_date_undefined, timestampfromdt from calibre.utils.icu import sort_key +from calibre.utils.localization import ngettext from calibre.utils.search_query_parser import ParseException from calibre.utils.xml_parse import safe_xml_fromstring from polyglot.binary import as_hex_unicode, from_hex_unicode From 36ff53ca30025db7a78e30b1aad5d1fea0d2bdf0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 9 Jan 2023 22:33:31 +0530 Subject: [PATCH 0101/2055] Remove use of global connect_lambda() --- src/calibre/gui2/actions/choose_library.py | 1 + src/calibre/gui2/actions/copy_to_library.py | 1 + src/calibre/gui2/actions/device.py | 5 +-- src/calibre/gui2/actions/mark_books.py | 8 +++-- src/calibre/gui2/actions/polish.py | 1 + src/calibre/gui2/actions/similar_books.py | 1 + src/calibre/gui2/actions/toc_edit.py | 10 ++++-- src/calibre/gui2/actions/tweak_epub.py | 6 ++-- src/calibre/gui2/book_details.py | 1 + src/calibre/gui2/comments_editor.py | 15 ++++---- src/calibre/gui2/convert/look_and_feel.py | 8 ++--- src/calibre/gui2/covers.py | 12 +++---- src/calibre/gui2/device.py | 1 + src/calibre/gui2/dialogs/book_info.py | 14 ++++---- src/calibre/gui2/dialogs/choose_format.py | 9 ++--- src/calibre/gui2/dialogs/confirm_delete.py | 2 +- .../gui2/dialogs/confirm_delete_location.py | 4 ++- src/calibre/gui2/dialogs/confirm_merge.py | 6 ++-- src/calibre/gui2/dialogs/exim.py | 17 ++++----- src/calibre/gui2/dialogs/metadata_bulk.py | 1 + src/calibre/gui2/dialogs/search.py | 15 ++++---- src/calibre/gui2/dialogs/tag_editor.py | 1 + src/calibre/gui2/gestures.py | 3 +- src/calibre/gui2/jobs.py | 1 + src/calibre/gui2/library/annotations.py | 1 + src/calibre/gui2/metadata/bulk_download.py | 1 + src/calibre/gui2/metadata/diff.py | 1 + src/calibre/gui2/preferences/emailp.py | 4 +-- src/calibre/gui2/preferences/look_feel.py | 1 + src/calibre/gui2/preferences/toolbar.py | 7 ++-- src/calibre/gui2/store/web_store.py | 7 ++-- src/calibre/gui2/tag_browser/ui.py | 1 + src/calibre/gui2/tag_mapper.py | 1 + src/calibre/gui2/toc/main.py | 1 + src/calibre/gui2/tweak_book/boss.py | 1 + src/calibre/gui2/tweak_book/char_select.py | 9 ++--- src/calibre/gui2/tweak_book/diff/main.py | 18 ++++++---- src/calibre/gui2/tweak_book/diff/view.py | 11 +++--- src/calibre/gui2/tweak_book/download.py | 1 + src/calibre/gui2/tweak_book/editor/image.py | 10 +++--- .../gui2/tweak_book/editor/insert_resource.py | 10 +++--- src/calibre/gui2/tweak_book/file_list.py | 1 + src/calibre/gui2/tweak_book/polish.py | 24 +++++++------ src/calibre/gui2/tweak_book/preferences.py | 1 + src/calibre/gui2/tweak_book/search.py | 20 +++++------ src/calibre/gui2/tweak_book/spell.py | 1 + src/calibre/gui2/tweak_book/text_search.py | 17 +++++---- src/calibre/gui2/tweak_book/widgets.py | 23 ++++++------ src/calibre/gui2/viewer/toolbars.py | 10 +++--- src/calibre/gui2/viewer/ui.py | 13 +++---- src/calibre/gui2/widgets.py | 35 ++++++++++--------- src/calibre/startup.py | 34 +++++++++--------- 52 files changed, 235 insertions(+), 172 deletions(-) diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index e68fe447f9..46f5025eb3 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -26,6 +26,7 @@ from calibre.gui2 import ( ) from calibre.gui2.actions import InterfaceAction from calibre.library import current_library_name +from calibre.startup import connect_lambda from calibre.utils.config import prefs, tweaks from calibre.utils.icu import sort_key from calibre.utils.localization import ngettext diff --git a/src/calibre/gui2/actions/copy_to_library.py b/src/calibre/gui2/actions/copy_to_library.py index fbbbf92ad8..6890459fc1 100644 --- a/src/calibre/gui2/actions/copy_to_library.py +++ b/src/calibre/gui2/actions/copy_to_library.py @@ -26,6 +26,7 @@ from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions.choose_library import library_qicon from calibre.gui2.dialogs.progress import ProgressDialog from calibre.gui2.widgets2 import Dialog +from calibre.startup import connect_lambda from calibre.utils.config import prefs from calibre.utils.icu import numeric_sort_key, sort_key from calibre.utils.localization import ngettext diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py index 665c9b91c5..2921b1d8e2 100644 --- a/src/calibre/gui2/actions/device.py +++ b/src/calibre/gui2/actions/device.py @@ -5,11 +5,12 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -from qt.core import QIcon, QMenu, QTimer, QToolButton, pyqtSignal, QUrl +from qt.core import QIcon, QMenu, QTimer, QToolButton, QUrl, pyqtSignal -from calibre.gui2 import info_dialog, question_dialog, open_url +from calibre.gui2 import info_dialog, open_url, question_dialog from calibre.gui2.actions import InterfaceAction from calibre.gui2.dialogs.smartdevice import SmartdeviceDialog +from calibre.startup import connect_lambda from calibre.utils.icu import primary_sort_key from calibre.utils.smtp import config as email_config diff --git a/src/calibre/gui2/actions/mark_books.py b/src/calibre/gui2/actions/mark_books.py index a1abe17174..619fffcf9d 100644 --- a/src/calibre/gui2/actions/mark_books.py +++ b/src/calibre/gui2/actions/mark_books.py @@ -5,13 +5,15 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' from functools import partial - -from qt.core import (QTimer, QApplication, Qt, QEvent, QDialog, QMenu, QIcon, - QDialogButtonBox, QPushButton, QLabel, QGridLayout) +from qt.core import ( + QApplication, QDialog, QDialogButtonBox, QEvent, QGridLayout, QIcon, QLabel, QMenu, + QPushButton, Qt, QTimer, +) from calibre.gui2 import error_dialog from calibre.gui2.actions import InterfaceAction from calibre.gui2.widgets2 import HistoryComboBox +from calibre.startup import connect_lambda from calibre.utils.icu import sort_key diff --git a/src/calibre/gui2/actions/polish.py b/src/calibre/gui2/actions/polish.py index 944978339f..2832c94679 100644 --- a/src/calibre/gui2/actions/polish.py +++ b/src/calibre/gui2/actions/polish.py @@ -22,6 +22,7 @@ from calibre.gui2.actions import InterfaceAction from calibre.gui2.convert.metadata import create_opf_file from calibre.gui2.dialogs.progress import ProgressDialog from calibre.ptempfile import PersistentTemporaryDirectory +from calibre.startup import connect_lambda from calibre.utils.config_base import tweaks from calibre.utils.localization import ngettext from polyglot.builtins import iteritems, itervalues diff --git a/src/calibre/gui2/actions/similar_books.py b/src/calibre/gui2/actions/similar_books.py index cac3aa2fe6..3d74bb6068 100644 --- a/src/calibre/gui2/actions/similar_books.py +++ b/src/calibre/gui2/actions/similar_books.py @@ -9,6 +9,7 @@ __docformat__ = 'restructuredtext en' from qt.core import QToolButton from calibre.gui2.actions import InterfaceAction +from calibre.startup import connect_lambda from calibre.utils.icu import lower as icu_lower from polyglot.builtins import string_or_bytes diff --git a/src/calibre/gui2/actions/toc_edit.py b/src/calibre/gui2/actions/toc_edit.py index d077ed8d83..3ff9503c15 100644 --- a/src/calibre/gui2/actions/toc_edit.py +++ b/src/calibre/gui2/actions/toc_edit.py @@ -6,14 +6,15 @@ __copyright__ = '2013, Kovid Goyal ' __docformat__ = 'restructuredtext en' import os -from itertools import count from collections import OrderedDict +from itertools import count from qt.core import ( - QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QIcon, QLabel, QTimer + QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QIcon, QLabel, QTimer, ) from calibre.gui2 import error_dialog, gprefs, question_dialog from calibre.gui2.actions import InterfaceAction +from calibre.startup import connect_lambda from calibre.utils.monotonic import monotonic from polyglot.builtins import iteritems @@ -148,7 +149,10 @@ class ToCEditAction(InterfaceAction): self.do_one(book_id, fmt) def do_one(self, book_id, fmt): - import struct, json, atexit + import atexit + import json + import struct + from calibre.utils.shm import SharedMemory db = self.gui.current_db path = db.format(book_id, fmt, index_is_id=True, as_path=True) diff --git a/src/calibre/gui2/actions/tweak_epub.py b/src/calibre/gui2/actions/tweak_epub.py index dfa20b5452..05d2cc943c 100644 --- a/src/calibre/gui2/actions/tweak_epub.py +++ b/src/calibre/gui2/actions/tweak_epub.py @@ -6,11 +6,13 @@ __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' import time - -from qt.core import QTimer, QDialog, QDialogButtonBox, QCheckBox, QVBoxLayout, QLabel, Qt +from qt.core import ( + QCheckBox, QDialog, QDialogButtonBox, QLabel, Qt, QTimer, QVBoxLayout, +) from calibre.gui2 import error_dialog, question_dialog from calibre.gui2.actions import InterfaceAction +from calibre.startup import connect_lambda class Choose(QDialog): diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index b085f4f9dd..fc38d86d41 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -30,6 +30,7 @@ from calibre.gui2.dnd import ( dnd_get_files, dnd_get_image, dnd_has_extension, dnd_has_image, image_extensions, ) from calibre.gui2.widgets2 import HTMLDisplay +from calibre.startup import connect_lambda from calibre.utils.config import tweaks from calibre.utils.img import blend_image, image_from_x from calibre.utils.localization import is_rtl, langnames_to_langcodes diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index 0bd714957d..d69d2b59d7 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -10,23 +10,24 @@ from contextlib import contextmanager from html5_parser import parse from lxml import html from qt.core import ( - QAction, QApplication, QBrush, QByteArray, QCheckBox, QColor, QColorDialog, - QDialog, QDialogButtonBox, QFont, QFontInfo, QFontMetrics, QFormLayout, QIcon, - QKeySequence, QLabel, QLineEdit, QMenu, QPalette, QPlainTextEdit, QPushButton, - QSize, QSyntaxHighlighter, Qt, QTabWidget, QTextBlockFormat, QTextCharFormat, - QTextCursor, QTextEdit, QTextFormat, QTextListFormat, QTimer, QToolButton, QUrl, - QVBoxLayout, QWidget, pyqtSignal, pyqtSlot + QAction, QApplication, QBrush, QByteArray, QCheckBox, QColor, QColorDialog, QDialog, + QDialogButtonBox, QFont, QFontInfo, QFontMetrics, QFormLayout, QIcon, QKeySequence, + QLabel, QLineEdit, QMenu, QPalette, QPlainTextEdit, QPushButton, QSize, + QSyntaxHighlighter, Qt, QTabWidget, QTextBlockFormat, QTextCharFormat, QTextCursor, + QTextEdit, QTextFormat, QTextListFormat, QTimer, QToolButton, QUrl, QVBoxLayout, + QWidget, pyqtSignal, pyqtSlot, ) from calibre import xml_replace_entities from calibre.ebooks.chardet import xml_to_unicode from calibre.gui2 import ( - NO_URL_FORMATTING, choose_files, error_dialog, gprefs, is_dark_theme + NO_URL_FORMATTING, choose_files, error_dialog, gprefs, is_dark_theme, ) from calibre.gui2.book_details import css from calibre.gui2.flow_toolbar import create_flow_toolbar from calibre.gui2.widgets import LineEditECM from calibre.gui2.widgets2 import to_plain_text +from calibre.startup import connect_lambda from calibre.utils.cleantext import clean_xml_chars from calibre.utils.config import tweaks from calibre.utils.imghdr import what diff --git a/src/calibre/gui2/convert/look_and_feel.py b/src/calibre/gui2/convert/look_and_feel.py index 0fcd6e6d3e..a3ea165681 100644 --- a/src/calibre/gui2/convert/look_and_feel.py +++ b/src/calibre/gui2/convert/look_and_feel.py @@ -6,12 +6,12 @@ __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' import json +from qt.core import QDialog, Qt -from qt.core import Qt, QDialog - -from calibre.gui2.convert.look_and_feel_ui import Ui_Form -from calibre.gui2.convert import Widget from calibre.ebooks.conversion.config import OPTIONS +from calibre.gui2.convert import Widget +from calibre.gui2.convert.look_and_feel_ui import Ui_Form +from calibre.startup import connect_lambda from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/covers.py b/src/calibre/gui2/covers.py index aa149041e8..7dbb2ccabb 100644 --- a/src/calibre/gui2/covers.py +++ b/src/calibre/gui2/covers.py @@ -6,19 +6,19 @@ from collections import OrderedDict from contextlib import suppress from copy import deepcopy from qt.core import ( - QCheckBox, QColor, QColorDialog, QDialog, QDialogButtonBox, - QFormLayout, QFrame, QGridLayout, QHBoxLayout, QIcon, QInputDialog, QLabel, - QLineEdit, QListWidget, QListWidgetItem, QMenu, QPixmap, QPushButton, QSize, - QSizePolicy, QSpinBox, Qt, QTabWidget, QTimer, QToolButton, QVBoxLayout, QWidget, - pyqtSignal + QCheckBox, QColor, QColorDialog, QDialog, QDialogButtonBox, QFormLayout, QFrame, + QGridLayout, QHBoxLayout, QIcon, QInputDialog, QLabel, QLineEdit, QListWidget, + QListWidgetItem, QMenu, QPixmap, QPushButton, QSize, QSizePolicy, QSpinBox, Qt, + QTabWidget, QTimer, QToolButton, QVBoxLayout, QWidget, pyqtSignal, ) from calibre.constants import config_dir from calibre.ebooks.covers import ( - all_styles, cprefs, default_color_themes, generate_cover, override_prefs + all_styles, cprefs, default_color_themes, generate_cover, override_prefs, ) from calibre.gui2 import error_dialog, gprefs from calibre.gui2.font_family_chooser import FontFamilyChooser +from calibre.startup import connect_lambda from calibre.utils.date import now from calibre.utils.filenames import make_long_path_useable from calibre.utils.icu import primary_sort_key, sort_key diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index a9da487129..e1a6db40c3 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -43,6 +43,7 @@ from calibre.library.save_to_disk import find_plugboard from calibre.ptempfile import ( PersistentTemporaryFile, force_unicode as filename_to_unicode, ) +from calibre.startup import connect_lambda from calibre.utils.config import device_prefs, tweaks from calibre.utils.filenames import ascii_filename from calibre.utils.img import scale_image diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index f6e31e6455..19ed838c92 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -4,21 +4,21 @@ import textwrap from qt.core import ( - QAction, QApplication, QBrush, QCheckBox, QDialog, QGridLayout, - QHBoxLayout, QIcon, QKeySequence, QLabel, QListView, QModelIndex, QPalette, - QPixmap, QPushButton, QShortcut, QSize, Qt, QTimer, QToolButton, - QVBoxLayout, QWidget, pyqtSignal, QDialogButtonBox, QSplitter + QAction, QApplication, QBrush, QCheckBox, QDialog, QDialogButtonBox, QGridLayout, + QHBoxLayout, QIcon, QKeySequence, QLabel, QListView, QModelIndex, QPalette, QPixmap, + QPushButton, QShortcut, QSize, QSplitter, Qt, QTimer, QToolButton, QVBoxLayout, + QWidget, pyqtSignal, ) from calibre import fit_image from calibre.gui2 import NO_URL_FORMATTING, gprefs from calibre.gui2.book_details import ( - create_open_cover_with_menu, css, details_context_menu_event, render_html, - set_html + create_open_cover_with_menu, css, details_context_menu_event, render_html, set_html, ) from calibre.gui2.ui import get_gui from calibre.gui2.widgets import CoverView from calibre.gui2.widgets2 import Dialog, HTMLDisplay +from calibre.startup import connect_lambda class Cover(CoverView): @@ -60,7 +60,7 @@ class Configure(Dialog): def setup_ui(self): from calibre.gui2.preferences.look_feel import ( - DisplayedFields, move_field_down, move_field_up + DisplayedFields, move_field_down, move_field_up, ) self.l = QVBoxLayout(self) self.field_display_order = fdo = QListView(self) diff --git a/src/calibre/gui2/dialogs/choose_format.py b/src/calibre/gui2/dialogs/choose_format.py index f82946f79d..32b58034e7 100644 --- a/src/calibre/gui2/dialogs/choose_format.py +++ b/src/calibre/gui2/dialogs/choose_format.py @@ -2,12 +2,13 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' from functools import partial - from qt.core import ( - QDialog, QListWidgetItem, QModelIndex, QIcon, QLabel, QVBoxLayout, QSize, - QDialogButtonBox, QListWidget, QHBoxLayout, QPushButton, QMenu) + QDialog, QDialogButtonBox, QHBoxLayout, QIcon, QLabel, QListWidget, QListWidgetItem, + QMenu, QModelIndex, QPushButton, QSize, QVBoxLayout, +) from calibre.gui2 import file_icon_provider +from calibre.startup import connect_lambda class ChooseFormatDialog(QDialog): @@ -51,7 +52,7 @@ class ChooseFormatDialog(QDialog): self.update_open_with_button() def populate_open_with(self): - from calibre.gui2.open_with import populate_menu, edit_programs + from calibre.gui2.open_with import edit_programs, populate_menu menu = self.own menu.clear() fmt = self._formats[self.formats.currentRow()] diff --git a/src/calibre/gui2/dialogs/confirm_delete.py b/src/calibre/gui2/dialogs/confirm_delete.py index 24d7cf1498..1e161f1749 100644 --- a/src/calibre/gui2/dialogs/confirm_delete.py +++ b/src/calibre/gui2/dialogs/confirm_delete.py @@ -6,7 +6,7 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' from qt.core import ( - QDialog, Qt, QIcon, QVBoxLayout, QHBoxLayout, QLabel, QCheckBox, QDialogButtonBox + QCheckBox, QDialog, QDialogButtonBox, QHBoxLayout, QIcon, QLabel, Qt, QVBoxLayout, ) from calibre import confirm_config_name diff --git a/src/calibre/gui2/dialogs/confirm_delete_location.py b/src/calibre/gui2/dialogs/confirm_delete_location.py index 98111edebb..3dacabbde0 100644 --- a/src/calibre/gui2/dialogs/confirm_delete_location.py +++ b/src/calibre/gui2/dialogs/confirm_delete_location.py @@ -6,8 +6,10 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' \ '2010, John Schember ' __docformat__ = 'restructuredtext en' +from qt.core import QDialog, QIcon, Qt + from calibre.gui2.dialogs.confirm_delete_location_ui import Ui_Dialog -from qt.core import QDialog, Qt, QIcon +from calibre.startup import connect_lambda class Dialog(QDialog, Ui_Dialog): diff --git a/src/calibre/gui2/dialogs/confirm_merge.py b/src/calibre/gui2/dialogs/confirm_merge.py index 7e6dfe71d7..268b9b21ca 100644 --- a/src/calibre/gui2/dialogs/confirm_merge.py +++ b/src/calibre/gui2/dialogs/confirm_merge.py @@ -5,14 +5,16 @@ __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' from qt.core import ( - QVBoxLayout, QSplitter, QWidget, QLabel, QCheckBox, QTextBrowser, Qt, QDialog, QDialogButtonBox + QCheckBox, QDialog, QDialogButtonBox, QLabel, QSplitter, Qt, QTextBrowser, + QVBoxLayout, QWidget, ) from calibre.ebooks.metadata import authors_to_string from calibre.ebooks.metadata.book.base import field_metadata from calibre.gui2 import dynamic, gprefs -from calibre.gui2.widgets2 import Dialog from calibre.gui2.dialogs.confirm_delete import confirm_config_name +from calibre.gui2.widgets2 import Dialog +from calibre.startup import connect_lambda from calibre.utils.config import tweaks from calibre.utils.date import format_date diff --git a/src/calibre/gui2/dialogs/exim.py b/src/calibre/gui2/dialogs/exim.py index 2d33a181fd..f0c9701ee3 100644 --- a/src/calibre/gui2/dialogs/exim.py +++ b/src/calibre/gui2/dialogs/exim.py @@ -2,22 +2,23 @@ # License: GPLv3 Copyright: 2015, Kovid Goyal +import os +import stat from functools import partial -from threading import Thread, Event -import os, stat - from qt.core import ( - QSize, QStackedLayout, QWidget, QVBoxLayout, QLabel, QPushButton, - QListWidget, QListWidgetItem, QIcon, Qt, pyqtSignal, QGridLayout, - QProgressBar, QDialog, QDialogButtonBox, QScrollArea, QLineEdit, QFrame, QAbstractItemView + QAbstractItemView, QDialog, QDialogButtonBox, QFrame, QGridLayout, QIcon, QLabel, + QLineEdit, QListWidget, QListWidgetItem, QProgressBar, QPushButton, QScrollArea, + QSize, QStackedLayout, Qt, QVBoxLayout, QWidget, pyqtSignal, ) +from threading import Event, Thread -from calibre import human_readable, as_unicode +from calibre import as_unicode, human_readable from calibre.constants import iswindows from calibre.db.legacy import LibraryDatabase from calibre.gui2 import choose_dir, error_dialog, question_dialog from calibre.gui2.widgets2 import Dialog -from calibre.utils.exim import all_known_libraries, export, Importer, import_data +from calibre.startup import connect_lambda +from calibre.utils.exim import Importer, all_known_libraries, export, import_data from calibre.utils.icu import numeric_sort_key diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index a5c975db55..ee84c037e9 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -28,6 +28,7 @@ from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor from calibre.gui2.widgets import LineEditECM +from calibre.startup import connect_lambda from calibre.utils.config import JSONConfig, dynamic, prefs, tweaks from calibre.utils.date import internal_iso_format_string, qt_to_dt from calibre.utils.icu import ( diff --git a/src/calibre/gui2/dialogs/search.py b/src/calibre/gui2/dialogs/search.py index c3589c8da8..876ceffbf8 100644 --- a/src/calibre/gui2/dialogs/search.py +++ b/src/calibre/gui2/dialogs/search.py @@ -1,22 +1,23 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import re, copy +import copy +import re from datetime import date - from qt.core import ( - QDialog, QDialogButtonBox, QFrame, QLabel, QComboBox, QIcon, QVBoxLayout, Qt, - QSize, QHBoxLayout, QTabWidget, QLineEdit, QWidget, QGroupBox, QFormLayout, - QSpinBox, QRadioButton, QPushButton, QToolButton + QComboBox, QDialog, QDialogButtonBox, QFormLayout, QFrame, QGroupBox, QHBoxLayout, + QIcon, QLabel, QLineEdit, QPushButton, QRadioButton, QSize, QSpinBox, Qt, + QTabWidget, QToolButton, QVBoxLayout, QWidget, ) from calibre import strftime -from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH from calibre.gui2 import gprefs from calibre.gui2.complete2 import EditWithComplete -from calibre.utils.icu import sort_key +from calibre.library.caches import CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH +from calibre.startup import connect_lambda from calibre.utils.config import tweaks from calibre.utils.date import now +from calibre.utils.icu import sort_key from calibre.utils.localization import localize_user_manual_link box_values = {} diff --git a/src/calibre/gui2/dialogs/tag_editor.py b/src/calibre/gui2/dialogs/tag_editor.py index 5c0bd60df0..1a979546ed 100644 --- a/src/calibre/gui2/dialogs/tag_editor.py +++ b/src/calibre/gui2/dialogs/tag_editor.py @@ -9,6 +9,7 @@ from calibre.constants import islinux from calibre.gui2 import error_dialog, gprefs, question_dialog from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.tag_editor_ui import Ui_TagEditor +from calibre.startup import connect_lambda from calibre.utils.icu import sort_key diff --git a/src/calibre/gui2/gestures.py b/src/calibre/gui2/gestures.py index 8688dae217..1b60d1951c 100644 --- a/src/calibre/gui2/gestures.py +++ b/src/calibre/gui2/gestures.py @@ -6,9 +6,10 @@ import os from functools import lru_cache from qt.core import ( QApplication, QEvent, QInputDevice, QMouseEvent, QObject, QPointF, QScroller, Qt, - pyqtSignal + pyqtSignal, ) +from calibre.startup import connect_lambda from calibre.utils.monotonic import monotonic from polyglot.builtins import itervalues diff --git a/src/calibre/gui2/jobs.py b/src/calibre/gui2/jobs.py index 700f24475c..6b1368e94e 100644 --- a/src/calibre/gui2/jobs.py +++ b/src/calibre/gui2/jobs.py @@ -27,6 +27,7 @@ from calibre.gui2.dialogs.jobs_ui import Ui_JobsDialog from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.threaded_jobs import ThreadedJob, ThreadedJobServer from calibre.gui2.widgets2 import Dialog +from calibre.startup import connect_lambda from calibre.utils.icu import lower from calibre.utils.ipc.job import ParallelJob from calibre.utils.ipc.server import Server diff --git a/src/calibre/gui2/library/annotations.py b/src/calibre/gui2/library/annotations.py index 60c36ab53d..ad0038553d 100644 --- a/src/calibre/gui2/library/annotations.py +++ b/src/calibre/gui2/library/annotations.py @@ -29,6 +29,7 @@ from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.viewer.widgets import ResultsDelegate, SearchBox from calibre.gui2.widgets import BusyCursor from calibre.gui2.widgets2 import Dialog, RightClickButton +from calibre.startup import connect_lambda from calibre.utils.localization import ngettext diff --git a/src/calibre/gui2/metadata/bulk_download.py b/src/calibre/gui2/metadata/bulk_download.py index 03cbeed404..b744635ce9 100644 --- a/src/calibre/gui2/metadata/bulk_download.py +++ b/src/calibre/gui2/metadata/bulk_download.py @@ -14,6 +14,7 @@ from threading import Thread from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.gui2.threaded_jobs import ThreadedJob from calibre.ptempfile import PersistentTemporaryDirectory, PersistentTemporaryFile +from calibre.startup import connect_lambda from calibre.utils.ipc.simple_worker import WorkerError, fork_job from calibre.utils.localization import ngettext from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/metadata/diff.py b/src/calibre/gui2/metadata/diff.py index 4266ba1239..5140fcb6b6 100644 --- a/src/calibre/gui2/metadata/diff.py +++ b/src/calibre/gui2/metadata/diff.py @@ -25,6 +25,7 @@ from calibre.gui2.languages import LanguagesEdit as LE from calibre.gui2.metadata.basic_widgets import PubdateEdit, RatingEdit from calibre.gui2.widgets2 import RightClickButton from calibre.ptempfile import PersistentTemporaryFile +from calibre.startup import connect_lambda from calibre.utils.date import UNDEFINED_DATE from calibre.utils.icu import lower as icu_lower from calibre.utils.localization import ngettext diff --git a/src/calibre/gui2/preferences/emailp.py b/src/calibre/gui2/preferences/emailp.py index bc5731d5c8..3b4c34b0a7 100644 --- a/src/calibre/gui2/preferences/emailp.py +++ b/src/calibre/gui2/preferences/emailp.py @@ -4,12 +4,12 @@ import re import textwrap - -from qt.core import QAbstractTableModel, QFont, Qt, QAbstractItemView +from qt.core import QAbstractItemView, QAbstractTableModel, QFont, Qt from calibre.gui2 import gprefs from calibre.gui2.preferences import AbortCommit, ConfigWidgetBase, test_widget from calibre.gui2.preferences.email_ui import Ui_Form +from calibre.startup import connect_lambda from calibre.utils.config import ConfigProxy from calibre.utils.icu import numeric_sort_key from calibre.utils.smtp import config as smtp_prefs diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index ae951cc5e8..80099d1134 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -37,6 +37,7 @@ from calibre.gui2.preferences.coloring import EditRules from calibre.gui2.preferences.look_feel_ui import Ui_Form from calibre.gui2.widgets import BusyCursor from calibre.gui2.widgets2 import Dialog +from calibre.startup import connect_lambda from calibre.utils.config import prefs from calibre.utils.icu import sort_key from calibre.utils.localization import available_translations, get_lang, get_language diff --git a/src/calibre/gui2/preferences/toolbar.py b/src/calibre/gui2/preferences/toolbar.py index aaeab85101..44c6945e3d 100644 --- a/src/calibre/gui2/preferences/toolbar.py +++ b/src/calibre/gui2/preferences/toolbar.py @@ -5,12 +5,13 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -from qt.core import QAbstractListModel, Qt, QIcon, QItemSelectionModel +from qt.core import QAbstractListModel, QIcon, QItemSelectionModel, Qt from calibre import force_unicode +from calibre.gui2 import error_dialog, gprefs, warning_dialog +from calibre.gui2.preferences import AbortCommit, ConfigWidgetBase, test_widget from calibre.gui2.preferences.toolbar_ui import Ui_Form -from calibre.gui2 import gprefs, warning_dialog, error_dialog -from calibre.gui2.preferences import ConfigWidgetBase, test_widget, AbortCommit +from calibre.startup import connect_lambda from calibre.utils.icu import primary_sort_key diff --git a/src/calibre/gui2/store/web_store.py b/src/calibre/gui2/store/web_store.py index 6895b5f031..4647d40b47 100644 --- a/src/calibre/gui2/store/web_store.py +++ b/src/calibre/gui2/store/web_store.py @@ -7,22 +7,23 @@ import os import shutil from qt.core import ( QApplication, QHBoxLayout, QIcon, QLabel, QProgressBar, QPushButton, QSize, QUrl, - QVBoxLayout, QWidget, pyqtSignal + QVBoxLayout, QWidget, pyqtSignal, ) from qt.webengine import ( - QWebEngineDownloadRequest, QWebEnginePage, QWebEngineProfile, QWebEngineView + QWebEngineDownloadRequest, QWebEnginePage, QWebEngineProfile, QWebEngineView, ) from calibre import random_user_agent, url_slash_cleaner from calibre.constants import STORE_DIALOG_APP_UID, islinux, iswindows from calibre.ebooks import BOOK_EXTENSIONS from calibre.gui2 import ( - Application, choose_save_file, error_dialog, gprefs, info_dialog, set_app_uid + Application, choose_save_file, error_dialog, gprefs, info_dialog, set_app_uid, ) from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.listener import send_message_in_process from calibre.gui2.main_window import MainWindow from calibre.ptempfile import PersistentTemporaryDirectory, reset_base_dir +from calibre.startup import connect_lambda from calibre.utils.webengine import setup_profile from polyglot.binary import as_base64_bytes, from_base64_bytes from polyglot.builtins import string_or_bytes diff --git a/src/calibre/gui2/tag_browser/ui.py b/src/calibre/gui2/tag_browser/ui.py index d4c67d2997..34afb2951d 100644 --- a/src/calibre/gui2/tag_browser/ui.py +++ b/src/calibre/gui2/tag_browser/ui.py @@ -22,6 +22,7 @@ from calibre.gui2.dialogs.tag_list_editor import TagListEditor from calibre.gui2.tag_browser.view import TagsView from calibre.gui2.widgets import HistoryLineEdit from calibre.library.field_metadata import category_icon_map +from calibre.startup import connect_lambda from calibre.utils.icu import sort_key from calibre.utils.localization import ngettext from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/tag_mapper.py b/src/calibre/gui2/tag_mapper.py index 0f11a3fd03..d3e990b2e1 100644 --- a/src/calibre/gui2/tag_mapper.py +++ b/src/calibre/gui2/tag_mapper.py @@ -16,6 +16,7 @@ from calibre.gui2 import Application, error_dialog, question_dialog from calibre.gui2.complete2 import EditWithComplete from calibre.gui2.ui import get_gui from calibre.gui2.widgets2 import Dialog +from calibre.startup import connect_lambda from calibre.utils.config import JSONConfig from calibre.utils.localization import localize_user_manual_link from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py index 50f1e6565b..fcb293c22c 100644 --- a/src/calibre/gui2/toc/main.py +++ b/src/calibre/gui2/toc/main.py @@ -26,6 +26,7 @@ from calibre.gui2.convert.xpath_wizard import XPathEdit from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.toc.location import ItemEdit from calibre.ptempfile import reset_base_dir +from calibre.startup import connect_lambda from calibre.utils.config import JSONConfig from calibre.utils.filenames import atomic_rename from calibre.utils.icu import lower as icu_lower, upper as icu_upper diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index 7958bef8eb..3b397203e4 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -61,6 +61,7 @@ from calibre.gui2.tweak_book.widgets import ( ) from calibre.gui2.widgets import BusyCursor from calibre.ptempfile import PersistentTemporaryDirectory, TemporaryDirectory +from calibre.startup import connect_lambda from calibre.utils.config import JSONConfig from calibre.utils.icu import numeric_sort_key from calibre.utils.imghdr import identify diff --git a/src/calibre/gui2/tweak_book/char_select.py b/src/calibre/gui2/tweak_book/char_select.py index 59cc8d6be7..d58ef2e8df 100644 --- a/src/calibre/gui2/tweak_book/char_select.py +++ b/src/calibre/gui2/tweak_book/char_select.py @@ -9,16 +9,17 @@ import textwrap from bisect import bisect from functools import partial from qt.core import ( - QAbstractItemModel, QAbstractListModel, QApplication, QCheckBox, QGridLayout, - QHBoxLayout, QIcon, QInputMethodEvent, QLabel, QListView, QMenu, QMimeData, - QModelIndex, QPen, QPushButton, QSize, QSizePolicy, QSplitter, - QStyledItemDelegate, Qt, QToolButton, QTreeView, pyqtSignal, QAbstractItemView, QDialogButtonBox + QAbstractItemModel, QAbstractItemView, QAbstractListModel, QApplication, QCheckBox, + QDialogButtonBox, QGridLayout, QHBoxLayout, QIcon, QInputMethodEvent, QLabel, + QListView, QMenu, QMimeData, QModelIndex, QPen, QPushButton, QSize, QSizePolicy, + QSplitter, QStyledItemDelegate, Qt, QToolButton, QTreeView, pyqtSignal, ) from calibre.gui2.tweak_book import tprefs from calibre.gui2.tweak_book.widgets import Dialog from calibre.gui2.widgets import BusyCursor from calibre.gui2.widgets2 import HistoryLineEdit2 +from calibre.startup import connect_lambda from calibre.utils.icu import safe_chr as codepoint_to_chr from calibre.utils.unicode_names import character_name_from_code, points_for_word from calibre_extensions.progress_indicator import set_no_activate_on_click diff --git a/src/calibre/gui2/tweak_book/diff/main.py b/src/calibre/gui2/tweak_book/diff/main.py index 04e8340fac..9ae3ded1d1 100644 --- a/src/calibre/gui2/tweak_book/diff/main.py +++ b/src/calibre/gui2/tweak_book/diff/main.py @@ -4,22 +4,26 @@ __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' -import sys, os, re, textwrap +import os +import re +import sys +import textwrap from functools import partial - from qt.core import ( - QGridLayout, QToolButton, QIcon, QRadioButton, QMenu, QApplication, Qt, - QSize, QWidget, QLabel, QStackedLayout, QPainter, QRect, QVBoxLayout, - QCursor, QEventLoop, QKeySequence, pyqtSignal, QTimer, QHBoxLayout, QDialogButtonBox) + QApplication, QCursor, QDialogButtonBox, QEventLoop, QGridLayout, QHBoxLayout, + QIcon, QKeySequence, QLabel, QMenu, QPainter, QRadioButton, QRect, QSize, + QStackedLayout, Qt, QTimer, QToolButton, QVBoxLayout, QWidget, pyqtSignal, +) from calibre.ebooks.oeb.polish.container import Container from calibre.ebooks.oeb.polish.utils import guess_type from calibre.gui2 import info_dialog from calibre.gui2.progress_indicator import ProgressIndicator -from calibre.gui2.tweak_book.editor import syntax_from_mime from calibre.gui2.tweak_book.diff.view import DiffView +from calibre.gui2.tweak_book.editor import syntax_from_mime from calibre.gui2.tweak_book.widgets import Dialog from calibre.gui2.widgets2 import HistoryLineEdit2 +from calibre.startup import connect_lambda from calibre.utils.filenames import samefile from calibre.utils.icu import numeric_sort_key from polyglot.builtins import iteritems @@ -97,7 +101,7 @@ def changed_files(list_of_names1, list_of_names2, get_data1, get_data2): def get_decoded_raw(name): - from calibre.ebooks.chardet import xml_to_unicode, force_encoding + from calibre.ebooks.chardet import force_encoding, xml_to_unicode with open(name, 'rb') as f: raw = f.read() syntax = syntax_from_mime(name, guess_type(name)) diff --git a/src/calibre/gui2/tweak_book/diff/view.py b/src/calibre/gui2/tweak_book/diff/view.py index 39ee3fc07c..3b023420ed 100644 --- a/src/calibre/gui2/tweak_book/diff/view.py +++ b/src/calibre/gui2/tweak_book/diff/view.py @@ -10,10 +10,10 @@ from functools import partial from itertools import chain from math import ceil from qt.core import ( - QApplication, QBrush, QColor, QEvent, QEventLoop, QFont, QHBoxLayout, - QIcon, QImage, QKeySequence, QMenu, QPainter, QPainterPath, QPalette, QPen, - QPixmap, QPlainTextEdit, QRect, QScrollBar, QSplitter, QSplitterHandle, Qt, - QTextCharFormat, QTextCursor, QTextLayout, QTimer, QWidget, pyqtSignal + QApplication, QBrush, QColor, QEvent, QEventLoop, QFont, QHBoxLayout, QIcon, QImage, + QKeySequence, QMenu, QPainter, QPainterPath, QPalette, QPen, QPixmap, + QPlainTextEdit, QRect, QScrollBar, QSplitter, QSplitterHandle, Qt, QTextCharFormat, + QTextCursor, QTextLayout, QTimer, QWidget, pyqtSignal, ) from calibre import fit_image, human_readable @@ -22,10 +22,11 @@ from calibre.gui2.tweak_book import tprefs from calibre.gui2.tweak_book.diff import get_sequence_matcher from calibre.gui2.tweak_book.diff.highlight import get_highlighter from calibre.gui2.tweak_book.editor.text import ( - LineNumbers, PlainTextEdit, default_font_family + LineNumbers, PlainTextEdit, default_font_family, ) from calibre.gui2.tweak_book.editor.themes import get_theme, theme_color from calibre.gui2.widgets import BusyCursor +from calibre.startup import connect_lambda from calibre.utils.icu import utf16_length from calibre.utils.xml_parse import safe_xml_fromstring from polyglot.builtins import as_bytes, iteritems diff --git a/src/calibre/gui2/tweak_book/download.py b/src/calibre/gui2/tweak_book/download.py index 246c5b46c1..97b09b84df 100644 --- a/src/calibre/gui2/tweak_book/download.py +++ b/src/calibre/gui2/tweak_book/download.py @@ -15,6 +15,7 @@ from calibre.gui2 import error_dialog, info_dialog, warning_dialog from calibre.gui2.progress_indicator import WaitStack from calibre.gui2.tweak_book import current_container from calibre.gui2.tweak_book.widgets import Dialog +from calibre.startup import connect_lambda from calibre.utils.localization import ngettext from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/tweak_book/editor/image.py b/src/calibre/gui2/tweak_book/editor/image.py index 277433123f..715ac47963 100644 --- a/src/calibre/gui2/tweak_book/editor/image.py +++ b/src/calibre/gui2/tweak_book/editor/image.py @@ -5,14 +5,16 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' import sys - from qt.core import ( - QMainWindow, Qt, QApplication, pyqtSignal, QLabel, QIcon, QFormLayout, QSize, - QDialog, QSpinBox, QCheckBox, QDialogButtonBox, QToolButton, QMenu, QInputDialog) + QApplication, QCheckBox, QDialog, QDialogButtonBox, QFormLayout, QIcon, + QInputDialog, QLabel, QMainWindow, QMenu, QSize, QSpinBox, Qt, QToolButton, + pyqtSignal, +) from calibre.gui2 import error_dialog -from calibre.gui2.tweak_book import actions, tprefs, editors +from calibre.gui2.tweak_book import actions, editors, tprefs from calibre.gui2.tweak_book.editor.canvas import Canvas +from calibre.startup import connect_lambda from polyglot.builtins import itervalues diff --git a/src/calibre/gui2/tweak_book/editor/insert_resource.py b/src/calibre/gui2/tweak_book/editor/insert_resource.py index d7ec9bde5c..d4ced61597 100644 --- a/src/calibre/gui2/tweak_book/editor/insert_resource.py +++ b/src/calibre/gui2/tweak_book/editor/insert_resource.py @@ -8,10 +8,11 @@ import os import sys from functools import partial from qt.core import ( - QAbstractListModel, QApplication, QCheckBox, QFormLayout, QGridLayout, QClipboard, - QHBoxLayout, QIcon, QInputDialog, QLabel, QLineEdit, QListView, QMenu, QPainter, - QPixmap, QRect, QSize, QSizePolicy, QSortFilterProxyModel, QStyledItemDelegate, - Qt, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, pyqtSignal, QDialog, QDialogButtonBox + QAbstractListModel, QApplication, QCheckBox, QClipboard, QDialog, QDialogButtonBox, + QFormLayout, QGridLayout, QHBoxLayout, QIcon, QInputDialog, QLabel, QLineEdit, + QListView, QMenu, QPainter, QPixmap, QRect, QSize, QSizePolicy, + QSortFilterProxyModel, QStyledItemDelegate, Qt, QToolButton, QTreeWidget, + QTreeWidgetItem, QVBoxLayout, pyqtSignal, ) from calibre import fit_image @@ -23,6 +24,7 @@ from calibre.gui2.tweak_book import current_container, tprefs from calibre.gui2.tweak_book.file_list import name_is_ok from calibre.gui2.tweak_book.widgets import Dialog from calibre.ptempfile import PersistentTemporaryFile +from calibre.startup import connect_lambda from calibre.utils.icu import numeric_sort_key from calibre.utils.localization import canonicalize_lang, get_lang from calibre_extensions.progress_indicator import set_no_activate_on_click diff --git a/src/calibre/gui2/tweak_book/file_list.py b/src/calibre/gui2/tweak_book/file_list.py index 1eb9dfef9f..056fc6df6c 100644 --- a/src/calibre/gui2/tweak_book/file_list.py +++ b/src/calibre/gui2/tweak_book/file_list.py @@ -35,6 +35,7 @@ from calibre.gui2.tweak_book import ( ) from calibre.gui2.tweak_book.editor import syntax_from_mime from calibre.gui2.tweak_book.templates import template_for +from calibre.startup import connect_lambda from calibre.utils.fonts.utils import get_font_names from calibre.utils.icu import numeric_sort_key from calibre.utils.localization import ngettext, pgettext diff --git a/src/calibre/gui2/tweak_book/polish.py b/src/calibre/gui2/tweak_book/polish.py index 80b23ea99e..4ddfa0b2f2 100644 --- a/src/calibre/gui2/tweak_book/polish.py +++ b/src/calibre/gui2/tweak_book/polish.py @@ -5,20 +5,20 @@ __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' import re +from qt.core import ( + QAbstractItemView, QApplication, QCheckBox, QDialog, QDialogButtonBox, QHBoxLayout, + QIcon, QLabel, QListWidget, QListWidgetItem, QPalette, QPen, QPixmap, QProgressBar, + QSize, QSpinBox, QStyle, QStyledItemDelegate, Qt, QTextBrowser, QVBoxLayout, + pyqtSignal, +) from threading import Thread -from qt.core import ( - QTextBrowser, QVBoxLayout, QDialog, QDialogButtonBox, QIcon, QLabel, - QCheckBox, Qt, QListWidgetItem, QHBoxLayout, QListWidget, QPixmap, - QSpinBox, QStyledItemDelegate, QSize, QStyle, QPen, QPalette, - QProgressBar, pyqtSignal, QApplication, QAbstractItemView -) - -from calibre import human_readable, fit_image, force_unicode +from calibre import fit_image, force_unicode, human_readable from calibre.ebooks.oeb.polish.main import CUSTOMIZATION from calibre.gui2 import empty_index, question_dialog -from calibre.gui2.tweak_book import tprefs, current_container, set_current_container +from calibre.gui2.tweak_book import current_container, set_current_container, tprefs from calibre.gui2.tweak_book.widgets import Dialog +from calibre.startup import connect_lambda from calibre.utils.icu import numeric_sort_key @@ -254,8 +254,8 @@ class CompressImagesProgress(Dialog): t.start() def run_compress(self): - from calibre.gui2.tweak_book import current_container from calibre.ebooks.oeb.polish.images import compress_images + from calibre.gui2.tweak_book import current_container report = [] try: self.result = (compress_images( @@ -304,7 +304,9 @@ class CompressImagesProgress(Dialog): if __name__ == '__main__': from calibre.gui2 import Application app = Application([]) - import sys, sip + import sip + import sys + from calibre.ebooks.oeb.polish.container import get_container c = get_container(sys.argv[-1], tweak_mode=True) set_current_container(c) diff --git a/src/calibre/gui2/tweak_book/preferences.py b/src/calibre/gui2/tweak_book/preferences.py index 506d316538..942fdc6f76 100644 --- a/src/calibre/gui2/tweak_book/preferences.py +++ b/src/calibre/gui2/tweak_book/preferences.py @@ -31,6 +31,7 @@ from calibre.gui2.tweak_book.editor.themes import ( from calibre.gui2.tweak_book.spell import ManageDictionaries from calibre.gui2.tweak_book.widgets import Dialog from calibre.gui2.widgets2 import ColorButton +from calibre.startup import connect_lambda from calibre.utils.localization import get_lang, ngettext from polyglot.builtins import iteritems, itervalues diff --git a/src/calibre/gui2/tweak_book/search.py b/src/calibre/gui2/tweak_book/search.py index d864bcd06f..80fd278c40 100644 --- a/src/calibre/gui2/tweak_book/search.py +++ b/src/calibre/gui2/tweak_book/search.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2013, Kovid Goyal - import copy import json import regex @@ -9,32 +8,33 @@ import time from collections import Counter, OrderedDict from functools import partial from qt.core import ( - QAbstractListModel, QAction, QApplication, QCheckBox, QComboBox, QFont, QFrame, - QGridLayout, QHBoxLayout, QIcon, QItemSelection, QKeySequence, QLabel, QLineEdit, - QListView, QMenu, QMimeData, QModelIndex, QPushButton, QScrollArea, QSize, QItemSelectionModel, - QSizePolicy, QStackedLayout, QStyledItemDelegate, Qt, QTimer, QToolBar, QDialog, - QToolButton, QVBoxLayout, QWidget, pyqtSignal, QAbstractItemView, QEvent, QDialogButtonBox + QAbstractItemView, QAbstractListModel, QAction, QApplication, QCheckBox, QComboBox, + QDialog, QDialogButtonBox, QEvent, QFont, QFrame, QGridLayout, QHBoxLayout, QIcon, + QItemSelection, QItemSelectionModel, QKeySequence, QLabel, QLineEdit, QListView, + QMenu, QMimeData, QModelIndex, QPushButton, QScrollArea, QSize, QSizePolicy, + QStackedLayout, QStyledItemDelegate, Qt, QTimer, QToolBar, QToolButton, QVBoxLayout, + QWidget, pyqtSignal, ) from calibre import prepare_string_for_xml from calibre.constants import iswindows from calibre.ebooks.conversion.search_replace import ( - REGEX_FLAGS, compile_regular_expression + REGEX_FLAGS, compile_regular_expression, ) from calibre.gui2 import choose_files, choose_save_file, error_dialog, info_dialog from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.message_box import MessageBox from calibre.gui2.tweak_book import current_container, editors, tprefs from calibre.gui2.tweak_book.editor.snippets import ( - KEY, MODIFIER, SnippetTextEdit, find_matching_snip, parse_template, - string_length + KEY, MODIFIER, SnippetTextEdit, find_matching_snip, parse_template, string_length, ) from calibre.gui2.tweak_book.function_replace import ( Function, FunctionBox, FunctionEditor, functions as replace_functions, - remove_function + remove_function, ) from calibre.gui2.widgets import BusyCursor from calibre.gui2.widgets2 import FlowLayout, HistoryComboBox +from calibre.startup import connect_lambda from calibre.utils.icu import primary_contains from polyglot.builtins import error_message, iteritems diff --git a/src/calibre/gui2/tweak_book/spell.py b/src/calibre/gui2/tweak_book/spell.py index 3ab38c25bf..34f7bfaca7 100644 --- a/src/calibre/gui2/tweak_book/spell.py +++ b/src/calibre/gui2/tweak_book/spell.py @@ -41,6 +41,7 @@ from calibre.spell.dictionary import ( get_dictionary, remove_dictionary, rename_dictionary, ) from calibre.spell.import_from import import_from_oxt +from calibre.startup import connect_lambda from calibre.utils.icu import contains, primary_contains, primary_sort_key, sort_key from calibre.utils.localization import ( calibre_langcode_to_name, canonicalize_lang, get_lang, get_language, diff --git a/src/calibre/gui2/tweak_book/text_search.py b/src/calibre/gui2/tweak_book/text_search.py index 5f28212c4e..d67c712dc8 100644 --- a/src/calibre/gui2/tweak_book/text_search.py +++ b/src/calibre/gui2/tweak_book/text_search.py @@ -2,19 +2,22 @@ # License: GPLv3 Copyright: 2016, Kovid Goyal -from qt.core import ( - QWidget, QHBoxLayout, QVBoxLayout, QLabel, QComboBox, QPushButton, QIcon, - pyqtSignal, QFont, QCheckBox, QSizePolicy -) from lxml.etree import tostring +from qt.core import ( + QCheckBox, QComboBox, QFont, QHBoxLayout, QIcon, QLabel, QPushButton, QSizePolicy, + QVBoxLayout, QWidget, pyqtSignal, +) from calibre import prepare_string_for_xml from calibre.gui2 import error_dialog -from calibre.gui2.tweak_book import tprefs, editors, current_container -from calibre.gui2.tweak_book.search import get_search_regex, InvalidRegex, initialize_search_request +from calibre.gui2.tweak_book import current_container, editors, tprefs +from calibre.gui2.tweak_book.search import ( + InvalidRegex, get_search_regex, initialize_search_request, +) from calibre.gui2.widgets import BusyCursor from calibre.gui2.widgets2 import HistoryComboBox -from polyglot.builtins import iteritems, error_message +from calibre.startup import connect_lambda +from polyglot.builtins import error_message, iteritems # UI {{{ diff --git a/src/calibre/gui2/tweak_book/widgets.py b/src/calibre/gui2/tweak_book/widgets.py index bc9e2bb889..1b492ae80f 100644 --- a/src/calibre/gui2/tweak_book/widgets.py +++ b/src/calibre/gui2/tweak_book/widgets.py @@ -10,35 +10,36 @@ import unicodedata from collections import OrderedDict from math import ceil from qt.core import ( - QAbstractListModel, QApplication, QCheckBox, QComboBox, QDialog, - QDialogButtonBox, QEvent, QFormLayout, QFrame, QGridLayout, QGroupBox, - QHBoxLayout, QIcon, QItemSelectionModel, QLabel, QLineEdit, QListView, QMimeData, - QModelIndex, QPainter, QPalette, QPixmap, QPlainTextEdit, QPoint, QRect, QSize, - QSizePolicy, QSplitter, QStaticText, QStyle, QStyledItemDelegate, Qt, QTextCursor, - QTextDocument, QTextOption, QToolButton, QVBoxLayout, QWidget, pyqtSignal + QAbstractListModel, QApplication, QCheckBox, QComboBox, QDialog, QDialogButtonBox, + QEvent, QFormLayout, QFrame, QGridLayout, QGroupBox, QHBoxLayout, QIcon, + QItemSelectionModel, QLabel, QLineEdit, QListView, QMimeData, QModelIndex, QPainter, + QPalette, QPixmap, QPlainTextEdit, QPoint, QRect, QSize, QSizePolicy, QSplitter, + QStaticText, QStyle, QStyledItemDelegate, Qt, QTextCursor, QTextDocument, + QTextOption, QToolButton, QVBoxLayout, QWidget, pyqtSignal, ) from calibre import human_readable, prepare_string_for_xml from calibre.constants import iswindows from calibre.ebooks.oeb.polish.cover import get_raster_cover_name from calibre.ebooks.oeb.polish.toc import ( - ensure_container_has_nav, get_guide_landmarks, get_nav_landmarks, set_landmarks + ensure_container_has_nav, get_guide_landmarks, get_nav_landmarks, set_landmarks, ) from calibre.ebooks.oeb.polish.upgrade import guide_epubtype_map from calibre.ebooks.oeb.polish.utils import guess_type, lead_text from calibre.gui2 import ( - choose_files, choose_images, choose_save_file, error_dialog, info_dialog + choose_files, choose_images, choose_save_file, error_dialog, info_dialog, ) from calibre.gui2.complete2 import EditWithComplete from calibre.gui2.tweak_book import current_container, tprefs from calibre.gui2.widgets2 import ( - PARAGRAPH_SEPARATOR, Dialog as BaseDialog, HistoryComboBox, to_plain_text + PARAGRAPH_SEPARATOR, Dialog as BaseDialog, HistoryComboBox, to_plain_text, ) +from calibre.startup import connect_lambda from calibre.utils.icu import ( - numeric_sort_key, primary_contains, primary_sort_key, sort_key + numeric_sort_key, primary_contains, primary_sort_key, sort_key, ) from calibre.utils.matcher import ( - DEFAULT_LEVEL1, DEFAULT_LEVEL2, DEFAULT_LEVEL3, Matcher, get_char + DEFAULT_LEVEL1, DEFAULT_LEVEL2, DEFAULT_LEVEL3, Matcher, get_char, ) from polyglot.builtins import iteritems diff --git a/src/calibre/gui2/viewer/toolbars.py b/src/calibre/gui2/viewer/toolbars.py index 5b358fcce5..aaa245b756 100644 --- a/src/calibre/gui2/viewer/toolbars.py +++ b/src/calibre/gui2/viewer/toolbars.py @@ -4,11 +4,10 @@ import os from functools import partial - from qt.core import ( - QAction, QGroupBox, QHBoxLayout, QIcon, QKeySequence, QLabel, QListWidget, - QListWidgetItem, QMenu, Qt, QToolBar, QToolButton, QVBoxLayout, pyqtSignal, QDialog, - QAbstractItemView, QDialogButtonBox + QAbstractItemView, QAction, QDialog, QDialogButtonBox, QGroupBox, QHBoxLayout, + QIcon, QKeySequence, QLabel, QListWidget, QListWidgetItem, QMenu, Qt, QToolBar, + QToolButton, QVBoxLayout, pyqtSignal, ) from qt.webengine import QWebEnginePage @@ -18,6 +17,7 @@ from calibre.gui2.viewer.config import get_session_pref from calibre.gui2.viewer.shortcuts import index_to_key_sequence from calibre.gui2.viewer.web_view import set_book_path, vprefs from calibre.gui2.widgets2 import Dialog +from calibre.startup import connect_lambda from calibre.utils.icu import primary_sort_key @@ -236,7 +236,7 @@ class ActionsToolBar(ToolBar): a.setToolTip(_('Switch to paged mode -- where the text is broken into pages')) def change_sleep_permission(self, disallow_sleep=True): - from .control_sleep import prevent_sleep, allow_sleep + from .control_sleep import allow_sleep, prevent_sleep if disallow_sleep: if self.prevent_sleep_cookie is None: try: diff --git a/src/calibre/gui2/viewer/ui.py b/src/calibre/gui2/viewer/ui.py index 5a764ba63f..2cebb9d851 100644 --- a/src/calibre/gui2/viewer/ui.py +++ b/src/calibre/gui2/viewer/ui.py @@ -10,8 +10,8 @@ from collections import defaultdict, namedtuple from hashlib import sha256 from qt.core import ( QApplication, QCursor, QDockWidget, QEvent, QMainWindow, QMenu, QMimeData, - QModelIndex, QPixmap, Qt, QTimer, QToolBar, QUrl, QVBoxLayout, QWidget, - pyqtSignal, sip + QModelIndex, QPixmap, Qt, QTimer, QToolBar, QUrl, QVBoxLayout, QWidget, pyqtSignal, + sip, ) from threading import Thread @@ -20,23 +20,23 @@ from calibre.constants import ismacos, iswindows from calibre.customize.ui import available_input_formats from calibre.db.annotations import merge_annotations from calibre.gui2 import ( - add_to_recent_docs, choose_files, error_dialog, sanitize_env_vars + add_to_recent_docs, choose_files, error_dialog, sanitize_env_vars, ) from calibre.gui2.dialogs.drm_error import DRMErrorMessage from calibre.gui2.image_popup import ImagePopup from calibre.gui2.main_window import MainWindow from calibre.gui2.viewer import get_current_book_data, performance_monitor from calibre.gui2.viewer.annotations import ( - AnnotationsSaveWorker, annotations_dir, parse_annotations + AnnotationsSaveWorker, annotations_dir, parse_annotations, ) from calibre.gui2.viewer.bookmarks import BookmarkManager from calibre.gui2.viewer.config import ( - get_session_pref, load_reading_rates, save_reading_rates, vprefs + get_session_pref, load_reading_rates, save_reading_rates, vprefs, ) from calibre.gui2.viewer.convert_book import clean_running_workers, prepare_book from calibre.gui2.viewer.highlights import HighlightsPanel from calibre.gui2.viewer.integration import ( - get_book_library_details, load_annotations_map_from_library + get_book_library_details, load_annotations_map_from_library, ) from calibre.gui2.viewer.lookup import Lookup from calibre.gui2.viewer.overlay import LoadingOverlay @@ -44,6 +44,7 @@ from calibre.gui2.viewer.search import SearchPanel from calibre.gui2.viewer.toc import TOC, TOCSearch, TOCView from calibre.gui2.viewer.toolbars import ActionsToolBar from calibre.gui2.viewer.web_view import WebView, get_path_for_name, set_book_path +from calibre.startup import connect_lambda from calibre.utils.date import utcnow from calibre.utils.img import image_from_path from calibre.utils.ipc.simple_worker import WorkerError diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index ae00f6feb1..d4766439d0 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -3,25 +3,28 @@ __copyright__ = '2008, Kovid Goyal ' ''' Miscellaneous widgets used in the GUI ''' -import re, os +import os +import re +from qt.core import ( + QAction, QApplication, QClipboard, QColor, QComboBox, QCompleter, QCursor, QEvent, + QFont, QGraphicsScene, QGraphicsView, QIcon, QKeySequence, QLabel, QLineEdit, + QListWidget, QListWidgetItem, QMenu, QPageSize, QPainter, QPalette, QPen, QPixmap, + QPrinter, QRect, QSize, QSplitter, QSplitterHandle, QStringListModel, + QSyntaxHighlighter, Qt, QTextCharFormat, QTimer, QToolButton, QWidget, pyqtSignal, +) -from qt.core import (QIcon, QFont, QLabel, QListWidget, QAction, QEvent, - QListWidgetItem, QTextCharFormat, QApplication, QSyntaxHighlighter, - QCursor, QColor, QWidget, QPixmap, QSplitterHandle, QToolButton, - Qt, pyqtSignal, QSize, QSplitter, QPainter, QPageSize, QPrinter, - QLineEdit, QComboBox, QPen, QGraphicsScene, QMenu, QStringListModel, QKeySequence, - QCompleter, QTimer, QRect, QGraphicsView, QPalette, QClipboard) - -from calibre.constants import iswindows, ismacos -from calibre.gui2 import (error_dialog, pixmap_to_data, gprefs, - warning_dialog) -from calibre.gui2.filename_pattern_ui import Ui_Form -from calibre import fit_image, strftime, force_unicode +from calibre import fit_image, force_unicode, strftime +from calibre.constants import ismacos, iswindows from calibre.ebooks import BOOK_EXTENSIONS -from calibre.utils.config import prefs, XMLConfig +from calibre.gui2 import error_dialog, gprefs, pixmap_to_data, warning_dialog +from calibre.gui2.dnd import ( + DownloadDialog, dnd_get_files, dnd_get_image, dnd_get_local_image_and_pixmap, + dnd_has_extension, dnd_has_image, image_extensions, +) +from calibre.gui2.filename_pattern_ui import Ui_Form from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator -from calibre.gui2.dnd import (dnd_has_image, dnd_get_image, dnd_get_files, - image_extensions, dnd_has_extension, dnd_get_local_image_and_pixmap, DownloadDialog) +from calibre.startup import connect_lambda +from calibre.utils.config import XMLConfig, prefs from calibre.utils.localization import localize_user_manual_link from polyglot.builtins import native_string_type diff --git a/src/calibre/startup.py b/src/calibre/startup.py index 5e92a98d82..4e31a9c6af 100644 --- a/src/calibre/startup.py +++ b/src/calibre/startup.py @@ -42,6 +42,24 @@ def get_debug_executable(): return [exe_name] +def connect_lambda(bound_signal, self, func, **kw): + import weakref + r = weakref.ref(self) + del self + num_args = func.__code__.co_argcount - 1 + if num_args < 0: + raise TypeError('lambda must take at least one argument') + + def slot(*args): + ctx = r() + if ctx is not None: + if len(args) != num_args: + args = args[:num_args] + func(ctx, *args) + + bound_signal.connect(slot, **kw) + + def initialize_calibre(): if hasattr(initialize_calibre, 'initialized'): return @@ -126,22 +144,6 @@ def initialize_calibre(): builtins.__dict__['icu_upper'] = icu_upper builtins.__dict__['icu_title'] = title_case - def connect_lambda(bound_signal, self, func, **kw): - import weakref - r = weakref.ref(self) - del self - num_args = func.__code__.co_argcount - 1 - if num_args < 0: - raise TypeError('lambda must take at least one argument') - - def slot(*args): - ctx = r() - if ctx is not None: - if len(args) != num_args: - args = args[:num_args] - func(ctx, *args) - - bound_signal.connect(slot, **kw) builtins.__dict__['connect_lambda'] = connect_lambda if islinux or ismacos or isfreebsd: From 8b71e251c4b3bd1d05b22a1d129ccdd87ef2eac5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 10 Jan 2023 08:46:42 +0530 Subject: [PATCH 0102/2055] Work on removing use of _ from builtins --- src/calibre/gui2/viewer/bookmarks.py | 3 ++- src/calibre/gui2/viewer/highlights.py | 2 +- src/calibre/gui2/viewer/lookup.py | 2 +- src/calibre/gui2/viewer/main.py | 2 ++ src/calibre/gui2/viewer/printing.py | 12 +++++----- src/calibre/gui2/viewer/search.py | 2 +- src/calibre/gui2/viewer/toc.py | 7 +++--- src/calibre/gui2/viewer/toolbars.py | 1 + src/calibre/gui2/viewer/tts.py | 1 + src/calibre/gui2/viewer/ui.py | 1 + src/calibre/gui2/viewer/web_view.py | 3 +-- src/calibre/gui2/viewer/widgets.py | 4 ++-- src/calibre/gui2/widgets.py | 2 +- src/calibre/gui2/widgets2.py | 15 +++++++------ src/calibre/gui2/win_file_dialogs.py | 4 ++-- src/calibre/gui2/wizard/__init__.py | 8 +++---- src/calibre/gui2/wizard/send_email.py | 16 +++++++------- src/calibre/gui_launch.py | 1 + src/calibre/library/caches.py | 2 +- src/calibre/library/catalogs/bibtex.py | 1 + src/calibre/library/catalogs/csv_xml.py | 12 ++++++---- src/calibre/library/catalogs/epub_mobi.py | 12 ++++++---- .../library/catalogs/epub_mobi_builder.py | 2 +- src/calibre/library/check_library.py | 6 ++++- src/calibre/library/coloring.py | 1 + src/calibre/library/custom_columns.py | 13 ++++++----- src/calibre/library/database.py | 12 +++++----- src/calibre/library/database2.py | 2 +- src/calibre/library/field_metadata.py | 2 +- src/calibre/library/restore.py | 14 +++++++----- src/calibre/library/save_to_disk.py | 22 ++++++++++--------- src/calibre/linux.py | 1 + src/calibre/spell/dictionary.py | 2 +- src/calibre/srv/ajax.py | 16 +++++++++----- src/calibre/srv/books.py | 1 + src/calibre/srv/code.py | 2 +- src/calibre/srv/content.py | 1 + src/calibre/srv/convert.py | 16 +++++++++----- src/calibre/srv/legacy.py | 4 ++-- src/calibre/srv/loop.py | 5 +++-- src/calibre/srv/manage_users_cli.py | 2 +- src/calibre/srv/metadata.py | 2 +- src/calibre/srv/opds.py | 2 +- src/calibre/srv/standalone.py | 4 ++-- src/calibre/srv/tests/ajax.py | 1 + src/calibre/srv/users.py | 1 + src/calibre/srv/users_api.py | 1 + src/calibre/utils/config.py | 9 ++++---- src/calibre/utils/config_base.py | 1 + src/calibre/utils/exim.py | 22 ++++++++++++++----- src/calibre/utils/filenames.py | 7 +++--- src/calibre/utils/html2text.py | 3 +++ src/calibre/utils/ipc/job.py | 7 +++--- 53 files changed, 180 insertions(+), 117 deletions(-) diff --git a/src/calibre/gui2/viewer/bookmarks.py b/src/calibre/gui2/viewer/bookmarks.py index 73e988a359..bc918ada01 100644 --- a/src/calibre/gui2/viewer/bookmarks.py +++ b/src/calibre/gui2/viewer/bookmarks.py @@ -7,7 +7,7 @@ from operator import itemgetter from qt.core import ( QAbstractItemView, QAction, QComboBox, QGridLayout, QHBoxLayout, QIcon, QInputDialog, QItemSelectionModel, QLabel, QListWidget, QListWidgetItem, - QPushButton, Qt, QWidget, pyqtSignal + QPushButton, Qt, QWidget, pyqtSignal, ) from calibre.gui2 import choose_files, choose_save_file @@ -17,6 +17,7 @@ from calibre.gui2.viewer.shortcuts import get_shortcut_for from calibre.gui2.viewer.web_view import vprefs from calibre.utils.date import EPOCH, utcnow from calibre.utils.icu import primary_sort_key +from calibre.utils.localization import _ class BookmarksList(QListWidget): diff --git a/src/calibre/gui2/viewer/highlights.py b/src/calibre/gui2/viewer/highlights.py index e652764857..18ba4a4721 100644 --- a/src/calibre/gui2/viewer/highlights.py +++ b/src/calibre/gui2/viewer/highlights.py @@ -28,7 +28,7 @@ from calibre.gui2.viewer.config import vprefs from calibre.gui2.viewer.search import SearchInput from calibre.gui2.viewer.shortcuts import get_shortcut_for, index_to_key_sequence from calibre.gui2.widgets2 import Dialog -from calibre.utils.localization import ngettext +from calibre.utils.localization import _, ngettext from calibre_extensions.progress_indicator import set_no_activate_on_click decoration_cache = {} diff --git a/src/calibre/gui2/viewer/lookup.py b/src/calibre/gui2/viewer/lookup.py index f1e74c1584..1dff97ae92 100644 --- a/src/calibre/gui2/viewer/lookup.py +++ b/src/calibre/gui2/viewer/lookup.py @@ -18,7 +18,7 @@ from calibre import prints, random_user_agent from calibre.gui2 import error_dialog from calibre.gui2.viewer.web_view import apply_font_settings, vprefs from calibre.gui2.widgets2 import Dialog -from calibre.utils.localization import canonicalize_lang, get_lang, lang_as_iso639_1 +from calibre.utils.localization import _, canonicalize_lang, get_lang, lang_as_iso639_1 from calibre.utils.resources import get_path as P from calibre.utils.webengine import ( create_script, insert_scripts, secure_webengine, setup_profile, diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index 3a9dcb45f4..3cdd57c850 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -16,6 +16,7 @@ from calibre.gui2.viewer.ui import EbookViewer, is_float from calibre.ptempfile import reset_base_dir from calibre.utils.config import JSONConfig from calibre.utils.ipc import viewer_socket_address +from calibre.utils.localization import _ singleinstance_name = 'calibre_viewer' @@ -168,6 +169,7 @@ def run_gui(app, opts, args, internal_book_data, listener=None): def main(args=sys.argv): from calibre.utils.webengine import setup_fake_protocol + # Ensure viewer can continue to function if GUI is closed os.environ.pop('CALIBRE_WORKER_TEMP_DIR', None) reset_base_dir() diff --git a/src/calibre/gui2/viewer/printing.py b/src/calibre/gui2/viewer/printing.py index 7dfe76f58b..65d3c5860b 100644 --- a/src/calibre/gui2/viewer/printing.py +++ b/src/calibre/gui2/viewer/printing.py @@ -5,18 +5,16 @@ import os import subprocess import sys -from threading import Thread - from qt.core import ( - QCheckBox, QDoubleSpinBox, QFormLayout, QHBoxLayout, QIcon, QLabel, QDialog, - QLineEdit, QPageSize, QProgressDialog, QTimer, QToolButton, QVBoxLayout + QCheckBox, QDialog, QDoubleSpinBox, QFormLayout, QHBoxLayout, QIcon, QLabel, + QLineEdit, QPageSize, QProgressDialog, QTimer, QToolButton, QVBoxLayout, ) +from threading import Thread from calibre import sanitize_file_name from calibre.ebooks.conversion.plugins.pdf_output import PAPER_SIZES from calibre.gui2 import ( - Application, choose_save_file, dynamic, elided_text, error_dialog, - open_local_file + Application, choose_save_file, dynamic, elided_text, error_dialog, open_local_file, ) from calibre.gui2.widgets import PaperSizes from calibre.gui2.widgets2 import Dialog @@ -24,9 +22,9 @@ from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.config import JSONConfig from calibre.utils.filenames import expanduser from calibre.utils.ipc.simple_worker import start_pipe_worker +from calibre.utils.localization import _ from calibre.utils.serialize import msgpack_dumps, msgpack_loads - vprefs = JSONConfig('viewer') diff --git a/src/calibre/gui2/viewer/search.py b/src/calibre/gui2/viewer/search.py index b59118b3ea..ad43b3ec8e 100644 --- a/src/calibre/gui2/viewer/search.py +++ b/src/calibre/gui2/viewer/search.py @@ -19,7 +19,7 @@ from calibre.gui2.viewer.config import vprefs from calibre.gui2.viewer.web_view import get_data, get_manifest from calibre.gui2.viewer.widgets import ResultsDelegate, SearchBox from calibre.utils.icu import primary_collator_without_punctuation -from calibre.utils.localization import ngettext +from calibre.utils.localization import _, ngettext from polyglot.builtins import iteritems from polyglot.functools import lru_cache from polyglot.queue import Queue diff --git a/src/calibre/gui2/viewer/toc.py b/src/calibre/gui2/viewer/toc.py index a0447c2a6a..e105464e07 100644 --- a/src/calibre/gui2/viewer/toc.py +++ b/src/calibre/gui2/viewer/toc.py @@ -6,14 +6,15 @@ import re from functools import partial from qt.core import ( QAbstractItemView, QApplication, QEvent, QFont, QHBoxLayout, QIcon, QMenu, - QModelIndex, QStandardItem, QStandardItemModel, QStyledItemDelegate, - Qt, QToolButton, QToolTip, QTreeView, QWidget, pyqtSignal + QModelIndex, QStandardItem, QStandardItemModel, QStyledItemDelegate, Qt, + QToolButton, QToolTip, QTreeView, QWidget, pyqtSignal, ) from calibre.gui2 import error_dialog -from calibre.gui2.search_box import SearchBox2 from calibre.gui2.gestures import GestureManager +from calibre.gui2.search_box import SearchBox2 from calibre.utils.icu import primary_contains +from calibre.utils.localization import _ class Delegate(QStyledItemDelegate): diff --git a/src/calibre/gui2/viewer/toolbars.py b/src/calibre/gui2/viewer/toolbars.py index aaa245b756..2d95566cdc 100644 --- a/src/calibre/gui2/viewer/toolbars.py +++ b/src/calibre/gui2/viewer/toolbars.py @@ -19,6 +19,7 @@ from calibre.gui2.viewer.web_view import set_book_path, vprefs from calibre.gui2.widgets2 import Dialog from calibre.startup import connect_lambda from calibre.utils.icu import primary_sort_key +from calibre.utils.localization import _ class Action: diff --git a/src/calibre/gui2/viewer/tts.py b/src/calibre/gui2/viewer/tts.py index 7354b9428f..3e2f77ddb2 100644 --- a/src/calibre/gui2/viewer/tts.py +++ b/src/calibre/gui2/viewer/tts.py @@ -6,6 +6,7 @@ from qt.core import QDialog, QDialogButtonBox, QObject, QVBoxLayout, pyqtSignal from calibre.gui2 import error_dialog from calibre.gui2.viewer.config import get_pref_group, vprefs from calibre.gui2.widgets2 import Dialog +from calibre.utils.localization import _ def set_sync_override(allowed): diff --git a/src/calibre/gui2/viewer/ui.py b/src/calibre/gui2/viewer/ui.py index 2cebb9d851..b9e6813d04 100644 --- a/src/calibre/gui2/viewer/ui.py +++ b/src/calibre/gui2/viewer/ui.py @@ -48,6 +48,7 @@ from calibre.startup import connect_lambda from calibre.utils.date import utcnow from calibre.utils.img import image_from_path from calibre.utils.ipc.simple_worker import WorkerError +from calibre.utils.localization import _ from polyglot.builtins import as_bytes, as_unicode, iteritems, itervalues diff --git a/src/calibre/gui2/viewer/web_view.py b/src/calibre/gui2/viewer/web_view.py index 40470c3ec0..fc077f1ea5 100644 --- a/src/calibre/gui2/viewer/web_view.py +++ b/src/calibre/gui2/viewer/web_view.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # License: GPL v3 Copyright: 2018, Kovid Goyal - import os import shutil import sys @@ -28,7 +27,7 @@ from calibre.gui2.viewer.config import viewer_config_dir, vprefs from calibre.gui2.viewer.tts import TTS from calibre.gui2.webengine import RestartingWebEngineView from calibre.srv.code import get_translations_data -from calibre.utils.localization import localize_user_manual_link +from calibre.utils.localization import _, localize_user_manual_link from calibre.utils.resources import get_path as P from calibre.utils.serialize import json_loads from calibre.utils.shared_file import share_open diff --git a/src/calibre/gui2/viewer/widgets.py b/src/calibre/gui2/viewer/widgets.py index 3c2d71b80b..514fa4e20c 100644 --- a/src/calibre/gui2/viewer/widgets.py +++ b/src/calibre/gui2/viewer/widgets.py @@ -2,13 +2,13 @@ # License: GPL v3 Copyright: 2020, Kovid Goyal import re - from qt.core import ( - QAction, QFont, QFontMetrics, QStyle, QStyledItemDelegate, Qt, pyqtSignal, QPalette + QAction, QFont, QFontMetrics, QPalette, QStyle, QStyledItemDelegate, Qt, pyqtSignal, ) from calibre.gui2 import QT_HIDDEN_CLEAR_ACTION from calibre.gui2.widgets2 import HistoryComboBox +from calibre.utils.localization import _ class ResultsDelegate(QStyledItemDelegate): # {{{ diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index d4766439d0..da290e9832 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -25,7 +25,7 @@ from calibre.gui2.filename_pattern_ui import Ui_Form from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator from calibre.startup import connect_lambda from calibre.utils.config import XMLConfig, prefs -from calibre.utils.localization import localize_user_manual_link +from calibre.utils.localization import _, localize_user_manual_link from polyglot.builtins import native_string_type history = XMLConfig('history') diff --git a/src/calibre/gui2/widgets2.py b/src/calibre/gui2/widgets2.py index 7a57ba31a2..4768b384e7 100644 --- a/src/calibre/gui2/widgets2.py +++ b/src/calibre/gui2/widgets2.py @@ -4,13 +4,13 @@ import weakref from qt.core import ( - QApplication, QBrush, QByteArray, QCalendarWidget, QCheckBox, QColor, - QColorDialog, QComboBox, QDate, QDateTime, QDateTimeEdit, QDialog, - QDialogButtonBox, QFont, QFontInfo, QFontMetrics, QFrame, QIcon, QKeySequence, - QLabel, QLayout, QMenu, QMimeData, QPainter, QPalette, QPixmap, QPoint, - QPushButton, QRect, QScrollArea, QSize, QSizePolicy, QStyle, QStyledItemDelegate, - QStyleOptionToolButton, QStylePainter, Qt, QTabWidget, QTextBrowser, QTextCursor, - QToolButton, QUndoCommand, QUndoStack, QUrl, QWidget, pyqtSignal + QApplication, QBrush, QByteArray, QCalendarWidget, QCheckBox, QColor, QColorDialog, + QComboBox, QDate, QDateTime, QDateTimeEdit, QDialog, QDialogButtonBox, QFont, + QFontInfo, QFontMetrics, QFrame, QIcon, QKeySequence, QLabel, QLayout, QMenu, + QMimeData, QPainter, QPalette, QPixmap, QPoint, QPushButton, QRect, QScrollArea, + QSize, QSizePolicy, QStyle, QStyledItemDelegate, QStyleOptionToolButton, + QStylePainter, Qt, QTabWidget, QTextBrowser, QTextCursor, QToolButton, QUndoCommand, + QUndoStack, QUrl, QWidget, pyqtSignal, ) from calibre.ebooks.metadata import rating_to_stars @@ -19,6 +19,7 @@ from calibre.gui2.complete2 import EditWithComplete, LineEdit from calibre.gui2.widgets import history from calibre.utils.config_base import tweaks from calibre.utils.date import UNDEFINED_DATE +from calibre.utils.localization import _ from polyglot.functools import lru_cache diff --git a/src/calibre/gui2/win_file_dialogs.py b/src/calibre/gui2/win_file_dialogs.py index 20c0da112d..52e0587781 100644 --- a/src/calibre/gui2/win_file_dialogs.py +++ b/src/calibre/gui2/win_file_dialogs.py @@ -6,11 +6,11 @@ import os import struct import subprocess import sys +from contextlib import suppress from threading import Thread from uuid import uuid4 -from contextlib import suppress - +from calibre.utils.localization import _ from polyglot.builtins import string_or_bytes base = sys.extensions_location if hasattr(sys, 'new_app_layout') else os.path.dirname(sys.executable) diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py index d4a691489e..1e42eabb0e 100644 --- a/src/calibre/gui2/wizard/__init__.py +++ b/src/calibre/gui2/wizard/__init__.py @@ -10,8 +10,8 @@ import re import traceback from contextlib import closing, suppress from qt.core import ( - QAbstractListModel, QDir, QIcon, QItemSelection, QItemSelectionModel, Qt, - QWizard, QWizardPage, pyqtSignal + QAbstractListModel, QDir, QIcon, QItemSelection, QItemSelectionModel, Qt, QWizard, + QWizardPage, pyqtSignal, ) from calibre import __appname__ @@ -24,7 +24,7 @@ from calibre.gui2.wizard.library_ui import Ui_WizardPage as LibraryUI from calibre.gui2.wizard.send_email import smtp_prefs from calibre.gui2.wizard.stanza_ui import Ui_WizardPage as StanzaUI from calibre.utils.config import dynamic, prefs -from calibre.utils.localization import localize_user_manual_link +from calibre.utils.localization import _, localize_user_manual_link from polyglot.builtins import iteritems # Devices {{{ @@ -717,7 +717,7 @@ class LibraryPage(QWizardPage, LibraryUI): self.language.blockSignals(True) self.language.clear() from calibre.utils.localization import ( - available_translations, get_lang, get_language, get_lc_messages_path + available_translations, get_lang, get_language, get_lc_messages_path, ) lang = get_lang() lang = get_lc_messages_path(lang) if lang else lang diff --git a/src/calibre/gui2/wizard/send_email.py b/src/calibre/gui2/wizard/send_email.py index 4701862742..8249790939 100644 --- a/src/calibre/gui2/wizard/send_email.py +++ b/src/calibre/gui2/wizard/send_email.py @@ -7,17 +7,17 @@ __docformat__ = 'restructuredtext en' import sys from functools import partial +from qt.core import ( + QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QHBoxLayout, QIcon, QLabel, + QLineEdit, QPlainTextEdit, QPushButton, Qt, QVBoxLayout, QWidget, pyqtSignal, +) from threading import Thread -from qt.core import ( - QWidget, pyqtSignal, QDialog, Qt, QLabel, QLineEdit, QDialogButtonBox, - QGridLayout, QCheckBox, QIcon, QVBoxLayout, QPushButton, QPlainTextEdit, - QHBoxLayout) - from calibre import prints -from calibre.gui2.wizard.send_email_ui import Ui_Form -from calibre.utils.smtp import config as smtp_prefs from calibre.gui2 import error_dialog, question_dialog +from calibre.gui2.wizard.send_email_ui import Ui_Form +from calibre.utils.localization import _ +from calibre.utils.smtp import config as smtp_prefs from polyglot.binary import as_hex_unicode, from_hex_unicode from polyglot.io import PolyglotStringIO @@ -211,7 +211,7 @@ class SendEmail(QWidget, Ui_Form): def test_email_settings(self, to): opts = smtp_prefs().parse() - from calibre.utils.smtp import sendmail, create_mail + from calibre.utils.smtp import create_mail, sendmail buf = PolyglotStringIO() debug_out = partial(prints, file=buf) oout, oerr = sys.stdout, sys.stderr diff --git a/src/calibre/gui_launch.py b/src/calibre/gui_launch.py index 4e48da22d2..e0bf492d2b 100644 --- a/src/calibre/gui_launch.py +++ b/src/calibre/gui_launch.py @@ -85,6 +85,7 @@ def is_possible_media_pack_error(e): def show_media_pack_error(): import traceback from calibre.gui2 import error_dialog, Application + from calibre.utils.localization import _ app = Application([]) error_dialog(None, _('Required component missing'), '

' + _( 'This computer is missing the Windows MediaPack, which is needed for calibre. Instructions' diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index efc3994a1d..eed672e9f6 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -20,7 +20,7 @@ from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.utils.config import prefs, tweaks from calibre.utils.date import UNDEFINED_DATE, clean_date_for_sort, now, parse_date from calibre.utils.icu import lower as icu_lower -from calibre.utils.localization import canonicalize_lang, get_udc, lang_map +from calibre.utils.localization import _, canonicalize_lang, get_udc, lang_map from calibre.utils.search_query_parser import ParseException, SearchQueryParser from polyglot.builtins import cmp, iteritems, itervalues, string_or_bytes diff --git a/src/calibre/library/catalogs/bibtex.py b/src/calibre/library/catalogs/bibtex.py index 27feade593..ac2397c290 100644 --- a/src/calibre/library/catalogs/bibtex.py +++ b/src/calibre/library/catalogs/bibtex.py @@ -16,6 +16,7 @@ from calibre.customize import CatalogPlugin from calibre.customize.conversion import DummyReporter from calibre.ebooks.metadata import format_isbn from calibre.library.catalogs import FIELDS, TEMPLATE_ALLOWED_FIELDS +from calibre.utils.localization import _ from polyglot.builtins import string_or_bytes diff --git a/src/calibre/library/catalogs/csv_xml.py b/src/calibre/library/catalogs/csv_xml.py index 21ca37b42e..9e192620de 100644 --- a/src/calibre/library/catalogs/csv_xml.py +++ b/src/calibre/library/catalogs/csv_xml.py @@ -5,12 +5,15 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import re, codecs, os +import codecs +import os +import re from collections import namedtuple from calibre.customize import CatalogPlugin -from calibre.library.catalogs import FIELDS from calibre.customize.conversion import DummyReporter +from calibre.library.catalogs import FIELDS +from calibre.utils.localization import _ class CSV_XML(CatalogPlugin): @@ -50,12 +53,13 @@ class CSV_XML(CatalogPlugin): "Applies to: CSV, XML output formats"))] def run(self, path_to_output, opts, db, notification=DummyReporter()): + from lxml import etree + + from calibre.ebooks.metadata import authors_to_string from calibre.library import current_library_name from calibre.utils.date import isoformat from calibre.utils.html2text import html2text from calibre.utils.logging import default_log as log - from lxml import etree - from calibre.ebooks.metadata import authors_to_string self.fmt = path_to_output.rpartition('.')[2] self.notification = notification diff --git a/src/calibre/library/catalogs/epub_mobi.py b/src/calibre/library/catalogs/epub_mobi.py index 0586bf0345..ae95691c71 100644 --- a/src/calibre/library/catalogs/epub_mobi.py +++ b/src/calibre/library/catalogs/epub_mobi.py @@ -5,16 +5,20 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import datetime, os, time +import datetime +import os +import time from collections import namedtuple from calibre import strftime from calibre.customize import CatalogPlugin -from calibre.customize.conversion import OptionRecommendation, DummyReporter +from calibre.customize.conversion import DummyReporter, OptionRecommendation from calibre.library import current_library_name from calibre.library.catalogs import AuthorSortMismatchException, EmptyCatalogException from calibre.ptempfile import PersistentTemporaryFile -from calibre.utils.localization import calibre_langcode_to_name, canonicalize_lang, get_lang +from calibre.utils.localization import ( + _, calibre_langcode_to_name, canonicalize_lang, get_lang, +) Option = namedtuple('Option', 'option, default, dest, action, help') @@ -190,8 +194,8 @@ class EPUB_MOBI(CatalogPlugin): def run(self, path_to_output, opts, db, notification=DummyReporter()): from calibre.library.catalogs.epub_mobi_builder import CatalogBuilder - from calibre.utils.logging import default_log as log from calibre.utils.config import JSONConfig + from calibre.utils.logging import default_log as log # If preset specified from the cli, insert stored options from JSON file if hasattr(opts, 'preset') and opts.preset: diff --git a/src/calibre/library/catalogs/epub_mobi_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py index cf6578c5b5..ca67089c38 100644 --- a/src/calibre/library/catalogs/epub_mobi_builder.py +++ b/src/calibre/library/catalogs/epub_mobi_builder.py @@ -38,7 +38,7 @@ from calibre.utils.formatter import TemplateFormatter from calibre.utils.icu import ( capitalize, collation_order, sort_key, title_case as icu_title, upper as icu_upper, ) -from calibre.utils.localization import get_lang, lang_as_iso639_1, ngettext +from calibre.utils.localization import _, get_lang, lang_as_iso639_1, ngettext from calibre.utils.resources import get_image_path as I, get_path as P from calibre.utils.xml_parse import safe_xml_fromstring from calibre.utils.zipfile import ZipFile diff --git a/src/calibre/library/check_library.py b/src/calibre/library/check_library.py index 02926c3f3d..dedbfef5db 100644 --- a/src/calibre/library/check_library.py +++ b/src/calibre/library/check_library.py @@ -5,11 +5,15 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import re, os, traceback, fnmatch +import fnmatch +import os +import re +import traceback from calibre import isbytestring from calibre.constants import filesystem_encoding from calibre.ebooks import BOOK_EXTENSIONS +from calibre.utils.localization import _ from polyglot.builtins import iteritems EBOOK_EXTENSIONS = frozenset(BOOK_EXTENSIONS) diff --git a/src/calibre/library/coloring.py b/src/calibre/library/coloring.py index 65c2e90888..69caf8548e 100644 --- a/src/calibre/library/coloring.py +++ b/src/calibre/library/coloring.py @@ -6,6 +6,7 @@ import json import re from textwrap import dedent +from calibre.utils.localization import _ from polyglot.binary import as_hex_unicode, from_hex_bytes color_row_key = '*row' diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index 5033b4c40c..7d6fe96593 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -5,14 +5,17 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import json, re, numbers +import json +import numbers +import re from functools import partial -from calibre import prints, force_unicode +from calibre import force_unicode, prints from calibre.constants import preferred_encoding from calibre.library.field_metadata import FieldMetadata -from calibre.utils.date import parse_date from calibre.utils.config import tweaks +from calibre.utils.date import parse_date +from calibre.utils.localization import _ from polyglot.builtins import string_or_bytes @@ -217,7 +220,7 @@ class CustomColumns: if data['display'].get('sort_alpha', False): ans.sort(key=lambda x:x.lower()) if data['datatype'] == 'datetime' and isinstance(ans, string_or_bytes): - from calibre.db.tables import c_parse, UNDEFINED_DATE + from calibre.db.tables import UNDEFINED_DATE, c_parse ans = c_parse(ans) if ans is UNDEFINED_DATE: ans = None @@ -249,7 +252,7 @@ class CustomColumns: if data['display'].get('sort_alpha', False): ans.sort(key=lambda x: x.lower()) if data['datatype'] == 'datetime' and isinstance(ans, string_or_bytes): - from calibre.db.tables import c_parse, UNDEFINED_DATE + from calibre.db.tables import UNDEFINED_DATE, c_parse ans = c_parse(ans) if ans is UNDEFINED_DATE: ans = None diff --git a/src/calibre/library/database.py b/src/calibre/library/database.py index 16757b20ac..7f43fbd8e1 100644 --- a/src/calibre/library/database.py +++ b/src/calibre/library/database.py @@ -5,14 +5,16 @@ __copyright__ = '2008, Kovid Goyal ' Backend that implements storage of ebooks in an sqlite database. ''' +import datetime +import re import sqlite3 as sqlite -import datetime, re, sre_constants +import sre_constants from zlib import compress, decompress -from calibre.ebooks.metadata import MetaInformation -from calibre.ebooks.metadata import string_to_authors -from calibre.utils.serialize import pickle_loads, pickle_dumps from calibre import isbytestring +from calibre.ebooks.metadata import MetaInformation, string_to_authors +from calibre.utils.localization import _ +from calibre.utils.serialize import pickle_dumps, pickle_loads class Concatenate: @@ -980,7 +982,7 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; data = self.conn.get('SELECT data FROM covers WHERE book=?', (id,), all=False) if not data: return None - return(decompress(data)) + return decompress(data) def tags(self, index, index_is_id=False): '''tags as a comma separated list or None''' diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 3ea0ecb429..1f63a0a59e 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -60,7 +60,7 @@ from calibre.utils.filenames import ( from calibre.utils.formatter_functions import load_user_template_functions from calibre.utils.icu import lower, lower as icu_lower, sort_key, strcmp from calibre.utils.img import save_cover_data_to -from calibre.utils.localization import calibre_langcode_to_name, canonicalize_lang +from calibre.utils.localization import _, calibre_langcode_to_name, canonicalize_lang from calibre.utils.recycle_bin import delete_file, delete_tree from calibre.utils.resources import get_path as P from calibre.utils.search_query_parser import saved_searches, set_saved_searches diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index 07f27a0935..20dfd10bd5 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -9,7 +9,7 @@ from collections import OrderedDict from calibre.utils.config_base import tweaks from calibre.utils.icu import lower as icu_lower -from calibre.utils.localization import ngettext +from calibre.utils.localization import _, ngettext from polyglot.builtins import iteritems, itervalues category_icon_map = { diff --git a/src/calibre/library/restore.py b/src/calibre/library/restore.py index a071524779..f1e953721a 100644 --- a/src/calibre/library/restore.py +++ b/src/calibre/library/restore.py @@ -5,17 +5,21 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import re, os, traceback, shutil -from threading import Thread +import os +import re +import shutil +import traceback from operator import itemgetter +from threading import Thread -from calibre.ptempfile import TemporaryDirectory +from calibre import isbytestring +from calibre.constants import filesystem_encoding from calibre.ebooks.metadata.opf2 import OPF from calibre.library.database2 import LibraryDatabase2 from calibre.library.prefs import DBPrefs -from calibre.constants import filesystem_encoding +from calibre.ptempfile import TemporaryDirectory from calibre.utils.date import utcfromtimestamp -from calibre import isbytestring +from calibre.utils.localization import _ from polyglot.builtins import iteritems NON_EBOOK_EXTENSIONS = frozenset([ diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py index e6d19dca71..eac71e3b75 100644 --- a/src/calibre/library/save_to_disk.py +++ b/src/calibre/library/save_to_disk.py @@ -5,19 +5,21 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, traceback, re, errno +import errno +import os +import re +import traceback -from calibre.constants import DEBUG +from calibre import prints, sanitize_file_name, strftime +from calibre.constants import DEBUG, preferred_encoding from calibre.db.errors import NoSuchFormat -from calibre.utils.config import Config, StringConfig, tweaks -from calibre.utils.formatter import TemplateFormatter -from calibre.utils.filenames import shorten_components_to, ascii_filename -from calibre.constants import preferred_encoding -from calibre.ebooks.metadata import fmt_sidx -from calibre.ebooks.metadata import title_sort -from calibre.utils.date import as_local_time -from calibre import strftime, prints, sanitize_file_name from calibre.db.lazy import FormatsList +from calibre.ebooks.metadata import fmt_sidx, title_sort +from calibre.utils.config import Config, StringConfig, tweaks +from calibre.utils.date import as_local_time +from calibre.utils.filenames import ascii_filename, shorten_components_to +from calibre.utils.formatter import TemplateFormatter +from calibre.utils.localization import _ plugboard_any_device_value = 'any device' plugboard_any_format_value = 'any format' diff --git a/src/calibre/linux.py b/src/calibre/linux.py index f81de08db5..c201c136ff 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -15,6 +15,7 @@ from calibre import CurrentDir, __appname__, guess_type, prints from calibre.constants import isbsd, islinux from calibre.customize.ui import all_input_formats from calibre.ptempfile import TemporaryDirectory +from calibre.utils.localization import _ from calibre.utils.resources import get_image_path as I, get_path as P from polyglot.builtins import iteritems diff --git a/src/calibre/spell/dictionary.py b/src/calibre/spell/dictionary.py index 89042bcf63..4194a0ef50 100644 --- a/src/calibre/spell/dictionary.py +++ b/src/calibre/spell/dictionary.py @@ -16,7 +16,7 @@ from calibre.constants import config_dir, filesystem_encoding, iswindows from calibre.spell import parse_lang_code from calibre.utils.config import JSONConfig from calibre.utils.icu import capitalize -from calibre.utils.localization import get_lang, get_system_locale +from calibre.utils.localization import _, get_lang, get_system_locale from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems, itervalues diff --git a/src/calibre/srv/ajax.py b/src/calibre/srv/ajax.py index 49055d2ca5..6fa95acab4 100644 --- a/src/calibre/srv/ajax.py +++ b/src/calibre/srv/ajax.py @@ -5,21 +5,24 @@ __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' from functools import partial -from polyglot.builtins import iteritems, itervalues, string_or_bytes from itertools import cycle from calibre import force_unicode -from calibre.library.field_metadata import category_icon_map from calibre.db.view import sanitize_sort_field_name from calibre.ebooks.metadata import title_sort from calibre.ebooks.metadata.book.json_codec import JsonCodec -from calibre.srv.errors import HTTPNotFound, BookNotFound -from calibre.srv.routes import endpoint, json +from calibre.library.field_metadata import category_icon_map from calibre.srv.content import get as get_content, icon as get_icon -from calibre.srv.utils import http_date, custom_fields_to_display, encode_name, decode_name, get_db +from calibre.srv.errors import BookNotFound, HTTPNotFound +from calibre.srv.routes import endpoint, json +from calibre.srv.utils import ( + custom_fields_to_display, decode_name, encode_name, get_db, http_date, +) from calibre.utils.config import prefs, tweaks from calibre.utils.date import isoformat, timestampfromdt from calibre.utils.icu import numeric_sort_key as sort_key +from calibre.utils.localization import _ +from polyglot.builtins import iteritems, itervalues, string_or_bytes def ensure_val(x, *allowed): @@ -126,9 +129,10 @@ def book_to_json(ctx, rd, db, book_id, data['_series_sort_'] = series if device_for_template: import posixpath + + from calibre.customize.ui import device_plugins from calibre.devices.utils import create_upload_path from calibre.utils.filenames import ascii_filename as sanitize - from calibre.customize.ui import device_plugins for device_class in device_plugins(): if device_class.__class__.__name__ == device_for_template: diff --git a/src/calibre/srv/books.py b/src/calibre/srv/books.py index 3af369b0d8..f8e939aa08 100644 --- a/src/calibre/srv/books.py +++ b/src/calibre/srv/books.py @@ -21,6 +21,7 @@ from calibre.srv.render_book import RENDER_VERSION from calibre.srv.routes import endpoint, json from calibre.srv.utils import get_db, get_library_data from calibre.utils.filenames import rmtree +from calibre.utils.localization import _ from calibre.utils.resources import get_path as P from calibre.utils.serialize import json_dumps from polyglot.builtins import as_unicode, itervalues diff --git a/src/calibre/srv/code.py b/src/calibre/srv/code.py index 4fa3a4d2ab..41c71dbbba 100644 --- a/src/calibre/srv/code.py +++ b/src/calibre/srv/code.py @@ -27,7 +27,7 @@ from calibre.srv.utils import get_library_data, get_use_roman from calibre.utils.config import prefs, tweaks from calibre.utils.icu import numeric_sort_key, sort_key from calibre.utils.localization import ( - get_lang, lang_code_for_user_manual, lang_map_for_ui, localize_website_link, + _, get_lang, lang_code_for_user_manual, lang_map_for_ui, localize_website_link, ) from calibre.utils.resources import get_path as P from calibre.utils.search_query_parser import ParseException diff --git a/src/calibre/srv/content.py b/src/calibre/srv/content.py index 91274eeb33..af28510b1f 100644 --- a/src/calibre/srv/content.py +++ b/src/calibre/srv/content.py @@ -28,6 +28,7 @@ from calibre.utils.config_base import tweaks from calibre.utils.date import timestampfromdt from calibre.utils.filenames import ascii_filename, atomic_rename from calibre.utils.img import image_from_data, scale_image +from calibre.utils.localization import _ from calibre.utils.resources import get_image_path as I, get_path as P from calibre.utils.shared_file import share_open from polyglot.binary import as_hex_unicode diff --git a/src/calibre/srv/convert.py b/src/calibre/srv/convert.py index cd2df6568e..b6407b3079 100644 --- a/src/calibre/srv/convert.py +++ b/src/calibre/srv/convert.py @@ -13,6 +13,7 @@ from calibre.srv.changes import formats_added from calibre.srv.errors import BookNotFound, HTTPNotFound from calibre.srv.routes import endpoint, json from calibre.srv.utils import get_library_data +from calibre.utils.localization import _ from calibre.utils.monotonic import monotonic from calibre.utils.shared_file import share_open from polyglot.builtins import iteritems @@ -120,9 +121,9 @@ def convert_book(path_to_ebook, opf_path, cover_path, output_fmt, recs): def queue_job(ctx, rd, library_id, db, fmt, book_id, conversion_data): - from calibre.ebooks.metadata.opf2 import metadata_to_opf - from calibre.ebooks.conversion.config import GuiRecommendations, save_specifics from calibre.customize.conversion import OptionRecommendation + from calibre.ebooks.conversion.config import GuiRecommendations, save_specifics + from calibre.ebooks.metadata.opf2 import metadata_to_opf tdir = tempfile.mkdtemp(dir=rd.tdir) with tempfile.NamedTemporaryFile(prefix='', suffix=('.' + fmt.lower()), dir=tdir, delete=False) as src_file: db.copy_format_to(book_id, fmt, src_file) @@ -202,10 +203,12 @@ def conversion_status(ctx, rd, job_id): def get_conversion_options(input_fmt, output_fmt, book_id, db): - from calibre.ebooks.conversion.plumber import create_dummy_plumber - from calibre.ebooks.conversion.config import ( - load_specifics, load_defaults, OPTIONS, options_for_input_fmt, options_for_output_fmt) from calibre.customize.conversion import OptionRecommendation + from calibre.ebooks.conversion.config import ( + OPTIONS, load_defaults, load_specifics, options_for_input_fmt, + options_for_output_fmt, + ) + from calibre.ebooks.conversion.plumber import create_dummy_plumber plumber = create_dummy_plumber(input_fmt, output_fmt) specifics = load_specifics(db, book_id) ans = {'options': {}, 'disabled': set(), 'defaults': {}, 'help': {}} @@ -265,7 +268,8 @@ def profiles(): @endpoint('/conversion/book-data/{book_id}', postprocess=json, types={'book_id': int}) def conversion_data(ctx, rd, book_id): from calibre.ebooks.conversion.config import ( - NoSupportedInputFormats, get_input_format_for_book, get_sorted_output_formats) + NoSupportedInputFormats, get_input_format_for_book, get_sorted_output_formats, + ) db = get_library_data(ctx, rd)[0] if not ctx.has_id(rd, db, book_id): raise BookNotFound(book_id, db) diff --git a/src/calibre/srv/legacy.py b/src/calibre/srv/legacy.py index 055228ebee..fa54504587 100644 --- a/src/calibre/srv/legacy.py +++ b/src/calibre/srv/legacy.py @@ -3,7 +3,6 @@ from functools import partial - from lxml.html import tostring from lxml.html.builder import E as E_ @@ -17,7 +16,8 @@ from calibre.srv.routes import endpoint from calibre.srv.utils import get_library_data, http_date from calibre.utils.cleantext import clean_xml_chars from calibre.utils.date import dt_as_local, is_date_undefined, timestampfromdt -from polyglot.builtins import iteritems, string_or_bytes, as_bytes +from calibre.utils.localization import _ +from polyglot.builtins import as_bytes, iteritems, string_or_bytes from polyglot.urllib import urlencode # /mobile {{{ diff --git a/src/calibre/srv/loop.py b/src/calibre/srv/loop.py index 1a280c912c..c284b8b7a4 100644 --- a/src/calibre/srv/loop.py +++ b/src/calibre/srv/loop.py @@ -11,7 +11,7 @@ import socket import ssl import traceback from contextlib import suppress -from functools import partial, lru_cache +from functools import lru_cache, partial from io import BytesIO from calibre import as_unicode @@ -23,8 +23,9 @@ from calibre.srv.opts import Options from calibre.srv.pool import PluginPool, ThreadPool from calibre.srv.utils import ( DESIRED_SEND_BUFFER_SIZE, HandleInterrupt, create_sock_pair, socket_errors_eintr, - socket_errors_nonblocking, socket_errors_socket_closed, start_cork, stop_cork + socket_errors_nonblocking, socket_errors_socket_closed, start_cork, stop_cork, ) +from calibre.utils.localization import _ from calibre.utils.logging import ThreadSafeLog from calibre.utils.mdns import get_external_ip from calibre.utils.monotonic import monotonic diff --git a/src/calibre/srv/manage_users_cli.py b/src/calibre/srv/manage_users_cli.py index 7544c9901d..87c7803201 100644 --- a/src/calibre/srv/manage_users_cli.py +++ b/src/calibre/srv/manage_users_cli.py @@ -7,7 +7,7 @@ from functools import partial from calibre import prints from calibre.constants import iswindows, preferred_encoding from calibre.utils.config import OptionParser -from calibre.utils.localization import ngettext +from calibre.utils.localization import _, ngettext from polyglot.builtins import iteritems diff --git a/src/calibre/srv/metadata.py b/src/calibre/srv/metadata.py index 8f3dabb16a..89e11ce94d 100644 --- a/src/calibre/srv/metadata.py +++ b/src/calibre/srv/metadata.py @@ -19,7 +19,7 @@ from calibre.utils.date import UNDEFINED_DATE, isoformat, local_tz from calibre.utils.file_type_icons import EXT_MAP from calibre.utils.formatter import EvalFormatter from calibre.utils.icu import collation_order_for_partitioning, upper as icu_upper -from calibre.utils.localization import calibre_langcode_to_name +from calibre.utils.localization import _, calibre_langcode_to_name from polyglot.builtins import iteritems, itervalues from polyglot.urllib import quote diff --git a/src/calibre/srv/opds.py b/src/calibre/srv/opds.py index 2ec3e0d242..0dd6ab8eb3 100644 --- a/src/calibre/srv/opds.py +++ b/src/calibre/srv/opds.py @@ -24,7 +24,7 @@ from calibre.srv.utils import Offsets, get_library_data, http_date from calibre.utils.config import prefs from calibre.utils.date import as_utc, is_date_undefined, timestampfromdt from calibre.utils.icu import sort_key -from calibre.utils.localization import ngettext +from calibre.utils.localization import _, ngettext from calibre.utils.search_query_parser import ParseException from calibre.utils.xml_parse import safe_xml_fromstring from polyglot.binary import as_hex_unicode, from_hex_unicode diff --git a/src/calibre/srv/standalone.py b/src/calibre/srv/standalone.py index 6bed06f780..5ad690e576 100644 --- a/src/calibre/srv/standalone.py +++ b/src/calibre/srv/standalone.py @@ -21,10 +21,10 @@ from calibre.srv.opts import opts_to_parser from calibre.srv.users import connect from calibre.srv.utils import HandleInterrupt, RotatingLog from calibre.utils.config import prefs -from calibre.utils.localization import localize_user_manual_link +from calibre.utils.localization import _, localize_user_manual_link from calibre.utils.lock import singleinstance -from polyglot.builtins import error_message from calibre_extensions import speedup +from polyglot.builtins import error_message def daemonize(): # {{{ diff --git a/src/calibre/srv/tests/ajax.py b/src/calibre/srv/tests/ajax.py index 1b2da59a9d..62f6969522 100644 --- a/src/calibre/srv/tests/ajax.py +++ b/src/calibre/srv/tests/ajax.py @@ -12,6 +12,7 @@ from io import BytesIO from calibre.ebooks.metadata.meta import get_metadata from calibre.srv.tests.base import LibraryBaseTest +from calibre.utils.localization import _ from polyglot.binary import as_base64_bytes from polyglot.http_client import FORBIDDEN, NOT_FOUND, OK from polyglot.urllib import quote, urlencode diff --git a/src/calibre/srv/users.py b/src/calibre/srv/users.py index 2e211f4bb6..3b26d96921 100644 --- a/src/calibre/srv/users.py +++ b/src/calibre/srv/users.py @@ -12,6 +12,7 @@ from threading import RLock from calibre import as_unicode from calibre.constants import config_dir from calibre.utils.config import from_json, to_json +from calibre.utils.localization import _ from polyglot.builtins import iteritems diff --git a/src/calibre/srv/users_api.py b/src/calibre/srv/users_api.py index c7147a1983..2776104d84 100644 --- a/src/calibre/srv/users_api.py +++ b/src/calibre/srv/users_api.py @@ -8,6 +8,7 @@ from calibre import as_unicode from calibre.srv.errors import HTTPBadRequest, HTTPForbidden from calibre.srv.routes import endpoint from calibre.srv.users import validate_password +from calibre.utils.localization import _ @endpoint('/users/change-pw', methods={'POST'}) diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py index 91a5482280..dc2ac2e1ba 100644 --- a/src/calibre/utils/config.py +++ b/src/calibre/utils/config.py @@ -11,13 +11,14 @@ import os from copy import deepcopy from calibre.constants import ( - CONFIG_DIR_MODE, __appname__, __author__, config_dir, get_version, iswindows + CONFIG_DIR_MODE, __appname__, __author__, config_dir, get_version, iswindows, ) from calibre.utils.config_base import ( - Config, ConfigInterface, ConfigProxy, Option, OptionSet, OptionValues, - StringConfig, commit_data, from_json, json_dumps, json_loads, make_config_dir, - plugin_dir, prefs, read_data, to_json, tweaks + Config, ConfigInterface, ConfigProxy, Option, OptionSet, OptionValues, StringConfig, + commit_data, from_json, json_dumps, json_loads, make_config_dir, plugin_dir, prefs, + read_data, to_json, tweaks, ) +from calibre.utils.localization import _ from polyglot.builtins import native_string_type, string_or_bytes # optparse uses gettext.gettext instead of _ from builtins, so we diff --git a/src/calibre/utils/config_base.py b/src/calibre/utils/config_base.py index 2caeaea00a..0c8b1759a3 100644 --- a/src/calibre/utils/config_base.py +++ b/src/calibre/utils/config_base.py @@ -17,6 +17,7 @@ from calibre.constants import ( CONFIG_DIR_MODE, config_dir, filesystem_encoding, get_umask, iswindows, preferred_encoding, ) +from calibre.utils.localization import _ from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems diff --git a/src/calibre/utils/exim.py b/src/calibre/utils/exim.py index dfc8865de4..b67fc08f20 100644 --- a/src/calibre/utils/exim.py +++ b/src/calibre/utils/exim.py @@ -2,20 +2,30 @@ # License: GPLv3 Copyright: 2015, Kovid Goyal -import os, json, struct, hashlib, sys, errno, tempfile, time, shutil, uuid +import errno +import hashlib +import json +import os +import shutil +import struct +import sys +import tempfile +import time +import uuid from collections import Counter from calibre import prints -from calibre.constants import config_dir, iswindows, filesystem_encoding -from calibre.utils.config_base import prefs, StringConfig, create_global_prefs +from calibre.constants import config_dir, filesystem_encoding, iswindows from calibre.utils.config import JSONConfig +from calibre.utils.config_base import StringConfig, create_global_prefs, prefs from calibre.utils.filenames import samefile -from polyglot.builtins import iteritems, error_message +from calibre.utils.localization import _ from polyglot.binary import as_hex_unicode - +from polyglot.builtins import error_message, iteritems # Export {{{ + def send_file(from_obj, to_obj, chunksize=1<<20): m = hashlib.sha1() while True: @@ -169,8 +179,8 @@ def all_known_libraries(): def export(destdir, library_paths=None, dbmap=None, progress1=None, progress2=None, abort=None): - from calibre.db.cache import Cache from calibre.db.backend import DB + from calibre.db.cache import Cache if library_paths is None: library_paths = all_known_libraries() dbmap = dbmap or {} diff --git a/src/calibre/utils/filenames.py b/src/calibre/utils/filenames.py index 0e6fd58635..90a6fb9cd8 100644 --- a/src/calibre/utils/filenames.py +++ b/src/calibre/utils/filenames.py @@ -7,14 +7,14 @@ import errno import os import shutil import time +from contextlib import closing, suppress from math import ceil -from contextlib import suppress, closing from calibre import force_unicode, isbytestring, prints, sanitize_file_name from calibre.constants import ( - filesystem_encoding, iswindows, preferred_encoding, ismacos + filesystem_encoding, ismacos, iswindows, preferred_encoding, ) -from calibre.utils.localization import get_udc +from calibre.utils.localization import _, get_udc from polyglot.builtins import iteritems, itervalues @@ -331,6 +331,7 @@ class WindowsAtomicFolderMove: def __init__(self, path): from collections import defaultdict + from calibre_extensions import winutil self.handle_map = {} diff --git a/src/calibre/utils/html2text.py b/src/calibre/utils/html2text.py index 82abab99e8..4ce9e0b1ec 100644 --- a/src/calibre/utils/html2text.py +++ b/src/calibre/utils/html2text.py @@ -2,6 +2,9 @@ # License: GPLv3 Copyright: 2019, Kovid Goyal +from calibre.utils.localization import _ + + def html2text(html, single_line_break=True): from html2text import HTML2Text import re diff --git a/src/calibre/utils/ipc/job.py b/src/calibre/utils/ipc/job.py index 9695235ea3..b1795e12ac 100644 --- a/src/calibre/utils/ipc/job.py +++ b/src/calibre/utils/ipc/job.py @@ -6,14 +6,15 @@ __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import time, io +import io +import time from itertools import count from calibre import prints from calibre.constants import DEBUG -from polyglot.queue import Queue, Empty +from calibre.utils.localization import _ from polyglot.builtins import cmp - +from polyglot.queue import Empty, Queue job_counter = count() From a54227469be5fb630afa1798b065b6ff0214729b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 10 Jan 2023 09:07:15 +0530 Subject: [PATCH 0103/2055] Bump version of pyqt --- bypy/sources.json | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/bypy/sources.json b/bypy/sources.json index 3ecadf8980..9a9c261ab1 100644 --- a/bypy/sources.json +++ b/bypy/sources.json @@ -933,8 +933,8 @@ "name": "sip", "comment": "build time dependency", "unix": { - "filename": "sip-6.6.2.tar.gz", - "hash": "sha256:0e3efac1c5dfd8e525ae57140927df26993e13f58b89d1577c314f4105bfd90d", + "filename": "sip-6.7.5.tar.gz", + "hash": "sha256:9655d089e1d0c5fbf66bde11558a874980729132b5bd0c2ae355ac1a7b893ab4", "urls": ["pypi"] } }, @@ -943,8 +943,8 @@ "name": "pyqt-builder", "comment": "build time dependency", "unix": { - "filename": "PyQt-builder-1.13.0.tar.gz", - "hash": "sha256:4877580c38ceb5320e129b381d083b0a8601c68166d8b99707f08fa0a1689eef", + "filename": "PyQt-builder-1.14.0.tar.gz", + "hash": "sha256:6755931c6d2f8940553e0334d10c933ce5cc18b64425e94fda1accf4ff774f59", "urls": ["pypi"] } }, @@ -962,8 +962,8 @@ { "name": "pyqt", "unix": { - "filename": "PyQt6-6.3.1.tar.gz", - "hash": "sha256:8cc6e21dbaf7047d1fc897e396ccd9710a12f2ef976563dad65f06017d2c9757", + "filename": "PyQt6-6.4.0.tar.gz", + "hash": "sha256:91392469be1f491905fa9e78fa4e4059a89ab616ddf2ecfd525bc1d65c26bb93", "urls": ["pypi"] } }, @@ -971,13 +971,12 @@ { "name": "pyqt-webengine", "unix": { - "filename": "PyQt6_WebEngine-6.3.1.tar.gz", - "hash": "sha256:c3d1f5527b4b15f44102d617c59b1d74d9af50f821629e9335f13df47de8f007", + "filename": "PyQt6_WebEngine-6.4.0.tar.gz", + "hash": "sha256:4c71c130860abcd11e04cafb22e33983fa9a3aee8323c51909b15a1701828e21", "urls": ["pypi"] } }, - { "name": "speech-dispatcher-client", "os": "linux", From e5992f9d87ebcc2c5445f74fe826ea4c17201ffb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 10 Jan 2023 19:08:06 +0530 Subject: [PATCH 0104/2055] Remove global __() --- .../ebooks/conversion/plugins/html_input.py | 4 +-- src/calibre/ebooks/docx/writer/links.py | 4 ++- src/calibre/ebooks/mobi/writer8/toc.py | 6 ++-- src/calibre/ebooks/oeb/base.py | 1 + src/calibre/ebooks/oeb/reader.py | 35 ++++++++++--------- src/calibre/ebooks/oeb/transforms/htmltoc.py | 7 ++-- src/calibre/utils/localization.py | 4 +++ 7 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/calibre/ebooks/conversion/plugins/html_input.py b/src/calibre/ebooks/conversion/plugins/html_input.py index 6f9c2084ea..ca5b729996 100644 --- a/src/calibre/ebooks/conversion/plugins/html_input.py +++ b/src/calibre/ebooks/conversion/plugins/html_input.py @@ -15,7 +15,7 @@ from calibre.constants import isbsd, islinux from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation from calibre.utils.filenames import ascii_filename from calibre.utils.imghdr import what -from calibre.utils.localization import get_lang +from calibre.utils.localization import __, get_lang from polyglot.builtins import as_unicode @@ -113,7 +113,7 @@ class HTMLInput(InputFormatPlugin): from calibre.ebooks.metadata import string_to_authors from calibre.ebooks.oeb.base import ( BINARY_MIME, OEB_STYLES, DirContainer, rewrite_links, urldefrag, - urlnormalize, urlquote, xpath + urlnormalize, urlquote, xpath, ) from calibre.ebooks.oeb.transforms.metadata import meta_info_to_oeb_metadata from calibre.utils.localization import canonicalize_lang diff --git a/src/calibre/ebooks/docx/writer/links.py b/src/calibre/ebooks/docx/writer/links.py index 46e9368e02..647b06298e 100644 --- a/src/calibre/ebooks/docx/writer/links.py +++ b/src/calibre/ebooks/docx/writer/links.py @@ -4,11 +4,13 @@ __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' -import posixpath, re +import posixpath +import re from uuid import uuid4 from calibre.ebooks.oeb.base import urlquote from calibre.utils.filenames import ascii_text +from calibre.utils.localization import __ from polyglot.urllib import urlparse diff --git a/src/calibre/ebooks/mobi/writer8/toc.py b/src/calibre/ebooks/mobi/writer8/toc.py index 33a3d884ca..3ec2e57cf0 100644 --- a/src/calibre/ebooks/mobi/writer8/toc.py +++ b/src/calibre/ebooks/mobi/writer8/toc.py @@ -5,9 +5,11 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' +from calibre.ebooks.oeb.base import ( + XHTML, XHTML_MIME, XHTML_NS, XPath, css_text, urlnormalize, +) +from calibre.utils.localization import __ from calibre.utils.xml_parse import safe_xml_fromstring -from calibre.ebooks.oeb.base import (urlnormalize, XPath, XHTML_NS, XHTML, - XHTML_MIME, css_text) DEFAULT_TITLE = __('Table of Contents') diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index a0ef201374..be1e855cca 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -26,6 +26,7 @@ from calibre.ebooks.oeb.parse_utils import ( from calibre.translations.dynamic import translate from calibre.utils.cleantext import clean_xml_chars from calibre.utils.icu import numeric_sort_key, title_case as icu_title +from calibre.utils.localization import __ from calibre.utils.short_uuid import uuid4 from calibre.utils.xml_parse import safe_xml_fromstring from polyglot.builtins import codepoint_to_chr, iteritems, itervalues, string_or_bytes diff --git a/src/calibre/ebooks/oeb/reader.py b/src/calibre/ebooks/oeb/reader.py index 2aea5ae48c..740d12187b 100644 --- a/src/calibre/ebooks/oeb/reader.py +++ b/src/calibre/ebooks/oeb/reader.py @@ -6,27 +6,28 @@ Container-/OPF-based input OEBBook reader. __license__ = 'GPL v3' __copyright__ = '2008, Marshall T. Vandegrift ' -import sys, os, uuid, copy, re, io +import copy +import io +import os +import re +import sys +import uuid from collections import defaultdict - from lxml import etree -from calibre.ebooks.oeb.base import OPF1_NS, OPF2_NS, OPF2_NSMAP, DC11_NS, \ - DC_NSES, OPF, xml2text, XHTML_MIME -from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES, OEB_IMAGES, \ - PAGE_MAP_MIME, JPEG_MIME, NCX_MIME, SVG_MIME -from calibre.ebooks.oeb.base import XMLDECL_RE, COLLAPSE_RE, \ - MS_COVER_TYPE, iterlinks -from calibre.ebooks.oeb.base import namespace, barename, XPath, xpath, \ - urlnormalize, BINARY_MIME, \ - OEBError, OEBBook, DirContainer -from calibre.ebooks.oeb.writer import OEBWriter -from calibre.utils.xml_parse import safe_xml_fromstring -from calibre.utils.cleantext import clean_xml_chars -from calibre.utils.localization import get_lang -from calibre.ptempfile import TemporaryDirectory -from calibre.constants import __appname__, __version__ from calibre import guess_type, xml_replace_entities +from calibre.constants import __appname__, __version__ +from calibre.ebooks.oeb.base import ( + BINARY_MIME, COLLAPSE_RE, DC11_NS, DC_NSES, JPEG_MIME, MS_COVER_TYPE, NCX_MIME, + OEB_DOCS, OEB_IMAGES, OEB_STYLES, OPF, OPF1_NS, OPF2_NS, OPF2_NSMAP, PAGE_MAP_MIME, + SVG_MIME, XHTML_MIME, XMLDECL_RE, DirContainer, OEBBook, OEBError, XPath, barename, + iterlinks, namespace, urlnormalize, xml2text, xpath, +) +from calibre.ebooks.oeb.writer import OEBWriter +from calibre.ptempfile import TemporaryDirectory +from calibre.utils.cleantext import clean_xml_chars +from calibre.utils.localization import __, get_lang +from calibre.utils.xml_parse import safe_xml_fromstring from polyglot.urllib import unquote, urldefrag, urlparse __all__ = ['OEBReader'] diff --git a/src/calibre/ebooks/oeb/transforms/htmltoc.py b/src/calibre/ebooks/oeb/transforms/htmltoc.py index 3fef82a001..135e224b7f 100644 --- a/src/calibre/ebooks/oeb/transforms/htmltoc.py +++ b/src/calibre/ebooks/oeb/transforms/htmltoc.py @@ -5,9 +5,10 @@ HTML-TOC-adding transform. __license__ = 'GPL v3' __copyright__ = '2008, Marshall T. Vandegrift ' -from calibre.ebooks.oeb.base import XML, XHTML, XHTML_NS -from calibre.ebooks.oeb.base import XHTML_MIME, CSS_MIME -from calibre.ebooks.oeb.base import element, XPath +from calibre.ebooks.oeb.base import ( + CSS_MIME, XHTML, XHTML_MIME, XHTML_NS, XML, XPath, element, +) +from calibre.utils.localization import __ __all__ = ['HTMLTOCAdder'] diff --git a/src/calibre/utils/localization.py b/src/calibre/utils/localization.py index 428dc89540..efb30ea1ed 100644 --- a/src/calibre/utils/localization.py +++ b/src/calibre/utils/localization.py @@ -268,6 +268,10 @@ def _(x: str) -> str: return default_translator.gettext(x) +def __(x: str) -> str: + return x + + def ngettext(singular: str, plural: str, n: int) -> str: return default_translator.ngettext(singular, plural, n) From a981e08b2c529e46f4c968cf539540b1cd8c572d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 10 Jan 2023 19:09:26 +0530 Subject: [PATCH 0105/2055] Move to ruff and pyproject.toml --- pyproject.toml | 22 ++++++++++++++++++++++ setup.cfg | 13 ------------- 2 files changed, 22 insertions(+), 13 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.cfg diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..59f2d8d8f7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[tool.ruff] +line-length = 160 +target-version = 'py37' +select = ['E', 'F'] +ignore = ['E741', 'E402', 'E722', 'E401'] +builtins = ['_'] + +[tool.ruff.per-file-ignores] +"src/calibre/ebooks/unihandecode/unicodepoints.py" = ["E501"] +"src/qt/__init__.py" = ["E501"] + +[tool.black] +target-version = ['py37'] + +[tool.isort] +profile = "black" +combine_as_imports = true +multi_line_output = 5 +known_future_library = "__python__" +known_third_party = "qt" +known_standard_library = "aes,elementmaker,encodings" +known_first_party = "calibre_extensions" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 6b0784ada4..0000000000 --- a/setup.cfg +++ /dev/null @@ -1,13 +0,0 @@ -[flake8] -max-line-length = 160 -builtins = _,__,P,I,lopen,icu_lower,icu_upper,icu_title,ngettext,connect_lambda -ignore = E12,E203,E22,E231,E241,E401,E402,E503,E731,W391,E722,E741,W504,W203 - -[isort] -profile = black -combine_as_imports = True -multi_line_output = 5 -known_future_library = __python__ -known_third_party = qt -known_standard_library = aes,elementmaker,encodings -known_first_party = calibre_extensions From 4419e7d65a733d8a7ae6d72b03a133c24b639faa Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 10 Jan 2023 19:31:32 +0530 Subject: [PATCH 0106/2055] Move to ruff for ./setup.py check --- .gitignore | 2 +- recipes/dunyahalleri.recipe | 3 ++- recipes/dunyahalleri_haftaninozeti.recipe | 3 ++- recipes/el_correo.recipe | 2 +- recipes/elpais_impreso.recipe | 2 +- recipes/expansion_spanish.recipe | 2 +- recipes/onda_rock.recipe | 15 +++++++++++++-- recipes/respekt_magazine.recipe | 2 +- setup/check.py | 2 +- src/calibre/translations/dynamic.py | 3 ++- src/calibre/utils/localization.py | 14 +++++++------- 11 files changed, 32 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 4e35b4d848..26d1a08a98 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,7 @@ setup/installer/windows/calibre/build.log setup/pyqt_enums tags nbproject/ -translations/ +/translations/ *.mdproj *.pidb *.sln diff --git a/recipes/dunyahalleri.recipe b/recipes/dunyahalleri.recipe index 2cb4ad86f9..8d7df9ba4d 100644 --- a/recipes/dunyahalleri.recipe +++ b/recipes/dunyahalleri.recipe @@ -10,6 +10,7 @@ from shutil import copyfile from calibre import strftime from calibre.ebooks.BeautifulSoup import Tag from calibre.web.feeds.recipes import BasicNewsRecipe +from calibre.utils.resources import get_path from PIL import Image, ImageDraw, ImageFont __license__ = 'GPL v3' @@ -168,7 +169,7 @@ class DunyaHalleri(BasicNewsRecipe): self.cover_img_path = None def draw_text(self, draw, text, text_size, top): - font_path = P('fonts/liberation/LiberationSerif-Bold.ttf') + font_path = get_path('fonts/liberation/LiberationSerif-Bold.ttf') font = ImageFont.truetype(font_path, text_size) width, height = draw.textsize(text, font=font) left = max(int((self.COVER_WIDTH - width) / 2.), 0) diff --git a/recipes/dunyahalleri_haftaninozeti.recipe b/recipes/dunyahalleri_haftaninozeti.recipe index 808976910c..60bedc5c5d 100644 --- a/recipes/dunyahalleri_haftaninozeti.recipe +++ b/recipes/dunyahalleri_haftaninozeti.recipe @@ -10,6 +10,7 @@ from shutil import copyfile from contextlib import closing from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag from calibre.web.feeds.recipes import BasicNewsRecipe +from calibre.utils.resources import get_path from PIL import Image, ImageDraw, ImageFont __license__ = 'GPL v3' @@ -232,7 +233,7 @@ class DunyaHalleri_HaftaninOzeti(BasicNewsRecipe): self.cover_img_path = None def draw_text(self, draw, text, text_size, top): - font_path = P('fonts/liberation/LiberationSerif-Bold.ttf') + font_path = get_path('fonts/liberation/LiberationSerif-Bold.ttf') font = ImageFont.truetype(font_path, text_size) width, height = draw.textsize(text, font=font) left = max(int((self.COVER_WIDTH - width) / 2.), 0) diff --git a/recipes/el_correo.recipe b/recipes/el_correo.recipe index 3854eb5074..7af992a5c6 100644 --- a/recipes/el_correo.recipe +++ b/recipes/el_correo.recipe @@ -130,7 +130,7 @@ class elcorreo(BasicNewsRecipe): # Controlamos si el artículo ha sido incluido en otro feed para eliminarlo - if not (link in self._processed_links): + if link not in self._processed_links: self._processed_links.append(link) else: link = None diff --git a/recipes/elpais_impreso.recipe b/recipes/elpais_impreso.recipe index fb67faedcb..8e288527e7 100644 --- a/recipes/elpais_impreso.recipe +++ b/recipes/elpais_impreso.recipe @@ -81,7 +81,7 @@ class ElPais_RSS(BasicNewsRecipe): def get_article_url(self, article): url = BasicNewsRecipe.get_article_url(self, article) - if url and (not('/album/' in url) and not('/futbol/partido/' in url)): + if url and ('/album/' not in url and '/futbol/partido/' not in url): return url self.log('Skipping non-article', url) return None diff --git a/recipes/expansion_spanish.recipe b/recipes/expansion_spanish.recipe index 74fec85199..7ba76a1723 100644 --- a/recipes/expansion_spanish.recipe +++ b/recipes/expansion_spanish.recipe @@ -147,7 +147,7 @@ class expansion_spanish(BasicNewsRecipe): # Eliminar artículos duplicados en otros feeds - if not (link in self._processed_links): + if link not in self._processed_links: self._processed_links.append(link) else: link = None diff --git a/recipes/onda_rock.recipe b/recipes/onda_rock.recipe index 7c96cc5385..fc0f0d811d 100644 --- a/recipes/onda_rock.recipe +++ b/recipes/onda_rock.recipe @@ -26,5 +26,16 @@ class AdvancedUserRecipe1328535130(BasicNewsRecipe): masthead_url = 'http://api.ning.com/files/4ot8ampp*-rYQuwL2NoaHvVqcyu7VMyWyan12a9QMsJUWxk-q5V1-34wnD-Wj9B5qWjc1yPMLGiwQg8hZJxaySeaG2lx8hpV/2009_banner_ondarock.gif' # noqa extra_css = ''' # noqa - .boxtabscontain_page {border: 1px solid #E0E0E0;clear: both;font-family: "Verdana", "Arial", "Helvetica", sans-serif;font-size: 10px;line-height: 17px;margin: 0px 0px 20px;padding: 10px 10px 10px 40px;position: relative;top: -1px;width: 258px;z-index: 1;} - ''' + .boxtabscontain_page { + border: 1px solid #E0E0E0;clear: both; + font-family: "Verdana", "Arial", "Helvetica", sans-serif; + font-size: 10px; + line-height: 17px; + margin: 0px 0px 20px; + padding: 10px 10px 10px 40px; + position: relative; + top: -1px; + width: 258px; + z-index: 1; + } + ''' diff --git a/recipes/respekt_magazine.recipe b/recipes/respekt_magazine.recipe index 41fed613e0..c9256aae49 100644 --- a/recipes/respekt_magazine.recipe +++ b/recipes/respekt_magazine.recipe @@ -159,7 +159,7 @@ class respektRecipe(BasicNewsRecipe): for par in paragraphs[:-1]: prev = par.getprevious() # Do not indent after headings - if hasattr(prev,'tag') and not (prev.tag in ['h2','h3']): + if hasattr(prev,'tag') and prev.tag not in ['h2', 'h3']: par.attrib['class']="indent_first_line" # Fix subtitle for Téma try: diff --git a/setup/check.py b/setup/check.py index f3e9e7a307..22c88ccd31 100644 --- a/setup/check.py +++ b/setup/check.py @@ -77,7 +77,7 @@ class Check(Command): def file_has_errors(self, f): ext = os.path.splitext(f)[1] if ext in {'.py', '.recipe'}: - p2 = subprocess.Popen(['flake8', '--filename', '*.py,*.recipe', f]) + p2 = subprocess.Popen(['ruff', '--no-update-check', f]) return p2.wait() != 0 if ext == '.pyj': p = subprocess.Popen(['rapydscript', 'lint', f]) diff --git a/src/calibre/translations/dynamic.py b/src/calibre/translations/dynamic.py index 97d1478fcf..5ad04e3cd0 100644 --- a/src/calibre/translations/dynamic.py +++ b/src/calibre/translations/dynamic.py @@ -8,6 +8,7 @@ __copyright__ = '2008, Marshall T. Vandegrift ' import io from gettext import GNUTranslations from calibre.utils.localization import get_lc_messages_path +from calibre.utils.resources import get_path from zipfile import ZipFile __all__ = ['translate'] @@ -22,7 +23,7 @@ def translate(lang, text): else: mpath = get_lc_messages_path(lang) if mpath is not None: - with ZipFile(P('localization/locales.zip', + with ZipFile(get_path('localization/locales.zip', allow_user_override=False), 'r') as zf: try: buf = io.BytesIO(zf.read(mpath + '/messages.mo')) diff --git a/src/calibre/utils/localization.py b/src/calibre/utils/localization.py index efb30ea1ed..6e0112e615 100644 --- a/src/calibre/utils/localization.py +++ b/src/calibre/utils/localization.py @@ -1,14 +1,14 @@ #!/usr/bin/env python +# License: GPLv3 Copyright: 2009, Kovid Goyal -__license__ = 'GPL v3' -__copyright__ = '2009, Kovid Goyal ' -__docformat__ = 'restructuredtext en' - -import os, locale, re, io +import io +import locale +import os +import re from gettext import GNUTranslations, NullTranslations -from calibre.utils.resources import get_path as P +from calibre.utils.resources import get_path as P from polyglot.builtins import iteritems _available_translations = None @@ -29,7 +29,7 @@ def available_translations(): def get_system_locale(): - from calibre.constants import iswindows, ismacos + from calibre.constants import ismacos, iswindows lang = None if iswindows: try: From ceb72752e44f06a78203e9684b1f92db15193ab0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 10 Jan 2023 19:35:37 +0530 Subject: [PATCH 0107/2055] Root the patterns in .gitignore --- .gitignore | 89 ++++++++++++++++++++++++++---------------------------- 1 file changed, 43 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index 26d1a08a98..6e51dafe12 100644 --- a/.gitignore +++ b/.gitignore @@ -6,57 +6,54 @@ .bzrignore .build-cache .cache -compile_commands.json -link_commands.json -src/calibre/plugins -resources/images.qrc -resources/icons.rcc -manual/generated -manual/locale -manual/.doctrees -build -dist -docs -resources/localization -resources/hyphenation -resources/scripts.calibre_msgpack -resources/changelog.json -resources/ebook-convert-complete.calibre_msgpack -resources/builtin_recipes.xml -resources/builtin_recipes.zip -resources/template-functions.json -resources/editor-functions.json -resources/user-manual-translation-stats.json -resources/editor.js -resources/viewer.js -resources/viewer.html -resources/content-server/index-generated.html -resources/content-server/locales.zip -resources/mathjax -resources/fonts/liberation -resources/mozilla-ca-certs.pem -resources/user-agent-data.json -icons/icns/*.iconset -setup/installer/windows/calibre/build.log -setup/pyqt_enums -tags -nbproject/ +/compile_commands.json +/link_commands.json +/src/calibre/plugins +/resources/images.qrc +/resources/icons.rcc +/manual/generated +/manual/locale +/manual/.doctrees +/build +/dist +/docs +/resources/localization +/resources/hyphenation +/resources/scripts.calibre_msgpack +/resources/changelog.json +/resources/ebook-convert-complete.calibre_msgpack +/resources/builtin_recipes.xml +/resources/builtin_recipes.zip +/resources/template-functions.json +/resources/editor-functions.json +/resources/user-manual-translation-stats.json +/resources/editor.js +/resources/viewer.js +/resources/viewer.html +/resources/content-server/index-generated.html +/resources/content-server/locales.zip +/resources/mathjax +/resources/fonts/liberation +/resources/mozilla-ca-certs.pem +/resources/user-agent-data.json +/icons/icns/*.iconset +/setup/installer/windows/calibre/build.log +/setup/pyqt_enums +/tags +/nbproject/ /translations/ *.mdproj *.pidb *.sln *.userprefs -.project -.pydevproject -.settings/ +/.project +/.pydevproject +/.settings/ *.DS_Store -calibre_plugins/ -recipes/*.mobi -recipes/*.epub -recipes/debug +/calibre_plugins/ /.metadata/ -.idea +/.idea /*env*/ -cmake-build-* -bypy/b -bypy/virtual-machines.conf +/cmake-build-* +/bypy/b +/bypy/virtual-machines.conf From 6dd38d512c8a5b608f90450ef58002acdf907b4c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 11 Jan 2023 13:19:39 +0530 Subject: [PATCH 0108/2055] Start work on wrapping the WinRT speech APIs They give access to more voices, but whether they will be workable remains to be seen. --- setup/extensions.json | 8 +++++++ setup/installers.py | 2 +- src/calibre/constants.py | 2 +- src/calibre/utils/windows/winspeech.cpp | 32 +++++++++++++++++++++++++ src/calibre/utils/windows/winspeech.py | 11 +++++++++ 5 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/calibre/utils/windows/winspeech.cpp create mode 100644 src/calibre/utils/windows/winspeech.py diff --git a/setup/extensions.json b/setup/extensions.json index 7fc30f2683..c07af3580d 100644 --- a/setup/extensions.json +++ b/setup/extensions.json @@ -184,6 +184,14 @@ "libraries": "SAPI Ole32", "cflags": "/X" }, + { + "name": "winspeech", + "only": "windows", + "headers": "calibre/utils/cpp_binding.h calibre/utils/windows/common.h", + "sources": "calibre/utils/windows/winspeech.cpp", + "libraries": "shlwapi runtimeobject", + "cflags": "/X" + }, { "name": "wpd", "only": "windows", diff --git a/setup/installers.py b/setup/installers.py index efc433b5d5..ba64955633 100644 --- a/setup/installers.py +++ b/setup/installers.py @@ -261,7 +261,7 @@ class ExtDev(Command): try: path = path.format(ext) src = os.path.join(ext_dir, os.path.basename(path)) - subprocess.check_call(['ssh', '-S', control_path, host, 'chmod', '+w', f'"{path}"']) + subprocess.check_call(['ssh', '-S', control_path, host, 'chmod', '+wx', f'"{path}"']) with open(src, 'rb') as f: p = subprocess.Popen(['ssh', '-S', control_path, host, f'cat - > "{path}"'], stdin=subprocess.PIPE) p.communicate(f.read()) diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 3f82bb6d91..9691910808 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -269,7 +269,7 @@ class ExtensionsImporter: 'uchardet', ) if iswindows: - extra = ('winutil', 'wpd', 'winfonts', 'winsapi') + extra = ('winutil', 'wpd', 'winfonts', 'winsapi', 'winspeech') elif ismacos: extra = ('usbobserver', 'cocoa', 'libusb', 'libmtp') elif isfreebsd or ishaiku or islinux: diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp new file mode 100644 index 0000000000..6af9621e26 --- /dev/null +++ b/src/calibre/utils/windows/winspeech.cpp @@ -0,0 +1,32 @@ +/* + * winspeech.cpp + * Copyright (C) 2023 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "common.h" + +#define M(name, args) { #name, name, args, ""} +static PyMethodDef methods[] = { + {NULL, NULL, 0, NULL} +}; +#undef M + + +static int +exec_module(PyObject *m) { + return 0; +} + +static PyModuleDef_Slot slots[] = { {Py_mod_exec, (void*)exec_module}, {0, NULL} }; + +static struct PyModuleDef module_def = {PyModuleDef_HEAD_INIT}; + +CALIBRE_MODINIT_FUNC PyInit_winspeech(void) { + module_def.m_name = "winspeech"; + module_def.m_doc = "Windows Speech API wrapper"; + module_def.m_methods = methods; + module_def.m_slots = slots; + return PyModuleDef_Init(&module_def); +} diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py new file mode 100644 index 0000000000..c8d98cefa4 --- /dev/null +++ b/src/calibre/utils/windows/winspeech.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +# License: GPLv3 Copyright: 2023, Kovid Goyal + + +import calibre_extensions.winspeech as winspeech + +winspeech + + +def develop(): + pass From b1d7a72494d7b314f4c02e6ea2d4a33f58de3057 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 11 Jan 2023 19:25:07 +0530 Subject: [PATCH 0109/2055] Fix #2002534 [MobileRead bookshop plugin crashes](https://bugs.launchpad.net/calibre/+bug/2002534) --- .../gui2/store/stores/mobileread/store_dialog.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/store/stores/mobileread/store_dialog.py b/src/calibre/gui2/store/stores/mobileread/store_dialog.py index 46c301ec7f..c62efd1acd 100644 --- a/src/calibre/gui2/store/stores/mobileread/store_dialog.py +++ b/src/calibre/gui2/store/stores/mobileread/store_dialog.py @@ -68,9 +68,13 @@ class MobileReadStoreDialog(QDialog, Ui_Dialog): self.results_view.resizeColumnToContents(i) self.results_view.model().sort_col = self.plugin.config.get('dialog_sort_col', 0) - self.results_view.model().sort_order = self.plugin.config.get('dialog_sort_order', Qt.SortOrder.AscendingOrder) - self.results_view.model().sort(self.results_view.model().sort_col, self.results_view.model().sort_order) - self.results_view.header().setSortIndicator(self.results_view.model().sort_col, self.results_view.model().sort_order) + try: + so = Qt.SortOrder(self.plugin.config.get('dialog_sort_order', Qt.SortOrder.AscendingOrder)) + except Exception: + so = Qt.SortOrder.AscendingOrder + self.results_view.model().sort_order = so + self.results_view.model().sort(self.results_view.model().sort_col, so) + self.results_view.header().setSortIndicator(self.results_view.model().sort_col, so) def save_state(self): self.save_geometry(self.plugin.config, 'dialog_geometry') From d1b1fa7209288b9ec8707f4489b2980d6df2dc4d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 11 Jan 2023 20:53:48 +0530 Subject: [PATCH 0110/2055] Get listing all available voices working --- setup/extensions.json | 4 +- src/calibre/utils/windows/common.h | 36 ++++++++- src/calibre/utils/windows/winspeech.cpp | 101 +++++++++++++++++++++++- src/calibre/utils/windows/winutil.cpp | 11 --- 4 files changed, 137 insertions(+), 15 deletions(-) diff --git a/setup/extensions.json b/setup/extensions.json index c07af3580d..6161b71d3f 100644 --- a/setup/extensions.json +++ b/setup/extensions.json @@ -189,8 +189,8 @@ "only": "windows", "headers": "calibre/utils/cpp_binding.h calibre/utils/windows/common.h", "sources": "calibre/utils/windows/winspeech.cpp", - "libraries": "shlwapi runtimeobject", - "cflags": "/X" + "libraries": "WindowsApp", + "cflags": "/X /std:c++17 /ZW /bigobj /await /permissive- /WX /Zc:twoPhase-" }, { "name": "wpd", diff --git a/src/calibre/utils/windows/common.h b/src/calibre/utils/windows/common.h index c732e1eb8c..76366f0921 100644 --- a/src/calibre/utils/windows/common.h +++ b/src/calibre/utils/windows/common.h @@ -24,8 +24,42 @@ set_error_from_hresult(PyObject *exc_type, const char *file, const int line, con } #define error_from_hresult(hr, ...) set_error_from_hresult(PyExc_OSError, __FILE__, __LINE__, hr, __VA_ARGS__) +class scoped_com_initializer { // {{{ + public: + scoped_com_initializer() : m_succeded(false), hr(0) { + hr = CoInitialize(NULL); + if (SUCCEEDED(hr)) m_succeded = true; + } + ~scoped_com_initializer() { if (succeeded()) CoUninitialize(); } + + explicit operator bool() const noexcept { return m_succeded; } + + bool succeeded() const noexcept { return m_succeded; } + + PyObject* set_python_error() const noexcept { + if (hr == RPC_E_CHANGED_MODE) { + PyErr_SetString(PyExc_OSError, "COM initialization failed as it was already initialized in multi-threaded mode"); + } else { + _com_error err(hr); + PyObject *pmsg = PyUnicode_FromWideChar(err.ErrorMessage(), -1); + PyErr_Format(PyExc_OSError, "COM initialization failed: %V", pmsg, "Out of memory"); + } + return NULL; + } + + void detach() noexcept { m_succeded = false; } + + private: + bool m_succeded; + HRESULT hr; + scoped_com_initializer( const scoped_com_initializer & ) ; + scoped_com_initializer & operator=( const scoped_com_initializer & ) ; +}; // }}} + +#define INITIALIZE_COM_IN_FUNCTION scoped_com_initializer com; if (!com) return com.set_python_error(); + static inline void co_task_mem_free(void* m) { CoTaskMemFree(m); } -typedef generic_raii com_wchar_raii; +typedef generic_raii(NULL)> com_wchar_raii; static inline void handle_destructor(HANDLE p) { CloseHandle(p); } typedef generic_raii handle_raii; diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 6af9621e26..9e811e9c4a 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -4,11 +4,94 @@ * * Distributed under terms of the GPL3 license. */ - #include "common.h" +#include +#include +#include +#include +#include +#include + +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::Media::SpeechSynthesis; +using namespace Windows::Storage::Streams; + +typedef struct { + PyObject_HEAD + SpeechSynthesizer ^synth; +} Synthesizer; + + +static PyTypeObject SynthesizerType = { + PyVarObject_HEAD_INIT(NULL, 0) +}; + +static PyObject * +Synthesizer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { INITIALIZE_COM_IN_FUNCTION + Synthesizer *self = (Synthesizer *) type->tp_alloc(type, 0); + if (self) { + self->synth = ref new SpeechSynthesizer(); + } + if (self && !PyErr_Occurred()) com.detach(); + return (PyObject*)self; +} + +static void +Synthesizer_dealloc(Synthesizer *self) { + self->synth = nullptr; + CoUninitialize(); +} + +static PyObject* +voice_as_dict(VoiceInformation ^voice) { + const char *gender = ""; + switch (voice->Gender) { + case VoiceGender::Male: gender = "male"; break; + case VoiceGender::Female: gender = "female"; break; + } + return Py_BuildValue("{su su su su ss}", + "display_name", voice->DisplayName? voice->DisplayName->Data() : NULL, + "description", voice->Description ? voice->Description->Data() : NULL, + "id", voice->Id ? voice->Id->Data(): NULL, + "language", voice->Language ? voice->Language->Data() : NULL, + "gender", gender + ); +} + +static PyObject* +all_voices(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTION + IVectorView^ voices = SpeechSynthesizer::AllVoices; + pyobject_raii ans(PyTuple_New(voices->Size)); + if (!ans) return NULL; + Py_ssize_t i = 0; + for(auto voice : voices) { + PyObject *v = voice_as_dict(voice); + if (v) { + PyTuple_SET_ITEM(ans.ptr(), i++, v); + } else { + return NULL; + } + } + return ans.detach(); +} + +static PyObject* +default_voice(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTION + return voice_as_dict(SpeechSynthesizer::DefaultVoice); +} + +#define M(name, args) { #name, (PyCFunction)Synthesizer_##name, args, ""} +static PyMethodDef Synthesizer_methods[] = { + {NULL, NULL, 0, NULL} +}; +#undef M + #define M(name, args) { #name, name, args, ""} static PyMethodDef methods[] = { + M(all_voices, METH_NOARGS), + M(default_voice, METH_NOARGS), {NULL, NULL, 0, NULL} }; #undef M @@ -16,6 +99,22 @@ static PyMethodDef methods[] = { static int exec_module(PyObject *m) { + SynthesizerType.tp_name = "winspeech.Synthesizer"; + SynthesizerType.tp_doc = "Wrapper for SpeechSynthesizer"; + SynthesizerType.tp_basicsize = sizeof(Synthesizer); + SynthesizerType.tp_itemsize = 0; + SynthesizerType.tp_flags = Py_TPFLAGS_DEFAULT; + SynthesizerType.tp_new = Synthesizer_new; + SynthesizerType.tp_methods = Synthesizer_methods; + SynthesizerType.tp_dealloc = (destructor)Synthesizer_dealloc; + if (PyType_Ready(&SynthesizerType) < 0) return -1; + + Py_INCREF(&SynthesizerType); + if (PyModule_AddObject(m, "Synthesizer", (PyObject *) &SynthesizerType) < 0) { + Py_DECREF(&SynthesizerType); + return -1; + } + return 0; } diff --git a/src/calibre/utils/windows/winutil.cpp b/src/calibre/utils/windows/winutil.cpp index f2a6608f6b..024ddf53f9 100644 --- a/src/calibre/utils/windows/winutil.cpp +++ b/src/calibre/utils/windows/winutil.cpp @@ -273,17 +273,6 @@ class DeleteFileProgressSink : public IFileOperationProgressSink { // {{{ ULONG m_cRef; }; // }}} -class scoped_com_initializer { // {{{ - public: - scoped_com_initializer() : m_succeded(false) { if (SUCCEEDED(CoInitialize(NULL))) m_succeded = true; } - ~scoped_com_initializer() { CoUninitialize(); } - bool succeeded() { return m_succeded; } - private: - bool m_succeded; - scoped_com_initializer( const scoped_com_initializer & ) ; - scoped_com_initializer & operator=( const scoped_com_initializer & ) ; -}; // }}} - static PyObject* get_computer_name(PyObject *self, PyObject *args) { COMPUTER_NAME_FORMAT fmt = ComputerNameDnsFullyQualified; From 882d56549157bb3bd95589d0242a2e3bbcb72648 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 12 Jan 2023 17:30:40 +0530 Subject: [PATCH 0111/2055] Method to save speech as WAV data --- src/calibre/utils/windows/winspeech.cpp | 81 +++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 9e811e9c4a..b468742092 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -6,8 +6,10 @@ */ #include "common.h" +#include #include #include +#include #include #include #include @@ -17,6 +19,15 @@ using namespace Windows::Foundation; using namespace Windows::Foundation::Collections; using namespace Windows::Media::SpeechSynthesis; using namespace Windows::Storage::Streams; +using namespace Platform; +using namespace Concurrency; + +// static void +// wait_for_async( Windows::Foundation::IAsyncInfo ^op ) { +// while(op->Status == Windows::Foundation::AsyncStatus::Started) { +// CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent); +// } +// } typedef struct { PyObject_HEAD @@ -44,6 +55,75 @@ Synthesizer_dealloc(Synthesizer *self) { CoUninitialize(); } +#define WM_DONE (WM_USER + 0) + +static void +ensure_current_thread_has_message_queue(void) { + MSG msg; + PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); +} + +static bool +send_done_message_to_thread(DWORD thread_id) { + return PostThreadMessageA(thread_id, WM_DONE, 0, 0); +} + +static bool +pump_till_done(void) { + MSG msg; + while (true) { + BOOL ret = GetMessage(&msg, NULL, 0, 0); + if (ret == 0) { PyErr_SetString(PyExc_OSError, "WM_QUIT received"); return false; } // WM_QUIT + if (ret == -1) { PyErr_SetFromWindowsErr(0); return false; } + if (msg.message == WM_DONE) { + break; + } + DispatchMessage(&msg); + } + return true; +} + +static PyObject* +Synthesizer_create_recording(Synthesizer *self, PyObject *args) { + wchar_raii pytext; + if (!PyArg_ParseTuple(args, "O&", py_to_wchar_no_none, &pytext)) return NULL; + StringReference text(pytext.ptr()); + bool error_ocurred = false; + HRESULT hr = S_OK; + std::array error_msg; + DataReader ^reader = nullptr; + DWORD main_thread_id = GetCurrentThreadId(); + unsigned long long stream_size; + unsigned int bytes_read; + + create_task(self->synth->SynthesizeTextToStreamAsync(text.GetString()), task_continuation_context::use_current() + ).then([&reader, &stream_size](task stream_task) { + SpeechSynthesisStream^ stream = stream_task.get(); + stream_size = stream->Size; + reader = ref new DataReader(stream); + return reader->LoadAsync((unsigned int)stream_size); + }).then([main_thread_id, &bytes_read, &error_msg, &error_ocurred, &reader](task bytes_read_task) { + try { + bytes_read = bytes_read_task.get(); + } catch (Exception ^ex) { + std::swprintf(error_msg.data(), error_msg.size(), L"Could not synthesize speech from text: %ls", ex->Message->Data()); + error_ocurred = true; + } + send_done_message_to_thread(main_thread_id); + }); + + if (!pump_till_done()) return NULL; + + if (error_ocurred) { + pyobject_raii err(PyUnicode_FromWideChar(error_msg.data(), -1)); + PyErr_Format(PyExc_OSError, "%V", err.ptr(), "Could not create error message unicode object"); + return NULL; + } + auto data = ref new Platform::Array(bytes_read); + reader->ReadBytes(data); + return PyBytes_FromStringAndSize((const char*)data->Data, bytes_read); +} + static PyObject* voice_as_dict(VoiceInformation ^voice) { const char *gender = ""; @@ -84,6 +164,7 @@ default_voice(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTI #define M(name, args) { #name, (PyCFunction)Synthesizer_##name, args, ""} static PyMethodDef Synthesizer_methods[] = { + M(create_recording, METH_VARARGS), {NULL, NULL, 0, NULL} }; #undef M From 4c89a7e697e02674b00f4be231e5c756bdddec81 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 13 Jan 2023 07:46:38 +0530 Subject: [PATCH 0112/2055] Fix #2002753 [Error when ejecting while in Device Mode](https://bugs.launchpad.net/calibre/+bug/2002753) --- src/calibre/gui2/library/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 96772474fc..c76ca43a37 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -1701,6 +1701,10 @@ class DeviceBooksModel(BooksModel): # {{{ def current_changed(self, current, previous, emit_signal=True): if current.isValid(): idx = current.row() + try: + self.db[self.map[idx]] + except Exception: + return # can happen if the device is ejected try: data = self.get_book_display_info(idx) except Exception: From c7468a5f9a30b74ec53d17902e8cf2f64a3eb3ae Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 13 Jan 2023 14:52:24 +0530 Subject: [PATCH 0113/2055] Switch to C++/WinRT from C++/CX --- setup/extensions.json | 2 +- src/calibre/utils/windows/winspeech.cpp | 247 +++++++++++++++--------- 2 files changed, 155 insertions(+), 94 deletions(-) diff --git a/setup/extensions.json b/setup/extensions.json index 6161b71d3f..b27071bf5b 100644 --- a/setup/extensions.json +++ b/setup/extensions.json @@ -190,7 +190,7 @@ "headers": "calibre/utils/cpp_binding.h calibre/utils/windows/common.h", "sources": "calibre/utils/windows/winspeech.cpp", "libraries": "WindowsApp", - "cflags": "/X /std:c++17 /ZW /bigobj /await /permissive- /WX /Zc:twoPhase-" + "cflags": "/X /std:c++17 /bigobj /await /permissive- /WX /Zc:twoPhase-" }, { "name": "wpd", diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index b468742092..3ffe5a15d8 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -7,32 +7,23 @@ #include "common.h" #include -#include +#include +#include #include -#include #include -#include -#include -#include +#include +#include -using namespace Windows::Foundation; -using namespace Windows::Foundation::Collections; -using namespace Windows::Media::SpeechSynthesis; -using namespace Windows::Storage::Streams; -using namespace Platform; -using namespace Concurrency; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::Media::SpeechSynthesis; +using namespace winrt::Windows::Storage::Streams; -// static void -// wait_for_async( Windows::Foundation::IAsyncInfo ^op ) { -// while(op->Status == Windows::Foundation::AsyncStatus::Started) { -// CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent); -// } -// } -typedef struct { +struct Synthesizer { PyObject_HEAD - SpeechSynthesizer ^synth; -} Synthesizer; + SpeechSynthesizer synth{nullptr}; +}; static PyTypeObject SynthesizerType = { @@ -43,7 +34,7 @@ static PyObject * Synthesizer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { INITIALIZE_COM_IN_FUNCTION Synthesizer *self = (Synthesizer *) type->tp_alloc(type, 0); if (self) { - self->synth = ref new SpeechSynthesizer(); + self->synth = SpeechSynthesizer(); } if (self && !PyErr_Occurred()) com.detach(); return (PyObject*)self; @@ -51,120 +42,190 @@ Synthesizer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { INITIALIZE static void Synthesizer_dealloc(Synthesizer *self) { - self->synth = nullptr; + self->synth = SpeechSynthesizer{nullptr}; CoUninitialize(); } -#define WM_DONE (WM_USER + 0) - static void ensure_current_thread_has_message_queue(void) { MSG msg; PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); } -static bool -send_done_message_to_thread(DWORD thread_id) { - return PostThreadMessageA(thread_id, WM_DONE, 0, 0); -} +/* +class CreateRecording { +private: + DWORD main_thread_id; + std::wstring error_msg; + winrt::Windows::Storage::Streams::DataReader reader{nullptr}; + unsigned long long stream_size, bytes_read; -static bool -pump_till_done(void) { - MSG msg; - while (true) { - BOOL ret = GetMessage(&msg, NULL, 0, 0); - if (ret == 0) { PyErr_SetString(PyExc_OSError, "WM_QUIT received"); return false; } // WM_QUIT - if (ret == -1) { PyErr_SetFromWindowsErr(0); return false; } - if (msg.message == WM_DONE) { - break; - } - DispatchMessage(&msg); +public: + CreateRecording() : main_thread_id(0), error_msg(), reader(nullptr), stream_size(0), bytes_read(0) { + main_thread_id = GetCurrentThreadId(); + ensure_current_thread_has_message_queue(); } - return true; -} + CreateRecording& operator = (const CreateRecording &) = delete; + CreateRecording(const CreateRecording&) = delete; + + void record_plain_text(SpeechSynthesizer ^synth, const wchar_t* text, PyObject *callback, std::shared_ptr self) { + StringReference rtext(text); + create_task(synth->SynthesizeTextToStreamAsync(rtext.GetString()), task_continuation_context::use_current()).then( + [self](task s) { self->threaded_save_stream(s, self); }); + this->run_loop(callback); + reader = winrt::Windows::Storage::Streams::DataReader{nullptr}; + } + + void record_ssml(SpeechSynthesizer ^synth, const wchar_t* text, PyObject *callback, std::shared_ptr self) { + StringReference rtext(text); + create_task(synth->SynthesizeSsmlToStreamAsync(rtext.GetString()), task_continuation_context::use_current()).then( + [self](task s) { self->threaded_save_stream(s, self); }); + this->run_loop(callback); + reader = winrt::Windows::Storage::Streams::DataReader{nullptr}; + } + +private: + + void send_message_to_main_thread(bool done = false) const { + PostThreadMessageA(main_thread_id, WM_USER, 0, done ? 1 : 0); + } + + void threaded_save_stream(task stream_task, std::shared_ptr self) { + try { + SpeechSynthesisStream^ stream = stream_task.get(); + stream_size = stream->Size; + reader = winrt::Windows::Storage::Streams::DataReader(stream); + this->chunked_read(self); + return; + } catch(winrt::hresult_error const& ex) { + error_msg += L"Could not synthesize speech from text: "; + error_msg += ex.message().c_str(); + } + this->send_message_to_main_thread(true); + } + + void chunked_read(std::shared_ptr self) { + create_task(reader.LoadAsync(16 * 1024), task_continuation_context::use_current()).then( + [self](task s) { self->threaded_dispatch_chunk(s, self); }); + } + + void threaded_dispatch_chunk(task bytes_loaded, std::shared_ptr self) { + try { + unsigned int n = bytes_loaded.get(); + bytes_read += n; + fprintf(stderr, "11111111 %u\n", n); + if (n > 0) { + this->send_message_to_main_thread(); + } + if (bytes_read < stream_size) { + this->chunked_read(self); + return; + } + } catch(winrt::hresult_error const& ex) { + error_msg += L"Could not read data from synthesized speech stream: "; + error_msg += ex.message().c_str(); + } + this->send_message_to_main_thread(true); + } + + void run_loop(PyObject *callback) { + MSG msg; + while (true) { + BOOL ret = GetMessage(&msg, NULL, 0, 0); + if (ret == 0) { PyErr_SetString(PyExc_OSError, "WM_QUIT received"); return; } + if (ret == -1) { PyErr_SetFromWindowsErr(0); return; } + if (msg.message == WM_USER) { + if (!this->commit_chunks(callback)) { break; } + if (msg.lParam == 1) break; + } else { + DispatchMessage(&msg); + } + } + + if (error_msg.size() > 0) { + pyobject_raii err(PyUnicode_FromWideChar(error_msg.data(), -1)); + PyErr_Format(PyExc_OSError, "%V", err.ptr(), "Could not create error message unicode object"); + return; + } + this->commit_chunks(callback); + } + + bool commit_chunks(PyObject *callback) { + // Platform::Array ^a; + // while ((a = queue.pop()) != nullptr) { + // pyobject_raii ret(PyObject_CallFunction(callback, "y#", (const char*)a->Data, static_cast(a->Length))); + // if (!ret) return false; + // } + return true; + } +}; + static PyObject* Synthesizer_create_recording(Synthesizer *self, PyObject *args) { wchar_raii pytext; - if (!PyArg_ParseTuple(args, "O&", py_to_wchar_no_none, &pytext)) return NULL; - StringReference text(pytext.ptr()); - bool error_ocurred = false; - HRESULT hr = S_OK; - std::array error_msg; - DataReader ^reader = nullptr; - DWORD main_thread_id = GetCurrentThreadId(); - unsigned long long stream_size; - unsigned int bytes_read; - - create_task(self->synth->SynthesizeTextToStreamAsync(text.GetString()), task_continuation_context::use_current() - ).then([&reader, &stream_size](task stream_task) { - SpeechSynthesisStream^ stream = stream_task.get(); - stream_size = stream->Size; - reader = ref new DataReader(stream); - return reader->LoadAsync((unsigned int)stream_size); - }).then([main_thread_id, &bytes_read, &error_msg, &error_ocurred, &reader](task bytes_read_task) { - try { - bytes_read = bytes_read_task.get(); - } catch (Exception ^ex) { - std::swprintf(error_msg.data(), error_msg.size(), L"Could not synthesize speech from text: %ls", ex->Message->Data()); - error_ocurred = true; - } - send_done_message_to_thread(main_thread_id); - }); - - if (!pump_till_done()) return NULL; - - if (error_ocurred) { - pyobject_raii err(PyUnicode_FromWideChar(error_msg.data(), -1)); - PyErr_Format(PyExc_OSError, "%V", err.ptr(), "Could not create error message unicode object"); - return NULL; - } - auto data = ref new Platform::Array(bytes_read); - reader->ReadBytes(data); - return PyBytes_FromStringAndSize((const char*)data->Data, bytes_read); + PyObject *callback; + if (!PyArg_ParseTuple(args, "O&O", py_to_wchar_no_none, &pytext, &callback)) return NULL; + if (!PyCallable_Check(callback)) { PyErr_SetString(PyExc_TypeError, "callback must be callable"); return NULL; } + auto cr = std::make_shared(); + cr->record_plain_text(self->synth, pytext.ptr(), callback, cr); + if (PyErr_Occurred()) return NULL; + Py_RETURN_NONE; } +*/ static PyObject* -voice_as_dict(VoiceInformation ^voice) { +voice_as_dict(VoiceInformation const& voice) { const char *gender = ""; - switch (voice->Gender) { + switch (voice.Gender()) { case VoiceGender::Male: gender = "male"; break; case VoiceGender::Female: gender = "female"; break; } return Py_BuildValue("{su su su su ss}", - "display_name", voice->DisplayName? voice->DisplayName->Data() : NULL, - "description", voice->Description ? voice->Description->Data() : NULL, - "id", voice->Id ? voice->Id->Data(): NULL, - "language", voice->Language ? voice->Language->Data() : NULL, + "display_name", voice.DisplayName().c_str(), + "description", voice.Description().c_str(), + "id", voice.Id().c_str(), + "language", voice.Language().c_str(), "gender", gender ); } + static PyObject* all_voices(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTION - IVectorView^ voices = SpeechSynthesizer::AllVoices; - pyobject_raii ans(PyTuple_New(voices->Size)); + auto voices = SpeechSynthesizer::AllVoices(); + pyobject_raii ans(PyTuple_New(voices.Size())); if (!ans) return NULL; Py_ssize_t i = 0; - for(auto voice : voices) { - PyObject *v = voice_as_dict(voice); - if (v) { - PyTuple_SET_ITEM(ans.ptr(), i++, v); - } else { - return NULL; + try { + for(auto const& voice : voices) { + PyObject *v = voice_as_dict(voice); + if (v) { + PyTuple_SET_ITEM(ans.ptr(), i++, v); + } else { + return NULL; + } } + } catch(winrt::hresult_error const& ex) { + error_from_hresult(ex.to_abi(), "Failed to list all voices"); + return NULL; } return ans.detach(); } static PyObject* default_voice(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTION - return voice_as_dict(SpeechSynthesizer::DefaultVoice); + try { + return voice_as_dict(SpeechSynthesizer::DefaultVoice()); + } catch(winrt::hresult_error const& ex) { + error_from_hresult(ex.to_abi(), "Failed to list all voices"); + return NULL; + } } #define M(name, args) { #name, (PyCFunction)Synthesizer_##name, args, ""} static PyMethodDef Synthesizer_methods[] = { - M(create_recording, METH_VARARGS), + // M(create_recording, METH_VARARGS), {NULL, NULL, 0, NULL} }; #undef M From 27f206f11634fa07109877e574d5c77c043b6a3a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 13 Jan 2023 15:40:45 +0530 Subject: [PATCH 0114/2055] wchar_raii now gives us a wstring_view on C++17 --- setup/extensions.json | 2 +- src/calibre/utils/cpp_binding.h | 29 ++++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/setup/extensions.json b/setup/extensions.json index b27071bf5b..6eae64d0d3 100644 --- a/setup/extensions.json +++ b/setup/extensions.json @@ -190,7 +190,7 @@ "headers": "calibre/utils/cpp_binding.h calibre/utils/windows/common.h", "sources": "calibre/utils/windows/winspeech.cpp", "libraries": "WindowsApp", - "cflags": "/X /std:c++17 /bigobj /await /permissive- /WX /Zc:twoPhase-" + "cflags": "/X /std:c++17 /Zc:__cplusplus /bigobj /await /permissive- /WX /Zc:twoPhase-" }, { "name": "wpd", diff --git a/src/calibre/utils/cpp_binding.h b/src/calibre/utils/cpp_binding.h index 5de1dd81d0..c27332cb81 100644 --- a/src/calibre/utils/cpp_binding.h +++ b/src/calibre/utils/cpp_binding.h @@ -11,6 +11,9 @@ #define _UNICODE #include #include +#if __cplusplus >= 201703L +#include +#endif #define arraysz(x) (sizeof(x)/sizeof(x[0])) @@ -41,7 +44,23 @@ class generic_raii { explicit operator bool() const noexcept { return handle != null; } }; +#if __cplusplus >= 201703L +class wchar_raii : public generic_raii { + private: + Py_ssize_t sz; + public: + int from_unicode(PyObject *obj) { + wchar_t *buf = PyUnicode_AsWideCharString(obj, &sz); + if (!buf) return 0; + attach(buf); + return 1; + } + std::wstring_view as_view() const { return std::wstring_view(handle, sz); } +}; +#else typedef generic_raii wchar_raii; +#endif + static inline void python_object_destructor(void *p) { PyObject *x = reinterpret_cast(p); Py_XDECREF(x); } typedef generic_raii pyobject_raii; @@ -93,8 +112,12 @@ py_to_wchar_no_none(PyObject *obj, wchar_raii *output) { PyErr_SetString(PyExc_TypeError, "unicode object expected"); return 0; } +#if __cplusplus >= 201703L + return output->from_unicode(obj); +#else wchar_t *buf = PyUnicode_AsWideCharString(obj, NULL); - if (!buf) { PyErr_NoMemory(); return 0; } - output->attach(buf); - return 1; + if (!buf) { return 0; } + output->attach(buf); + return 1; +#endif } From a7f713f68f578974b29f429f504ce969c8d64f0b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 13 Jan 2023 16:40:20 +0530 Subject: [PATCH 0115/2055] Much nicer implementation of create_recording using winrt facilities --- src/calibre/utils/windows/winspeech.cpp | 199 ++++++++---------------- 1 file changed, 65 insertions(+), 134 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 3ffe5a15d8..3e8a881ab0 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -19,6 +19,16 @@ using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::Media::SpeechSynthesis; using namespace winrt::Windows::Storage::Streams; +static PyObject* +runtime_error_as_python_error(PyObject *exc_type, winrt::hresult_error const &ex, const char *file, const int line, const char *prefix="", PyObject *name=NULL) { + pyobject_raii msg(PyUnicode_FromWideChar(ex.message().c_str(), -1)); + const HRESULT hr = ex.to_abi(); + if (name) PyErr_Format(exc_type, "%s:%d:%s:[hr=0x%x] %V: %S", file, line, prefix, hr, msg.ptr(), "Out of memory", name); + else PyErr_Format(exc_type, "%s:%d:%s:[hr=0x%x] %V", file, line, prefix, hr, msg.ptr(), "Out of memory"); + return NULL; +} +#define set_python_error_from_runtime(ex, ...) runtime_error_as_python_error(PyExc_OSError, ex, __FILE__, __LINE__, __VA_ARGS__) + struct Synthesizer { PyObject_HEAD @@ -52,152 +62,75 @@ ensure_current_thread_has_message_queue(void) { PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); } -/* -class CreateRecording { -private: - DWORD main_thread_id; - std::wstring error_msg; - winrt::Windows::Storage::Streams::DataReader reader{nullptr}; - unsigned long long stream_size, bytes_read; - -public: - CreateRecording() : main_thread_id(0), error_msg(), reader(nullptr), stream_size(0), bytes_read(0) { - main_thread_id = GetCurrentThreadId(); - ensure_current_thread_has_message_queue(); - } - CreateRecording& operator = (const CreateRecording &) = delete; - CreateRecording(const CreateRecording&) = delete; - - void record_plain_text(SpeechSynthesizer ^synth, const wchar_t* text, PyObject *callback, std::shared_ptr self) { - StringReference rtext(text); - create_task(synth->SynthesizeTextToStreamAsync(rtext.GetString()), task_continuation_context::use_current()).then( - [self](task s) { self->threaded_save_stream(s, self); }); - this->run_loop(callback); - reader = winrt::Windows::Storage::Streams::DataReader{nullptr}; - } - - void record_ssml(SpeechSynthesizer ^synth, const wchar_t* text, PyObject *callback, std::shared_ptr self) { - StringReference rtext(text); - create_task(synth->SynthesizeSsmlToStreamAsync(rtext.GetString()), task_continuation_context::use_current()).then( - [self](task s) { self->threaded_save_stream(s, self); }); - this->run_loop(callback); - reader = winrt::Windows::Storage::Streams::DataReader{nullptr}; - } - -private: - - void send_message_to_main_thread(bool done = false) const { - PostThreadMessageA(main_thread_id, WM_USER, 0, done ? 1 : 0); - } - - void threaded_save_stream(task stream_task, std::shared_ptr self) { - try { - SpeechSynthesisStream^ stream = stream_task.get(); - stream_size = stream->Size; - reader = winrt::Windows::Storage::Streams::DataReader(stream); - this->chunked_read(self); - return; - } catch(winrt::hresult_error const& ex) { - error_msg += L"Could not synthesize speech from text: "; - error_msg += ex.message().c_str(); - } - this->send_message_to_main_thread(true); - } - - void chunked_read(std::shared_ptr self) { - create_task(reader.LoadAsync(16 * 1024), task_continuation_context::use_current()).then( - [self](task s) { self->threaded_dispatch_chunk(s, self); }); - } - - void threaded_dispatch_chunk(task bytes_loaded, std::shared_ptr self) { - try { - unsigned int n = bytes_loaded.get(); - bytes_read += n; - fprintf(stderr, "11111111 %u\n", n); - if (n > 0) { - this->send_message_to_main_thread(); - } - if (bytes_read < stream_size) { - this->chunked_read(self); - return; - } - } catch(winrt::hresult_error const& ex) { - error_msg += L"Could not read data from synthesized speech stream: "; - error_msg += ex.message().c_str(); - } - this->send_message_to_main_thread(true); - } - - void run_loop(PyObject *callback) { - MSG msg; - while (true) { - BOOL ret = GetMessage(&msg, NULL, 0, 0); - if (ret == 0) { PyErr_SetString(PyExc_OSError, "WM_QUIT received"); return; } - if (ret == -1) { PyErr_SetFromWindowsErr(0); return; } - if (msg.message == WM_USER) { - if (!this->commit_chunks(callback)) { break; } - if (msg.lParam == 1) break; - } else { - DispatchMessage(&msg); - } - } - - if (error_msg.size() > 0) { - pyobject_raii err(PyUnicode_FromWideChar(error_msg.data(), -1)); - PyErr_Format(PyExc_OSError, "%V", err.ptr(), "Could not create error message unicode object"); - return; - } - this->commit_chunks(callback); - } - - bool commit_chunks(PyObject *callback) { - // Platform::Array ^a; - // while ((a = queue.pop()) != nullptr) { - // pyobject_raii ret(PyObject_CallFunction(callback, "y#", (const char*)a->Data, static_cast(a->Length))); - // if (!ret) return false; - // } - return true; - } -}; - - static PyObject* Synthesizer_create_recording(Synthesizer *self, PyObject *args) { wchar_raii pytext; PyObject *callback; - if (!PyArg_ParseTuple(args, "O&O", py_to_wchar_no_none, &pytext, &callback)) return NULL; + int is_ssml = 0; + if (!PyArg_ParseTuple(args, "O&O|p", py_to_wchar_no_none, &pytext, &callback, &is_ssml)) return NULL; if (!PyCallable_Check(callback)) { PyErr_SetString(PyExc_TypeError, "callback must be callable"); return NULL; } - auto cr = std::make_shared(); - cr->record_plain_text(self->synth, pytext.ptr(), callback, cr); + + ensure_current_thread_has_message_queue(); + SpeechSynthesisStream stream{nullptr}; + try { + if (is_ssml) stream = self->synth.SynthesizeSsmlToStreamAsync(pytext.as_view()).get(); + else stream = self->synth.SynthesizeTextToStreamAsync(pytext.as_view()).get(); + } catch(winrt::hresult_error const& ex) { + return set_python_error_from_runtime(ex, "Failed to get SpeechSynthesisStream from text"); + } + unsigned long long stream_size = stream.Size(), bytes_read = 0; + DataReader reader(stream); + unsigned int n; + const static unsigned int chunk_size = 16 * 1024; + while (bytes_read < stream_size) { + try { + n = reader.LoadAsync(chunk_size).get(); + } catch(winrt::hresult_error const& ex) { + return set_python_error_from_runtime(ex, "Failed to load data from DataReader"); + } + if (n > 0) { + bytes_read += n; + pyobject_raii b(PyBytes_FromStringAndSize(NULL, n)); + if (!b) return NULL; + unsigned char *p = reinterpret_cast(PyBytes_AS_STRING(b.ptr())); + reader.ReadBytes(winrt::array_view(p, p + n)); + pyobject_raii ret(PyObject_CallFunctionObjArgs(callback, b.ptr(), NULL)); + } + } + if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } -*/ + static PyObject* voice_as_dict(VoiceInformation const& voice) { - const char *gender = ""; - switch (voice.Gender()) { - case VoiceGender::Male: gender = "male"; break; - case VoiceGender::Female: gender = "female"; break; + try { + const char *gender = ""; + switch (voice.Gender()) { + case VoiceGender::Male: gender = "male"; break; + case VoiceGender::Female: gender = "female"; break; + } + return Py_BuildValue("{su su su su ss}", + "display_name", voice.DisplayName().c_str(), + "description", voice.Description().c_str(), + "id", voice.Id().c_str(), + "language", voice.Language().c_str(), + "gender", gender + ); + } catch(winrt::hresult_error const& ex) { + return set_python_error_from_runtime(ex); } - return Py_BuildValue("{su su su su ss}", - "display_name", voice.DisplayName().c_str(), - "description", voice.Description().c_str(), - "id", voice.Id().c_str(), - "language", voice.Language().c_str(), - "gender", gender - ); } static PyObject* all_voices(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTION - auto voices = SpeechSynthesizer::AllVoices(); - pyobject_raii ans(PyTuple_New(voices.Size())); - if (!ans) return NULL; - Py_ssize_t i = 0; try { + auto voices = SpeechSynthesizer::AllVoices(); + pyobject_raii ans(PyTuple_New(voices.Size())); + if (!ans) return NULL; + Py_ssize_t i = 0; for(auto const& voice : voices) { PyObject *v = voice_as_dict(voice); if (v) { @@ -206,11 +139,10 @@ all_voices(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTION return NULL; } } + return ans.detach(); } catch(winrt::hresult_error const& ex) { - error_from_hresult(ex.to_abi(), "Failed to list all voices"); - return NULL; + return set_python_error_from_runtime(ex); } - return ans.detach(); } static PyObject* @@ -218,14 +150,13 @@ default_voice(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTI try { return voice_as_dict(SpeechSynthesizer::DefaultVoice()); } catch(winrt::hresult_error const& ex) { - error_from_hresult(ex.to_abi(), "Failed to list all voices"); - return NULL; + return set_python_error_from_runtime(ex); } } #define M(name, args) { #name, (PyCFunction)Synthesizer_##name, args, ""} static PyMethodDef Synthesizer_methods[] = { - // M(create_recording, METH_VARARGS), + M(create_recording, METH_VARARGS), {NULL, NULL, 0, NULL} }; #undef M From 092bafbe7e0e6ecd63ea505206d54530e61c1d2a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 14 Jan 2023 11:47:19 +0530 Subject: [PATCH 0116/2055] Spell check dialog: move down after correcting word, not up. Fixes #2002864 [Suggested spellcheck UI improvements](https://bugs.launchpad.net/calibre/+bug/2002864) --- src/calibre/gui2/tweak_book/spell.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/gui2/tweak_book/spell.py b/src/calibre/gui2/tweak_book/spell.py index 34f7bfaca7..56aca7efc4 100644 --- a/src/calibre/gui2/tweak_book/spell.py +++ b/src/calibre/gui2/tweak_book/spell.py @@ -1243,6 +1243,8 @@ class SpellCheck(Dialog): row = self.words_model.row_for_word(w) if row == -1: row = self.words_view.currentIndex().row() + if row < self.words_model.rowCount() - 1: + row += 1 if row > -1: self.words_view.highlight_row(row) From 1adeef4743526455a581144d78f8d2d6181fede6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 14 Jan 2023 11:57:59 +0530 Subject: [PATCH 0117/2055] Conversion dialog: Regex builder: Workaround bug in Qt that prevented searching for non breaking spaces in the wizard used to test search expressions --- src/calibre/gui2/convert/regex_builder.py | 8 ++++---- src/calibre/gui2/convert/regex_builder.ui | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/convert/regex_builder.py b/src/calibre/gui2/convert/regex_builder.py index 8b7ac30296..bdf8136b35 100644 --- a/src/calibre/gui2/convert/regex_builder.py +++ b/src/calibre/gui2/convert/regex_builder.py @@ -4,8 +4,7 @@ import os from contextlib import suppress from qt.core import ( - QBrush, QDialog, QDialogButtonBox, Qt, QTextCursor, - QTextEdit, pyqtSignal + QBrush, QDialog, QDialogButtonBox, Qt, QTextCursor, QTextEdit, pyqtSignal, ) from calibre.constants import iswindows @@ -14,6 +13,7 @@ from calibre.gui2 import choose_files, error_dialog, gprefs from calibre.gui2.convert.regex_builder_ui import Ui_RegexBuilder from calibre.gui2.convert.xpath_wizard import XPathEdit from calibre.gui2.dialogs.choose_format import ChooseFormatDialog +from calibre.gui2.widgets2 import to_plain_text from calibre.ptempfile import TemporaryFile from calibre.utils.icu import utf16_length from calibre.utils.ipc.simple_worker import WorkerError, fork_job @@ -84,7 +84,7 @@ class RegexBuilder(QDialog, Ui_RegexBuilder): qt: int = 0 if self.regex_valid(): - text = str(self.preview.toPlainText()) + text = to_plain_text(self.preview) regex = str(self.regex.text()) cursor = QTextCursor(self.preview.document()) extsel = QTextEdit.ExtraSelection() @@ -205,7 +205,7 @@ class RegexBuilder(QDialog, Ui_RegexBuilder): self.open_book(files[0]) def doc(self): - return str(self.preview.toPlainText()) + return to_plain_text(self.preview) class RegexEdit(XPathEdit): diff --git a/src/calibre/gui2/convert/regex_builder.ui b/src/calibre/gui2/convert/regex_builder.ui index 0441e40357..8d31c928d4 100644 --- a/src/calibre/gui2/convert/regex_builder.ui +++ b/src/calibre/gui2/convert/regex_builder.ui @@ -11,7 +11,7 @@ - Regex Builder + Regex builder From 6f107061fcc2defc2a8be82bed3e1bb81591753e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 14 Jan 2023 12:18:02 +0530 Subject: [PATCH 0118/2055] Spell check dialog: Allow up and down arrow keys to work regardless of focus --- src/calibre/gui2/tweak_book/spell.py | 31 ++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/tweak_book/spell.py b/src/calibre/gui2/tweak_book/spell.py index 56aca7efc4..b01fc40360 100644 --- a/src/calibre/gui2/tweak_book/spell.py +++ b/src/calibre/gui2/tweak_book/spell.py @@ -10,12 +10,12 @@ from collections import OrderedDict, defaultdict from functools import partial from itertools import chain from qt.core import ( - QT_VERSION_STR, QAbstractItemView, QAbstractTableModel, QApplication, QCheckBox, - QComboBox, QDialog, QDialogButtonBox, QFont, QFormLayout, QGridLayout, QHBoxLayout, - QIcon, QInputDialog, QKeySequence, QLabel, QLineEdit, QListWidget, QListWidgetItem, - QMenu, QModelIndex, QPlainTextEdit, QPushButton, QSize, QStackedLayout, Qt, - QTableView, QTimer, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, - pyqtSignal, + QT_VERSION_STR, QAbstractItemView, QAbstractTableModel, QAction, QApplication, + QCheckBox, QComboBox, QDialog, QDialogButtonBox, QFont, QFormLayout, QGridLayout, + QHBoxLayout, QIcon, QInputDialog, QKeySequence, QLabel, QLineEdit, QListWidget, + QListWidgetItem, QMenu, QModelIndex, QPlainTextEdit, QPushButton, QSize, + QStackedLayout, Qt, QTableView, QTimer, QToolButton, QTreeWidget, QTreeWidgetItem, + QVBoxLayout, QWidget, pyqtSignal, ) from threading import Thread @@ -856,6 +856,17 @@ class WordsView(QTableView): self.setTabKeyNavigation(False) self.verticalHeader().close() + def change_current_word_by(self, delta=1): + row = self.currentIndex().row() + row = (row + delta + self.model().rowCount()) % self.model().rowCount() + self.highlight_row(row) + + def next_word(self): + self.change_current_word_by(1) + + def previous_word(self): + self.change_current_word_by(-1) + def keyPressEvent(self, ev): if ev == QKeySequence.StandardKey.Copy: self.copy_to_clipboard() @@ -1099,6 +1110,14 @@ class SpellCheck(Dialog): self.hb = h = FlowLayout() self.summary = s = QLabel('') self.main.l.addLayout(h), h.addWidget(s), h.addWidget(om), h.addWidget(cs), h.addWidget(cs2) + self.action_next_word = a = QAction(self) + a.setShortcut(QKeySequence(Qt.Key.Key_Down)) + a.triggered.connect(self.words_view.next_word) + self.addAction(a) + self.action_previous_word = a = QAction(self) + a.triggered.connect(self.words_view.previous_word) + a.setShortcut(QKeySequence(Qt.Key.Key_Up)) + self.addAction(a) def keyPressEvent(self, ev): if ev.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return): From 0097a59a5c95ad1d970f364022e193dd3658c4d7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 14 Jan 2023 12:38:34 +0530 Subject: [PATCH 0119/2055] Up/down should work on suggested word list when it has focus --- src/calibre/gui2/tweak_book/spell.py | 29 +++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/tweak_book/spell.py b/src/calibre/gui2/tweak_book/spell.py index b01fc40360..766791f835 100644 --- a/src/calibre/gui2/tweak_book/spell.py +++ b/src/calibre/gui2/tweak_book/spell.py @@ -961,6 +961,17 @@ class ManageExcludedFiles(Dialog): return {item.text() for item in self.files.selectedItems()} +class SuggestedList(QListWidget): + + def next_word(self): + row = (self.currentRow() + 1) % self.count() + self.setCurrentRow(row) + + def previous_word(self): + row = (self.currentRow() - 1 + self.count()) % self.count() + self.setCurrentRow(row) + + class SpellCheck(Dialog): work_finished = pyqtSignal(object, object, object) @@ -1089,7 +1100,7 @@ class SpellCheck(Dialog): sw.setPlaceholderText(_('The replacement word')) sw.returnPressed.connect(self.change_word) l.addWidget(sw) - self.suggested_list = sl = QListWidget(self) + self.suggested_list = sl = SuggestedList(self) sl.currentItemChanged.connect(self.current_suggestion_changed) sl.itemActivated.connect(self.change_word) set_no_activate_on_click(sl) @@ -1112,13 +1123,25 @@ class SpellCheck(Dialog): self.main.l.addLayout(h), h.addWidget(s), h.addWidget(om), h.addWidget(cs), h.addWidget(cs2) self.action_next_word = a = QAction(self) a.setShortcut(QKeySequence(Qt.Key.Key_Down)) - a.triggered.connect(self.words_view.next_word) + a.triggered.connect(self.next_word) self.addAction(a) self.action_previous_word = a = QAction(self) - a.triggered.connect(self.words_view.previous_word) + a.triggered.connect(self.previous_word) a.setShortcut(QKeySequence(Qt.Key.Key_Up)) self.addAction(a) + def next_word(self): + if self.focusWidget() is self.suggested_list: + self.suggested_list.next_word() + else: + self.words_view.next_word() + + def previous_word(self): + if self.focusWidget() is self.suggested_list: + self.suggested_list.previous_word() + else: + self.words_view.next_word() + def keyPressEvent(self, ev): if ev.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return): ev.accept() From e5ab35f1b49698fb67dad6e2003ae7631b024dc0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 14 Jan 2023 12:39:37 +0530 Subject: [PATCH 0120/2055] ... --- src/calibre/gui2/tweak_book/spell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/tweak_book/spell.py b/src/calibre/gui2/tweak_book/spell.py index 766791f835..a373afb1bf 100644 --- a/src/calibre/gui2/tweak_book/spell.py +++ b/src/calibre/gui2/tweak_book/spell.py @@ -1140,7 +1140,7 @@ class SpellCheck(Dialog): if self.focusWidget() is self.suggested_list: self.suggested_list.previous_word() else: - self.words_view.next_word() + self.words_view.previous_word() def keyPressEvent(self, ev): if ev.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return): From f1fd9bebde720e79958e13eb3d58cb5185e4705a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 14 Jan 2023 12:44:07 +0530 Subject: [PATCH 0121/2055] DRYer --- src/calibre/gui2/tweak_book/spell.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/calibre/gui2/tweak_book/spell.py b/src/calibre/gui2/tweak_book/spell.py index a373afb1bf..61a5e44588 100644 --- a/src/calibre/gui2/tweak_book/spell.py +++ b/src/calibre/gui2/tweak_book/spell.py @@ -1131,16 +1131,12 @@ class SpellCheck(Dialog): self.addAction(a) def next_word(self): - if self.focusWidget() is self.suggested_list: - self.suggested_list.next_word() - else: - self.words_view.next_word() + v = self.suggested_list if self.focusWidget() is self.suggested_list else self.words_view + v.next_word() def previous_word(self): - if self.focusWidget() is self.suggested_list: - self.suggested_list.previous_word() - else: - self.words_view.previous_word() + v = self.suggested_list if self.focusWidget() is self.suggested_list else self.words_view + v.previous_word() def keyPressEvent(self, ev): if ev.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return): From 6aadba36a2852c49300f241b44246b8c34b05027 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 14 Jan 2023 13:16:32 +0530 Subject: [PATCH 0122/2055] Add dedicated shortcuts for change/show next occurrence --- src/calibre/gui2/tweak_book/spell.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/calibre/gui2/tweak_book/spell.py b/src/calibre/gui2/tweak_book/spell.py index 61a5e44588..695f9a5794 100644 --- a/src/calibre/gui2/tweak_book/spell.py +++ b/src/calibre/gui2/tweak_book/spell.py @@ -1130,6 +1130,17 @@ class SpellCheck(Dialog): a.setShortcut(QKeySequence(Qt.Key.Key_Up)) self.addAction(a) + def button_action(sc, tt, button): + a = QAction(self) + self.addAction(a) + a.setShortcut(QKeySequence(sc, QKeySequence.SequenceFormat.PortableText)) + button.setToolTip(tt + f' [{a.shortcut().toString(QKeySequence.SequenceFormat.NativeText)}]') + a.triggered.connect(button.click) + return a + + self.action_change_word = button_action('ctrl+right', _('Change all occurrences of this word'), self.change_button) + self.action_show_next_occurrence = button_action('alt+right', _('Show next occurrence of this word in the book'), self.next_occurrence) + def next_word(self): v = self.suggested_list if self.focusWidget() is self.suggested_list else self.words_view v.next_word() From 3b1cc8156272eff1cb66a052b04e107ddffb31e9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Jan 2023 11:35:49 +0530 Subject: [PATCH 0123/2055] Install instructions for Windows SDK --- bypy/windows.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bypy/windows.conf b/bypy/windows.conf index 3134411853..9fd759e9fa 100644 --- a/bypy/windows.conf +++ b/bypy/windows.conf @@ -1,4 +1,7 @@ # Requires installation of Visual Studio 2019 Community Edition, WiX Toolset, Git, Ruby, NodeJS, Python (2 and 3) and Perl +# Windows SDK >= 10.0.20348.0 which can be installed by downloading the ISO from +# https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/ and running +# ./WinSDKSetup.exe /features "OptionId.UWPCpp" "OptionId.DesktopCPPx64" "OptionId.DesktopCPPx86" "OptionID.DesktopCPPARM" "OptionID.DesktopCPPARM64" /q # git.exe must be in PATH. Must have ~120GB available disk space and 8GB RAM # Install certifi in python 3 with: # py.exe -m pip install certifi From c54a31ca5f549dce082c71236e79fc252aadbc1f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Jan 2023 11:44:33 +0530 Subject: [PATCH 0124/2055] Observer Reach Foundation by unkn0wn --- recipes/observer_reach_foundation.recipe | 60 ++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 recipes/observer_reach_foundation.recipe diff --git a/recipes/observer_reach_foundation.recipe b/recipes/observer_reach_foundation.recipe new file mode 100644 index 0000000000..c1b88a6ea6 --- /dev/null +++ b/recipes/observer_reach_foundation.recipe @@ -0,0 +1,60 @@ +from calibre.web.feeds.news import BasicNewsRecipe, classes + +class ORF(BasicNewsRecipe): + title = u'Observer Research Foundation' + description = ( + 'Set up in 1990, ORF seeks to lead and aid policy thinking towards building a strong and prosperous India' + ' in a fair and equitable world. It helps discover and inform India’s choices, and carries Indian voices ' + 'and ideas to forums shaping global debates. ' + ) + language = 'en_IN' + __author__ = 'unkn0wn' + oldest_article = 7.5 # days + max_articles_per_feed = 25 + encoding = 'utf-8' + masthead_url = 'https://www.orfonline.org/wp-content/uploads/2015/09/Logo_ORF_JPEG.jpg' + remove_attributes = ['style', 'height', 'width'] + ignore_duplicate_articles = {'url'} + + extra_css = ''' + .report-slider {font-size:small; color:#404040;} + .report {font-size:small; font-weight:bold;} + .excert-italic, .recent-block-people {font-style:italic; color:#202020;} + blockquote, em {color:#202020;} + ''' + + def get_browser(self): + return BasicNewsRecipe.get_browser(self, user_agent='common_words/based') + + resolve_internal_links = True + remove_empty_feeds = True + + keep_only_tags = [classes('recent-updates-block recent-block-people')] + remove_tags = [ + classes( + 'social socialshare comment-area-section telegramhtml post-tag ' + 'research-prev research-next' + ) + ] + + feeds = [ + ('Commentaries', 'https://www.orfonline.org/content-type/commentary/feed/'), + ('Expert Speak', 'https://www.orfonline.org/expert-speak/feed/'), + ('Books and Monographs', 'https://www.orfonline.org/content-type/books/feed/'), + ('Event Reports', 'https://www.orfonline.org/content-type/event-reports/feed/'), + ('Events', 'https://www.orfonline.org/content-type/events/feed/'), + ('Forums', 'https://www.orfonline.org/content-type/forums/feed/'), + ('GP-ORF Series', 'https://www.orfonline.org/content-type/gp-orf-series/feed/'), + ('Issue Briefs & Special Reports', 'https://www.orfonline.org/content-type/issue-brief/feed/'), + ('Monitors', 'https://www.orfonline.org/content-type/monitors/feed/'), + ('Occasional Papers', 'https://www.orfonline.org/content-type/occasional-paper/feed/'), + ('Primer', 'https://www.orfonline.org/content-type/primer/feed/'), + ('Series', 'https://www.orfonline.org/content-type/series/feed/'), + ('Surveys & Polls', 'https://www.orfonline.org/content-type/surveys-polls/feed/'), + ('Young Voices', 'https://www.orfonline.org/content-type/young-voices/feed/'), + ] + + def print_version(self, url): + if 'marathi' in url or 'hindi' in url or 'bangla' in url: + return '' + return url From 11672c4a717c5b73112bde6bb84b3546c7b52507 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Jan 2023 11:45:54 +0530 Subject: [PATCH 0125/2055] Reduce image size in bloomberg recipes --- recipes/bloomberg-business-week.recipe | 2 ++ recipes/bloomberg.recipe | 2 ++ 2 files changed, 4 insertions(+) diff --git a/recipes/bloomberg-business-week.recipe b/recipes/bloomberg-business-week.recipe index 4374c75969..763cc0185a 100644 --- a/recipes/bloomberg-business-week.recipe +++ b/recipes/bloomberg-business-week.recipe @@ -124,4 +124,6 @@ class Bloomberg(BasicNewsRecipe): img['src'] = img['data-native-src'] else: img['src'] = '' + for img in soup.findAll('img', attrs={'src':lambda x: x and x.endswith(('-1x-1.jpg', '-1x-1.png'))}): + img['src'] = img['src'].replace('-1x-1', '750x-1') return soup diff --git a/recipes/bloomberg.recipe b/recipes/bloomberg.recipe index 28210d9baf..75f0812f09 100644 --- a/recipes/bloomberg.recipe +++ b/recipes/bloomberg.recipe @@ -102,4 +102,6 @@ class Bloomberg(BasicNewsRecipe): img['src'] = img['data-native-src'] else: img['src'] = '' + for img in soup.findAll('img', attrs={'src':lambda x: x and x.endswith(('-1x-1.jpg', '-1x-1.png'))}): + img['src'] = img['src'].replace('-1x-1', '750x-1') return soup From 37f042affc80f7a3634a616ae74e0d70cc28a26f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Jan 2023 13:18:57 +0530 Subject: [PATCH 0126/2055] ... --- bypy/windows.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bypy/windows.conf b/bypy/windows.conf index 9fd759e9fa..1527975d50 100644 --- a/bypy/windows.conf +++ b/bypy/windows.conf @@ -2,7 +2,7 @@ # Windows SDK >= 10.0.20348.0 which can be installed by downloading the ISO from # https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/ and running # ./WinSDKSetup.exe /features "OptionId.UWPCpp" "OptionId.DesktopCPPx64" "OptionId.DesktopCPPx86" "OptionID.DesktopCPPARM" "OptionID.DesktopCPPARM64" /q -# git.exe must be in PATH. Must have ~120GB available disk space and 8GB RAM +# git.exe must be in PATH. Must have ~120GB available disk space and 24GB RAM (with 4 threads) to build Qt WebEngine # Install certifi in python 3 with: # py.exe -m pip install certifi # Note that python2 is needed to build Qt WebEngine From c56109c1f31c240ff635b3f96cfccf0bbbb655fc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 16 Jan 2023 09:08:57 +0530 Subject: [PATCH 0127/2055] Book list: Fix a regression in the previous release that broke dragging to select multiple books --- src/calibre/gui2/library/views.py | 16 +++++++++++++--- src/calibre/utils/windows/winspeech.cpp | 2 ++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 00742c8951..e3b4b5dad6 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -1177,6 +1177,14 @@ class BooksView(QTableView): # {{{ def handle_mouse_press_event(self, ev): m = ev.modifiers() + b = ev.button() + if ( + m == Qt.KeyboardModifier.NoModifier and b == Qt.MouseButton.LeftButton and + self.editTriggers() & QAbstractItemView.EditTrigger.SelectedClicked + ): + index = self.indexAt(ev.pos()) + self.last_mouse_press_on_row = index.row() + if m & Qt.KeyboardModifier.ShiftModifier: # Shift-Click in QTableView is badly behaved. index = self.indexAt(ev.pos()) @@ -1240,9 +1248,11 @@ class BooksView(QTableView): # {{{ # edit triggers contain SelectedClicked and the clicked row is # already selected, so do it ourselves index = self.indexAt(ev.pos()) - sm = self.selectionModel() - if index.isValid() and sm.isSelected(index): - self.select_rows((index,), using_ids=False, change_current=False, scroll=False) + last_press, self.last_mouse_press_on_row = getattr(self, 'last_mouse_press_on_row', -111), -112 + if index.row() == last_press: + sm = self.selectionModel() + if index.isValid() and sm.isSelected(index): + self.select_rows((index,), using_ids=False, change_current=False, scroll=False) QTableView.mouseReleaseEvent(self, ev) @property diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 3e8a881ab0..b8aab7d9a4 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -13,10 +13,12 @@ #include #include #include +#include using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::Media::SpeechSynthesis; +using namespace winrt::Windows::Media::Playback; using namespace winrt::Windows::Storage::Streams; static PyObject* From 34cb9ccffce0f8f7aa25a927a3e10fca664d09f3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 16 Jan 2023 22:17:00 +0530 Subject: [PATCH 0128/2055] Change the macOS VM to a Big Sur based one since Qt WebEngine 6.4 needs XCode 13 which needs Big Sur --- bypy/macos.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bypy/macos.conf b/bypy/macos.conf index 7f278e1bb4..50ffe7740f 100644 --- a/bypy/macos.conf +++ b/bypy/macos.conf @@ -1,7 +1,7 @@ # Requires installation of XCode 10.3 and Python 3 and # python3 -m pip install certifi html5lib -vm_name 'macos-calibre-qt6' +vm_name 'macos-calibre' root '/Users/Shared/calibre-build' python '/usr/local/bin/python3' universal 'true' From 7093e848a9fc2731271140979aea4737fe86d2d0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 17 Jan 2023 22:14:51 +0530 Subject: [PATCH 0129/2055] More work on the new speech api bindings --- src/calibre/utils/windows/winspeech.cpp | 100 ++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 7 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index b8aab7d9a4..a2d1628d1d 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -9,17 +9,23 @@ #include #include #include +#include +#include +#include #include #include #include #include +#include #include using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::Media::SpeechSynthesis; using namespace winrt::Windows::Media::Playback; +using namespace winrt::Windows::Media::Core; using namespace winrt::Windows::Storage::Streams; +typedef unsigned long long id_type; static PyObject* runtime_error_as_python_error(PyObject *exc_type, winrt::hresult_error const &ex, const char *file, const int line, const char *prefix="", PyObject *name=NULL) { @@ -31,31 +37,88 @@ runtime_error_as_python_error(PyObject *exc_type, winrt::hresult_error const &ex } #define set_python_error_from_runtime(ex, ...) runtime_error_as_python_error(PyExc_OSError, ex, __FILE__, __LINE__, __VA_ARGS__) +template +class WeakRefs { + private: + std::mutex weak_ref_lock; + std::unordered_map refs; + id_type counter; + public: + void register_ref(T *self) { + std::scoped_lock lock(weak_ref_lock); + self->id = ++counter; + refs[self->id] = self; + } + void unregister_ref(T *self, std::function dealloc) { + std::scoped_lock lock(weak_ref_lock); + dealloc(self); + refs.erase(self->id); + self->id = 0; + } + void use_ref(id_type id, DWORD creation_thread_id, std::function callback) { + if (GetCurrentThreadId() == creation_thread_id) { + try { + callback(at(id)); + } catch (std::out_of_range) { + callback(NULL); + } + } + else { + std::scoped_lock lock(weak_ref_lock); + try { + callback(at(id)); + } catch (std::out_of_range) { + callback(NULL); + } + } + } +}; struct Synthesizer { PyObject_HEAD + id_type id; + DWORD creation_thread_id; SpeechSynthesizer synth{nullptr}; + MediaPlayer player{nullptr}; }; - static PyTypeObject SynthesizerType = { PyVarObject_HEAD_INIT(NULL, 0) }; -static PyObject * +static WeakRefs synthesizer_weakrefs; + + +static PyObject* Synthesizer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { INITIALIZE_COM_IN_FUNCTION Synthesizer *self = (Synthesizer *) type->tp_alloc(type, 0); if (self) { - self->synth = SpeechSynthesizer(); + try { + self->synth = SpeechSynthesizer(); + self->player = MediaPlayer(); + self->player.AudioCategory(MediaPlayerAudioCategory::Speech); + } catch(winrt::hresult_error const& ex) { + set_python_error_from_runtime(ex, "Failed to get SpeechSynthesisStream from text"); + Py_CLEAR(self); + } + } + if (PyErr_Occurred()) { Py_CLEAR(self); } + if (self) { + self->creation_thread_id = GetCurrentThreadId(); + synthesizer_weakrefs.register_ref(self); + com.detach(); } - if (self && !PyErr_Occurred()) com.detach(); return (PyObject*)self; } static void -Synthesizer_dealloc(Synthesizer *self) { - self->synth = SpeechSynthesizer{nullptr}; - CoUninitialize(); +Synthesizer_dealloc(Synthesizer *self_) { + synthesizer_weakrefs.unregister_ref(self_, [](Synthesizer *self) { + self->synth = SpeechSynthesizer{nullptr}; + self->player = MediaPlayer{nullptr}; + Py_TYPE(self)->tp_free((PyObject*)self); + CoUninitialize(); + }); } static void @@ -64,6 +127,28 @@ ensure_current_thread_has_message_queue(void) { PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); } +static PyObject* +Synthesizer_speak(Synthesizer *self, PyObject *args) { + wchar_raii pytext; + PyObject *callback; + int is_ssml = 0; + if (!PyArg_ParseTuple(args, "O&O|p", py_to_wchar_no_none, &pytext, &callback, &is_ssml)) return NULL; + if (!PyCallable_Check(callback)) { PyErr_SetString(PyExc_TypeError, "callback must be callable"); return NULL; } + ensure_current_thread_has_message_queue(); + SpeechSynthesisStream stream{nullptr}; + try { + if (is_ssml) stream = self->synth.SynthesizeSsmlToStreamAsync(pytext.as_view()).get(); + else stream = self->synth.SynthesizeTextToStreamAsync(pytext.as_view()).get(); + } catch (winrt::hresult_error const& ex) { + return set_python_error_from_runtime(ex, "Failed to get SpeechSynthesisStream from text"); + } + MediaSource source = winrt::Windows::Media::Core::MediaSource::CreateFromStream(stream, stream.ContentType()); + self->player.Source(source); + self->player.Play(); + Py_RETURN_NONE; +} + + static PyObject* Synthesizer_create_recording(Synthesizer *self, PyObject *args) { wchar_raii pytext; @@ -159,6 +244,7 @@ default_voice(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTI #define M(name, args) { #name, (PyCFunction)Synthesizer_##name, args, ""} static PyMethodDef Synthesizer_methods[] = { M(create_recording, METH_VARARGS), + M(speak, METH_VARARGS), {NULL, NULL, 0, NULL} }; #undef M From 21994332d606a97d01c15b41c4ca8f1a6f9f2b3b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 18 Jan 2023 07:40:39 +0530 Subject: [PATCH 0130/2055] ... --- manual/template_lang.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/template_lang.rst b/manual/template_lang.rst index 028a75fd29..a7f325b65a 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -610,7 +610,7 @@ In `GPM` the functions described in `Single Function Mode` all require an additi * ``strlen(value)`` -- Returns the length of the string ``value``. * ``substr(str, start, end)`` -- returns the ``start``'th through the ``end``'th characters of ``str``. The first character in ``str`` is the zero'th character. If ``end`` is negative, then it indicates that many characters counting from the right. If ``end`` is zero, then it indicates the last character. For example, ``substr('12345', 1, 0)`` returns ``'2345'``, and ``substr('12345', 1, -1)`` returns ``'234'``. * ``subtract(x, y)`` -- returns ``x - y``. Throws an exception if either ``x`` or ``y`` are not numbers. This function can usually be replaced by the ``-`` operator. -* ``switch_if([test_expression, value_expression,]+ else_expression)`` -- for each ``test_expression, value_expression`` pair, checks if ``test_expression`` is True (non-empty) and if so returns the result of ``value_expression``. If no ``test_expression`` is True then the result of ``else_expression` is returned. You can have as many ``test_expression, value_expressio`` pairs as you want. +* ``switch_if([test_expression, value_expression,]+ else_expression)`` -- for each ``test_expression, value_expression`` pair, checks if ``test_expression`` is True (non-empty) and if so returns the result of ``value_expression``. If no ``test_expression`` is True then the result of ``else_expression` is returned. You can have as many ``test_expression, value_expression`` pairs as you want. * ``today()`` -- return a date+time string for today (now). This value is designed for use in `format_date` or `days_between`, but can be manipulated like any other string. The date is in `ISO `_ date/time format. * ``template(x)`` -- evaluates ``x`` as a template. The evaluation is done in its own context, meaning that variables are not shared between the caller and the template evaluation. * ``to_hex(val)`` -- returns the string ``val`` encoded in hex. This is useful when constructing calibre URLs. From 09a5a1cb1b023fd4bfd870dd965a01a5609ba2e9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 18 Jan 2023 08:14:47 +0530 Subject: [PATCH 0131/2055] Fix detection of identifier URL when template contains regex special chars --- src/calibre/gui2/metadata/basic_widgets.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 8e83ee72ad..10a616d3e8 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -1714,12 +1714,13 @@ class IdentifiersEdit(QLineEdit, ToMetadataMixin): rules = msprefs['id_link_rules'] if rules: formatter = EvalFormatter() - vals = {'id' : '(?P.+)'} + vals = {'id' : '__ID_REGEX_PLACEHOLDER__'} for key in rules.keys(): rule = rules[key] for name, template in rule: try: url_pattern = formatter.safe_format(template, vals, '', vals) + url_pattern = re.escape(url_pattern).replace('__ID_REGEX_PLACEHOLDER__', '(?P.+)') if url_pattern.startswith('http:') or url_pattern.startswith('https:'): url_pattern = '(?:http|https):' + url_pattern.partition(':')[2] new_id = re.compile(url_pattern) @@ -1731,7 +1732,7 @@ class IdentifiersEdit(QLineEdit, ToMetadataMixin): return True except Exception: import traceback - traceback.format_exc() + traceback.print_exc() continue from calibre.customize.ui import all_metadata_plugins From d4ad11687cd80fd23fe4d132b9f323561314a14d Mon Sep 17 00:00:00 2001 From: Jonatan <84130654+reportxx@users.noreply.github.com> Date: Wed, 18 Jan 2023 08:22:58 +0000 Subject: [PATCH 0132/2055] Update copyright year --- COPYRIGHT | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/COPYRIGHT b/COPYRIGHT index beea784254..3425d82a02 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,5 +1,5 @@ Files: * -Copyright: Copyright (C) 2008-2022 Kovid Goyal +Copyright: Copyright (C) 2008-2023 Kovid Goyal License: GPL-3 The full text of the GPL is distributed as in /usr/share/common-licenses/GPL-3 on Debian systems. From bd0b511b8194053b98e1c5292a9f1c15dd5242c6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 18 Jan 2023 20:41:22 +0530 Subject: [PATCH 0133/2055] More robust output to stderr for rcc --- src/calibre/utils/rcc/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/calibre/utils/rcc/__init__.py b/src/calibre/utils/rcc/__init__.py index 94ac2cf066..e3253eac25 100644 --- a/src/calibre/utils/rcc/__init__.py +++ b/src/calibre/utils/rcc/__init__.py @@ -2,7 +2,7 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2022, Kovid Goyal -import os +import os, io import sys import tempfile from posixpath import normpath @@ -14,7 +14,12 @@ from calibre_extensions import rcc_backend def compile_qrc(output_path, *qrc_file_paths): rcc = rcc_backend.RCCResourceLibrary() err_device = QFile() - if not err_device.open(sys.stderr.fileno(), QIODevice.OpenModeFlag.WriteOnly | QIODevice.OpenModeFlag.Text): + + try: + fd = sys.__stderr__.fileno() + except io.UnsupportedOperation: + fd = 2 + if not err_device.open(fd, QIODevice.OpenModeFlag.WriteOnly | QIODevice.OpenModeFlag.Text): raise ValueError('Failed to open STDERR for writing') if not qrc_file_paths: raise TypeError('Must specify at least one .qrc file') From b67c5a21040f658ca7995344822042a4ec625d20 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 18 Jan 2023 21:58:57 +0530 Subject: [PATCH 0134/2055] Add by ISBN: Allow adding using identifiers other than ISBN as well. Fixes #2003227 [enhancement request regarding add from isbn](https://bugs.launchpad.net/calibre/+bug/2003227) --- src/calibre/gui2/actions/add.py | 27 +++++++++++++---- src/calibre/gui2/dialogs/add_from_isbn.py | 36 +++++++++++++++-------- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/src/calibre/gui2/actions/add.py b/src/calibre/gui2/actions/add.py index d2bc9bcd53..fdc1e4ce07 100644 --- a/src/calibre/gui2/actions/add.py +++ b/src/calibre/gui2/actions/add.py @@ -371,14 +371,26 @@ class AddAction(InterfaceAction): existing_isbns.pop('', None) ok = [] duplicates = [] + checker_maps = {} for book in books: - q = normalize_isbn(book['isbn']) - if q and q in existing_isbns: - duplicates.append((book, existing_isbns[q])) + if 'isbn' in book: + q = normalize_isbn(book['isbn']) + if q and q in existing_isbns: + duplicates.append((book, existing_isbns[q])) + else: + ok.append(book) else: - ok.append(book) + key = book[''] + if key not in checker_maps: + checker_maps[key] = {ids.get(key, ''): book_id for book_id, ids in book_id_identifiers.items()} + checker_maps[key].pop('', None) + q = book[key] + if q in checker_maps[key]: + duplicates.append((book, checker_maps[key][q])) + else: + ok.append(book) if duplicates: - det_msg = '\n'.join(f'{book["isbn"]}: {db.field_for("title", book_id)}' for book, book_id in duplicates) + det_msg = '\n'.join(f'{book[book[""]]}: {db.field_for("title", book_id)}' for book, book_id in duplicates) if question_dialog(self.gui, _('Duplicates found'), _( 'Books with some of the specified ISBNs already exist in the calibre library.' ' Click "Show details" for the full list. Do you want to add them anyway?'), det_msg=det_msg @@ -416,7 +428,10 @@ class AddAction(InterfaceAction): return mi = MetaInformation(None) - mi.isbn = x['isbn'] + if x[''] == 'isbn': + mi.isbn = x['isbn'] + else: + mi.set_identifiers({x['']:x[x['']]}) if self.isbn_add_tags: mi.tags = list(self.isbn_add_tags) fmts = [] if x['path'] is None else [x['path']] diff --git a/src/calibre/gui2/dialogs/add_from_isbn.py b/src/calibre/gui2/dialogs/add_from_isbn.py index f72d37e4b3..83302f326a 100644 --- a/src/calibre/gui2/dialogs/add_from_isbn.py +++ b/src/calibre/gui2/dialogs/add_from_isbn.py @@ -55,7 +55,11 @@ class AddFromISBN(QDialog): "

Any invalid ISBNs in the list will be ignored.

\n" "

You can also specify a file that will be added with each ISBN. To do this enter the full" " path to the file after a >>. For example:

\n" - "

9788842915232 >> %s

"), self) + "

9788842915232 >> %s

" + "

To use identifiers other than ISBN use key:value syntax, For example:

\n" + "

amazon:B001JK9C72

" + ), self) + l.addWidget(la), la.setWordWrap(True) l.addSpacing(20) self.la2 = la = QLabel(_("&Tags to set on created book entries:"), self) @@ -100,18 +104,26 @@ class AddFromISBN(QDialog): parts = [x.strip() for x in parts] if not parts[0]: continue - isbn = check_isbn(parts[0]) - if isbn is not None: - isbn = isbn.upper() - if isbn not in self.isbns: - self.isbns.append(isbn) - book = {'isbn': isbn, 'path': None} - if len(parts) > 1 and parts[1] and \ - os.access(parts[1], os.R_OK) and os.path.isfile(parts[1]): - book['path'] = parts[1] - self.books.append(book) + if ':' in parts[0]: + prefix, val = parts[0].partition(':')[::2] else: - bad.add(parts[0]) + prefix, val = 'isbn', parts[0] + path = None + if len(parts) > 1 and parts[1] and os.access(parts[1], os.R_OK) and os.path.isfile(parts[1]): + path = parts[1] + + if prefix == 'isbn': + isbn = check_isbn(parts[0]) + if isbn is not None: + isbn = isbn.upper() + if isbn not in self.isbns: + self.isbns.append(isbn) + self.books.append({'isbn': isbn, 'path': path, '': 'isbn'}) + else: + bad.add(parts[0]) + else: + if prefix != 'path': + self.books.append({prefix: val, 'path': path, '':prefix}) if bad: if self.books: if not question_dialog(self, _('Some invalid ISBNs'), From 3d797236a26f297e5ab89fbb6dfd9dfb6fc82cbc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 19 Jan 2023 08:41:43 +0530 Subject: [PATCH 0135/2055] Remove date from description --- recipes/bsi_news.recipe | 6 ------ recipes/google_news.recipe | 5 ----- recipes/my_dealz_de.recipe | 5 ----- recipes/spiegelde.recipe | 4 ---- 4 files changed, 20 deletions(-) diff --git a/recipes/bsi_news.recipe b/recipes/bsi_news.recipe index cacd527bcc..49ff39d7ab 100644 --- a/recipes/bsi_news.recipe +++ b/recipes/bsi_news.recipe @@ -1,8 +1,6 @@ #!/usr/bin/env python from __future__ import absolute_import, division, print_function, unicode_literals -from datetime import datetime - from calibre.web.feeds.news import BasicNewsRecipe @@ -22,10 +20,6 @@ class germanyBSI(BasicNewsRecipe): simultaneous_downloads = 10 # description, some Reader show this in titlepage description = u'News from BSI' - # add date to description so for dayly downloads you can find them easier - # ---- can be edit by user - description = description + ' fetched: ' + \ - datetime.now().strftime("%Y-%m-%d") # %H:%M:%S") # Who published the content? publisher = u'Newsfeeds des BSI' # What is the content of? diff --git a/recipes/google_news.recipe b/recipes/google_news.recipe index 7b0becf263..042810743b 100644 --- a/recipes/google_news.recipe +++ b/recipes/google_news.recipe @@ -2,7 +2,6 @@ # vim:fileencoding=utf-8 from __future__ import unicode_literals, division, absolute_import, print_function from calibre.web.feeds.news import BasicNewsRecipe -from datetime import datetime import json # a serarch topic, filled into the string below. You can change that to anything google news should be searched for... @@ -27,10 +26,6 @@ class google_news_de(BasicNewsRecipe): simultaneous_downloads = 10 # description, some Reader show this in titlepage description = u'Google News filter by your own recipe. Please read it in calibre software!' - # add date to description so for dayly downloads you can find them easier - # ---- can be edit by user - description = description + ' fetched: ' + \ - datetime.now().strftime("%Y-%m-%d") # %H:%M:%S") # What is the content of? category = u'NEWS' # describes itself, ---- can be edit by user diff --git a/recipes/my_dealz_de.recipe b/recipes/my_dealz_de.recipe index 9b2c686d22..f5720ce7d5 100644 --- a/recipes/my_dealz_de.recipe +++ b/recipes/my_dealz_de.recipe @@ -2,7 +2,6 @@ from __future__ import unicode_literals, division, absolute_import, print_function from calibre.web.feeds.news import BasicNewsRecipe -from datetime import datetime class MyDealzDE(BasicNewsRecipe): @@ -21,10 +20,6 @@ class MyDealzDE(BasicNewsRecipe): simultaneous_downloads = 10 # description, some Reader show this in titlepage description = u'MyDealz - Shopping Deals for Germany' - # add date to description so for dayly downloads you can find them easier - # ---- can be edit by user - description = description + ' fetched: ' + \ - datetime.now().strftime("%Y-%m-%d") # %H:%M:%S") # Who published the content? publisher = u'https://www.mydealz.de' # What is the content of? diff --git a/recipes/spiegelde.recipe b/recipes/spiegelde.recipe index 02b5cd4961..02575fd335 100644 --- a/recipes/spiegelde.recipe +++ b/recipes/spiegelde.recipe @@ -1,7 +1,6 @@ #!/usr/bin/env python # License: 'CC-BY-4.0' # Copyright: '2019, vohe Based on the recipe by Darko Miletic ' -from datetime import datetime from calibre.web.feeds.news import BasicNewsRecipe ''' @@ -35,9 +34,6 @@ class Spiegel_DE_all(BasicNewsRecipe): # "selbst wenn Sie keinen Internet-Browser geöffnet haben. Sie können unsere Nachrichten-Feeds kostenlos abonnieren - nach Ihren Themenvorlieben.") # above a long description, but we use a shorter one description = u'Spiegel Online RSS News' - # add fetching date to the description - description = description + ' fetched: ' + datetime.now( - ).strftime("%Y-%m-%d") # %H:%M:%S") # Who published the content? publisher = u'SPIEGEL ONLINE Gmbh' cover_url = 'https://de.m.wikipedia.org/wiki/Datei:Spiegel_Online_logo.svg' From e5074150a99f0e65ac8efe774cefd50accbe6c8f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 19 Jan 2023 08:42:14 +0530 Subject: [PATCH 0136/2055] Dont use lxml to build recipe collection It's crashing on macOS CI but not my local machine. Debugging on CI is too time consuming. --- src/calibre/web/feeds/recipes/collection.py | 23 ++++++++++----------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/calibre/web/feeds/recipes/collection.py b/src/calibre/web/feeds/recipes/collection.py index e064a43cd7..92095af45c 100644 --- a/src/calibre/web/feeds/recipes/collection.py +++ b/src/calibre/web/feeds/recipes/collection.py @@ -44,20 +44,22 @@ def iterate_over_builtin_recipe_files(): def serialize_recipe(urn, recipe_class): + from xml.sax.saxutils import quoteattr def attr(n, d): ans = getattr(recipe_class, n, d) if isinstance(ans, bytes): ans = ans.decode('utf-8', 'replace') - return ans + return quoteattr(ans) default_author = _('You') if urn.startswith('custom:') else _('Unknown') - ns = attr('needs_subscription', False) + ns = getattr(recipe_class, 'needs_subscription', False) if not ns: ns = 'no' if ns is True: ns = 'yes' - return E.recipe({ + return (' ').format(**{ 'id' : str(urn), 'title' : attr('title', _('Unknown')), 'author' : attr('__author__', default_author), @@ -68,12 +70,7 @@ def serialize_recipe(urn, recipe_class): def serialize_collection(mapping_of_recipe_classes): - collection = E.recipe_collection() - '''for u, x in mapping_of_recipe_classes.items(): - print 11111, u, repr(x.title) - if isinstance(x.title, bytes): - x.title.decode('ascii') - ''' + collection = [] for urn in sorted(mapping_of_recipe_classes.keys(), key=lambda key: force_unicode( getattr(mapping_of_recipe_classes[key], 'title', 'zzz'), @@ -85,9 +82,11 @@ def serialize_collection(mapping_of_recipe_classes): traceback.print_exc() continue collection.append(recipe) - collection.set('count', str(len(collection))) - return etree.tostring(collection, encoding='utf-8', xml_declaration=True, - pretty_print=True) + items = '\n'.join(collection) + return f''' + +{items} +'''.encode('utf-8') def serialize_builtin_recipes(): From 4a69c3ae8ab0f14dd8428539f83c55e2336d18fa Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 19 Jan 2023 08:43:43 +0530 Subject: [PATCH 0137/2055] ... --- src/calibre/web/feeds/recipes/collection.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/calibre/web/feeds/recipes/collection.py b/src/calibre/web/feeds/recipes/collection.py index 92095af45c..40a8ec84f6 100644 --- a/src/calibre/web/feeds/recipes/collection.py +++ b/src/calibre/web/feeds/recipes/collection.py @@ -58,13 +58,13 @@ def serialize_recipe(urn, recipe_class): ns = 'no' if ns is True: ns = 'yes' - return (' ').format(**{ - 'id' : str(urn), + return (' ').format(**{ + 'id' : quoteattr(str(urn)), 'title' : attr('title', _('Unknown')), 'author' : attr('__author__', default_author), 'language' : attr('language', 'und'), - 'needs_subscription' : ns, + 'needs_subscription' : quoteattr(ns), 'description' : attr('description', '') }) From 4cab9b598e4443d08afffa9e500af6c8dd22e047 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 19 Jan 2023 09:09:00 +0530 Subject: [PATCH 0138/2055] Document XCode version --- bypy/macos.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bypy/macos.conf b/bypy/macos.conf index 50ffe7740f..adc1c387fe 100644 --- a/bypy/macos.conf +++ b/bypy/macos.conf @@ -1,4 +1,4 @@ -# Requires installation of XCode 10.3 and Python 3 and +# Requires installation of XCode 13.2.1 and Python 3 and # python3 -m pip install certifi html5lib vm_name 'macos-calibre' From 3ae27e38f34ef2aa671081d43f20df09e565e14e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 19 Jan 2023 10:38:19 +0530 Subject: [PATCH 0139/2055] More work on winspeech --- src/calibre/utils/windows/winspeech.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index a2d1628d1d..1174fde0fe 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -127,14 +127,15 @@ ensure_current_thread_has_message_queue(void) { PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); } +#define PREPARE_METHOD_CALL ensure_current_thread_has_message_queue(); if (GetCurrentThreadId() != self->creation_thread_id) { PyErr_SetString(PyExc_RuntimeError, "Cannot use a Synthesizer object from a thread other than the thread it was created in"); return NULL; } + + static PyObject* Synthesizer_speak(Synthesizer *self, PyObject *args) { + PREPARE_METHOD_CALL; wchar_raii pytext; - PyObject *callback; int is_ssml = 0; - if (!PyArg_ParseTuple(args, "O&O|p", py_to_wchar_no_none, &pytext, &callback, &is_ssml)) return NULL; - if (!PyCallable_Check(callback)) { PyErr_SetString(PyExc_TypeError, "callback must be callable"); return NULL; } - ensure_current_thread_has_message_queue(); + if (!PyArg_ParseTuple(args, "O&|p", py_to_wchar_no_none, &pytext, &is_ssml)) return NULL; SpeechSynthesisStream stream{nullptr}; try { if (is_ssml) stream = self->synth.SynthesizeSsmlToStreamAsync(pytext.as_view()).get(); @@ -151,13 +152,13 @@ Synthesizer_speak(Synthesizer *self, PyObject *args) { static PyObject* Synthesizer_create_recording(Synthesizer *self, PyObject *args) { + PREPARE_METHOD_CALL; wchar_raii pytext; PyObject *callback; int is_ssml = 0; if (!PyArg_ParseTuple(args, "O&O|p", py_to_wchar_no_none, &pytext, &callback, &is_ssml)) return NULL; if (!PyCallable_Check(callback)) { PyErr_SetString(PyExc_TypeError, "callback must be callable"); return NULL; } - ensure_current_thread_has_message_queue(); SpeechSynthesisStream stream{nullptr}; try { if (is_ssml) stream = self->synth.SynthesizeSsmlToStreamAsync(pytext.as_view()).get(); From 16b94a79ef18861380ded993d88ac4ab439fbda7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 19 Jan 2023 11:07:42 +0530 Subject: [PATCH 0140/2055] DRYer --- setup/__init__.py | 7 ++++--- setup/resources.py | 1 - setup/revendor.py | 4 +--- setup/translations.py | 3 +-- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/setup/__init__.py b/setup/__init__.py index 84fcab0a4e..a1476b4f3b 100644 --- a/setup/__init__.py +++ b/setup/__init__.py @@ -25,6 +25,7 @@ isdragonflybsd = 'dragonfly' in sys.platform isbsd = isnetbsd or isfreebsd or isdragonflybsd ishaiku = 'haiku1' in sys.platform islinux = not ismacos and not iswindows and not isbsd and not ishaiku +is_ci = os.environ.get('CI', '').lower() == 'true' sys.setup_dir = os.path.dirname(os.path.abspath(__file__)) SRC = os.path.abspath(os.path.join(os.path.dirname(sys.setup_dir), 'src')) sys.path.insert(0, SRC) @@ -69,7 +70,7 @@ def download_securely(url): # We use curl here as on some OSes (OS X) when bootstrapping calibre, # python will be unable to validate certificates until after cacerts is # installed - if os.environ.get('CI') and iswindows: + if is_ci and iswindows: # curl is failing for wikipedia urls on CI (used for browser_data) from urllib.request import urlopen return urlopen(url).read() @@ -211,7 +212,7 @@ class Command: def running(self, cmd): from setup.commands import command_names - if os.environ.get('CI'): + if is_ci: self.info('::group::' + command_names[cmd]) self.info('\n*') self.info('* Running', command_names[cmd]) @@ -227,7 +228,7 @@ class Command: self.running(cmd) cmd.run(opts) self.info(f'* {command_names[cmd]} took {time.time() - st:.1f} seconds') - if os.environ.get('CI'): + if is_ci: self.info('::endgroup::') def run_all(self, opts): diff --git a/setup/resources.py b/setup/resources.py index ee60669b8c..60168ee92f 100644 --- a/setup/resources.py +++ b/setup/resources.py @@ -7,7 +7,6 @@ __docformat__ = 'restructuredtext en' import os, re, shutil, zipfile, glob, json, errno from zlib import compress -is_ci = os.environ.get('CI', '').lower() == 'true' from setup import Command, basenames, __appname__, download_securely, dump_json from polyglot.builtins import codepoint_to_chr, itervalues, iteritems, only_unicode_recursive diff --git a/setup/revendor.py b/setup/revendor.py index b9786639ec..33f39412d1 100755 --- a/setup/revendor.py +++ b/setup/revendor.py @@ -7,9 +7,7 @@ import tarfile import time from io import BytesIO -from setup import Command, download_securely - -is_ci = os.environ.get('CI', '').lower() == 'true' +from setup import Command, download_securely, is_ci class ReVendor(Command): diff --git a/setup/translations.py b/setup/translations.py index 4a1764e282..c764ff4ad7 100644 --- a/setup/translations.py +++ b/setup/translations.py @@ -10,10 +10,9 @@ from collections import defaultdict from locale import normalize as normalize_locale from functools import partial -from setup import Command, __appname__, __version__, require_git_master, build_cache_dir, edit_file, dump_json +from setup import Command, __appname__, __version__, require_git_master, build_cache_dir, edit_file, dump_json, is_ci from setup.parallel_build import batched_parallel_jobs from polyglot.builtins import codepoint_to_chr, iteritems -is_ci = os.environ.get('CI', '').lower() == 'true' def qt_sources(): From f95180349c5044f4cdcba17029a2b7c09d28a037 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 19 Jan 2023 11:30:42 +0530 Subject: [PATCH 0141/2055] Delay load MobiReader --- .../devices/kindle/apnx_page_generator/i_page_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/kindle/apnx_page_generator/i_page_generator.py b/src/calibre/devices/kindle/apnx_page_generator/i_page_generator.py index 124a606101..e815c904df 100644 --- a/src/calibre/devices/kindle/apnx_page_generator/i_page_generator.py +++ b/src/calibre/devices/kindle/apnx_page_generator/i_page_generator.py @@ -7,7 +7,6 @@ from abc import abstractmethod, ABCMeta from typing import Optional from calibre.devices.kindle.apnx_page_generator.pages import Pages -from calibre.ebooks.mobi.reader.mobi6 import MobiReader from calibre.utils.logging import default_log from polyglot.builtins import as_bytes from calibre.ebooks.pdb.header import PdbHeaderReader @@ -40,6 +39,7 @@ class IPageGenerator(metaclass=ABCMeta): def mobi_html(mobi_file_path: str) -> bytes: + from calibre.ebooks.mobi.reader.mobi6 import MobiReader mr = MobiReader(mobi_file_path, default_log) if mr.book_header.encryption_type != 0: raise Exception("DRMed book") From 0bdde99372183eb321cb47fb45487dc52accd333 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 19 Jan 2023 11:10:47 +0530 Subject: [PATCH 0142/2055] See if forcibly loading the correct libxml2 dylib before running the tests fixes the lxml test failure on CI --- setup/test.py | 9 +++++++-- setup/unix-ci.py | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/setup/test.py b/setup/test.py index 5ee7c78240..6855681be6 100644 --- a/setup/test.py +++ b/setup/test.py @@ -6,8 +6,7 @@ import os import subprocess import sys -from setup import Command - +from setup import Command, ismacos, is_ci TEST_MODULES = frozenset('srv db polish opf css docx cfi matcher icu smartypants build misc dbcli ebooks'.split()) @@ -41,6 +40,12 @@ class Test(Command): self.info(f'Re-execing with LD_PRELOAD={os.environ["LD_PRELOAD"]}') sys.stdout.flush() os.execl('setup.py', *sys.argv) + if is_ci and ismacos: + import ctypes + sys.libxml2_dylib = ctypes.CDLL(os.path.join(os.environ['SW'], 'lib', 'libxml2.dylib')) + sys.libxslt_dylib = ctypes.CDLL(os.path.join(os.environ['SW'], 'lib', 'libxslt.dylib')) + sys.libexslt_dylib = ctypes.CDLL(os.path.join(os.environ['SW'], 'lib', 'libexslt.dylib')) + print(sys.libxml2_dylib, sys.libxslt_dylib, sys.libexslt_dylib, file=sys.stderr, flush=True) from calibre.utils.run_tests import ( filter_tests_by_name, remove_tests_by_name, run_cli, find_tests ) diff --git a/setup/unix-ci.py b/setup/unix-ci.py index 1a6ba4e3dd..733df81c70 100644 --- a/setup/unix-ci.py +++ b/setup/unix-ci.py @@ -161,6 +161,7 @@ username = api if ismacos: os.environ['SSL_CERT_FILE'] = os.path.abspath( 'resources/mozilla-ca-certs.pem') + os.environ['DYLD_LIBRARY_PATH'] = os.path.join(SW, 'lib') # needed to ensure correct libxml2.dylib is loaded install_env() run_python('setup.py test') From c3ee09d45928f98044acac2969d7553d9f43e29b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 19 Jan 2023 12:36:58 +0530 Subject: [PATCH 0143/2055] ... --- src/calibre/srv/tests/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/srv/tests/auth.py b/src/calibre/srv/tests/auth.py index 2a5cf24a80..94fd56b8e4 100644 --- a/src/calibre/srv/tests/auth.py +++ b/src/calibre/srv/tests/auth.py @@ -233,7 +233,7 @@ class TestAuth(BaseTest): if curl: def docurl(data, *args): cmd = [curl] + list(args) + ['http://localhost:%d/closed' % server.address[1]] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=open(os.devnull, 'wb')) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) x = p.stdout.read() p.wait() self.ae(x, data) From 508fbc057fd18bf5f7b49b0c8b4b72ab31702832 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 19 Jan 2023 12:42:43 +0530 Subject: [PATCH 0144/2055] Update The Seattle Times --- recipes/seattle_times.recipe | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/recipes/seattle_times.recipe b/recipes/seattle_times.recipe index 1628bd9719..fafada2e9f 100644 --- a/recipes/seattle_times.recipe +++ b/recipes/seattle_times.recipe @@ -55,9 +55,5 @@ class SeattleTimes(BasicNewsRecipe): u'https://www.seattletimes.com/photo-video/feed/'), ] - def get_browser(self, *a, **kw): - # MyClatchy servers dont like the user-agent header, they hang forever - # when it is present - br = BasicNewsRecipe.get_browser(self, *a, **kw) - br.addheaders = [x for x in br.addheaders if x[0].lower() != 'user-agent'] - return br + def get_browser(self): + return BasicNewsRecipe.get_browser(self, user_agent='common_words/based') From 3998c2490c21326f0c2106e156565f8b7e01fb6c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 19 Jan 2023 12:55:59 +0530 Subject: [PATCH 0145/2055] Sanitize env when running curl in test --- setup/test.py | 2 ++ src/calibre/srv/tests/auth.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/setup/test.py b/setup/test.py index 6855681be6..6cdf028785 100644 --- a/setup/test.py +++ b/setup/test.py @@ -46,6 +46,8 @@ class Test(Command): sys.libxslt_dylib = ctypes.CDLL(os.path.join(os.environ['SW'], 'lib', 'libxslt.dylib')) sys.libexslt_dylib = ctypes.CDLL(os.path.join(os.environ['SW'], 'lib', 'libexslt.dylib')) print(sys.libxml2_dylib, sys.libxslt_dylib, sys.libexslt_dylib, file=sys.stderr, flush=True) + os.non_calibre_subprocess_env = e = os.environ + del e['DYLD_LIBRARY_PATH'] from calibre.utils.run_tests import ( filter_tests_by_name, remove_tests_by_name, run_cli, find_tests ) diff --git a/src/calibre/srv/tests/auth.py b/src/calibre/srv/tests/auth.py index 94fd56b8e4..e277a34deb 100644 --- a/src/calibre/srv/tests/auth.py +++ b/src/calibre/srv/tests/auth.py @@ -233,7 +233,7 @@ class TestAuth(BaseTest): if curl: def docurl(data, *args): cmd = [curl] + list(args) + ['http://localhost:%d/closed' % server.address[1]] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, env=getattr(os, 'non_calibre_subprocess_env', os.environ)) x = p.stdout.read() p.wait() self.ae(x, data) From 829f3d4e0eb4609751351e7c95e55470a5e10684 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 19 Jan 2023 13:13:48 +0530 Subject: [PATCH 0146/2055] ... --- src/calibre/ebooks/oeb/polish/tests/container.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/oeb/polish/tests/container.py b/src/calibre/ebooks/oeb/polish/tests/container.py index 597ff2c297..9e284421c2 100644 --- a/src/calibre/ebooks/oeb/polish/tests/container.py +++ b/src/calibre/ebooks/oeb/polish/tests/container.py @@ -48,7 +48,8 @@ class ContainerTests(BaseTest): for name in c1.name_path_map: self.assertIn(name, c2.name_path_map) - self.assertEqual(c1.open(name).read(), c2.open(name).read(), 'The file %s differs' % name) + with c1.open(name) as one, c2.open(name) as two: + self.assertEqual(one.read(), two.read(), 'The file %s differs' % name) spine_names = tuple(x[0] for x in c1.spine_names) text = spine_names[0] From 7ee339ab146b80cb12d3beecc05f9d960ffa4519 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 19 Jan 2023 13:17:20 +0530 Subject: [PATCH 0147/2055] Try using DYLD_INSERT_LIBRARIES instead --- setup/test.py | 2 -- setup/unix-ci.py | 3 ++- src/calibre/srv/tests/auth.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/setup/test.py b/setup/test.py index 6cdf028785..6855681be6 100644 --- a/setup/test.py +++ b/setup/test.py @@ -46,8 +46,6 @@ class Test(Command): sys.libxslt_dylib = ctypes.CDLL(os.path.join(os.environ['SW'], 'lib', 'libxslt.dylib')) sys.libexslt_dylib = ctypes.CDLL(os.path.join(os.environ['SW'], 'lib', 'libexslt.dylib')) print(sys.libxml2_dylib, sys.libxslt_dylib, sys.libexslt_dylib, file=sys.stderr, flush=True) - os.non_calibre_subprocess_env = e = os.environ - del e['DYLD_LIBRARY_PATH'] from calibre.utils.run_tests import ( filter_tests_by_name, remove_tests_by_name, run_cli, find_tests ) diff --git a/setup/unix-ci.py b/setup/unix-ci.py index 733df81c70..5b05c9aebe 100644 --- a/setup/unix-ci.py +++ b/setup/unix-ci.py @@ -161,7 +161,8 @@ username = api if ismacos: os.environ['SSL_CERT_FILE'] = os.path.abspath( 'resources/mozilla-ca-certs.pem') - os.environ['DYLD_LIBRARY_PATH'] = os.path.join(SW, 'lib') # needed to ensure correct libxml2.dylib is loaded + # needed to ensure correct libxml2 is loaded + os.environ['DYLD_INSERT_LIBRARIES'] = ':'.join(os.path.join(SW, 'lib', x) for x in 'libxml2.dylib libxslt.dylib libexslt.dylib'.split()) install_env() run_python('setup.py test') diff --git a/src/calibre/srv/tests/auth.py b/src/calibre/srv/tests/auth.py index e277a34deb..94fd56b8e4 100644 --- a/src/calibre/srv/tests/auth.py +++ b/src/calibre/srv/tests/auth.py @@ -233,7 +233,7 @@ class TestAuth(BaseTest): if curl: def docurl(data, *args): cmd = [curl] + list(args) + ['http://localhost:%d/closed' % server.address[1]] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, env=getattr(os, 'non_calibre_subprocess_env', os.environ)) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) x = p.stdout.read() p.wait() self.ae(x, data) From e8e95f888d1b99022f4d12bcb1b96629f82ae6c8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 20 Jan 2023 18:14:34 +0530 Subject: [PATCH 0148/2055] ... --- src/calibre/utils/windows/winspeech.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 1174fde0fe..732408f452 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -114,8 +114,11 @@ Synthesizer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { INITIALIZE static void Synthesizer_dealloc(Synthesizer *self_) { synthesizer_weakrefs.unregister_ref(self_, [](Synthesizer *self) { - self->synth = SpeechSynthesizer{nullptr}; - self->player = MediaPlayer{nullptr}; + try { + self->~Synthesizer(); + } catch (...) { + fprintf(stderr, "Unhandled exception during Synthesizer object destruction, ignored.\n"); + } Py_TYPE(self)->tp_free((PyObject*)self); CoUninitialize(); }); From cd7d100eca75b3e85e9e30f30332f0b65b5ce1a0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 22 Jan 2023 14:51:38 +0530 Subject: [PATCH 0149/2055] More work on winspeech --- src/calibre/utils/windows/winspeech.cpp | 210 +++++++++++++++--------- 1 file changed, 133 insertions(+), 77 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 732408f452..5155443e26 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -35,7 +36,15 @@ runtime_error_as_python_error(PyObject *exc_type, winrt::hresult_error const &ex else PyErr_Format(exc_type, "%s:%d:%s:[hr=0x%x] %V", file, line, prefix, hr, msg.ptr(), "Out of memory"); return NULL; } -#define set_python_error_from_runtime(ex, ...) runtime_error_as_python_error(PyExc_OSError, ex, __FILE__, __LINE__, __VA_ARGS__) + +#define CATCH_ALL_EXCEPTIONS(msg) catch(winrt::hresult_error const& ex) { \ + runtime_error_as_python_error(PyExc_OSError, ex, __FILE__, __LINE__, msg); \ +} catch (std::exception const &ex) { \ + PyErr_Format(PyExc_OSError, "%s:%d:%s: %s", __FILE__, __LINE__, msg, ex.what()); \ +} catch (...) { \ + PyErr_Format(PyExc_OSError, "%s:%d:%s: Unknown exception type was raised", __FILE__, __LINE__, msg); \ +} + template class WeakRefs { @@ -44,84 +53,144 @@ class WeakRefs { std::unordered_map refs; id_type counter; public: - void register_ref(T *self) { + id_type register_ref(T *self) { std::scoped_lock lock(weak_ref_lock); - self->id = ++counter; - refs[self->id] = self; + auto id = ++counter; + refs[id] = self; + return id; } - void unregister_ref(T *self, std::function dealloc) { + void unregister_ref(T* self) { std::scoped_lock lock(weak_ref_lock); - dealloc(self); - refs.erase(self->id); - self->id = 0; + auto id = self->clear_id(); + refs.erase(id); + self->~T(); } - void use_ref(id_type id, DWORD creation_thread_id, std::function callback) { - if (GetCurrentThreadId() == creation_thread_id) { - try { - callback(at(id)); - } catch (std::out_of_range) { - callback(NULL); - } - } - else { - std::scoped_lock lock(weak_ref_lock); - try { - callback(at(id)); - } catch (std::out_of_range) { - callback(NULL); - } + void use_ref(id_type id, std::function callback) { + std::scoped_lock lock(weak_ref_lock); + try { + callback(refs.at(id)); + } catch (std::out_of_range) { + callback(NULL); } } }; -struct Synthesizer { - PyObject_HEAD +enum class EventType { + playback_state_changed = 1, media_opened, media_failed, media_ended +}; + +class Event { + private: + EventType type; + public: + Event(EventType type) : type(type) {} + Event(const Event &source) : type(source.type) {} +}; + +class SynthesizerImplementation { + private: id_type id; DWORD creation_thread_id; SpeechSynthesizer synth{nullptr}; MediaPlayer player{nullptr}; + + struct { + MediaPlaybackSession::PlaybackStateChanged_revoker playback_state_changed; + MediaPlayer::MediaEnded_revoker media_ended; + MediaPlayer::MediaOpened_revoker media_opened; + MediaPlayer::MediaFailed_revoker media_failed; + } revoker; + + std::vector events; + std::mutex events_lock; + public: + SynthesizerImplementation(); + void add_simple_event(EventType type) { + try { + std::scoped_lock lock(events_lock); + events.emplace_back(type); + } catch(...) {} + } + + SpeechSynthesisStream synthesize(const std::wstring_view &text, bool is_ssml = false) { + if (is_ssml) return synth.SynthesizeSsmlToStreamAsync(text).get(); + return synth.SynthesizeTextToStreamAsync(text).get(); + } + + void speak(const std::wstring_view &text, bool is_ssml = false) { + SpeechSynthesisStream stream = synthesize(text, is_ssml); + MediaSource source = winrt::Windows::Media::Core::MediaSource::CreateFromStream(stream, stream.ContentType()); + player.Source(source); + player.Play(); + } + + bool is_creation_thread() const noexcept { + return creation_thread_id == GetCurrentThreadId(); + } + + id_type clear_id() noexcept { + auto ans = id; + id = 0; + return ans; + } + +}; + +struct Synthesizer { + PyObject_HEAD + SynthesizerImplementation impl; }; static PyTypeObject SynthesizerType = { PyVarObject_HEAD_INIT(NULL, 0) }; -static WeakRefs synthesizer_weakrefs; +static WeakRefs synthesizer_weakrefs; +SynthesizerImplementation::SynthesizerImplementation() { + events.reserve(128); + synth = SpeechSynthesizer(); + player = MediaPlayer(); + player.AudioCategory(MediaPlayerAudioCategory::Speech); + creation_thread_id = GetCurrentThreadId(); + id = synthesizer_weakrefs.register_ref(this); + id_type self_id = id; +#define simple_event_listener(method, event_type) \ + revoker.event_type = method(winrt::auto_revoke, [self_id](auto, const auto &args) { \ + fprintf(stderr, "111111111 %s\n", #event_type); fflush(stderr); \ + synthesizer_weakrefs.use_ref(self_id, [](auto s) { \ + if (s) s->add_simple_event(EventType::event_type); \ + }); \ + }); + simple_event_listener(player.PlaybackSession().PlaybackStateChanged, playback_state_changed); + simple_event_listener(player.MediaOpened, media_opened); + simple_event_listener(player.MediaEnded, media_ended); +#undef simple_event_listener +} static PyObject* Synthesizer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { INITIALIZE_COM_IN_FUNCTION Synthesizer *self = (Synthesizer *) type->tp_alloc(type, 0); if (self) { + auto i = &self->impl; try { - self->synth = SpeechSynthesizer(); - self->player = MediaPlayer(); - self->player.AudioCategory(MediaPlayerAudioCategory::Speech); - } catch(winrt::hresult_error const& ex) { - set_python_error_from_runtime(ex, "Failed to get SpeechSynthesisStream from text"); - Py_CLEAR(self); - } - } - if (PyErr_Occurred()) { Py_CLEAR(self); } - if (self) { - self->creation_thread_id = GetCurrentThreadId(); - synthesizer_weakrefs.register_ref(self); - com.detach(); + new (i) SynthesizerImplementation(); + } CATCH_ALL_EXCEPTIONS("Failed to create SynthesizerImplementation object"); + if (PyErr_Occurred()) { Py_CLEAR(self); } } + if (self) com.detach(); return (PyObject*)self; } static void -Synthesizer_dealloc(Synthesizer *self_) { - synthesizer_weakrefs.unregister_ref(self_, [](Synthesizer *self) { - try { - self->~Synthesizer(); - } catch (...) { - fprintf(stderr, "Unhandled exception during Synthesizer object destruction, ignored.\n"); - } - Py_TYPE(self)->tp_free((PyObject*)self); - CoUninitialize(); - }); +Synthesizer_dealloc(Synthesizer *self) { + auto *i = &self->impl; + try { + synthesizer_weakrefs.unregister_ref(i); + } CATCH_ALL_EXCEPTIONS("Failed to destruct SynthesizerImplementation"); + if (PyErr_Occurred()) { PyErr_Print(); } + Py_TYPE(self)->tp_free((PyObject*)self); + CoUninitialize(); } static void @@ -130,8 +199,7 @@ ensure_current_thread_has_message_queue(void) { PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); } -#define PREPARE_METHOD_CALL ensure_current_thread_has_message_queue(); if (GetCurrentThreadId() != self->creation_thread_id) { PyErr_SetString(PyExc_RuntimeError, "Cannot use a Synthesizer object from a thread other than the thread it was created in"); return NULL; } - +#define PREPARE_METHOD_CALL ensure_current_thread_has_message_queue(); if (!self->impl.is_creation_thread()) { PyErr_SetString(PyExc_RuntimeError, "Cannot use a Synthesizer object from a thread other than the thread it was created in"); return NULL; } static PyObject* Synthesizer_speak(Synthesizer *self, PyObject *args) { @@ -139,16 +207,10 @@ Synthesizer_speak(Synthesizer *self, PyObject *args) { wchar_raii pytext; int is_ssml = 0; if (!PyArg_ParseTuple(args, "O&|p", py_to_wchar_no_none, &pytext, &is_ssml)) return NULL; - SpeechSynthesisStream stream{nullptr}; try { - if (is_ssml) stream = self->synth.SynthesizeSsmlToStreamAsync(pytext.as_view()).get(); - else stream = self->synth.SynthesizeTextToStreamAsync(pytext.as_view()).get(); - } catch (winrt::hresult_error const& ex) { - return set_python_error_from_runtime(ex, "Failed to get SpeechSynthesisStream from text"); - } - MediaSource source = winrt::Windows::Media::Core::MediaSource::CreateFromStream(stream, stream.ContentType()); - self->player.Source(source); - self->player.Play(); + self->impl.speak(pytext.as_view(), (bool)is_ssml); + } CATCH_ALL_EXCEPTIONS("Failed to start speaking text"); + if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; } @@ -164,11 +226,9 @@ Synthesizer_create_recording(Synthesizer *self, PyObject *args) { SpeechSynthesisStream stream{nullptr}; try { - if (is_ssml) stream = self->synth.SynthesizeSsmlToStreamAsync(pytext.as_view()).get(); - else stream = self->synth.SynthesizeTextToStreamAsync(pytext.as_view()).get(); - } catch(winrt::hresult_error const& ex) { - return set_python_error_from_runtime(ex, "Failed to get SpeechSynthesisStream from text"); - } + stream = self->impl.synthesize(pytext.as_view(), (bool)is_ssml); + } CATCH_ALL_EXCEPTIONS( "Failed to get SpeechSynthesisStream from text"); + if (PyErr_Occurred()) return NULL; unsigned long long stream_size = stream.Size(), bytes_read = 0; DataReader reader(stream); unsigned int n; @@ -176,9 +236,8 @@ Synthesizer_create_recording(Synthesizer *self, PyObject *args) { while (bytes_read < stream_size) { try { n = reader.LoadAsync(chunk_size).get(); - } catch(winrt::hresult_error const& ex) { - return set_python_error_from_runtime(ex, "Failed to load data from DataReader"); - } + } CATCH_ALL_EXCEPTIONS("Failed to load data from DataReader"); + if (PyErr_Occurred()) return NULL; if (n > 0) { bytes_read += n; pyobject_raii b(PyBytes_FromStringAndSize(NULL, n)); @@ -209,9 +268,8 @@ voice_as_dict(VoiceInformation const& voice) { "language", voice.Language().c_str(), "gender", gender ); - } catch(winrt::hresult_error const& ex) { - return set_python_error_from_runtime(ex); - } + } CATCH_ALL_EXCEPTIONS("Could not convert Voice to dict"); + return NULL; } @@ -231,18 +289,16 @@ all_voices(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTION } } return ans.detach(); - } catch(winrt::hresult_error const& ex) { - return set_python_error_from_runtime(ex); - } + } CATCH_ALL_EXCEPTIONS("Could not get all voices"); + return NULL; } static PyObject* default_voice(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTION try { return voice_as_dict(SpeechSynthesizer::DefaultVoice()); - } catch(winrt::hresult_error const& ex) { - return set_python_error_from_runtime(ex); - } + } CATCH_ALL_EXCEPTIONS("Could not get default voice"); + return NULL; } #define M(name, args) { #name, (PyCFunction)Synthesizer_##name, args, ""} From 79bb34fd3683a8bd432465eb75b501840385e7b9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 22 Jan 2023 22:46:58 +0530 Subject: [PATCH 0150/2055] More work on winspeech --- src/calibre/utils/cpp_binding.h | 1 + src/calibre/utils/windows/winspeech.cpp | 120 +++++++++++++++++++++--- 2 files changed, 109 insertions(+), 12 deletions(-) diff --git a/src/calibre/utils/cpp_binding.h b/src/calibre/utils/cpp_binding.h index c27332cb81..58b130a1bb 100644 --- a/src/calibre/utils/cpp_binding.h +++ b/src/calibre/utils/cpp_binding.h @@ -56,6 +56,7 @@ class wchar_raii : public generic_raii { return 1; } std::wstring_view as_view() const { return std::wstring_view(handle, sz); } + std::wstring as_copy() const { return std::wstring(handle, sz); } }; #else typedef generic_raii wchar_raii; diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 5155443e26..11418b1e4c 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -76,7 +76,7 @@ class WeakRefs { }; enum class EventType { - playback_state_changed = 1, media_opened, media_failed, media_ended + playback_state_changed = 1, media_opened, media_failed, media_ended, source_changed, cue_entered, cue_exited, track_failed }; class Event { @@ -93,18 +93,27 @@ class SynthesizerImplementation { DWORD creation_thread_id; SpeechSynthesizer synth{nullptr}; MediaPlayer player{nullptr}; + MediaSource current_source{nullptr}; + SpeechSynthesisStream current_stream{nullptr}; + MediaPlaybackItem currently_playing{nullptr}; struct { MediaPlaybackSession::PlaybackStateChanged_revoker playback_state_changed; - MediaPlayer::MediaEnded_revoker media_ended; - MediaPlayer::MediaOpened_revoker media_opened; - MediaPlayer::MediaFailed_revoker media_failed; + MediaPlayer::MediaEnded_revoker media_ended; MediaPlayer::MediaOpened_revoker media_opened; + MediaPlayer::MediaFailed_revoker media_failed; MediaPlayer::SourceChanged_revoker source_changed; + + MediaPlaybackItem::TimedMetadataTracksChanged_revoker timed_metadata_tracks_changed; + std::vector cue_entered; + std::vector cue_exited; + std::vector track_failed; } revoker; std::vector events; std::mutex events_lock; + public: SynthesizerImplementation(); + void add_simple_event(EventType type) { try { std::scoped_lock lock(events_lock); @@ -118,10 +127,33 @@ class SynthesizerImplementation { } void speak(const std::wstring_view &text, bool is_ssml = false) { - SpeechSynthesisStream stream = synthesize(text, is_ssml); - MediaSource source = winrt::Windows::Media::Core::MediaSource::CreateFromStream(stream, stream.ContentType()); - player.Source(source); - player.Play(); + revoker.cue_entered.clear(); + revoker.cue_exited.clear(); + revoker.track_failed.clear(); + current_stream = synthesize(text, is_ssml); + current_source = MediaSource::CreateFromStream(current_stream, current_stream.ContentType()); + currently_playing = MediaPlaybackItem(current_source); + auto self_id = id; + revoker.timed_metadata_tracks_changed = currently_playing.TimedMetadataTracksChanged(winrt::auto_revoke, [self_id](auto, auto const &args) { + auto change_type = args.CollectionChange(); + auto index = args.Index(); + synthesizer_weakrefs.use_ref(self_id, [change_type, index](auto s) { + if (!s) return; + switch (change_type) { + case CollectionChange::ItemInserted: { + s->register_metadata_handler_for_speech(s->currently_playing.TimedMetadataTracks().GetAt(index)); + } break; + case CollectionChange::Reset: + for (auto const& track : s->currently_playing.TimedMetadataTracks()) { + s->register_metadata_handler_for_speech(track); + } + break; + }}); + }); + player.Source(currently_playing); + for (auto const &track : currently_playing.TimedMetadataTracks()) { + register_metadata_handler_for_speech(track); + } } bool is_creation_thread() const noexcept { @@ -134,6 +166,28 @@ class SynthesizerImplementation { return ans; } + void register_metadata_handler_for_speech(TimedMetadataTrack const& track) { + fprintf(stderr, "99999999999 registering metadata handler\n"); + auto self_id = id; +#define simple_event_listener(method, event_type) \ + revoker.event_type.push_back(method(winrt::auto_revoke, [self_id](auto, const auto&) { \ + fprintf(stderr, "111111111 %s %u\n", #event_type, GetCurrentThreadId()); fflush(stderr); \ + synthesizer_weakrefs.use_ref(self_id, [](auto s) { \ + if (!s) return; \ + s->add_simple_event(EventType::event_type); \ + fprintf(stderr, "2222222222 %d\n", s->player.PlaybackSession().PlaybackState()); \ + }); \ + })); + simple_event_listener(track.CueEntered, cue_entered); + simple_event_listener(track.CueExited, cue_exited); + simple_event_listener(track.TrackFailed, track_failed); +#undef simple_event_listener + track.CueEntered([](auto, const auto&) { + fprintf(stderr, "cue entered\n"); fflush(stderr); + }); +} + + }; struct Synthesizer { @@ -150,22 +204,41 @@ static WeakRefs synthesizer_weakrefs; SynthesizerImplementation::SynthesizerImplementation() { events.reserve(128); synth = SpeechSynthesizer(); + synth.Options().IncludeSentenceBoundaryMetadata(true); + synth.Options().IncludeWordBoundaryMetadata(true); player = MediaPlayer(); player.AudioCategory(MediaPlayerAudioCategory::Speech); + player.AutoPlay(true); creation_thread_id = GetCurrentThreadId(); id = synthesizer_weakrefs.register_ref(this); - id_type self_id = id; + auto self_id = id; #define simple_event_listener(method, event_type) \ - revoker.event_type = method(winrt::auto_revoke, [self_id](auto, const auto &args) { \ - fprintf(stderr, "111111111 %s\n", #event_type); fflush(stderr); \ + revoker.event_type = method(winrt::auto_revoke, [self_id](auto, const auto&) { \ + fprintf(stderr, "111111111 %s %u\n", #event_type, GetCurrentThreadId()); fflush(stderr); \ synthesizer_weakrefs.use_ref(self_id, [](auto s) { \ - if (s) s->add_simple_event(EventType::event_type); \ + if (!s) return; \ + s->add_simple_event(EventType::event_type); \ + fprintf(stderr, "2222222222 %d\n", s->player.PlaybackSession().PlaybackState()); \ }); \ }); simple_event_listener(player.PlaybackSession().PlaybackStateChanged, playback_state_changed); simple_event_listener(player.MediaOpened, media_opened); + simple_event_listener(player.MediaFailed, media_failed); simple_event_listener(player.MediaEnded, media_ended); + simple_event_listener(player.SourceChanged, source_changed); #undef simple_event_listener + player.PlaybackSession().PlaybackStateChanged([](auto, const auto&) { + fprintf(stderr, "111111111 %s %u\n", "playback state changed", GetCurrentThreadId()); fflush(stderr); \ + }); + player.MediaOpened([](auto, const auto&) { + fprintf(stderr, "111111111 %s %u\n", "media opened", GetCurrentThreadId()); fflush(stderr); \ + }); + player.MediaFailed([](auto, const auto&) { + fprintf(stderr, "111111111 %s %u\n", "media failed", GetCurrentThreadId()); fflush(stderr); \ + }); + player.MediaEnded([](auto, const auto&) { + fprintf(stderr, "111111111 %s %u\n", "media ended", GetCurrentThreadId()); fflush(stderr); \ + }); } static PyObject* @@ -309,10 +382,33 @@ static PyMethodDef Synthesizer_methods[] = { }; #undef M +static PyObject* +pump_waiting_messages(PyObject*, PyObject*) { + UINT firstMsg = 0, lastMsg = 0; + MSG msg; + bool found = false; + // Read all of the messages in this next loop, + // removing each message as we read it. + while (PeekMessage(&msg, NULL, firstMsg, lastMsg, PM_REMOVE)) { + // If it's a quit message, we're out of here. + if (msg.message == WM_QUIT) { + Py_RETURN_NONE; + } + found = true; + // Otherwise, dispatch the message. + DispatchMessage(&msg); + } // End of PeekMessage while loop + + if (found) Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + + #define M(name, args) { #name, name, args, ""} static PyMethodDef methods[] = { M(all_voices, METH_NOARGS), M(default_voice, METH_NOARGS), + M(pump_waiting_messages, METH_NOARGS), {NULL, NULL, 0, NULL} }; #undef M From 084cf4e65f005f3c1352d9e4addee2d714a56a75 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 23 Jan 2023 07:04:42 +0530 Subject: [PATCH 0151/2055] Move checkboxes to left of text --- src/pyj/read_book/prefs/selection.pyj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyj/read_book/prefs/selection.pyj b/src/pyj/read_book/prefs/selection.pyj index 5b2bcb0b42..45a34c8fe5 100644 --- a/src/pyj/read_book/prefs/selection.pyj +++ b/src/pyj/read_book/prefs/selection.pyj @@ -131,9 +131,9 @@ def update_quick_action_table(): style='margin: 1ex; display: flex; align-contents: center', hs.make_swatch(E.span(), is_dark_theme()), '\xa0', - hs.friendly_name, - '\xa0', E.input(type='checkbox', value=hs.key, checked=current[hs.key]), + '\xa0', + hs.friendly_name, )) From 7e67e62c11b4dbad4563242d7d00b6f1cc2809b8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 23 Jan 2023 07:42:36 +0530 Subject: [PATCH 0152/2055] Use an icon rather than a color to report errors in line edits. Fixes #2003652 [color frame in edit metadata](https://bugs.launchpad.net/calibre/+bug/2003652) --- src/calibre/gui2/__init__.py | 4 -- src/calibre/gui2/dialogs/metadata_bulk.py | 17 ++++---- src/calibre/gui2/metadata/basic_widgets.py | 48 +++++++++++++--------- src/calibre/gui2/search_box.py | 7 +--- src/calibre/gui2/widgets.py | 29 +++++++++++++ 5 files changed, 68 insertions(+), 37 deletions(-) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 1e397c42ff..024e403ca7 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -1291,10 +1291,6 @@ class Application(QApplication): ans.setDevicePixelRatio(device_pixel_ratio) return ans - def stylesheet_for_line_edit(self, is_error=False): - col = '#FF2400' if is_error else '#50c878' - return f'QLineEdit {{ border: 2px solid {col}; border-radius: 3px }}' - def _send_file_open_events(self): with self._file_open_lock: if self._file_open_paths: diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index ee84c037e9..f09d28329e 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -7,9 +7,8 @@ import regex from collections import defaultdict, namedtuple from io import BytesIO from qt.core import ( - QApplication, QComboBox, QCompleter, QDateTime, QDialog, QDialogButtonBox, QFont, - QGridLayout, QInputDialog, QLabel, QLineEdit, QProgressBar, QSize, Qt, QVBoxLayout, - pyqtSignal, + QComboBox, QCompleter, QDateTime, QDialog, QDialogButtonBox, QFont, QGridLayout, + QInputDialog, QLabel, QLineEdit, QProgressBar, QSize, Qt, QVBoxLayout, pyqtSignal, ) from threading import Thread @@ -27,7 +26,9 @@ from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor -from calibre.gui2.widgets import LineEditECM +from calibre.gui2.widgets import ( + LineEditECM, setup_status_actions, update_status_actions, +) from calibre.startup import connect_lambda from calibre.utils.config import JSONConfig, dynamic, prefs, tweaks from calibre.utils.date import internal_iso_format_string, qt_to_dt @@ -499,6 +500,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): def __init__(self, window, rows, model, tab, refresh_books): QDialog.__init__(self, window) self.setupUi(self) + setup_status_actions(self.test_result) self.series.set_sort_func(title_sort) self.model = model self.db = model.db @@ -899,10 +901,11 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): self.s_r_search_field_changed(self.search_field.currentIndex()) def s_r_set_colors(self): + tt = '' if self.s_r_error is not None: - self.test_result.setText(error_message(self.s_r_error)) - self.test_result.setStyleSheet( - QApplication.instance().stylesheet_for_line_edit(self.s_r_error is not None)) + tt = error_message(self.s_r_error) + self.test_result.setText(tt) + update_status_actions(self.test_result, self.s_r_error is None, tt) for i in range(0,self.s_r_number_of_books): getattr(self, 'book_%d_result'%(i+1)).setText('') diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 10a616d3e8..d1130e5aa7 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -37,7 +37,7 @@ from calibre.gui2.comments_editor import Editor from calibre.gui2.complete2 import EditWithComplete from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.languages import LanguagesEdit as LE -from calibre.gui2.widgets import EnLineEdit, FormatList as _FormatList, ImageView +from calibre.gui2.widgets import EnLineEdit, FormatList as _FormatList, ImageView, LineEditIndicators from calibre.gui2.widgets2 import ( DateTimeEdit, Dialog, RatingEditor, RightClickButton, access_key, populate_standard_spinbox_context_menu, @@ -261,7 +261,7 @@ class TitleEdit(EnLineEdit, ToMetadataMixin): self.dialog = None -class TitleSortEdit(TitleEdit, ToMetadataMixin): +class TitleSortEdit(TitleEdit, ToMetadataMixin, LineEditIndicators): TITLE_ATTR = FIELD_NAME = 'title_sort' TOOLTIP = _('Specify how this book should be sorted when by title.' @@ -270,15 +270,16 @@ class TitleSortEdit(TitleEdit, ToMetadataMixin): def __init__(self, parent, title_edit, autogen_button, languages_edit): TitleEdit.__init__(self, parent) + self.setup_status_actions() self.title_edit = title_edit self.languages_edit = languages_edit base = self.TOOLTIP ok_tooltip = '

' + textwrap.fill(base+'

' + _( - ' The green color indicates that the current ' + ' The ok icon indicates that the current ' 'title sort matches the current title')) bad_tooltip = '

'+textwrap.fill(base + '

' + _( - ' The red color warns that the current ' + ' The error icon warns that the current ' 'title sort does not match the current title. ' 'No action is required if this is what you want.')) self.tooltips = (ok_tooltip, bad_tooltip) @@ -314,8 +315,8 @@ class TitleSortEdit(TitleEdit, ToMetadataMixin): def update_state(self, *args): ts = title_sort(self.title_edit.current_val, lang=self.book_lang) normal = ts == self.current_val - self.setStyleSheet(QApplication.instance().stylesheet_for_line_edit(not normal)) tt = self.tooltips[0 if normal else 1] + self.update_status_actions(normal, tt) self.setToolTip(tt) self.setWhatsThis(tt) @@ -456,7 +457,7 @@ class AuthorsEdit(EditWithComplete, ToMetadataMixin): pass -class AuthorSortEdit(EnLineEdit, ToMetadataMixin): +class AuthorSortEdit(EnLineEdit, ToMetadataMixin, LineEditIndicators): TOOLTIP = _('Specify how the author(s) of this book should be sorted. ' 'For example Charles Dickens should be sorted as Dickens, ' @@ -470,15 +471,16 @@ class AuthorSortEdit(EnLineEdit, ToMetadataMixin): def __init__(self, parent, authors_edit, autogen_button, db, copy_a_to_as_action, copy_as_to_a_action, a_to_as, as_to_a): EnLineEdit.__init__(self, parent) + self.setup_status_actions() self.authors_edit = authors_edit self.db = db base = self.TOOLTIP ok_tooltip = '

' + textwrap.fill(base+'

' + _( - ' The green color indicates that the current ' + ' The ok icon indicates that the current ' 'author sort matches the current author')) bad_tooltip = '

'+textwrap.fill(base + '

'+ _( - ' The red color indicates that the current ' + ' The error icon indicates that the current ' 'author sort does not match the current author. ' 'No action is required if this is what you want.')) self.tooltips = (ok_tooltip, bad_tooltip) @@ -529,8 +531,8 @@ class AuthorSortEdit(EnLineEdit, ToMetadataMixin): au = self.author_sort_from_authors(string_to_authors(au)) normal = au == self.current_val - self.setStyleSheet(QApplication.instance().stylesheet_for_line_edit(not normal)) tt = self.tooltips[0 if normal else 1] + self.update_status_actions(normal, tt) self.setToolTip(tt) self.setWhatsThis(tt) @@ -1572,7 +1574,7 @@ class Identifiers(Dialog): Dialog.accept(self) -class IdentifiersEdit(QLineEdit, ToMetadataMixin): +class IdentifiersEdit(QLineEdit, ToMetadataMixin, LineEditIndicators): LABEL = _('&Ids:') BASE_TT = _('Edit the identifiers for this book. ' 'For example: \n\n%s\n\nIf an identifier value contains a comma, you can use the | character to represent it.')%( @@ -1582,6 +1584,7 @@ class IdentifiersEdit(QLineEdit, ToMetadataMixin): def __init__(self, parent): QLineEdit.__init__(self, parent) + self.setup_status_actions() self.pat = re.compile(r'[^0-9a-zA-Z]') self.textChanged.connect(self.validate) self.textChanged.connect(self.data_changed) @@ -1652,16 +1655,17 @@ class IdentifiersEdit(QLineEdit, ToMetadataMixin): isbn = identifiers.get('isbn', '') tt = self.BASE_TT extra = '' + ok = None if not isbn: - sheet = '' + pass elif check_isbn(isbn) is not None: - sheet = QApplication.instance().stylesheet_for_line_edit() + ok = True extra = '\n\n'+_('This ISBN is valid') else: - sheet = QApplication.instance().stylesheet_for_line_edit(True) + ok = False extra = '\n\n' + _('This ISBN is invalid') self.setToolTip(tt+extra) - self.setStyleSheet(sheet) + self.update_status_actions(ok, self.toolTip()) def paste_identifier(self): identifier_found = self.parse_clipboard_for_identifier() @@ -1751,6 +1755,9 @@ class IdentifiersEdit(QLineEdit, ToMetadataMixin): return False # }}} +class IndicatorLineEdit(QLineEdit, LineEditIndicators): + pass + class ISBNDialog(QDialog): # {{{ @@ -1763,7 +1770,8 @@ class ISBNDialog(QDialog): # {{{ l.addWidget(w, 0, 0, 1, 2) w = QLabel(_('ISBN:')) l.addWidget(w, 1, 0, 1, 1) - self.line_edit = w = QLineEdit() + self.line_edit = w = IndicatorLineEdit() + w.setup_status_actions() w.setText(txt) w.selectAll() w.textChanged.connect(self.checkText) @@ -1787,17 +1795,17 @@ class ISBNDialog(QDialog): # {{{ def checkText(self, txt): isbn = str(txt) + ok = None if not isbn: - sheet = '' - extra = '' + pass elif check_isbn(isbn) is not None: - sheet = QApplication.instance().stylesheet_for_line_edit() extra = _('This ISBN is valid') + ok = True else: - sheet = QApplication.instance().stylesheet_for_line_edit(True) extra = _('This ISBN is invalid') + ok = False self.line_edit.setToolTip(extra) - self.line_edit.setStyleSheet(sheet) + self.line_edit.update_status_actions(ok, extra) def text(self): return check_isbn(str(self.line_edit.text())) diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 4f2b5dc3ff..6070a5fe7a 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -158,7 +158,6 @@ class SearchBox2(QComboBox): # {{{ items.append(item) self.addItems(items) self.line_edit.setPlaceholderText(help_text) - self.colorize = colorize self.clear() def clear_history(self): @@ -210,10 +209,6 @@ class SearchBox2(QComboBox): # {{{ self.clear(emit_search=False) return self._in_a_search = ok - if self.colorize: - self.line_edit.setStyleSheet(QApplication.instance().stylesheet_for_line_edit(not ok)) - else: - self.line_edit.setStyleSheet('') # Comes from the lineEdit control def key_pressed(self, event): @@ -337,7 +332,7 @@ class SearchBoxMixin: # {{{ pass def init_search_box_mixin(self): - self.search.initialize('main_search_history', colorize=True, + self.search.initialize('main_search_history', help_text=_('Search (For advanced search click the gear icon to the left)')) self.search.cleared.connect(self.search_box_cleared) # Queued so that search.current_text will be correct diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index da290e9832..aff4faf2df 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -546,6 +546,35 @@ class EnLineEdit(LineEditECM, QLineEdit): # {{{ # }}} +# LineEditIndicators {{{ + +def setup_status_actions(self: QLineEdit): + self.status_actions = ( + self.addAction(QIcon.ic('ok.png'), QLineEdit.ActionPosition.TrailingPosition), + self.addAction(QIcon.ic('dialog_error.png'), QLineEdit.ActionPosition.TrailingPosition)) + self.status_actions[0].setVisible(False) + self.status_actions[1].setVisible(False) + +def update_status_actions(self: QLineEdit, ok, tooltip: str = ''): + self.status_actions[0].setVisible(bool(ok)) + self.status_actions[1].setVisible(not ok) + if ok: + self.status_actions[0].setToolTip(tooltip) + elif ok is None: + self.status_actions[1].setVisible(False) + else: + self.status_actions[1].setToolTip(tooltip) + +class LineEditIndicators: + + def setup_status_actions(self): + setup_status_actions(self) + + def update_status_actions(self, ok, tooltip=''): + update_status_actions(self, ok, tooltip) +# }}} + + class ItemsCompleter(QCompleter): # {{{ ''' From f6f43ba4af1066e55ab8ff77a14f2b24acf12e24 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 23 Jan 2023 13:38:05 +0530 Subject: [PATCH 0153/2055] Move to running winspeech in its own process --- src/calibre/utils/windows/winspeech.cpp | 230 ++++++++++++++++++++---- 1 file changed, 198 insertions(+), 32 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 11418b1e4c..9fd15def90 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -7,12 +7,16 @@ #include "common.h" #include +#include +#include #include #include +#include #include #include #include #include +#include #include #include #include @@ -28,23 +32,116 @@ using namespace winrt::Windows::Media::Core; using namespace winrt::Windows::Storage::Streams; typedef unsigned long long id_type; -static PyObject* -runtime_error_as_python_error(PyObject *exc_type, winrt::hresult_error const &ex, const char *file, const int line, const char *prefix="", PyObject *name=NULL) { - pyobject_raii msg(PyUnicode_FromWideChar(ex.message().c_str(), -1)); - const HRESULT hr = ex.to_abi(); - if (name) PyErr_Format(exc_type, "%s:%d:%s:[hr=0x%x] %V: %S", file, line, prefix, hr, msg.ptr(), "Out of memory", name); - else PyErr_Format(exc_type, "%s:%d:%s:[hr=0x%x] %V", file, line, prefix, hr, msg.ptr(), "Out of memory"); - return NULL; +static std::mutex output_lock; +static DWORD main_thread_id; +enum { + STDIN_FAILED = 1, + STDIN_MSG, + EXIT_REQUESTED +}; + +static std::string +serialize_string_for_json(std::string const &src) { + std::string ans("\""); + ans.reserve(src.size() + 16); + for (auto ch : src) { + switch(ch) { + case '\\': + ans += "\\\\"; break; + case '"': + ans += "\\\""; break; + case '\n': + ans += "\\n"; break; + case '\r': + ans += "\\r"; break; + default: + ans += ch; break; + } + } + ans += '"'; + return ans; +} + +class json_val { +private: + enum { DT_INT, DT_STRING, DT_LIST, DT_OBJECT, DT_NONE, DT_BOOL } type; + std::string s; + bool b; + long long i; + std::vector list; + std::map object; +public: + json_val() : type(DT_NONE) {} + json_val(bool x) : type(DT_BOOL), b(x) {} + json_val(std::string &&text) : type(DT_STRING), s(text) {} + json_val(std::string_view text) : type(DT_STRING), s(text) {} + json_val(long long num) : type(DT_INT), i(num) {} + json_val(std::vector &&items) : type(DT_LIST), list(items) {} + json_val(std::map &&m) : type(DT_OBJECT), object(m) {} + json_val(std::initializer_list> vals) : type(DT_OBJECT), object(vals) { } + + std::string serialize() const { + switch(type) { + case DT_NONE: + return "nil"; + case DT_BOOL: + return b ? "true" : "false"; + case DT_INT: + // this is not really correct since JS has various limits on numeric types, but good enough for us + return std::to_string(i); + case DT_STRING: + return serialize_string_for_json(s); + case DT_LIST: { + std::string ans("["); + ans.reserve(list.size() * 32); + for (auto const &i : list) { + ans += i.serialize(); + ans += ", "; + } + ans.erase(ans.size() - 2); ans += "]"; + return ans; + } + case DT_OBJECT: { + std::string ans("{"); + ans.reserve(object.size() * 64); + for (const auto& [key, value]: object) { + ans += serialize_string_for_json(key); + ans += ": "; + ans += value.serialize(); + ans += ", "; + } + ans.erase(ans.size() - 2); ans += "}"; + return ans; + } + } + return ""; + } +}; + +static void +output(std::string const &msg_type, json_val const &&msg) { + std::scoped_lock lock(output_lock); + std::cout << msg_type; + std::cout << " " << msg.serialize(); + std::cout << std::endl; +} + +static void +output_error(std::string_view const &msg, const char *file, long long line, HRESULT hr=S_OK) { + std::map m = {{"msg", json_val(msg)}, {"file", json_val(file)}, {"line", json_val(line)}}; + if (hr != S_OK) m["hr"] = json_val((long long)hr); + output("error", std::move(m)); } #define CATCH_ALL_EXCEPTIONS(msg) catch(winrt::hresult_error const& ex) { \ - runtime_error_as_python_error(PyExc_OSError, ex, __FILE__, __LINE__, msg); \ + output_error(std::string(msg) + std::string(": ") + winrt::to_string(ex.message()), __FILE__, __LINE__, ex.to_abi()); \ } catch (std::exception const &ex) { \ - PyErr_Format(PyExc_OSError, "%s:%d:%s: %s", __FILE__, __LINE__, msg, ex.what()); \ + output_error(std::string(msg) + std::string(": ") + ex.what(), __FILE__, __LINE__); \ } catch (...) { \ - PyErr_Format(PyExc_OSError, "%s:%d:%s: Unknown exception type was raised", __FILE__, __LINE__, msg); \ + output_error(std::string(msg) + std::string(": ") + "Unknown exception type was raised", __FILE__, __LINE__); \ } +/* Legacy code {{{ template class WeakRefs { @@ -347,7 +444,7 @@ voice_as_dict(VoiceInformation const& voice) { static PyObject* -all_voices(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTION +all_voices(PyObject*, PyObject*) { INITIALIZE_COM_IN_FUNCTION try { auto voices = SpeechSynthesizer::AllVoices(); pyobject_raii ans(PyTuple_New(voices.Size())); @@ -367,7 +464,7 @@ all_voices(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTION } static PyObject* -default_voice(PyObject* /*self*/, PyObject* /*args*/) { INITIALIZE_COM_IN_FUNCTION +default_voice(PyObject*, PyObject*) { INITIALIZE_COM_IN_FUNCTION try { return voice_as_dict(SpeechSynthesizer::DefaultVoice()); } CATCH_ALL_EXCEPTIONS("Could not get default voice"); @@ -404,34 +501,103 @@ pump_waiting_messages(PyObject*, PyObject*) { } +}}} */ + +static std::vector stdin_messages; +static std::mutex stdin_messages_lock; + +static void +post_message(LPARAM type, WPARAM data = 0) { + PostThreadMessageA(main_thread_id, WM_USER, data, type); +} + + +static winrt::fire_and_forget +run_input_loop(void) { + co_await winrt::resume_background(); + std::string line; + while(!std::cin.eof() && std::getline(std::cin, line)) { + if (line.size() > 0) { + { + std::scoped_lock lock(stdin_messages_lock); + stdin_messages.push_back(line); + } + post_message(STDIN_MSG); + } + } + post_message(STDIN_FAILED, std::cin.fail() ? 1 : 0); +} + +static winrt::fire_and_forget +handle_stdin_messages(void) { + co_await winrt::resume_background(); + std::scoped_lock lock(stdin_messages_lock); + for (auto const& msg : stdin_messages) { + try { + auto pos = msg.find(" "); + std::string rest; + std::string command = msg; + if (pos != std::string::npos) { + command = msg.substr(0, pos); + rest = msg.substr(pos + 1); + } + if (command == "exit") { + try { + post_message(EXIT_REQUESTED, std::stoi(rest)); + } catch(...) { + post_message(EXIT_REQUESTED); + } + break; + } + else if (command == "echo") { + output(command, {{"msg", json_val(std::move(rest))}}); + } + else output_error(std::string("Unknown command: ") + command, __FILE__, __LINE__); + } CATCH_ALL_EXCEPTIONS(std::string("Error handling input message: " + msg)); + } + stdin_messages.clear(); +} + +static PyObject* +run_main_loop(PyObject*, PyObject*) { + winrt::init_apartment(); + main_thread_id = GetCurrentThreadId(); + MSG msg; + unsigned long long exit_code = 0; + Py_BEGIN_ALLOW_THREADS; + PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); // ensure we have a message queue + + if (_isatty(_fileno(stdin))) { + std::cout << "Welcome to winspeech. Type exit to quit." << std::endl; + } + run_input_loop(); + + while (true) { + BOOL ret = GetMessage(&msg, NULL, 0, 0); + if (ret <= 0) { // WM_QUIT or error + exit_code = msg.message == WM_QUIT ? msg.wParam : 1; + break; + } + if (msg.message == WM_USER) { + if (msg.lParam == STDIN_FAILED || msg.lParam == EXIT_REQUESTED) { exit_code = msg.wParam; break; } + else if (msg.lParam == STDIN_MSG) handle_stdin_messages(); + } else { + DispatchMessage(&msg); + } + } + Py_END_ALLOW_THREADS; + return PyLong_FromUnsignedLongLong(exit_code); +} + #define M(name, args) { #name, name, args, ""} static PyMethodDef methods[] = { - M(all_voices, METH_NOARGS), - M(default_voice, METH_NOARGS), - M(pump_waiting_messages, METH_NOARGS), + M(run_main_loop, METH_NOARGS), {NULL, NULL, 0, NULL} }; #undef M - static int exec_module(PyObject *m) { - SynthesizerType.tp_name = "winspeech.Synthesizer"; - SynthesizerType.tp_doc = "Wrapper for SpeechSynthesizer"; - SynthesizerType.tp_basicsize = sizeof(Synthesizer); - SynthesizerType.tp_itemsize = 0; - SynthesizerType.tp_flags = Py_TPFLAGS_DEFAULT; - SynthesizerType.tp_new = Synthesizer_new; - SynthesizerType.tp_methods = Synthesizer_methods; - SynthesizerType.tp_dealloc = (destructor)Synthesizer_dealloc; - if (PyType_Ready(&SynthesizerType) < 0) return -1; - - Py_INCREF(&SynthesizerType); - if (PyModule_AddObject(m, "Synthesizer", (PyObject *) &SynthesizerType) < 0) { - Py_DECREF(&SynthesizerType); - return -1; - } - return 0; } From 5f4437f7fae44d6a1647c89dd4e83c853f6087f6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 23 Jan 2023 14:00:39 +0530 Subject: [PATCH 0154/2055] Port voices output --- src/calibre/utils/windows/winspeech.cpp | 47 ++++++++++++++++++++----- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 9fd15def90..5ac3f05cde 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -72,13 +72,37 @@ private: std::map object; public: json_val() : type(DT_NONE) {} - json_val(bool x) : type(DT_BOOL), b(x) {} json_val(std::string &&text) : type(DT_STRING), s(text) {} + json_val(const char *ns) : type(DT_STRING), s(ns) {} + json_val(winrt::hstring const& text) : type(DT_STRING), s(winrt::to_string(text)) {} json_val(std::string_view text) : type(DT_STRING), s(text) {} json_val(long long num) : type(DT_INT), i(num) {} json_val(std::vector &&items) : type(DT_LIST), list(items) {} json_val(std::map &&m) : type(DT_OBJECT), object(m) {} json_val(std::initializer_list> vals) : type(DT_OBJECT), object(vals) { } + json_val(bool x) : type(DT_BOOL), b(x) {} + + json_val(VoiceInformation const& voice) : type(DT_OBJECT) { + const char *gender = ""; + switch (voice.Gender()) { + case VoiceGender::Male: gender = "male"; break; + case VoiceGender::Female: gender = "female"; break; + } + object = { + {"display_name", json_val(voice.DisplayName())}, + {"description", json_val(voice.Description())}, + {"id", json_val(voice.Id())}, + {"language", json_val(voice.Language())}, + {"gender", json_val(gender)}, + }; + } + + json_val(IVectorView const& voices) : type(DT_LIST) { + list.reserve(voices.Size()); + for(auto const& voice : voices) { + list.emplace_back(json_val(voice)); + } + } std::string serialize() const { switch(type) { @@ -127,18 +151,19 @@ output(std::string const &msg_type, json_val const &&msg) { } static void -output_error(std::string_view const &msg, const char *file, long long line, HRESULT hr=S_OK) { +output_error(std::string_view const &msg, const char *file, long long line, HRESULT hr=S_OK, std::string const &key = "") { std::map m = {{"msg", json_val(msg)}, {"file", json_val(file)}, {"line", json_val(line)}}; if (hr != S_OK) m["hr"] = json_val((long long)hr); + if (key.size() > 0) m["key"] = json_val(key); output("error", std::move(m)); } -#define CATCH_ALL_EXCEPTIONS(msg) catch(winrt::hresult_error const& ex) { \ - output_error(std::string(msg) + std::string(": ") + winrt::to_string(ex.message()), __FILE__, __LINE__, ex.to_abi()); \ +#define CATCH_ALL_EXCEPTIONS(msg, key) catch(winrt::hresult_error const& ex) { \ + output_error(winrt::to_string(ex.message()), __FILE__, __LINE__, ex.to_abi(), key); \ } catch (std::exception const &ex) { \ - output_error(std::string(msg) + std::string(": ") + ex.what(), __FILE__, __LINE__); \ + output_error(ex.what(), __FILE__, __LINE__, S_OK, key); \ } catch (...) { \ - output_error(std::string(msg) + std::string(": ") + "Unknown exception type was raised", __FILE__, __LINE__); \ + output_error("Unknown exception type was raised", __FILE__, __LINE__, S_OK, key); \ } /* Legacy code {{{ @@ -552,8 +577,14 @@ handle_stdin_messages(void) { else if (command == "echo") { output(command, {{"msg", json_val(std::move(rest))}}); } - else output_error(std::string("Unknown command: ") + command, __FILE__, __LINE__); - } CATCH_ALL_EXCEPTIONS(std::string("Error handling input message: " + msg)); + else if (command == "default_voice") { + output("default_voice", SpeechSynthesizer::DefaultVoice()); + } + else if (command == "all_voices") { + output("all_voices", SpeechSynthesizer::AllVoices()); + } + else output_error("Unknown command" , __FILE__, __LINE__, S_OK, command); + } CATCH_ALL_EXCEPTIONS(std::string("Error handling input message"), msg); } stdin_messages.clear(); } From d17c1c6ce84d05021466be50cf5bdddf79dab871 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 23 Jan 2023 16:27:19 +0530 Subject: [PATCH 0155/2055] usbobserver: use surrogateescape for undecodable file names --- src/calibre/devices/usbobserver/usbobserver.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/usbobserver/usbobserver.c b/src/calibre/devices/usbobserver/usbobserver.c index 8165332cd3..7268380e63 100644 --- a/src/calibre/devices/usbobserver/usbobserver.c +++ b/src/calibre/devices/usbobserver/usbobserver.c @@ -283,7 +283,11 @@ usbobserver_get_mounted_filesystems(PyObject *self, PyObject *args) { for (i = 0 ; i < num; i++) { val = PyUnicode_FromString(buf[i].f_mntonname); - if (!val) { NUKE(ans); goto end; } + if (!val) { + PyErr_Clear(); + val = PyUnicode_DecodeLocale(buf[i].f_mntonname, "surrogateescape"); + if (!val) { NUKE(ans); goto end; } + } if (PyDict_SetItemString(ans, buf[i].f_mntfromname, val) != 0) { NUKE(ans); NUKE(val); goto end; } NUKE(val); } From d6e1365432375a977e349d9fbb66ae87c2406e6f Mon Sep 17 00:00:00 2001 From: ping Date: Mon, 23 Jan 2023 20:18:00 +0800 Subject: [PATCH 0156/2055] Fix Nature cover resolution --- recipes/nature.recipe | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/recipes/nature.recipe b/recipes/nature.recipe index 91eaa74328..60ff8864a7 100644 --- a/recipes/nature.recipe +++ b/recipes/nature.recipe @@ -1,5 +1,5 @@ #!/usr/bin/env python - +import re from collections import defaultdict from calibre.web.feeds.news import BasicNewsRecipe, classes @@ -55,7 +55,7 @@ class Nature(BasicNewsRecipe): 'img', attrs={'data-test': check_words('issue-cover-image')} )['src'] try: - self.cover_url = self.cover_url.replace("w200", "w500") # enlarge cover size resolution + self.cover_url = re.sub(r"\bw\d+\b", "w1000", self.cover_url) # enlarge cover size resolution except: """ failed, img src might have changed, use default width 200 From cf570250b2950e6f9b0f4e55972a71bef2ab6d53 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 23 Jan 2023 21:33:58 +0530 Subject: [PATCH 0157/2055] More work on winspeech --- src/calibre/utils/windows/winspeech.cpp | 122 +++++++++++++++++++----- 1 file changed, 96 insertions(+), 26 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 5ac3f05cde..71c063e617 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,60 @@ enum { EXIT_REQUESTED }; +// trim from start (in place) +static inline void +ltrim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { + return !std::isspace(ch); + })); +} + +// trim from end (in place) +static inline void +rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { + return !std::isspace(ch); + }).base(), s.end()); +} + +static std::vector +split(std::string const &src_, std::string const &delim = " ") { + size_t pos; + std::vector ans; ans.reserve(16); + std::string_view sv(src_); + while ((pos = sv.find(delim)) != std::string_view::npos) { + if (pos > 0) ans.emplace_back(sv.substr(0, pos)); + sv = sv.substr(pos + 1); + } + if (sv.size() > 0) ans.emplace_back(sv); + return ans; +} + +static std::string +join(std::vector parts, std::string const &delim = " ") { + std::string ans; ans.reserve(1024); + for (auto const &x : parts) { + ans.append(x); + ans.append(delim); + } + ans.erase(ans.size() - delim.size()); + return ans; +} + +static id_type +parse_id(std::string_view const& s) { + id_type ans = 0; + for (auto ch : s) { + auto delta = ch - '0'; + if (delta < 0 || delta > 9) { + throw std::invalid_argument(std::string("Not a valid id: ") + std::string(s)); + } + ans = (ans * 10) + delta; + } + return ans; +} + + static std::string serialize_string_for_json(std::string const &src) { std::string ans("\""); @@ -143,27 +198,26 @@ public: }; static void -output(std::string const &msg_type, json_val const &&msg) { +output(id_type cmd_id, std::string_view const &msg_type, json_val const &&msg) { std::scoped_lock lock(output_lock); - std::cout << msg_type; - std::cout << " " << msg.serialize(); - std::cout << std::endl; + std::cout << cmd_id << " " << msg_type << " " << msg.serialize() << std::endl; } static void -output_error(std::string_view const &msg, const char *file, long long line, HRESULT hr=S_OK, std::string const &key = "") { - std::map m = {{"msg", json_val(msg)}, {"file", json_val(file)}, {"line", json_val(line)}}; +output_error(id_type cmd_id, std::string_view const &msg, std::string_view const &error, long long line, HRESULT hr=S_OK) { + std::map m = {{"msg", json_val(msg)}, {"error", json_val(error)}, {"file", json_val("winspeech.cpp")}, {"line", json_val(line)}}; if (hr != S_OK) m["hr"] = json_val((long long)hr); - if (key.size() > 0) m["key"] = json_val(key); - output("error", std::move(m)); + output(cmd_id, "error", std::move(m)); } -#define CATCH_ALL_EXCEPTIONS(msg, key) catch(winrt::hresult_error const& ex) { \ - output_error(winrt::to_string(ex.message()), __FILE__, __LINE__, ex.to_abi(), key); \ +#define CATCH_ALL_EXCEPTIONS(msg, cmd_id) catch(winrt::hresult_error const& ex) { \ + output_error(cmd_id, msg, winrt::to_string(ex.message()), __LINE__, ex.to_abi()); \ } catch (std::exception const &ex) { \ - output_error(ex.what(), __FILE__, __LINE__, S_OK, key); \ + output_error(cmd_id, msg, ex.what(), __LINE__); \ +} catch (std::string const &ex) { \ + output_error(cmd_id, msg, ex, __LINE__); \ } catch (...) { \ - output_error("Unknown exception type was raised", __FILE__, __LINE__, S_OK, key); \ + output_error(cmd_id, msg, "Unknown exception type was raised", __LINE__); \ } /* Legacy code {{{ @@ -553,38 +607,54 @@ run_input_loop(void) { post_message(STDIN_FAILED, std::cin.fail() ? 1 : 0); } +static void +handle_speak(id_type cmd_id, std::vector &parts) { +} + static winrt::fire_and_forget handle_stdin_messages(void) { co_await winrt::resume_background(); std::scoped_lock lock(stdin_messages_lock); - for (auto const& msg : stdin_messages) { + std::vector parts; + std::string_view command; + id_type cmd_id; + for (auto & msg : stdin_messages) { + rtrim(msg); + bool ok = false; + if (msg == "exit") { + post_message(EXIT_REQUESTED); + break; + } + try { + parts = split(msg); + command = parts.at(1); cmd_id = parse_id(parts.at(0)); + parts.erase(parts.begin(), parts.begin() + 2); + ok = true; + } CATCH_ALL_EXCEPTIONS((std::string("Invalid input message: ") + msg), 0); + if (!ok) continue; try { - auto pos = msg.find(" "); - std::string rest; - std::string command = msg; - if (pos != std::string::npos) { - command = msg.substr(0, pos); - rest = msg.substr(pos + 1); - } if (command == "exit") { try { - post_message(EXIT_REQUESTED, std::stoi(rest)); + post_message(EXIT_REQUESTED, parse_id(parts.at(2))); } catch(...) { post_message(EXIT_REQUESTED); } break; } else if (command == "echo") { - output(command, {{"msg", json_val(std::move(rest))}}); + output(cmd_id, command, {{"msg", json_val(std::move(join(parts)))}}); } else if (command == "default_voice") { - output("default_voice", SpeechSynthesizer::DefaultVoice()); + output(cmd_id, "default_voice", SpeechSynthesizer::DefaultVoice()); } else if (command == "all_voices") { - output("all_voices", SpeechSynthesizer::AllVoices()); + output(cmd_id, "all_voices", SpeechSynthesizer::AllVoices()); } - else output_error("Unknown command" , __FILE__, __LINE__, S_OK, command); - } CATCH_ALL_EXCEPTIONS(std::string("Error handling input message"), msg); + else if (command == "speak") { + handle_speak(cmd_id, parts); + } + else throw std::string("Unknown command: ") + std::string(command); + } CATCH_ALL_EXCEPTIONS("Error handling input message", cmd_id); } stdin_messages.clear(); } From 7aec1cda0dfcd0d7246ef79056d6b59ad38bfd17 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 23 Jan 2023 21:47:21 +0530 Subject: [PATCH 0158/2055] Fix #2003729 [RecursionError when embedding metadata into PDF](https://bugs.launchpad.net/calibre/+bug/2003729) --- src/calibre/db/cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index cab398e9fa..0b5e0eda77 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2651,7 +2651,7 @@ class Cache: fmts = field.table.book_col_map.get(book_id, ()) if not fmts: continue - mi = self.get_metadata(book_id, get_cover=True, cover_as_data=True) + mi = self._get_metadata(book_id, get_cover=True, cover_as_data=True) try: path = self._field_for('path', book_id).replace('/', os.sep) except: From 6bc65e9dc7d677256e2fafbc8824b77c7411cc80 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 23 Jan 2023 22:23:56 +0530 Subject: [PATCH 0159/2055] Start work on porting speak() --- src/calibre/utils/windows/winspeech.cpp | 64 +++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 71c063e617..686db5a6a6 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -607,8 +607,56 @@ run_input_loop(void) { post_message(STDIN_FAILED, std::cin.fail() ? 1 : 0); } +struct Revokers { + MediaPlaybackSession::PlaybackStateChanged_revoker playback_state_changed; + MediaPlayer::MediaEnded_revoker media_ended; MediaPlayer::MediaOpened_revoker media_opened; + MediaPlayer::MediaFailed_revoker media_failed; MediaPlayer::SourceChanged_revoker source_changed; + + MediaPlaybackItem::TimedMetadataTracksChanged_revoker timed_metadata_tracks_changed; + std::vector cue_entered; + std::vector cue_exited; + std::vector track_failed; +}; + +class Synthesizer { + private: + SpeechSynthesizer synth{nullptr}; + MediaPlayer player{nullptr}; + MediaSource current_source{nullptr}; + SpeechSynthesisStream current_stream{nullptr}; + MediaPlaybackItem current_item{nullptr}; + id_type current_cmd_id; + + Revokers revoker; + std::recursive_mutex lock; + + public: + void initialize() { + synth = SpeechSynthesizer(); + player = MediaPlayer(); + } + void stop_current_activity() { + std::scoped_lock sl(lock); + if (current_cmd_id) { + current_cmd_id = 0; + revoker = {}; + current_source = MediaSource{nullptr}; + current_stream = SpeechSynthesisStream{nullptr}; + current_item = MediaPlaybackItem{nullptr}; + player.Pause(); + } + } + +}; + +static Synthesizer sx; + static void handle_speak(id_type cmd_id, std::vector &parts) { + bool is_ssml = parts.at(0) == "ssml"; parts.erase(parts.begin()); + bool is_shm = parts.at(0) == "shm"; parts.erase(parts.begin()); + auto address = join(parts); + if (address.size() == 0) throw std::string("Address missing"); } static winrt::fire_and_forget @@ -628,6 +676,9 @@ handle_stdin_messages(void) { try { parts = split(msg); command = parts.at(1); cmd_id = parse_id(parts.at(0)); + if (cmd_id == 0) { + throw std::exception("Command id of zero is not allowed"); + } parts.erase(parts.begin(), parts.begin() + 2); ok = true; } CATCH_ALL_EXCEPTIONS((std::string("Invalid input message: ") + msg), 0); @@ -665,6 +716,14 @@ run_main_loop(PyObject*, PyObject*) { main_thread_id = GetCurrentThreadId(); MSG msg; unsigned long long exit_code = 0; + bool ok = false; + try { + new (&sx) Synthesizer(); + sx.initialize(); + ok = true; + } CATCH_ALL_EXCEPTIONS("Error initializing Synthesizer", 0); + if (!ok) return PyLong_FromUnsignedLongLong(1); + Py_BEGIN_ALLOW_THREADS; PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); // ensure we have a message queue @@ -687,6 +746,11 @@ run_main_loop(PyObject*, PyObject*) { } } Py_END_ALLOW_THREADS; + try { + sx.stop_current_activity(); + (&sx)->~Synthesizer(); + } CATCH_ALL_EXCEPTIONS("Error stopping all activity", 0); + return PyLong_FromUnsignedLongLong(exit_code); } From 687b340b1c7a0e9ccfc7c30b116ee09a52c00bb0 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 23 Jan 2023 19:22:59 +0000 Subject: [PATCH 0160/2055] Enhancement #2003712: URL scheme: 'virtual_library' for show-book. I will update the documentation in a later commit. --- src/calibre/gui2/ui.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index c545397146..ec6975259b 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -675,6 +675,16 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ return bytes.fromhex(x[6:]).decode('utf-8') return x + def get_virtual_library(query): + vl = None + if query.get('encoded_virtual_library'): + vl = bytes.fromhex(query.get('encoded_virtual_library')[0]).decode('utf-8') + elif query.get('virtual_library'): + vl = query.get('virtual_library')[0] + if vl == '-': + vl = None + return vl + if action == 'switch-library': library_id = decode_library_id(posixpath.basename(path)) library_path = self.library_broker.path_for_library_id(library_id) @@ -694,8 +704,11 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ library_path = self.library_broker.path_for_library_id(library_id) if library_path is None: return + vl = get_virtual_library(query) def doit(): + if vl != '_': + self.apply_virtual_library(vl) rows = self.library_view.select_rows((book_id,)) db = self.current_db if not rows and (db.data.get_base_restriction_name() or db.data.get_search_restriction_name()): @@ -743,13 +756,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ if sq: sq = sq[0] sq = sq or '' - vl = None - if query.get('encoded_virtual_library'): - vl = bytes.fromhex(query.get('encoded_virtual_library')[0]).decode('utf-8') - elif query.get('virtual_library'): - vl = query.get('virtual_library')[0] - if vl == '-': - vl = None + vl = get_virtual_library(query) def doit(): if vl != '_': From ba79438b31c2ce2cdc6194bd8cf313fc492947cf Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 23 Jan 2023 20:24:26 +0000 Subject: [PATCH 0161/2055] 1) Maintain compatibility with URLS that don't specify a virtual library. 2) Documentation. --- manual/url_scheme.rst | 12 ++++++++++++ src/calibre/gui2/ui.py | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/manual/url_scheme.rst b/manual/url_scheme.rst index 7ec7bae5cb..108e6d773b 100644 --- a/manual/url_scheme.rst +++ b/manual/url_scheme.rst @@ -56,6 +56,18 @@ brackets at the end of the path to the book folder. You can copy a link to the current book displayed in calibre by right clicking the :guilabel:`Book details` panel and choosing :guilabel:`Copy link to book`. +If a Virtual library is selected, calibre will use it when showing the book. If +the book isn't found in that virtual library then the virtual library is cleared. + +If you want to switch to a particular Virtual library when showing the book, use:: + + calibre://show-book/Library_Name/book_id?virtual_library=Library%20Name + or + calibre://show-book/Library_Name/book_id?encoded_virtual_library=hex_encoded_virtual_library_name + +replacing spaces in the Virtual library name by ``%20``. If the book isn't in that +virtual library then it is ignored. + Open a specific book in the E-book viewer at a specific position ------------------------------------------------------------------- diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index ec6975259b..ef19689963 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -707,7 +707,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ vl = get_virtual_library(query) def doit(): - if vl != '_': + # To maintain compatibility, don't change the VL if it isn't specified. + if vl is not None and vl != '_': self.apply_virtual_library(vl) rows = self.library_view.select_rows((book_id,)) db = self.current_db From ce490257704f8ddf03ea04c362f3753ba3e33181 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 24 Jan 2023 07:34:53 +0530 Subject: [PATCH 0162/2055] Also sort the actions in the quick highlights preferences --- src/pyj/read_book/prefs/selection.pyj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pyj/read_book/prefs/selection.pyj b/src/pyj/read_book/prefs/selection.pyj index 45a34c8fe5..11cbddae29 100644 --- a/src/pyj/read_book/prefs/selection.pyj +++ b/src/pyj/read_book/prefs/selection.pyj @@ -126,7 +126,9 @@ def update_quick_action_table(): c.style.display = 'flex' c.style.flexWrap = 'wrap' current = {x: True for x in JSON.parse(c.dataset.actions)} - for hs in all_styles(): + actions = list(all_styles()) + actions.pysort(key=def (a): return (a.friendly_name or '').toLowerCase();) + for hs in actions: c.appendChild(E.label( style='margin: 1ex; display: flex; align-contents: center', hs.make_swatch(E.span(), is_dark_theme()), From 603dcc777e873a625c11f6b0bb827abdfb7c7a3d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 24 Jan 2023 11:55:36 +0530 Subject: [PATCH 0163/2055] Special case zero font size when calculating columns per screen in paged mode --- src/pyj/read_book/paged_mode.pyj | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/pyj/read_book/paged_mode.pyj b/src/pyj/read_book/paged_mode.pyj index 9db51a3ba5..3b417a6b2e 100644 --- a/src/pyj/read_book/paged_mode.pyj +++ b/src/pyj/read_book/paged_mode.pyj @@ -152,18 +152,21 @@ def cps_by_em_size(): ans = cps_by_em_size.ans fs = window.getComputedStyle(document.body).fontSize if not ans or cps_by_em_size.at_font_size is not fs: - d = document.createElement('span') - d.style.position = 'absolute' - d.style.visibility = 'hidden' - d.style.width = '1rem' - d.style.fontSize = '1rem' - d.style.paddingTop = d.style.paddingBottom = d.style.paddingLeft = d.style.paddingRight = '0' - d.style.marginTop = d.style.marginBottom = d.style.marginLeft = d.style.marginRight = '0' - d.style.borderStyle = 'none' - document.body.appendChild(d) - w = d.clientWidth - document.body.removeChild(d) - ans = cps_by_em_size.ans = max(2, w) + if fs is 0: + ans = cps_by_em_size.ans = 16 + else: + d = document.createElement('span') + d.style.position = 'absolute' + d.style.visibility = 'hidden' + d.style.width = '1rem' + d.style.fontSize = '1rem' + d.style.paddingTop = d.style.paddingBottom = d.style.paddingLeft = d.style.paddingRight = '0' + d.style.marginTop = d.style.marginBottom = d.style.marginLeft = d.style.marginRight = '0' + d.style.borderStyle = 'none' + document.body.appendChild(d) + w = d.clientWidth + document.body.removeChild(d) + ans = cps_by_em_size.ans = max(2, w) cps_by_em_size.at_font_size = fs return ans From c6ec13ae7e42bc76ddaaf37d98d9dbe2405df93a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 24 Jan 2023 11:57:43 +0530 Subject: [PATCH 0164/2055] More work on winspeech --- src/calibre/utils/windows/winspeech.cpp | 123 +++++++++++++++++++++--- 1 file changed, 112 insertions(+), 11 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 686db5a6a6..1e019575a2 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -6,6 +6,7 @@ */ #include "common.h" +#include #include #include #include @@ -34,6 +35,7 @@ using namespace winrt::Windows::Storage::Streams; typedef unsigned long long id_type; static std::mutex output_lock; +static std::atomic_bool main_loop_is_running; static DWORD main_thread_id; enum { STDIN_FAILED = 1, @@ -159,6 +161,17 @@ public: } } + json_val(MediaPlaybackState const& state) : type(DT_STRING) { + switch(state) { + case MediaPlaybackState::None: s = "none"; break; + case MediaPlaybackState::Opening: s = "opening"; break; + case MediaPlaybackState::Buffering: s = "buffering"; break; + case MediaPlaybackState::Playing: s = "playing"; break; + case MediaPlaybackState::Paused: s = "paused"; break; + default: s = "unknown"; break; + } + } + std::string serialize() const { switch(type) { case DT_NONE: @@ -199,8 +212,10 @@ public: static void output(id_type cmd_id, std::string_view const &msg_type, json_val const &&msg) { - std::scoped_lock lock(output_lock); - std::cout << cmd_id << " " << msg_type << " " << msg.serialize() << std::endl; + std::scoped_lock sl(output_lock); + try { + std::cout << cmd_id << " " << msg_type << " " << msg.serialize() << std::endl; + } catch(...) {} } static void @@ -598,7 +613,7 @@ run_input_loop(void) { while(!std::cin.eof() && std::getline(std::cin, line)) { if (line.size() > 0) { { - std::scoped_lock lock(stdin_messages_lock); + std::scoped_lock sl(stdin_messages_lock); stdin_messages.push_back(line); } post_message(STDIN_MSG); @@ -625,20 +640,55 @@ class Synthesizer { MediaSource current_source{nullptr}; SpeechSynthesisStream current_stream{nullptr}; MediaPlaybackItem current_item{nullptr}; - id_type current_cmd_id; + std::atomic current_cmd_id; Revokers revoker; - std::recursive_mutex lock; + std::recursive_mutex recursive_lock; + void load_stream_for_playback(SpeechSynthesisStream const &stream, id_type cmd_id) { + std::scoped_lock sl(recursive_lock); + if (cmd_id != current_cmd_id.load()) return; + revoker.playback_state_changed = player.PlaybackSession().PlaybackStateChanged( + winrt::auto_revoke, [cmd_id](auto session, auto const&) { + if (main_loop_is_running.load()) sx.output( + cmd_id, "playback_state_changed", {{"state", json_val(session.PlaybackState())}}); + }); + revoker.media_opened = player.MediaOpened(winrt::auto_revoke, [cmd_id](auto player, auto const&) { + if (main_loop_is_running.load()) sx.output( + cmd_id, "media_state_changed", {{"state", json_val("opened")}}); + }); + revoker.media_ended = player.MediaEnded(winrt::auto_revoke, [cmd_id](auto player, auto const&) { + if (main_loop_is_running.load()) sx.output( + cmd_id, "media_state_changed", {{"state", json_val("ended")}}); + }); + revoker.media_failed = player.MediaFailed(winrt::auto_revoke, [cmd_id](auto player, auto const&) { + if (main_loop_is_running.load()) sx.output( + cmd_id, "media_state_changed", {{"state", json_val("failed")}}); + }); + current_stream = stream; + current_source = MediaSource::CreateFromStream(current_stream, current_stream.ContentType()); + current_item = MediaPlaybackItem(current_source); + player.Source(current_item); + } public: + bool cmd_id_is_current(id_type cmd_id) const noexcept { return current_cmd_id.load() == cmd_id; } + void output(id_type cmd_id, std::string_view const& type, json_val const& x) { + std::scoped_lock sl(recursive_lock); + if (cmd_id_is_current(cmd_id)) output(cmd_id, type, x); + } void initialize() { synth = SpeechSynthesizer(); + synth.Options().IncludeSentenceBoundaryMetadata(true); + synth.Options().IncludeWordBoundaryMetadata(true); player = MediaPlayer(); + player.AudioCategory(MediaPlayerAudioCategory::Speech); + player.AutoPlay(true); } + void stop_current_activity() { - std::scoped_lock sl(lock); - if (current_cmd_id) { - current_cmd_id = 0; + std::scoped_lock sl(recursive_lock); + if (current_cmd_id.load()) { + current_cmd_id.store(0); revoker = {}; current_source = MediaSource{nullptr}; current_stream = SpeechSynthesisStream{nullptr}; @@ -647,22 +697,71 @@ class Synthesizer { } } + winrt::fire_and_forget speak(id_type cmd_id, std::wstring_view const &text, bool is_ssml) { + winrt::apartment_context main_thread; // capture calling thread + SpeechSynthesisStream stream{nullptr}; + { std::scoped_lock sl(recursive_lock); + stop_current_activity(); + current_cmd_id.store(cmd_id); + } + if (is_ssml) stream = co_await synth.SynthesizeSsmlToStreamAsync(text); + else stream = co_await synth.SynthesizeTextToStreamAsync(text); + co_await main_thread; + if (main_loop_is_running.load()) { + load_stream_for_playback(stream, cmd_id); + } + } + }; static Synthesizer sx; +static inline std::wstring +decode_utf8(std::string_view const& src) { + std::wstring ans(src.length() + 1, 0); + size_t count = MultiByteToWideChar(CP_UTF8, 0, src.data(), (int)src.length(), ans.data(), (int)ans.length()); + if (count == 0) { + switch(GetLastError()) { + case ERROR_INSUFFICIENT_BUFFER: + throw std::exception("Could not convert UTF-8 to UTF-16: buffer too small"); + case ERROR_INVALID_PARAMETER: + throw std::exception("Could not convert UTF-8 to UTF-16: invalid parameter"); + case ERROR_NO_UNICODE_TRANSLATION: + throw std::exception("Could not convert UTF-8 to UTF-16: invalid UTF-8 encountered"); + default: + throw std::exception("Could not convert UTF-8 to UTF-16: unknown error"); + } + } + count++; // ensure trailing null + if (ans.length() > count) { + auto extra = ans.length() - count; + ans.erase(ans.length() - extra, extra); + } + return ans; +} + static void handle_speak(id_type cmd_id, std::vector &parts) { - bool is_ssml = parts.at(0) == "ssml"; parts.erase(parts.begin()); - bool is_shm = parts.at(0) == "shm"; parts.erase(parts.begin()); + bool is_ssml = false, is_shm = false; + try { + is_ssml = parts.at(0) == "ssml"; + is_shm = parts.at(1) == "shm"; + } catch (std::exception const&) { + throw std::string("Not a well formed speak command"); + } + parts.erase(parts.begin(), parts.begin() + 2); auto address = join(parts); if (address.size() == 0) throw std::string("Address missing"); + if (is_shm) { + throw std::string("TODO: Implement support for SHM"); + } + sx.speak(cmd_id, decode_utf8(address), is_ssml); } static winrt::fire_and_forget handle_stdin_messages(void) { co_await winrt::resume_background(); - std::scoped_lock lock(stdin_messages_lock); + std::scoped_lock sl(stdin_messages_lock); std::vector parts; std::string_view command; id_type cmd_id; @@ -725,6 +824,7 @@ run_main_loop(PyObject*, PyObject*) { if (!ok) return PyLong_FromUnsignedLongLong(1); Py_BEGIN_ALLOW_THREADS; + main_loop_is_running.store(true); PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); // ensure we have a message queue if (_isatty(_fileno(stdin))) { @@ -745,6 +845,7 @@ run_main_loop(PyObject*, PyObject*) { DispatchMessage(&msg); } } + main_loop_is_running.store(false); Py_END_ALLOW_THREADS; try { sx.stop_current_activity(); From 7b47a70493edaf3998486d81179ddeb41ec989a1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 24 Jan 2023 14:12:51 +0530 Subject: [PATCH 0165/2055] Get basic speaking working again --- src/calibre/utils/windows/winspeech.cpp | 15 ++++++++--- src/calibre/utils/windows/winspeech.py | 35 ++++++++++++++++++++++--- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 1e019575a2..9da95078ed 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -34,6 +34,13 @@ using namespace winrt::Windows::Media::Core; using namespace winrt::Windows::Storage::Streams; typedef unsigned long long id_type; +#define debug(format_string, ...) { \ + std::scoped_lock _sl_(output_lock); \ + DWORD _tid_ = GetCurrentThreadId(); \ + char _buf_[64] = {0}; snprintf(_buf_, sizeof(_buf_)-1, "thread-%u", _tid_); \ + fprintf(stderr, "%s " format_string "\n", main_thread_id == _tid_ ? "thread-main" : _buf_, __VA_ARGS__); fflush(stderr);\ +} + static std::mutex output_lock; static std::atomic_bool main_loop_is_running; static DWORD main_thread_id; @@ -119,7 +126,7 @@ serialize_string_for_json(std::string const &src) { return ans; } -class json_val { +class json_val { // {{{ private: enum { DT_INT, DT_STRING, DT_LIST, DT_OBJECT, DT_NONE, DT_BOOL } type; std::string s; @@ -208,7 +215,7 @@ public: } return ""; } -}; +}; // }}} static void output(id_type cmd_id, std::string_view const &msg_type, json_val const &&msg) { @@ -672,9 +679,9 @@ class Synthesizer { } public: bool cmd_id_is_current(id_type cmd_id) const noexcept { return current_cmd_id.load() == cmd_id; } - void output(id_type cmd_id, std::string_view const& type, json_val const& x) { + void output(id_type cmd_id, std::string_view const& type, json_val const && x) { std::scoped_lock sl(recursive_lock); - if (cmd_id_is_current(cmd_id)) output(cmd_id, type, x); + if (cmd_id_is_current(cmd_id)) ::output(cmd_id, type, std::move(x)); } void initialize() { synth = SpeechSynthesizer(); diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index c8d98cefa4..b2d66ffe86 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -2,10 +2,37 @@ # License: GPLv3 Copyright: 2023, Kovid Goyal -import calibre_extensions.winspeech as winspeech +import sys +import time +from contextlib import closing +from threading import Thread -winspeech +from calibre.utils.ipc.simple_worker import start_pipe_worker -def develop(): - pass +def develop_speech(text='Lucca brazzi sleeps with the fishes'): + p = start_pipe_worker('from calibre_extensions.winspeech import run_main_loop; run_main_loop()') + print('\x1b[32mSpeaking', text, '\x1b[39m', flush=True) + + def echo_output(p): + for line in p.stdout: + sys.stdout.buffer.write(b'\x1b[33m' + line + b'\x1b[39m') + sys.stdout.buffer.flush() + + def send(*a): + cmd = ' '.join(map(str, a)) + '\n' + p.stdin.write(cmd.encode()) + p.stdin.flush() + + Thread(name='Echo', target=echo_output, args=(p,), daemon=True).start() + with closing(p.stdin), closing(p.stdout): + try: + send('1 echo Synthesizer started') + send('2 speak text inline', text) + time.sleep(6) + send('3 echo Synthesizer exiting') + send('exit') + time.sleep(1) + finally: + if p.poll() is None: + p.kill() From 61a16f78a8519fb61eba03b374f22c641ab181e3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 24 Jan 2023 17:11:55 +0530 Subject: [PATCH 0166/2055] No need for event loop just use co-routines --- src/calibre/utils/windows/winspeech.cpp | 178 +++++++++++------------- 1 file changed, 78 insertions(+), 100 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 9da95078ed..51b081ad4b 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -66,12 +67,12 @@ rtrim(std::string &s) { }).base(), s.end()); } -static std::vector -split(std::string const &src_, std::string const &delim = " ") { +static std::vector +split(std::wstring_view const &src, std::wstring const &delim = L" ") { size_t pos; - std::vector ans; ans.reserve(16); - std::string_view sv(src_); - while ((pos = sv.find(delim)) != std::string_view::npos) { + std::vector ans; ans.reserve(16); + std::wstring_view sv(src); + while ((pos = sv.find(delim)) != std::wstring_view::npos) { if (pos > 0) ans.emplace_back(sv.substr(0, pos)); sv = sv.substr(pos + 1); } @@ -79,9 +80,9 @@ split(std::string const &src_, std::string const &delim = " ") { return ans; } -static std::string -join(std::vector parts, std::string const &delim = " ") { - std::string ans; ans.reserve(1024); +static std::wstring +join(std::vector parts, std::wstring const &delim = L" ") { + std::wstring ans; ans.reserve(1024); for (auto const &x : parts) { ans.append(x); ans.append(delim); @@ -91,12 +92,12 @@ join(std::vector parts, std::string const &delim = " ") { } static id_type -parse_id(std::string_view const& s) { +parse_id(std::wstring_view const& s) { id_type ans = 0; for (auto ch : s) { auto delta = ch - '0'; if (delta < 0 || delta > 9) { - throw std::invalid_argument(std::string("Not a valid id: ") + std::string(s)); + throw std::wstring(L"Not a valid id: ") + std::wstring(s); } ans = (ans * 10) + delta; } @@ -139,6 +140,7 @@ public: json_val(std::string &&text) : type(DT_STRING), s(text) {} json_val(const char *ns) : type(DT_STRING), s(ns) {} json_val(winrt::hstring const& text) : type(DT_STRING), s(winrt::to_string(text)) {} + json_val(std::wstring const& text) : type(DT_STRING), s(winrt::to_string(text)) {} json_val(std::string_view text) : type(DT_STRING), s(text) {} json_val(long long num) : type(DT_INT), i(num) {} json_val(std::vector &&items) : type(DT_LIST), list(items) {} @@ -232,12 +234,15 @@ output_error(id_type cmd_id, std::string_view const &msg, std::string_view const output(cmd_id, "error", std::move(m)); } -#define CATCH_ALL_EXCEPTIONS(msg, cmd_id) catch(winrt::hresult_error const& ex) { \ +#define CATCH_ALL_EXCEPTIONS(msg, cmd_id) \ + catch(winrt::hresult_error const& ex) { \ output_error(cmd_id, msg, winrt::to_string(ex.message()), __LINE__, ex.to_abi()); \ } catch (std::exception const &ex) { \ output_error(cmd_id, msg, ex.what(), __LINE__); \ } catch (std::string const &ex) { \ output_error(cmd_id, msg, ex, __LINE__); \ +} catch (std::wstring const &ex) { \ + output_error(cmd_id, msg, winrt::to_string(ex), __LINE__); \ } catch (...) { \ output_error(cmd_id, msg, "Unknown exception type was raised", __LINE__); \ } @@ -604,30 +609,6 @@ pump_waiting_messages(PyObject*, PyObject*) { }}} */ -static std::vector stdin_messages; -static std::mutex stdin_messages_lock; - -static void -post_message(LPARAM type, WPARAM data = 0) { - PostThreadMessageA(main_thread_id, WM_USER, data, type); -} - - -static winrt::fire_and_forget -run_input_loop(void) { - co_await winrt::resume_background(); - std::string line; - while(!std::cin.eof() && std::getline(std::cin, line)) { - if (line.size() > 0) { - { - std::scoped_lock sl(stdin_messages_lock); - stdin_messages.push_back(line); - } - post_message(STDIN_MSG); - } - } - post_message(STDIN_FAILED, std::cin.fail() ? 1 : 0); -} struct Revokers { MediaPlaybackSession::PlaybackStateChanged_revoker playback_state_changed; @@ -748,11 +729,11 @@ decode_utf8(std::string_view const& src) { } static void -handle_speak(id_type cmd_id, std::vector &parts) { +handle_speak(id_type cmd_id, std::vector &parts) { bool is_ssml = false, is_shm = false; try { - is_ssml = parts.at(0) == "ssml"; - is_shm = parts.at(1) == "shm"; + is_ssml = parts.at(0) == L"ssml"; + is_shm = parts.at(1) == L"shm"; } catch (std::exception const&) { throw std::string("Not a well formed speak command"); } @@ -762,66 +743,58 @@ handle_speak(id_type cmd_id, std::vector &parts) { if (is_shm) { throw std::string("TODO: Implement support for SHM"); } - sx.speak(cmd_id, decode_utf8(address), is_ssml); + sx.speak(cmd_id, address, is_ssml); } -static winrt::fire_and_forget -handle_stdin_messages(void) { - co_await winrt::resume_background(); - std::scoped_lock sl(stdin_messages_lock); - std::vector parts; - std::string_view command; - id_type cmd_id; - for (auto & msg : stdin_messages) { - rtrim(msg); - bool ok = false; - if (msg == "exit") { - post_message(EXIT_REQUESTED); - break; - } - try { - parts = split(msg); - command = parts.at(1); cmd_id = parse_id(parts.at(0)); - if (cmd_id == 0) { - throw std::exception("Command id of zero is not allowed"); - } - parts.erase(parts.begin(), parts.begin() + 2); - ok = true; - } CATCH_ALL_EXCEPTIONS((std::string("Invalid input message: ") + msg), 0); - if (!ok) continue; - try { - if (command == "exit") { - try { - post_message(EXIT_REQUESTED, parse_id(parts.at(2))); - } catch(...) { - post_message(EXIT_REQUESTED); - } - break; - } - else if (command == "echo") { - output(cmd_id, command, {{"msg", json_val(std::move(join(parts)))}}); - } - else if (command == "default_voice") { - output(cmd_id, "default_voice", SpeechSynthesizer::DefaultVoice()); - } - else if (command == "all_voices") { - output(cmd_id, "all_voices", SpeechSynthesizer::AllVoices()); - } - else if (command == "speak") { - handle_speak(cmd_id, parts); - } - else throw std::string("Unknown command: ") + std::string(command); - } CATCH_ALL_EXCEPTIONS("Error handling input message", cmd_id); +static long long +handle_stdin_message(winrt::hstring const &&msg) { + if (msg == L"exit") { + return 0; } - stdin_messages.clear(); + id_type cmd_id; + std::wstring_view command; + bool ok = false; + std::vector parts; + try { + parts = split(msg); + command = parts.at(1); cmd_id = parse_id(parts.at(0)); + if (cmd_id == 0) { + throw std::exception("Command id of zero is not allowed"); + } + parts.erase(parts.begin(), parts.begin() + 2); + ok = true; + } CATCH_ALL_EXCEPTIONS((std::string("Invalid input message: ") + winrt::to_string(msg)), 0); + if (!ok) return -1; + try { + if (command == L"exit") { + try { + return parse_id(parts.at(0)); + } catch(...) { } + return 0; + } + else if (command == L"echo") { + output(cmd_id, "echo", {{"msg", json_val(std::move(join(parts)))}}); + } + else if (command == L"default_voice") { + output(cmd_id, "default_voice", SpeechSynthesizer::DefaultVoice()); + } + else if (command == L"all_voices") { + output(cmd_id, "all_voices", SpeechSynthesizer::AllVoices()); + } + else if (command == L"speak") { + handle_speak(cmd_id, parts); + } + else throw std::string("Unknown command: ") + winrt::to_string(command); + } CATCH_ALL_EXCEPTIONS("Error handling input message", cmd_id); + return -1; } static PyObject* run_main_loop(PyObject*, PyObject*) { - winrt::init_apartment(); + winrt::init_apartment(); // MTA (multi-threaded apartment) main_thread_id = GetCurrentThreadId(); MSG msg; - unsigned long long exit_code = 0; + long long exit_code = 0; bool ok = false; try { new (&sx) Synthesizer(); @@ -837,29 +810,34 @@ run_main_loop(PyObject*, PyObject*) { if (_isatty(_fileno(stdin))) { std::cout << "Welcome to winspeech. Type exit to quit." << std::endl; } - run_input_loop(); + std::string input_buffer; while (true) { - BOOL ret = GetMessage(&msg, NULL, 0, 0); - if (ret <= 0) { // WM_QUIT or error - exit_code = msg.message == WM_QUIT ? msg.wParam : 1; + try { + if (!std::getline(std::cin, input_buffer)) { + if (!std::cin.eof()) exit_code = 1; + break; + } + rtrim(input_buffer); + if (input_buffer.size() > 0) { + if ((exit_code = handle_stdin_message(std::move(winrt::to_hstring(input_buffer)))) >= 0) break; + } + } catch(...) { + exit_code = 1; + output_error(0, "Unknown exception type reading and handling line of input", "", __LINE__); break; } - if (msg.message == WM_USER) { - if (msg.lParam == STDIN_FAILED || msg.lParam == EXIT_REQUESTED) { exit_code = msg.wParam; break; } - else if (msg.lParam == STDIN_MSG) handle_stdin_messages(); - } else { - DispatchMessage(&msg); - } } + main_loop_is_running.store(false); Py_END_ALLOW_THREADS; + try { sx.stop_current_activity(); (&sx)->~Synthesizer(); } CATCH_ALL_EXCEPTIONS("Error stopping all activity", 0); - return PyLong_FromUnsignedLongLong(exit_code); + return PyLong_FromLongLong(exit_code); } #define M(name, args) { #name, name, args, ""} From 20f58d721196f7697ca47fdb4d107a6c29fe8bd5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 24 Jan 2023 17:34:42 +0530 Subject: [PATCH 0167/2055] Serialize JSON directly to cout --- src/calibre/utils/windows/winspeech.cpp | 85 +++++++++---------------- 1 file changed, 29 insertions(+), 56 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 51b081ad4b..e54e1c5b89 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -105,26 +105,24 @@ parse_id(std::wstring_view const& s) { } -static std::string -serialize_string_for_json(std::string const &src) { - std::string ans("\""); - ans.reserve(src.size() + 16); +static void +serialize_string_for_json(std::string const &src, std::ostream &out) { + out << '"'; for (auto ch : src) { switch(ch) { case '\\': - ans += "\\\\"; break; + out << "\\\\"; break; case '"': - ans += "\\\""; break; + out << "\\\""; break; case '\n': - ans += "\\n"; break; + out << "\\n"; break; case '\r': - ans += "\\r"; break; + out << "\\r"; break; default: - ans += ch; break; + out << ch; break; } } - ans += '"'; - return ans; + out << '"'; } class json_val { // {{{ @@ -181,41 +179,38 @@ public: } } - std::string serialize() const { + void serialize(std::ostream &out) const { switch(type) { case DT_NONE: - return "nil"; + out << "nil"; break; case DT_BOOL: - return b ? "true" : "false"; + out << b ? "true" : "false"; break; case DT_INT: // this is not really correct since JS has various limits on numeric types, but good enough for us - return std::to_string(i); + out << i; break; case DT_STRING: - return serialize_string_for_json(s); + return serialize_string_for_json(s, out); case DT_LIST: { - std::string ans("["); - ans.reserve(list.size() * 32); + out << '['; for (auto const &i : list) { - ans += i.serialize(); - ans += ", "; + i.serialize(out); + out << ", "; } - ans.erase(ans.size() - 2); ans += "]"; - return ans; + out << ']'; + break; } case DT_OBJECT: { - std::string ans("{"); - ans.reserve(object.size() * 64); + out << '{'; for (const auto& [key, value]: object) { - ans += serialize_string_for_json(key); - ans += ": "; - ans += value.serialize(); - ans += ", "; + serialize_string_for_json(key, out); + out << ": "; + value.serialize(out); + out << ", "; } - ans.erase(ans.size() - 2); ans += "}"; - return ans; + out << '}'; + break; } } - return ""; } }; // }}} @@ -223,7 +218,9 @@ static void output(id_type cmd_id, std::string_view const &msg_type, json_val const &&msg) { std::scoped_lock sl(output_lock); try { - std::cout << cmd_id << " " << msg_type << " " << msg.serialize() << std::endl; + std::cout << cmd_id << " " << msg_type << " "; + msg.serialize(std::cout); + std::cout << std::endl; } catch(...) {} } @@ -704,30 +701,6 @@ class Synthesizer { static Synthesizer sx; -static inline std::wstring -decode_utf8(std::string_view const& src) { - std::wstring ans(src.length() + 1, 0); - size_t count = MultiByteToWideChar(CP_UTF8, 0, src.data(), (int)src.length(), ans.data(), (int)ans.length()); - if (count == 0) { - switch(GetLastError()) { - case ERROR_INSUFFICIENT_BUFFER: - throw std::exception("Could not convert UTF-8 to UTF-16: buffer too small"); - case ERROR_INVALID_PARAMETER: - throw std::exception("Could not convert UTF-8 to UTF-16: invalid parameter"); - case ERROR_NO_UNICODE_TRANSLATION: - throw std::exception("Could not convert UTF-8 to UTF-16: invalid UTF-8 encountered"); - default: - throw std::exception("Could not convert UTF-8 to UTF-16: unknown error"); - } - } - count++; // ensure trailing null - if (ans.length() > count) { - auto extra = ans.length() - count; - ans.erase(ans.length() - extra, extra); - } - return ans; -} - static void handle_speak(id_type cmd_id, std::vector &parts) { bool is_ssml = false, is_shm = false; From 6e4301ecad6e6f22e883846ba791085954e7e720 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 24 Jan 2023 17:39:10 +0530 Subject: [PATCH 0168/2055] ... --- src/calibre/utils/windows/winspeech.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index e54e1c5b89..29b9a971ae 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -143,7 +143,7 @@ public: json_val(long long num) : type(DT_INT), i(num) {} json_val(std::vector &&items) : type(DT_LIST), list(items) {} json_val(std::map &&m) : type(DT_OBJECT), object(m) {} - json_val(std::initializer_list> vals) : type(DT_OBJECT), object(vals) { } + json_val(std::initializer_list> const& vals) : type(DT_OBJECT), object(vals) { } json_val(bool x) : type(DT_BOOL), b(x) {} json_val(VoiceInformation const& voice) : type(DT_OBJECT) { From a58baba3954a5977602ca5da978daef948b1e18a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 24 Jan 2023 17:50:14 +0530 Subject: [PATCH 0169/2055] Report type of media failed error --- src/calibre/utils/windows/winspeech.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 29b9a971ae..d51392d6c4 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -179,6 +179,19 @@ public: } } + json_val(MediaPlayerError const& e) : type(DT_STRING) { + // https://learn.microsoft.com/en-us/uwp/api/windows.media.playback.mediaplayererror + switch(e) { + case MediaPlayerError::Unknown: s = "unknown"; break; + case MediaPlayerError::Aborted: s = "aborted"; break; + case MediaPlayerError::NetworkError: s = "network_error"; break; + case MediaPlayerError::DecodingError: s = "decoding_error"; break; + case MediaPlayerError::SourceNotSupported: s = "source_not_supported"; break; + default: s = "unknown"; break; + } + } + + void serialize(std::ostream &out) const { switch(type) { case DT_NONE: @@ -646,9 +659,9 @@ class Synthesizer { if (main_loop_is_running.load()) sx.output( cmd_id, "media_state_changed", {{"state", json_val("ended")}}); }); - revoker.media_failed = player.MediaFailed(winrt::auto_revoke, [cmd_id](auto player, auto const&) { + revoker.media_failed = player.MediaFailed(winrt::auto_revoke, [cmd_id](auto player, auto const& args) { if (main_loop_is_running.load()) sx.output( - cmd_id, "media_state_changed", {{"state", json_val("failed")}}); + cmd_id, "media_state_changed", {{"state", json_val("failed")}, {"error", args.ErrorMessage()}, {"code", json_val(args.Error())}}); }); current_stream = stream; current_source = MediaSource::CreateFromStream(current_stream, current_stream.ContentType()); From 4d23f9649aa63defbc01101647500e0a840fe427 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 24 Jan 2023 18:07:08 +0530 Subject: [PATCH 0170/2055] Cleanup develop code and dont output trailing commas for JSON --- src/calibre/utils/windows/winspeech.cpp | 8 ++++++-- src/calibre/utils/windows/winspeech.py | 23 +++++++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index d51392d6c4..b75d23bfa8 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -205,20 +205,24 @@ public: return serialize_string_for_json(s, out); case DT_LIST: { out << '['; + bool first = true; for (auto const &i : list) { + if (!first) out << ", "; + first = false; i.serialize(out); - out << ", "; } out << ']'; break; } case DT_OBJECT: { out << '{'; + bool first = true; for (const auto& [key, value]: object) { + if (!first) out << ", "; + first = false; serialize_string_for_json(key, out); out << ": "; value.serialize(out); - out << ", "; } out << '}'; break; diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index b2d66ffe86..6a29a217c4 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -2,22 +2,34 @@ # License: GPLv3 Copyright: 2023, Kovid Goyal +import json import sys import time from contextlib import closing +from queue import Queue from threading import Thread from calibre.utils.ipc.simple_worker import start_pipe_worker +def decode_msg(line: bytes) -> dict: + parts = line.strip().split(b' ', 2) + msg_id, msg_type, ans = int(parts[0]), parts[1].decode(), json.loads(parts[2]) + ans['related_to'] = msg_id + ans['payload_type'] = msg_type + return ans + + def develop_speech(text='Lucca brazzi sleeps with the fishes'): p = start_pipe_worker('from calibre_extensions.winspeech import run_main_loop; run_main_loop()') - print('\x1b[32mSpeaking', text, '\x1b[39m', flush=True) + print('\x1b[32mSpeaking', text, '\x1b[39m', flush=True) # ]] + q = Queue() def echo_output(p): for line in p.stdout: - sys.stdout.buffer.write(b'\x1b[33m' + line + b'\x1b[39m') + sys.stdout.buffer.write(b'\x1b[33m' + line + b'\x1b[39m') # ]] sys.stdout.buffer.flush() + q.put(decode_msg(line)) def send(*a): cmd = ' '.join(map(str, a)) + '\n' @@ -29,10 +41,13 @@ def develop_speech(text='Lucca brazzi sleeps with the fishes'): try: send('1 echo Synthesizer started') send('2 speak text inline', text) - time.sleep(6) + while True: + m = q.get() + if m['payload_type'] == 'media_state_changed' and m['state'] == 'ended': + break send('3 echo Synthesizer exiting') send('exit') - time.sleep(1) + p.wait(1) finally: if p.poll() is None: p.kill() From 86041db5e45109d4173adc7fd2135794841103c2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 24 Jan 2023 18:07:36 +0530 Subject: [PATCH 0171/2055] ... --- src/calibre/utils/windows/winspeech.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index 6a29a217c4..7ad865bb52 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -4,7 +4,6 @@ import json import sys -import time from contextlib import closing from queue import Queue from threading import Thread From c0db29016b5cf119cdca8c8e4c9c880643c47b1e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 24 Jan 2023 18:16:21 +0530 Subject: [PATCH 0172/2055] make vim's python indent happy --- src/calibre/utils/windows/winspeech.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index 7ad865bb52..a23bfe4c33 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -21,12 +21,12 @@ def decode_msg(line: bytes) -> dict: def develop_speech(text='Lucca brazzi sleeps with the fishes'): p = start_pipe_worker('from calibre_extensions.winspeech import run_main_loop; run_main_loop()') - print('\x1b[32mSpeaking', text, '\x1b[39m', flush=True) # ]] + print('\x1b[32mSpeaking', text, '\x1b[39m]]'[:-2], flush=True) q = Queue() def echo_output(p): for line in p.stdout: - sys.stdout.buffer.write(b'\x1b[33m' + line + b'\x1b[39m') # ]] + sys.stdout.buffer.write(b'\x1b[33m' + line + b'\x1b[39m]]'[:-2]) sys.stdout.buffer.flush() q.put(decode_msg(line)) From 6829e650348181b1088bf8c1fe4a04c9164ae6f3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 24 Jan 2023 18:21:21 +0530 Subject: [PATCH 0173/2055] forward the exit code --- src/calibre/utils/windows/winspeech.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index a23bfe4c33..05c9353539 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -19,8 +19,12 @@ def decode_msg(line: bytes) -> dict: return ans +def start_worker(): + return start_pipe_worker('from calibre_extensions.winspeech import run_main_loop; raise SystemExit(run_main_loop())') + + def develop_speech(text='Lucca brazzi sleeps with the fishes'): - p = start_pipe_worker('from calibre_extensions.winspeech import run_main_loop; run_main_loop()') + p = start_worker() print('\x1b[32mSpeaking', text, '\x1b[39m]]'[:-2], flush=True) q = Queue() From 8467a9f0bd21e41b8780620d986b9fdbcd02c62b Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Tue, 24 Jan 2023 13:52:40 +0000 Subject: [PATCH 0174/2055] Bug #2003780: Unexpected behaviour: Marked books & Hide Empty Categories --- src/calibre/gui2/tag_browser/view.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/tag_browser/view.py b/src/calibre/gui2/tag_browser/view.py index b3b88bb36a..326bb5949c 100644 --- a/src/calibre/gui2/tag_browser/view.py +++ b/src/calibre/gui2/tag_browser/view.py @@ -22,7 +22,7 @@ from calibre.gui2.tag_browser.model import (TagTreeItem, TAG_SEARCH_STATES, TagsModel, DRAG_IMAGE_ROLE, COUNT_ROLE, rename_only_in_vl_question) from calibre.gui2.widgets import EnLineEdit from calibre.gui2 import (config, gprefs, choose_files, pixmap_to_data, - rating_font, empty_index, question_dialog) + rating_font, empty_index, question_dialog, FunctionDispatcher) from calibre.utils.icu import sort_key from calibre.utils.serialize import json_loads @@ -217,6 +217,7 @@ class TagsView(QTreeView): # {{{ self._model.convert_requested.connect(self.convert_requested) self.set_look_and_feel(first=True) QApplication.instance().palette_changed.connect(self.set_style_sheet, type=Qt.ConnectionType.QueuedConnection) + self.marked_change_listener = FunctionDispatcher(self.recount_on_mark_change) def convert_requested(self, book_ids, to_fmt): from calibre.gui2.ui import get_gui @@ -330,6 +331,7 @@ class TagsView(QTreeView): # {{{ db.add_listener(self.database_changed) self.expanded.connect(self.item_expanded) self.collapsed.connect(self.collapse_node_and_children) + db.data.add_marked_listener(self.marked_change_listener) def keyPressEvent(self, event): @@ -1252,6 +1254,10 @@ class TagsView(QTreeView): # {{{ idx = idx.parent() return self.isExpanded(idx) + def recount_on_mark_change(self, *args): + # Let other marked listeners run before we do the recount + QTimer.singleShot(0, self.recount) + def recount_with_position_based_index(self): self._model.use_position_based_index_on_next_recount = True self.recount() From 692a99dce6cbf8c913e3a35736d297177120073e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 24 Jan 2023 19:36:40 +0530 Subject: [PATCH 0175/2055] More work on winspeech --- src/calibre/utils/windows/winspeech.cpp | 54 +++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index b75d23bfa8..17297f71fa 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -647,6 +647,23 @@ class Synthesizer { Revokers revoker; std::recursive_mutex recursive_lock; + void register_metadata_handler_for_track(TimedMetadataTrack const& track, id_type cmd_id) { + std::scoped_lock sl(recursive_lock); + if (current_cmd_id.load() != cmd_id) return; + track.CueEntered([cmd_id](auto, const auto&) { + if (main_loop_is_running.load()) sx.output( + cmd_id, "cue", {{"state", "entered"}}); + }); + track.CueExited([cmd_id](auto, const auto&) { + if (main_loop_is_running.load()) sx.output( + cmd_id, "cue", {{"state", "exited"}}); + }); + track.TrackFailed([cmd_id](auto, const auto&) { + if (main_loop_is_running.load()) sx.output( + cmd_id, "track_failed", {}); + }); + } + void load_stream_for_playback(SpeechSynthesisStream const &stream, id_type cmd_id) { std::scoped_lock sl(recursive_lock); if (cmd_id != current_cmd_id.load()) return; @@ -667,17 +684,46 @@ class Synthesizer { if (main_loop_is_running.load()) sx.output( cmd_id, "media_state_changed", {{"state", json_val("failed")}, {"error", args.ErrorMessage()}, {"code", json_val(args.Error())}}); }); - current_stream = stream; - current_source = MediaSource::CreateFromStream(current_stream, current_stream.ContentType()); - current_item = MediaPlaybackItem(current_source); - player.Source(current_item); + current_stream = stream; + current_source = MediaSource::CreateFromStream(current_stream, current_stream.ContentType()); + current_item = MediaPlaybackItem(current_source); + + revoker.timed_metadata_tracks_changed = current_item.TimedMetadataTracksChanged(winrt::auto_revoke, + [cmd_id](auto, auto const &args) { + auto change_type = args.CollectionChange(); + long index; + switch (change_type) { + case CollectionChange::ItemInserted: index = args.Index(); break; + case CollectionChange::Reset: index = -1; break; + default: index = -2; break; + } + if (index > -2 && main_loop_is_running.load()) sx.register_metadata_handler_for_speech(cmd_id, index); + }); + register_metadata_handler_for_speech(cmd_id, -1); + + player.Source(current_item); } + public: + void register_metadata_handler_for_speech(id_type cmd_id, long index) { + std::scoped_lock sl(recursive_lock); + if (!cmd_id_is_current(cmd_id)) return; + if (index < 0) { + for (auto const &track : current_item.TimedMetadataTracks()) { + register_metadata_handler_for_track(track, cmd_id); + } + } else { + register_metadata_handler_for_track(current_item.TimedMetadataTracks().GetAt(index), cmd_id); + } + } + bool cmd_id_is_current(id_type cmd_id) const noexcept { return current_cmd_id.load() == cmd_id; } + void output(id_type cmd_id, std::string_view const& type, json_val const && x) { std::scoped_lock sl(recursive_lock); if (cmd_id_is_current(cmd_id)) ::output(cmd_id, type, std::move(x)); } + void initialize() { synth = SpeechSynthesizer(); synth.Options().IncludeSentenceBoundaryMetadata(true); From f56708d11b8907fc5ba3a531c4fcc212b08a45a2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 24 Jan 2023 20:59:58 +0530 Subject: [PATCH 0176/2055] string changes --- manual/creating_plugins.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manual/creating_plugins.rst b/manual/creating_plugins.rst index 5033ef1127..419e612e5d 100644 --- a/manual/creating_plugins.rst +++ b/manual/creating_plugins.rst @@ -144,8 +144,8 @@ calibre's plugin loading system defines a couple of built-in functions that allo a forward slash as the path separator, even on Windows. When you pass in a single name, the function will return the raw bytes of that file or None if the name was not found in the ZIP file. If you pass in more - than one name then it returns a dict mapping the names to bytes. If a - name is not found, it will not be present in the returned dict. + than one name then it returns a dictionary mapping the names to bytes. If a + name is not found, it will not be present in the returned dictionary. **get_icons(name_or_list_of_names, plugin_name='')** A wrapper for get_resources() that creates QIcon objects From f9fb4d5504e0f69c5951b0b30a5d76e0c1bfddda Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 24 Jan 2023 21:43:53 +0530 Subject: [PATCH 0177/2055] Get speech cue events working --- src/calibre/utils/windows/winspeech.cpp | 80 +++++++++++++++++++------ 1 file changed, 61 insertions(+), 19 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 17297f71fa..cdfeef5d86 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -33,7 +33,7 @@ using namespace winrt::Windows::Media::SpeechSynthesis; using namespace winrt::Windows::Media::Playback; using namespace winrt::Windows::Media::Core; using namespace winrt::Windows::Storage::Streams; -typedef unsigned long long id_type; +typedef uint64_t id_type; #define debug(format_string, ...) { \ std::scoped_lock _sl_(output_lock); \ @@ -130,7 +130,7 @@ private: enum { DT_INT, DT_STRING, DT_LIST, DT_OBJECT, DT_NONE, DT_BOOL } type; std::string s; bool b; - long long i; + int64_t i; std::vector list; std::map object; public: @@ -140,7 +140,8 @@ public: json_val(winrt::hstring const& text) : type(DT_STRING), s(winrt::to_string(text)) {} json_val(std::wstring const& text) : type(DT_STRING), s(winrt::to_string(text)) {} json_val(std::string_view text) : type(DT_STRING), s(text) {} - json_val(long long num) : type(DT_INT), i(num) {} + json_val(int32_t num) : type(DT_INT), i(num) {} + json_val(int64_t num) : type(DT_INT), i(num) {} json_val(std::vector &&items) : type(DT_LIST), list(items) {} json_val(std::map &&m) : type(DT_OBJECT), object(m) {} json_val(std::initializer_list> const& vals) : type(DT_OBJECT), object(vals) { } @@ -191,6 +192,45 @@ public: } } + json_val(winrt::Windows::Foundation::TimeSpan const &t) : type(DT_INT) { + i = std::chrono::nanoseconds(t).count(); + } + + json_val(winrt::hstring const &label, SpeechCue const &cue) : type(DT_OBJECT) { +#define common_fields \ + {"start_time", json_val(cue.StartTime())}, \ + {"start_pos_in_text", json_val(cue.StartPositionInInput().Value())}, \ + {"end_pos_in_text", json_val(cue.EndPositionInInput().Value())}, + + if (label == L"SpeechBookmark") { + object = { + {"type", json_val("bookmark")}, + {"id", json_val(cue.Id())}, + common_fields + }; + + } else if (label == L"SpeechWord") { + object = { + {"type", json_val("word")}, + {"text", json_val(cue.Text())}, + common_fields + }; + } else if (label == L"SpeechSentence") { + object = { + {"type", json_val("sentence")}, + {"text", json_val(cue.Text())}, + common_fields + }; + } else { + object = { + {"type", json_val(label)}, + {"text", json_val(cue.Text())}, + common_fields + }; + } +#undef common_fields + } + void serialize(std::ostream &out) const { switch(type) { @@ -242,9 +282,9 @@ output(id_type cmd_id, std::string_view const &msg_type, json_val const &&msg) { } static void -output_error(id_type cmd_id, std::string_view const &msg, std::string_view const &error, long long line, HRESULT hr=S_OK) { +output_error(id_type cmd_id, std::string_view const &msg, std::string_view const &error, int64_t line, HRESULT hr=S_OK) { std::map m = {{"msg", json_val(msg)}, {"error", json_val(error)}, {"file", json_val("winspeech.cpp")}, {"line", json_val(line)}}; - if (hr != S_OK) m["hr"] = json_val((long long)hr); + if (hr != S_OK) m["hr"] = json_val((int64_t)hr); output(cmd_id, "error", std::move(m)); } @@ -647,21 +687,23 @@ class Synthesizer { Revokers revoker; std::recursive_mutex recursive_lock; - void register_metadata_handler_for_track(TimedMetadataTrack const& track, id_type cmd_id) { + void register_metadata_handler_for_track(uint32_t index, id_type cmd_id) { + TimedMetadataTrack track = current_item.TimedMetadataTracks().GetAt(index); std::scoped_lock sl(recursive_lock); if (current_cmd_id.load() != cmd_id) return; - track.CueEntered([cmd_id](auto, const auto&) { + revoker.cue_entered.push_back(track.CueEntered(winrt::auto_revoke, [cmd_id](auto track, const auto& args) { if (main_loop_is_running.load()) sx.output( - cmd_id, "cue", {{"state", "entered"}}); - }); - track.CueExited([cmd_id](auto, const auto&) { + cmd_id, "cue_entered", json_val(track.Label(), args.Cue().as())); + })); + revoker.cue_exited.push_back(track.CueExited(winrt::auto_revoke, [cmd_id](auto track, const auto& args) { if (main_loop_is_running.load()) sx.output( - cmd_id, "cue", {{"state", "exited"}}); - }); - track.TrackFailed([cmd_id](auto, const auto&) { + cmd_id, "cue_exited", json_val(track.Label(), args.Cue().as())); + })); + revoker.track_failed.push_back(track.TrackFailed(winrt::auto_revoke, [cmd_id](auto, const auto& args) { if (main_loop_is_running.load()) sx.output( cmd_id, "track_failed", {}); - }); + })); + current_item.TimedMetadataTracks().SetPresentationMode((unsigned int)index, TimedMetadataTrackPresentationMode::Hidden); } void load_stream_for_playback(SpeechSynthesisStream const &stream, id_type cmd_id) { @@ -709,11 +751,11 @@ class Synthesizer { std::scoped_lock sl(recursive_lock); if (!cmd_id_is_current(cmd_id)) return; if (index < 0) { - for (auto const &track : current_item.TimedMetadataTracks()) { - register_metadata_handler_for_track(track, cmd_id); + for (uint32_t i = 0; i < current_item.TimedMetadataTracks().Size(); i++) { + register_metadata_handler_for_track(i, cmd_id); } } else { - register_metadata_handler_for_track(current_item.TimedMetadataTracks().GetAt(index), cmd_id); + register_metadata_handler_for_track(index, cmd_id); } } @@ -782,7 +824,7 @@ handle_speak(id_type cmd_id, std::vector &parts) { sx.speak(cmd_id, address, is_ssml); } -static long long +static int64_t handle_stdin_message(winrt::hstring const &&msg) { if (msg == L"exit") { return 0; @@ -830,7 +872,7 @@ run_main_loop(PyObject*, PyObject*) { winrt::init_apartment(); // MTA (multi-threaded apartment) main_thread_id = GetCurrentThreadId(); MSG msg; - long long exit_code = 0; + int64_t exit_code = 0; bool ok = false; try { new (&sx) Synthesizer(); From a8cc81af9e2e44d97a9634b759c926cf55b59e2a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 25 Jan 2023 06:32:50 +0530 Subject: [PATCH 0178/2055] Implement volume control and faster serialization of numeric types to JSON --- src/calibre/utils/windows/winspeech.cpp | 116 +++++++++++++++--------- 1 file changed, 74 insertions(+), 42 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index cdfeef5d86..427e5e0f66 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -104,6 +105,14 @@ parse_id(std::wstring_view const& s) { return ans; } +static double +parse_double(const wchar_t *raw) { + std::wistringstream s(raw, std::ios_base::in); + s.imbue(std::locale("C")); + double ans; + s >> ans; + return ans; +} static void serialize_string_for_json(std::string const &src, std::ostream &out) { @@ -125,11 +134,33 @@ serialize_string_for_json(std::string const &src, std::ostream &out) { out << '"'; } +template static void +serialize_integer(std::ostream &out, T val, int base = 10) { + std::array str; + if (auto [ptr, ec] = std::to_chars(str.data(), str.data() + str.size(), val, base); ec == std::errc()) { + out << std::string_view(str.data(), ptr - str.data()); + } else { + throw std::exception(std::make_error_code(ec).message().c_str()); + } +} + +templatestatic void +serialize_float(std::ostream &out, T val, std::chars_format fmt = std::chars_format::fixed) { + std::array str; + if (auto [ptr, ec] = std::to_chars(str.data(), str.data() + str.size(), val, fmt); ec == std::errc()) { + out << std::string_view(str.data(), ptr - str.data()); + } else { + throw std::exception(std::make_error_code(ec).message().c_str()); + } +} + + class json_val { // {{{ private: - enum { DT_INT, DT_STRING, DT_LIST, DT_OBJECT, DT_NONE, DT_BOOL } type; + enum { DT_INT, DT_STRING, DT_LIST, DT_OBJECT, DT_NONE, DT_BOOL, DT_FLOAT } type; std::string s; bool b; + double f; int64_t i; std::vector list; std::map object; @@ -146,6 +177,7 @@ public: json_val(std::map &&m) : type(DT_OBJECT), object(m) {} json_val(std::initializer_list> const& vals) : type(DT_OBJECT), object(vals) { } json_val(bool x) : type(DT_BOOL), b(x) {} + json_val(double x) : type(DT_FLOAT), f(x) {} json_val(VoiceInformation const& voice) : type(DT_OBJECT) { const char *gender = ""; @@ -197,38 +229,13 @@ public: } json_val(winrt::hstring const &label, SpeechCue const &cue) : type(DT_OBJECT) { -#define common_fields \ - {"start_time", json_val(cue.StartTime())}, \ - {"start_pos_in_text", json_val(cue.StartPositionInInput().Value())}, \ - {"end_pos_in_text", json_val(cue.EndPositionInInput().Value())}, - - if (label == L"SpeechBookmark") { - object = { - {"type", json_val("bookmark")}, - {"id", json_val(cue.Id())}, - common_fields - }; - - } else if (label == L"SpeechWord") { - object = { - {"type", json_val("word")}, - {"text", json_val(cue.Text())}, - common_fields - }; - } else if (label == L"SpeechSentence") { - object = { - {"type", json_val("sentence")}, - {"text", json_val(cue.Text())}, - common_fields - }; - } else { - object = { - {"type", json_val(label)}, - {"text", json_val(cue.Text())}, - common_fields - }; - } -#undef common_fields + object = { + {"type", json_val(label)}, + {"text", json_val(cue.Text())}, + {"start_time", json_val(cue.StartTime())}, + {"start_pos_in_text", json_val(cue.StartPositionInInput().Value())}, + {"end_pos_in_text", json_val(cue.EndPositionInInput().Value())}, + }; } @@ -240,7 +247,10 @@ public: out << b ? "true" : "false"; break; case DT_INT: // this is not really correct since JS has various limits on numeric types, but good enough for us - out << i; break; + serialize_integer(out, i); break; + case DT_FLOAT: + // again not technically correct + serialize_float(out, f); break; case DT_STRING: return serialize_string_for_json(s, out); case DT_LIST: { @@ -703,7 +713,7 @@ class Synthesizer { if (main_loop_is_running.load()) sx.output( cmd_id, "track_failed", {}); })); - current_item.TimedMetadataTracks().SetPresentationMode((unsigned int)index, TimedMetadataTrackPresentationMode::Hidden); + current_item.TimedMetadataTracks().SetPresentationMode((unsigned int)index, TimedMetadataTrackPresentationMode::ApplicationPresented); } void load_stream_for_playback(SpeechSynthesisStream const &stream, id_type cmd_id) { @@ -768,7 +778,7 @@ class Synthesizer { void initialize() { synth = SpeechSynthesizer(); - synth.Options().IncludeSentenceBoundaryMetadata(true); + // synth.Options().IncludeSentenceBoundaryMetadata(true); synth.Options().IncludeWordBoundaryMetadata(true); player = MediaPlayer(); player.AudioCategory(MediaPlayerAudioCategory::Speech); @@ -788,20 +798,32 @@ class Synthesizer { } winrt::fire_and_forget speak(id_type cmd_id, std::wstring_view const &text, bool is_ssml) { - winrt::apartment_context main_thread; // capture calling thread + // winrt::apartment_context main_thread; // capture calling thread SpeechSynthesisStream stream{nullptr}; { std::scoped_lock sl(recursive_lock); stop_current_activity(); current_cmd_id.store(cmd_id); } - if (is_ssml) stream = co_await synth.SynthesizeSsmlToStreamAsync(text); - else stream = co_await synth.SynthesizeTextToStreamAsync(text); - co_await main_thread; - if (main_loop_is_running.load()) { - load_stream_for_playback(stream, cmd_id); + bool ok = false; + try { + if (is_ssml) stream = co_await synth.SynthesizeSsmlToStreamAsync(text); + else stream = co_await synth.SynthesizeTextToStreamAsync(text); + ok = true; + } CATCH_ALL_EXCEPTIONS("Failed to synthesize speech", cmd_id); + if (ok) { + // co_await main_thread; + if (main_loop_is_running.load()) { + load_stream_for_playback(stream, cmd_id); + } } } + double volume() const { return player.Volume(); } + void volume(double val) { + if (val < 0 || val > 1) throw std::out_of_range("Invalid volume value must be between 0 and 1"); + player.Volume(val); + } + }; static Synthesizer sx; @@ -862,6 +884,13 @@ handle_stdin_message(winrt::hstring const &&msg) { else if (command == L"speak") { handle_speak(cmd_id, parts); } + else if (command == L"volume") { + if (parts.size()) { + auto vol = parse_double(parts[0].data()); + sx.volume(vol); + } + output(cmd_id, "volume", {{"value", json_val(sx.volume())}}); + } else throw std::string("Unknown command: ") + winrt::to_string(command); } CATCH_ALL_EXCEPTIONS("Error handling input message", cmd_id); return -1; @@ -869,6 +898,9 @@ handle_stdin_message(winrt::hstring const &&msg) { static PyObject* run_main_loop(PyObject*, PyObject*) { + std::cout.imbue(std::locale("C")); + std::cin.imbue(std::locale("C")); + std::cerr.imbue(std::locale("C")); winrt::init_apartment(); // MTA (multi-threaded apartment) main_thread_id = GetCurrentThreadId(); MSG msg; From 6065f1847ff2213b429b53fbc90b9d56ba30bcb7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 25 Jan 2023 06:45:06 +0530 Subject: [PATCH 0179/2055] Cleanup develop_speech() --- src/calibre/utils/windows/winspeech.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index 05c9353539..cb433b500d 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -10,6 +10,13 @@ from threading import Thread from calibre.utils.ipc.simple_worker import start_pipe_worker +SSML_SAMPLE = ''' + + + We are selling roses and daisies. + + +''' def decode_msg(line: bytes) -> dict: parts = line.strip().split(b' ', 2) @@ -23,7 +30,7 @@ def start_worker(): return start_pipe_worker('from calibre_extensions.winspeech import run_main_loop; raise SystemExit(run_main_loop())') -def develop_speech(text='Lucca brazzi sleeps with the fishes'): +def develop_speech(text=SSML_SAMPLE): p = start_worker() print('\x1b[32mSpeaking', text, '\x1b[39m]]'[:-2], flush=True) q = Queue() @@ -40,17 +47,25 @@ def develop_speech(text='Lucca brazzi sleeps with the fishes'): p.stdin.flush() Thread(name='Echo', target=echo_output, args=(p,), daemon=True).start() + exit_code = 0 with closing(p.stdin), closing(p.stdout): + text = text.replace('\n', ' ') + st = 'ssml' if ' Date: Wed, 25 Jan 2023 08:57:26 +0530 Subject: [PATCH 0180/2055] Nicer debug() function, now like print() in Python Also report HRESULT in hexadecimal form --- src/calibre/utils/windows/winspeech.cpp | 136 +++++++++++++++--------- 1 file changed, 83 insertions(+), 53 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 427e5e0f66..9fc38f178f 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -36,14 +36,27 @@ using namespace winrt::Windows::Media::Core; using namespace winrt::Windows::Storage::Streams; typedef uint64_t id_type; -#define debug(format_string, ...) { \ - std::scoped_lock _sl_(output_lock); \ - DWORD _tid_ = GetCurrentThreadId(); \ - char _buf_[64] = {0}; snprintf(_buf_, sizeof(_buf_)-1, "thread-%u", _tid_); \ - fprintf(stderr, "%s " format_string "\n", main_thread_id == _tid_ ? "thread-main" : _buf_, __VA_ARGS__); fflush(stderr);\ +static std::mutex output_lock; + +template static void +__debug_multiple(T x, Args... args) { + std::cerr << x << " "; + __debug_multiple(args...); +} + +template static void +__debug_multiple(T x) { + std::cerr << x << std::endl; +} + +template static void +debug(Args... args) { + std::scoped_lock _sl_(output_lock); + DWORD tid = GetCurrentThreadId(); + if (tid == main_thread_id) std::cerr << "thread-main"; else std::cerr << "thread-" << tid << ": "; + __debug_multiple(args...); } -static std::mutex output_lock; static std::atomic_bool main_loop_is_running; static DWORD main_thread_id; enum { @@ -164,6 +177,48 @@ private: int64_t i; std::vector list; std::map object; + + void serialize(std::ostream &out) const { + switch(type) { + case DT_NONE: + out << "nil"; break; + case DT_BOOL: + out << b ? "true" : "false"; break; + case DT_INT: + // this is not really correct since JS has various limits on numeric types, but good enough for us + serialize_integer(out, i); break; + case DT_FLOAT: + // again not technically correct + serialize_float(out, f); break; + case DT_STRING: + return serialize_string_for_json(s, out); + case DT_LIST: { + out << '['; + bool first = true; + for (auto const &i : list) { + if (!first) out << ", "; + first = false; + i.serialize(out); + } + out << ']'; + break; + } + case DT_OBJECT: { + out << '{'; + bool first = true; + for (const auto& [key, value]: object) { + if (!first) out << ", "; + first = false; + serialize_string_for_json(key, out); + out << ": "; + value.serialize(out); + } + out << '}'; + break; + } + } + } + public: json_val() : type(DT_NONE) {} json_val(std::string &&text) : type(DT_STRING), s(text) {} @@ -179,6 +234,16 @@ public: json_val(bool x) : type(DT_BOOL), b(x) {} json_val(double x) : type(DT_FLOAT), f(x) {} + json_val(HRESULT hr) : type(DT_STRING) { + std::array str; + str[0] = '0'; str[1] = 'x'; + if (auto [ptr, ec] = std::to_chars(str.data()+2, str.data() + str.size(), (uint32_t)hr, 16); ec == std::errc()) { + s = std::string(str.data(), ptr - str.data()); + } else { + throw std::exception(std::make_error_code(ec).message().c_str()); + } + } + json_val(VoiceInformation const& voice) : type(DT_OBJECT) { const char *gender = ""; switch (voice.Gender()) { @@ -238,63 +303,25 @@ public: }; } - - void serialize(std::ostream &out) const { - switch(type) { - case DT_NONE: - out << "nil"; break; - case DT_BOOL: - out << b ? "true" : "false"; break; - case DT_INT: - // this is not really correct since JS has various limits on numeric types, but good enough for us - serialize_integer(out, i); break; - case DT_FLOAT: - // again not technically correct - serialize_float(out, f); break; - case DT_STRING: - return serialize_string_for_json(s, out); - case DT_LIST: { - out << '['; - bool first = true; - for (auto const &i : list) { - if (!first) out << ", "; - first = false; - i.serialize(out); - } - out << ']'; - break; - } - case DT_OBJECT: { - out << '{'; - bool first = true; - for (const auto& [key, value]: object) { - if (!first) out << ", "; - first = false; - serialize_string_for_json(key, out); - out << ": "; - value.serialize(out); - } - out << '}'; - break; - } - } + friend std::ostream& operator<<(std::ostream &os, const json_val &self) { + self.serialize(os); + return os; } + }; // }}} static void output(id_type cmd_id, std::string_view const &msg_type, json_val const &&msg) { std::scoped_lock sl(output_lock); try { - std::cout << cmd_id << " " << msg_type << " "; - msg.serialize(std::cout); - std::cout << std::endl; + std::cout << cmd_id << " " << msg_type << " " << msg << std::endl; } catch(...) {} } static void output_error(id_type cmd_id, std::string_view const &msg, std::string_view const &error, int64_t line, HRESULT hr=S_OK) { std::map m = {{"msg", json_val(msg)}, {"error", json_val(error)}, {"file", json_val("winspeech.cpp")}, {"line", json_val(line)}}; - if (hr != S_OK) m["hr"] = json_val((int64_t)hr); + if (hr != S_OK) m["hr"] = json_val(hr); output(cmd_id, "error", std::move(m)); } @@ -896,11 +923,14 @@ handle_stdin_message(winrt::hstring const &&msg) { return -1; } + static PyObject* run_main_loop(PyObject*, PyObject*) { - std::cout.imbue(std::locale("C")); - std::cin.imbue(std::locale("C")); - std::cerr.imbue(std::locale("C")); + try { + std::cout.imbue(std::locale("C")); + std::cin.imbue(std::locale("C")); + std::cerr.imbue(std::locale("C")); + } CATCH_ALL_EXCEPTIONS("Failed to set stdio locales to C", 0); winrt::init_apartment(); // MTA (multi-threaded apartment) main_thread_id = GetCurrentThreadId(); MSG msg; From 83891ed63e15cf44f4d1cb8bf7286441c34111d6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 25 Jan 2023 09:13:00 +0530 Subject: [PATCH 0181/2055] Code to get the memory address of a SharedMemory object --- src/calibre/utils/shm.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/calibre/utils/shm.py b/src/calibre/utils/shm.py index abb083c75a..6857b18a44 100644 --- a/src/calibre/utils/shm.py +++ b/src/calibre/utils/shm.py @@ -132,6 +132,15 @@ class SharedMemory: self._size = size + @property + def memory_address(self) -> int: + import ctypes + obj = ctypes.py_object(self.mmap) + address = ctypes.c_void_p() + length = ctypes.c_ssize_t() + ctypes.pythonapi.PyObject_AsReadBuffer(obj, ctypes.byref(address), ctypes.byref(length)) + return address.value + def read(self, sz: int = 0) -> bytes: if sz <= 0: sz = self.size From 623f999a54568e94ee6e1a42b6e6391be20c41e5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 25 Jan 2023 10:12:55 +0530 Subject: [PATCH 0182/2055] Allow passing speech text with shared memory --- src/calibre/utils/windows/common.h | 2 ++ src/calibre/utils/windows/winspeech.cpp | 21 ++++++++++-- src/calibre/utils/windows/winspeech.py | 43 ++++++++++++++++++++++--- 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/src/calibre/utils/windows/common.h b/src/calibre/utils/windows/common.h index 76366f0921..dc3627b090 100644 --- a/src/calibre/utils/windows/common.h +++ b/src/calibre/utils/windows/common.h @@ -62,6 +62,8 @@ static inline void co_task_mem_free(void* m) { CoTaskMemFree(m); } typedef generic_raii(NULL)> com_wchar_raii; static inline void handle_destructor(HANDLE p) { CloseHandle(p); } typedef generic_raii handle_raii; +static inline void mapping_destructor(void *p) { UnmapViewOfFile(p); } +typedef generic_raii(NULL)> mapping_raii; struct prop_variant : PROPVARIANT { prop_variant(VARTYPE vt=VT_EMPTY) noexcept : PROPVARIANT{} { PropVariantInit(this); this->vt = vt; } diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 9fc38f178f..7c41293716 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -865,10 +865,25 @@ handle_speak(id_type cmd_id, std::vector &parts) { throw std::string("Not a well formed speak command"); } parts.erase(parts.begin(), parts.begin() + 2); - auto address = join(parts); - if (address.size() == 0) throw std::string("Address missing"); + std::wstring address; + id_type shm_size = 0; if (is_shm) { - throw std::string("TODO: Implement support for SHM"); + shm_size = parse_id(parts.at(0)); + address = parts.at(1); + handle_raii handle(OpenFileMappingW(FILE_MAP_READ, false, address.data())); + if (handle.ptr() == INVALID_HANDLE_VALUE) { + output_error(cmd_id, "Could not open shared memory at", winrt::to_string(address), __LINE__); + return; + } + mapping_raii mapping(MapViewOfFile(handle.ptr(), FILE_MAP_READ, 0, 0, (SIZE_T)shm_size)); + if (mapping.ptr() == NULL) { + output_error(cmd_id, "Could not map shared memory with error", std::to_string(GetLastError()), __LINE__); + return; + } + address = winrt::to_hstring((const char*)mapping.ptr()); + } else { + address = join(parts); + if (address.size() == 0) throw std::string("Address missing"); } sx.speak(cmd_id, address, is_ssml); } diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index cb433b500d..a6ee9745ee 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -3,11 +3,13 @@ import json +import struct import sys from contextlib import closing from queue import Queue from threading import Thread +from calibre.utils.shm import SharedMemory from calibre.utils.ipc.simple_worker import start_pipe_worker SSML_SAMPLE = ''' @@ -30,7 +32,36 @@ def start_worker(): return start_pipe_worker('from calibre_extensions.winspeech import run_main_loop; raise SystemExit(run_main_loop())') -def develop_speech(text=SSML_SAMPLE): +def max_buffer_size(text) -> int: + if isinstance(text, str): + text = [text] + ans = 0 + for x in text: + if isinstance(x, int): + ans += 5 + else: + ans += 4 * len(x) + return ans + + +def encode_to_file_object(text, output) -> int: + if isinstance(text, str): + text = [text] + p = struct.pack + sz = 0 + for x in text: + if isinstance(x, int): + output.write(b'\0') + output.write(p('=I', x)) + sz += 5 + else: + b = x.encode('utf-8') + output.write(b) + sz += len(b) + return sz + + +def develop_speech(text='Lucca Brazzi sleeps with the fishes.'): p = start_worker() print('\x1b[32mSpeaking', text, '\x1b[39m]]'[:-2], flush=True) q = Queue() @@ -48,18 +79,20 @@ def develop_speech(text=SSML_SAMPLE): Thread(name='Echo', target=echo_output, args=(p,), daemon=True).start() exit_code = 0 - with closing(p.stdin), closing(p.stdout): - text = text.replace('\n', ' ') + with closing(p.stdin), closing(p.stdout), SharedMemory(size=max_buffer_size(text)) as shm: st = 'ssml' if ' Date: Wed, 25 Jan 2023 10:37:12 +0530 Subject: [PATCH 0183/2055] Fix JSON serialization of numeric types --- src/calibre/utils/windows/winspeech.cpp | 27 ++++++++++++++----------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 7c41293716..40a6190fab 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -183,7 +183,7 @@ private: case DT_NONE: out << "nil"; break; case DT_BOOL: - out << b ? "true" : "false"; break; + out << (b ? "true" : "false"); break; case DT_INT: // this is not really correct since JS has various limits on numeric types, but good enough for us serialize_integer(out, i); break; @@ -226,22 +226,24 @@ public: json_val(winrt::hstring const& text) : type(DT_STRING), s(winrt::to_string(text)) {} json_val(std::wstring const& text) : type(DT_STRING), s(winrt::to_string(text)) {} json_val(std::string_view text) : type(DT_STRING), s(text) {} - json_val(int32_t num) : type(DT_INT), i(num) {} - json_val(int64_t num) : type(DT_INT), i(num) {} json_val(std::vector &&items) : type(DT_LIST), list(items) {} json_val(std::map &&m) : type(DT_OBJECT), object(m) {} json_val(std::initializer_list> const& vals) : type(DT_OBJECT), object(vals) { } - json_val(bool x) : type(DT_BOOL), b(x) {} - json_val(double x) : type(DT_FLOAT), f(x) {} - json_val(HRESULT hr) : type(DT_STRING) { + static json_val from_bool(bool x) { json_val ans; ans.type = DT_BOOL; ans.b = x; return ans; } + static json_val from_double(double x) { json_val ans; ans.type = DT_FLOAT; ans.f = x; return ans; } + static json_val from_integer(int64_t x) { json_val ans; ans.type = DT_INT; ans.i = x; return ans; } + + static json_val from_hresult(HRESULT hr) { + json_val ans; ans.type = DT_STRING; std::array str; str[0] = '0'; str[1] = 'x'; if (auto [ptr, ec] = std::to_chars(str.data()+2, str.data() + str.size(), (uint32_t)hr, 16); ec == std::errc()) { - s = std::string(str.data(), ptr - str.data()); + ans.s = std::string(str.data(), ptr - str.data()); } else { throw std::exception(std::make_error_code(ec).message().c_str()); } + return ans; } json_val(VoiceInformation const& voice) : type(DT_OBJECT) { @@ -298,8 +300,8 @@ public: {"type", json_val(label)}, {"text", json_val(cue.Text())}, {"start_time", json_val(cue.StartTime())}, - {"start_pos_in_text", json_val(cue.StartPositionInInput().Value())}, - {"end_pos_in_text", json_val(cue.EndPositionInInput().Value())}, + {"start_pos_in_text", json_val::from_integer(cue.StartPositionInInput().Value())}, + {"end_pos_in_text", json_val::from_integer(cue.EndPositionInInput().Value())}, }; } @@ -320,8 +322,8 @@ output(id_type cmd_id, std::string_view const &msg_type, json_val const &&msg) { static void output_error(id_type cmd_id, std::string_view const &msg, std::string_view const &error, int64_t line, HRESULT hr=S_OK) { - std::map m = {{"msg", json_val(msg)}, {"error", json_val(error)}, {"file", json_val("winspeech.cpp")}, {"line", json_val(line)}}; - if (hr != S_OK) m["hr"] = json_val(hr); + std::map m = {{"msg", json_val(msg)}, {"error", json_val(error)}, {"file", json_val("winspeech.cpp")}, {"line", json_val::from_integer(line)}}; + if (hr != S_OK) m["hr"] = json_val::from_hresult(hr); output(cmd_id, "error", std::move(m)); } @@ -831,6 +833,7 @@ class Synthesizer { stop_current_activity(); current_cmd_id.store(cmd_id); } + output(cmd_id, "synthesizing", {{"ssml", json_val::from_bool(is_ssml)}}); bool ok = false; try { if (is_ssml) stream = co_await synth.SynthesizeSsmlToStreamAsync(text); @@ -931,7 +934,7 @@ handle_stdin_message(winrt::hstring const &&msg) { auto vol = parse_double(parts[0].data()); sx.volume(vol); } - output(cmd_id, "volume", {{"value", json_val(sx.volume())}}); + output(cmd_id, "volume", {{"value", json_val::from_double(sx.volume())}}); } else throw std::string("Unknown command: ") + winrt::to_string(command); } CATCH_ALL_EXCEPTIONS("Error handling input message", cmd_id); From aa11af35103dc27ef33e15a595140c87a062cde8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 25 Jan 2023 11:04:43 +0530 Subject: [PATCH 0184/2055] Smarter integer type handing in json_val --- src/calibre/utils/windows/winspeech.cpp | 39 ++++++++++++++++++------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 40a6190fab..40a84169d8 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -170,11 +170,12 @@ serialize_float(std::ostream &out, T val, std::chars_format fmt = std::chars_for class json_val { // {{{ private: - enum { DT_INT, DT_STRING, DT_LIST, DT_OBJECT, DT_NONE, DT_BOOL, DT_FLOAT } type; + enum { DT_INT, DT_UINT, DT_STRING, DT_LIST, DT_OBJECT, DT_NONE, DT_BOOL, DT_FLOAT } type; std::string s; bool b; double f; int64_t i; + uint64_t u; std::vector list; std::map object; @@ -187,6 +188,9 @@ private: case DT_INT: // this is not really correct since JS has various limits on numeric types, but good enough for us serialize_integer(out, i); break; + case DT_UINT: + // this is not really correct since JS has various limits on numeric types, but good enough for us + serialize_integer(out, u); break; case DT_FLOAT: // again not technically correct serialize_float(out, f); break; @@ -230,10 +234,6 @@ public: json_val(std::map &&m) : type(DT_OBJECT), object(m) {} json_val(std::initializer_list> const& vals) : type(DT_OBJECT), object(vals) { } - static json_val from_bool(bool x) { json_val ans; ans.type = DT_BOOL; ans.b = x; return ans; } - static json_val from_double(double x) { json_val ans; ans.type = DT_FLOAT; ans.f = x; return ans; } - static json_val from_integer(int64_t x) { json_val ans; ans.type = DT_INT; ans.i = x; return ans; } - static json_val from_hresult(HRESULT hr) { json_val ans; ans.type = DT_STRING; std::array str; @@ -300,11 +300,30 @@ public: {"type", json_val(label)}, {"text", json_val(cue.Text())}, {"start_time", json_val(cue.StartTime())}, - {"start_pos_in_text", json_val::from_integer(cue.StartPositionInInput().Value())}, - {"end_pos_in_text", json_val::from_integer(cue.EndPositionInInput().Value())}, + {"start_pos_in_text", json_val(cue.StartPositionInInput().Value())}, + {"end_pos_in_text", json_val(cue.EndPositionInInput().Value())}, }; } + template json_val(T x) { + static_assert(sizeof(bool) < sizeof(int16_t), "The bool type on this machine is more than one byte"); + if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { + type = DT_INT; + i = x; + } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { + type = DT_UINT; + u = x; + } else if constexpr (std::is_same_v || std::is_same_v) { + type = DT_FLOAT; + f = x; + } else if constexpr (std::is_same_v) { + type = DT_BOOL; + b = x; + } else { + static_assert(false, "Unknown type T cannot be converted to JSON"); + } + } + friend std::ostream& operator<<(std::ostream &os, const json_val &self) { self.serialize(os); return os; @@ -322,7 +341,7 @@ output(id_type cmd_id, std::string_view const &msg_type, json_val const &&msg) { static void output_error(id_type cmd_id, std::string_view const &msg, std::string_view const &error, int64_t line, HRESULT hr=S_OK) { - std::map m = {{"msg", json_val(msg)}, {"error", json_val(error)}, {"file", json_val("winspeech.cpp")}, {"line", json_val::from_integer(line)}}; + std::map m = {{"msg", json_val(msg)}, {"error", json_val(error)}, {"file", json_val("winspeech.cpp")}, {"line", json_val(line)}}; if (hr != S_OK) m["hr"] = json_val::from_hresult(hr); output(cmd_id, "error", std::move(m)); } @@ -833,7 +852,7 @@ class Synthesizer { stop_current_activity(); current_cmd_id.store(cmd_id); } - output(cmd_id, "synthesizing", {{"ssml", json_val::from_bool(is_ssml)}}); + output(cmd_id, "synthesizing", {{"ssml", json_val(is_ssml)}}); bool ok = false; try { if (is_ssml) stream = co_await synth.SynthesizeSsmlToStreamAsync(text); @@ -934,7 +953,7 @@ handle_stdin_message(winrt::hstring const &&msg) { auto vol = parse_double(parts[0].data()); sx.volume(vol); } - output(cmd_id, "volume", {{"value", json_val::from_double(sx.volume())}}); + output(cmd_id, "volume", {{"value", json_val(sx.volume())}}); } else throw std::string("Unknown command: ") + winrt::to_string(command); } CATCH_ALL_EXCEPTIONS("Error handling input message", cmd_id); From f7bbbbaf3fd2f29b69b4f713a31191c6b8a7ad56 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 25 Jan 2023 11:14:22 +0530 Subject: [PATCH 0185/2055] Remove a bunch on unnecessary copies when constructing output messages Also start work on custom mark support --- src/calibre/utils/windows/winspeech.cpp | 23 +++++++++++++---------- src/calibre/utils/windows/winspeech.py | 12 ++++++++++-- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 40a84169d8..b0a3aeeb27 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -341,7 +341,7 @@ output(id_type cmd_id, std::string_view const &msg_type, json_val const &&msg) { static void output_error(id_type cmd_id, std::string_view const &msg, std::string_view const &error, int64_t line, HRESULT hr=S_OK) { - std::map m = {{"msg", json_val(msg)}, {"error", json_val(error)}, {"file", json_val("winspeech.cpp")}, {"line", json_val(line)}}; + std::map m = {{"msg", msg}, {"error", error}, {"file", "winspeech.cpp"}, {"line", line}}; if (hr != S_OK) m["hr"] = json_val::from_hresult(hr); output(cmd_id, "error", std::move(m)); } @@ -770,19 +770,19 @@ class Synthesizer { revoker.playback_state_changed = player.PlaybackSession().PlaybackStateChanged( winrt::auto_revoke, [cmd_id](auto session, auto const&) { if (main_loop_is_running.load()) sx.output( - cmd_id, "playback_state_changed", {{"state", json_val(session.PlaybackState())}}); + cmd_id, "playback_state_changed", {{"state", session.PlaybackState()}}); }); revoker.media_opened = player.MediaOpened(winrt::auto_revoke, [cmd_id](auto player, auto const&) { if (main_loop_is_running.load()) sx.output( - cmd_id, "media_state_changed", {{"state", json_val("opened")}}); + cmd_id, "media_state_changed", {{"state", "opened"}}); }); revoker.media_ended = player.MediaEnded(winrt::auto_revoke, [cmd_id](auto player, auto const&) { if (main_loop_is_running.load()) sx.output( - cmd_id, "media_state_changed", {{"state", json_val("ended")}}); + cmd_id, "media_state_changed", {{"state", "ended"}}); }); revoker.media_failed = player.MediaFailed(winrt::auto_revoke, [cmd_id](auto player, auto const& args) { if (main_loop_is_running.load()) sx.output( - cmd_id, "media_state_changed", {{"state", json_val("failed")}, {"error", args.ErrorMessage()}, {"code", json_val(args.Error())}}); + cmd_id, "media_state_changed", {{"state", "failed"}, {"error", args.ErrorMessage()}, {"code", args.Error()}}); }); current_stream = stream; current_source = MediaSource::CreateFromStream(current_stream, current_stream.ContentType()); @@ -826,8 +826,6 @@ class Synthesizer { void initialize() { synth = SpeechSynthesizer(); - // synth.Options().IncludeSentenceBoundaryMetadata(true); - synth.Options().IncludeWordBoundaryMetadata(true); player = MediaPlayer(); player.AudioCategory(MediaPlayerAudioCategory::Speech); player.AutoPlay(true); @@ -851,8 +849,10 @@ class Synthesizer { { std::scoped_lock sl(recursive_lock); stop_current_activity(); current_cmd_id.store(cmd_id); + synth.Options().IncludeSentenceBoundaryMetadata(true); + synth.Options().IncludeWordBoundaryMetadata(true); } - output(cmd_id, "synthesizing", {{"ssml", json_val(is_ssml)}}); + output(cmd_id, "synthesizing", {{"ssml", is_ssml}}); bool ok = false; try { if (is_ssml) stream = co_await synth.SynthesizeSsmlToStreamAsync(text); @@ -888,6 +888,7 @@ handle_speak(id_type cmd_id, std::vector &parts) { } parts.erase(parts.begin(), parts.begin() + 2); std::wstring address; + std::wstring_view text; id_type shm_size = 0; if (is_shm) { shm_size = parse_id(parts.at(0)); @@ -903,11 +904,13 @@ handle_speak(id_type cmd_id, std::vector &parts) { return; } address = winrt::to_hstring((const char*)mapping.ptr()); + text = address; } else { address = join(parts); if (address.size() == 0) throw std::string("Address missing"); + text = address; } - sx.speak(cmd_id, address, is_ssml); + sx.speak(cmd_id, text, is_ssml); } static int64_t @@ -953,7 +956,7 @@ handle_stdin_message(winrt::hstring const &&msg) { auto vol = parse_double(parts[0].data()); sx.volume(vol); } - output(cmd_id, "volume", {{"value", json_val(sx.volume())}}); + output(cmd_id, "volume", {{"value", sx.volume()}}); } else throw std::string("Unknown command: ") + winrt::to_string(command); } CATCH_ALL_EXCEPTIONS("Error handling input message", cmd_id); diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index a6ee9745ee..9f6b5742da 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -61,7 +61,7 @@ def encode_to_file_object(text, output) -> int: return sz -def develop_speech(text='Lucca Brazzi sleeps with the fishes.'): +def develop_speech(text='Lucca Brazzi sleeps with the fishes.', mark_words=False): p = start_worker() print('\x1b[32mSpeaking', text, '\x1b[39m]]'[:-2], flush=True) q = Queue() @@ -79,8 +79,16 @@ def develop_speech(text='Lucca Brazzi sleeps with the fishes.'): Thread(name='Echo', target=echo_output, args=(p,), daemon=True).start() exit_code = 0 + st = 'ssml' if ' Date: Wed, 25 Jan 2023 23:22:24 +0530 Subject: [PATCH 0186/2055] Work on supporting word marks --- src/calibre/utils/windows/winspeech.cpp | 130 ++++++++++++++++++++---- src/calibre/utils/windows/winspeech.py | 3 +- 2 files changed, 111 insertions(+), 22 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index b0a3aeeb27..23d2c9b39b 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -53,7 +53,8 @@ template static void debug(Args... args) { std::scoped_lock _sl_(output_lock); DWORD tid = GetCurrentThreadId(); - if (tid == main_thread_id) std::cerr << "thread-main"; else std::cerr << "thread-" << tid << ": "; + if (tid == main_thread_id) std::cerr << "thread-main"; else std::cerr << "thread-" << tid; + std::cerr << ": "; __debug_multiple(args...); } @@ -297,11 +298,11 @@ public: json_val(winrt::hstring const &label, SpeechCue const &cue) : type(DT_OBJECT) { object = { - {"type", json_val(label)}, - {"text", json_val(cue.Text())}, - {"start_time", json_val(cue.StartTime())}, - {"start_pos_in_text", json_val(cue.StartPositionInInput().Value())}, - {"end_pos_in_text", json_val(cue.EndPositionInInput().Value())}, + {"type", label}, + {"text", cue.Text()}, + {"start_time", cue.StartTime()}, + {"start_pos_in_text", cue.StartPositionInInput().Value()}, + {"end_pos_in_text", cue.EndPositionInInput().Value()}, }; } @@ -733,6 +734,12 @@ struct Revokers { std::vector track_failed; }; +struct Mark { + uint32_t id, pos_in_text; + Mark(uint32_t id, uint32_t pos) : id(id), pos_in_text(pos) {} +}; +typedef std::vector Marks; + class Synthesizer { private: SpeechSynthesizer synth{nullptr}; @@ -740,6 +747,8 @@ class Synthesizer { MediaSource current_source{nullptr}; SpeechSynthesisStream current_stream{nullptr}; MediaPlaybackItem current_item{nullptr}; + std::vector current_text_storage; + Marks current_marks; std::atomic current_cmd_id; Revokers revoker; @@ -764,9 +773,26 @@ class Synthesizer { current_item.TimedMetadataTracks().SetPresentationMode((unsigned int)index, TimedMetadataTrackPresentationMode::ApplicationPresented); } - void load_stream_for_playback(SpeechSynthesisStream const &stream, id_type cmd_id) { + void add_cues() { + TimedMetadataTrack track(L"mark", L"en-us", TimedMetadataKind::Speech); + track.Label(L"mark"); + for (const Mark &mark : current_marks) { + SpeechCue cue; + cue.StartPositionInInput(IReference{(int)mark.pos_in_text}); + cue.EndPositionInInput(IReference{(int)mark.pos_in_text + 1}); + cue.Text(winrt::to_hstring(mark.id)); + track.AddCue(cue); + } + current_source.ExternalTimedMetadataTracks().Append(track); + } + + void load_stream_for_playback(SpeechSynthesisStream const &stream, id_type cmd_id, bool is_cued) { std::scoped_lock sl(recursive_lock); if (cmd_id != current_cmd_id.load()) return; + current_stream = stream; + current_source = MediaSource::CreateFromStream(current_stream, current_stream.ContentType()); + if (is_cued) add_cues(); + revoker.playback_state_changed = player.PlaybackSession().PlaybackStateChanged( winrt::auto_revoke, [cmd_id](auto session, auto const&) { if (main_loop_is_running.load()) sx.output( @@ -784,8 +810,6 @@ class Synthesizer { if (main_loop_is_running.load()) sx.output( cmd_id, "media_state_changed", {{"state", "failed"}, {"error", args.ErrorMessage()}, {"code", args.Error()}}); }); - current_stream = stream; - current_source = MediaSource::CreateFromStream(current_stream, current_stream.ContentType()); current_item = MediaPlaybackItem(current_source); revoker.timed_metadata_tracks_changed = current_item.TimedMetadataTracksChanged(winrt::auto_revoke, @@ -840,19 +864,22 @@ class Synthesizer { current_stream = SpeechSynthesisStream{nullptr}; current_item = MediaPlaybackItem{nullptr}; player.Pause(); + current_text_storage = std::vector(); + current_marks = Marks(); } } - winrt::fire_and_forget speak(id_type cmd_id, std::wstring_view const &text, bool is_ssml) { - // winrt::apartment_context main_thread; // capture calling thread + winrt::fire_and_forget speak(id_type cmd_id, std::wstring_view const &text, bool is_ssml, bool is_cued, std::vector &&buf, Marks const && marks) { SpeechSynthesisStream stream{nullptr}; { std::scoped_lock sl(recursive_lock); stop_current_activity(); current_cmd_id.store(cmd_id); + current_text_storage = std::move(buf); + current_marks = std::move(marks); synth.Options().IncludeSentenceBoundaryMetadata(true); synth.Options().IncludeWordBoundaryMetadata(true); } - output(cmd_id, "synthesizing", {{"ssml", is_ssml}}); + output(cmd_id, "synthesizing", {{"ssml", is_ssml}, {"num_marks", current_marks.size()}, {"text_length", text.size()}}); bool ok = false; try { if (is_ssml) stream = co_await synth.SynthesizeSsmlToStreamAsync(text); @@ -860,9 +887,10 @@ class Synthesizer { ok = true; } CATCH_ALL_EXCEPTIONS("Failed to synthesize speech", cmd_id); if (ok) { - // co_await main_thread; if (main_loop_is_running.load()) { - load_stream_for_playback(stream, cmd_id); + try { + load_stream_for_playback(stream, cmd_id, is_cued); + } CATCH_ALL_EXCEPTIONS("Failed to load synthesized stream for playback", cmd_id); } } } @@ -877,19 +905,68 @@ class Synthesizer { static Synthesizer sx; +static size_t +decode_into(std::string_view src, std::wstring_view dest) { + int n = MultiByteToWideChar(CP_UTF8, 0, src.data(), (int)src.size(), (wchar_t*)dest.data(), (int)dest.size()); + if (n == 0 && src.size() > 0) { + switch (GetLastError()) { + case ERROR_INSUFFICIENT_BUFFER: + throw std::exception("Output buffer too small while decoding cued text"); + case ERROR_INVALID_FLAGS: + throw std::exception("Invalid flags while decoding cued text"); + case ERROR_INVALID_PARAMETER: + throw std::exception("Invalid parameters while decoding cued text"); + case ERROR_NO_UNICODE_TRANSLATION: + throw std::exception("Invalid UTF-8 found while decoding cued text"); + default: + throw std::exception("Unknown error while decoding cued text"); + } + } + return n; +} + +static std::wstring_view +parse_cued_text(std::string_view src, Marks &marks, std::wstring_view dest) { + size_t dest_pos = 0; + if (dest.size() < src.size()) throw std::exception("Destination buffer for parse_cued_text() too small"); + while (src.size()) { + auto pos = src.find('\0'); + size_t limit = pos == std::string_view::npos ? src.size() : pos; + if (limit) { + dest_pos += decode_into( + std::string_view(src.data(), limit), + std::wstring_view(dest.data() + dest_pos, dest.size() - dest_pos)); + src = std::string_view(src.data() + limit, src.size() - limit); + } + if (pos != std::string_view::npos) { + src = std::string_view(src.data() + 1, src.size() - 1); + if (src.size() >= 4) { + uint32_t mark = *((uint32_t*)src.data()); + marks.emplace_back(mark, (uint32_t)dest_pos); + src = std::string_view(src.data() + 4, src.size() - 4); + } + } + } + *((wchar_t*)dest.data() + dest_pos) = 0; // ensure NULL termination + return std::wstring_view(dest.data(), dest_pos); +} + static void handle_speak(id_type cmd_id, std::vector &parts) { - bool is_ssml = false, is_shm = false; + bool is_ssml = false, is_shm = false, is_cued = false; try { is_ssml = parts.at(0) == L"ssml"; is_shm = parts.at(1) == L"shm"; + is_cued = parts.at(0) == L"cued"; } catch (std::exception const&) { throw std::string("Not a well formed speak command"); } parts.erase(parts.begin(), parts.begin() + 2); std::wstring address; - std::wstring_view text; id_type shm_size = 0; + Marks marks; + std::vector buf; + std::wstring_view text; if (is_shm) { shm_size = parse_id(parts.at(0)); address = parts.at(1); @@ -903,14 +980,25 @@ handle_speak(id_type cmd_id, std::vector &parts) { output_error(cmd_id, "Could not map shared memory with error", std::to_string(GetLastError()), __LINE__); return; } - address = winrt::to_hstring((const char*)mapping.ptr()); - text = address; + buf.reserve(shm_size + 2); + std::string_view src((const char*)mapping.ptr(), shm_size); + std::wstring_view dest(buf.data(), buf.capacity()); + if (is_cued) { + text = parse_cued_text(src, marks, dest); + } else { + size_t n = decode_into(src, dest); + *(buf.data() + n) = 0; // ensure null termination + text = std::wstring_view(buf.data(), n); + } } else { address = join(parts); if (address.size() == 0) throw std::string("Address missing"); - text = address; + buf.reserve(address.size() + 1); + text = std::wstring_view(buf.data(), address.size() + 1); + memcpy(buf.data(), address.c_str(), address.size()); + *(buf.data() + address.size()) = 0; // ensure null termination } - sx.speak(cmd_id, text, is_ssml); + sx.speak(cmd_id, text, is_ssml, is_cued, std::move(buf), std::move(marks)); } static int64_t @@ -940,7 +1028,7 @@ handle_stdin_message(winrt::hstring const &&msg) { return 0; } else if (command == L"echo") { - output(cmd_id, "echo", {{"msg", json_val(std::move(join(parts)))}}); + output(cmd_id, "echo", {{"msg", join(parts)}}); } else if (command == L"default_voice") { output(cmd_id, "default_voice", SpeechSynthesizer::DefaultVoice()); diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index 9f6b5742da..ad2c762884 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -61,7 +61,7 @@ def encode_to_file_object(text, output) -> int: return sz -def develop_speech(text='Lucca Brazzi sleeps with the fishes.', mark_words=False): +def develop_speech(text='Lucca Brazzi sleeps with the fishes.', mark_words=True): p = start_worker() print('\x1b[32mSpeaking', text, '\x1b[39m]]'[:-2], flush=True) q = Queue() @@ -81,6 +81,7 @@ def develop_speech(text='Lucca Brazzi sleeps with the fishes.', mark_words=False exit_code = 0 st = 'ssml' if ' Date: Thu, 26 Jan 2023 09:17:09 +0530 Subject: [PATCH 0187/2055] Cleanup some code --- src/calibre/utils/windows/winspeech.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 23d2c9b39b..b643bd0489 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -933,22 +933,20 @@ parse_cued_text(std::string_view src, Marks &marks, std::wstring_view dest) { auto pos = src.find('\0'); size_t limit = pos == std::string_view::npos ? src.size() : pos; if (limit) { - dest_pos += decode_into( - std::string_view(src.data(), limit), - std::wstring_view(dest.data() + dest_pos, dest.size() - dest_pos)); - src = std::string_view(src.data() + limit, src.size() - limit); + dest_pos += decode_into(src.substr(0, limit), dest.substr(dest_pos, dest.size() - dest_pos)); + src = src.substr(limit, src.size() - limit); } if (pos != std::string_view::npos) { - src = std::string_view(src.data() + 1, src.size() - 1); + src = src.substr(1, src.size() - 1); if (src.size() >= 4) { uint32_t mark = *((uint32_t*)src.data()); marks.emplace_back(mark, (uint32_t)dest_pos); - src = std::string_view(src.data() + 4, src.size() - 4); + src = src.substr(4, src.size() - 4); } } } *((wchar_t*)dest.data() + dest_pos) = 0; // ensure NULL termination - return std::wstring_view(dest.data(), dest_pos); + return dest.substr(0, dest_pos); } static void @@ -987,16 +985,16 @@ handle_speak(id_type cmd_id, std::vector &parts) { text = parse_cued_text(src, marks, dest); } else { size_t n = decode_into(src, dest); - *(buf.data() + n) = 0; // ensure null termination + buf[n] = 0; // ensure null termination text = std::wstring_view(buf.data(), n); } } else { address = join(parts); if (address.size() == 0) throw std::string("Address missing"); buf.reserve(address.size() + 1); - text = std::wstring_view(buf.data(), address.size() + 1); - memcpy(buf.data(), address.c_str(), address.size()); - *(buf.data() + address.size()) = 0; // ensure null termination + text = std::wstring_view(buf.data(), address.size()); + address.copy(buf.data(), address.size()); + buf[address.size()] = 0; // null terminate } sx.speak(cmd_id, text, is_ssml, is_cued, std::move(buf), std::move(marks)); } From ad4aeaa1a04de7446c5a2395ae611cbe2c3940bd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 26 Jan 2023 09:38:08 +0530 Subject: [PATCH 0188/2055] Fix bug reported by sanitizer --- src/calibre/utils/spell/hunspell_wrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/spell/hunspell_wrapper.cpp b/src/calibre/utils/spell/hunspell_wrapper.cpp index 0a672fd6d6..098b496b5f 100644 --- a/src/calibre/utils/spell/hunspell_wrapper.cpp +++ b/src/calibre/utils/spell/hunspell_wrapper.cpp @@ -77,7 +77,7 @@ suggest(Dictionary *self, PyObject *args) { const std::vector& word_list = self->handle->suggest(word); ans = PyTuple_New(word_list.size()); - if (ans == NULL) PyErr_NoMemory(); + if (ans == NULL) return PyErr_NoMemory(); Py_ssize_t i = 0; for(auto const& s: word_list) { temp = PyUnicode_Decode(s.c_str(), s.size(), self->encoding, "strict"); From 0ec5ae02e90b39a14168b789cd91c02d818c72c6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 26 Jan 2023 09:38:37 +0530 Subject: [PATCH 0189/2055] DRYer --- src/calibre/utils/windows/common.h | 2 +- src/calibre/utils/windows/winspeech.cpp | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/calibre/utils/windows/common.h b/src/calibre/utils/windows/common.h index dc3627b090..ee903c805b 100644 --- a/src/calibre/utils/windows/common.h +++ b/src/calibre/utils/windows/common.h @@ -8,7 +8,7 @@ #define PY_SSIZE_T_CLEAN #define UNICODE #define _UNICODE -#include +#include #include #include #include "../cpp_binding.h" diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index b643bd0489..64e89195be 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -945,7 +945,6 @@ parse_cued_text(std::string_view src, Marks &marks, std::wstring_view dest) { } } } - *((wchar_t*)dest.data() + dest_pos) = 0; // ensure NULL termination return dest.substr(0, dest_pos); } @@ -985,7 +984,6 @@ handle_speak(id_type cmd_id, std::vector &parts) { text = parse_cued_text(src, marks, dest); } else { size_t n = decode_into(src, dest); - buf[n] = 0; // ensure null termination text = std::wstring_view(buf.data(), n); } } else { @@ -994,8 +992,8 @@ handle_speak(id_type cmd_id, std::vector &parts) { buf.reserve(address.size() + 1); text = std::wstring_view(buf.data(), address.size()); address.copy(buf.data(), address.size()); - buf[address.size()] = 0; // null terminate } + *((wchar_t*)text.data() + text.size()) = 0; // ensure NULL termination sx.speak(cmd_id, text, is_ssml, is_cued, std::move(buf), std::move(marks)); } From a92149c5b839950d4b58a010960254838a9bdff4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 26 Jan 2023 13:27:05 +0530 Subject: [PATCH 0190/2055] Move the compile_commands database into build dir --- .gitignore | 2 -- setup/build.py | 2 +- src/calibre/utils/speedup.c | 6 +++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 6e51dafe12..04ed5e5a4b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,6 @@ .bzrignore .build-cache .cache -/compile_commands.json -/link_commands.json /src/calibre/plugins /resources/images.qrc /resources/icons.rcc diff --git a/setup/build.py b/setup/build.py index 67b980de43..b545e46ce7 100644 --- a/setup/build.py +++ b/setup/build.py @@ -301,7 +301,7 @@ class Build(Command): def dump_db(self, name, db): try: - with open(f'{name}_commands.json', 'w') as f: + with open(f'build/{name}_commands.json', 'w') as f: json.dump(db, f, indent=2) except OSError as err: if err.errno != errno.EROFS: diff --git a/src/calibre/utils/speedup.c b/src/calibre/utils/speedup.c index ef6d87c752..c521bd7693 100644 --- a/src/calibre/utils/speedup.c +++ b/src/calibre/utils/speedup.c @@ -164,7 +164,7 @@ speedup_create_texture(PyObject *self, PyObject *args, PyObject *kw) { if (radius <= 0) { PyErr_SetString(PyExc_ValueError, "The radius must be positive"); return NULL; } if (width > 100000 || height > 10000) { PyErr_SetString(PyExc_ValueError, "The width or height is too large"); return NULL; } if (width < 1 || height < 1) { PyErr_SetString(PyExc_ValueError, "The width or height is too small"); return NULL; } - snprintf(header, 99, "P6\n%d %d\n255\n", (int)width, (int)height); + snprintf(header, sizeof(header)-1, "P6\n%d %d\n255\n", (int)width, (int)height); // NOLINT kernel = (double*)calloc(weight * weight, sizeof(double)); if (kernel == NULL) { PyErr_NoMemory(); return NULL; } @@ -177,7 +177,7 @@ speedup_create_texture(PyObject *self, PyObject *args, PyObject *kw) { // Random noise, noisy pixels are blend_alpha, other pixels are 0 for (i = 0; i < width * height; i++) { - if (((float)(rand()) / RAND_MAX) <= density) mask[i] = blend_alpha; + if (((float)(rand()) / (float)RAND_MAX) <= density) mask[i] = blend_alpha; } // Blur the noise using the gaussian kernel @@ -195,7 +195,7 @@ speedup_create_texture(PyObject *self, PyObject *args, PyObject *kw) { } // Create the texture in PPM (P6) format - memcpy(ppm, header, strlen(header)); + memcpy(ppm, header, strlen(header)); // NOLINT t = ppm + strlen(header); for (i = 0, j = 0; j < width * height; i += 3, j += 1) { #define BLEND(src, dest) ( ((unsigned char)(src * mask[j])) + ((unsigned char)(dest * (1 - mask[j]))) ) From 4e4088c27c2a432faea4c0b57f6dccbbf04e6971 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 26 Jan 2023 16:30:02 +0530 Subject: [PATCH 0191/2055] ... --- src/calibre/utils/windows/winspeech.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 64e89195be..40fdc45b2a 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -37,6 +37,7 @@ using namespace winrt::Windows::Storage::Streams; typedef uint64_t id_type; static std::mutex output_lock; +static DWORD main_thread_id; template static void __debug_multiple(T x, Args... args) { @@ -59,7 +60,6 @@ debug(Args... args) { } static std::atomic_bool main_loop_is_running; -static DWORD main_thread_id; enum { STDIN_FAILED = 1, STDIN_MSG, From 2d386d87da99b2e8b390a03bdd390c38f95aceb0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 26 Jan 2023 17:44:59 +0530 Subject: [PATCH 0192/2055] free more carefully making sure we dont double free --- src/calibre/utils/cpp_binding.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/utils/cpp_binding.h b/src/calibre/utils/cpp_binding.h index 58b130a1bb..31bbc1dcd5 100644 --- a/src/calibre/utils/cpp_binding.h +++ b/src/calibre/utils/cpp_binding.h @@ -32,8 +32,9 @@ class generic_raii { void release() noexcept { if (handle != null) { - free_T(handle); + T temp = handle; handle = null; + free_T(temp); } } From 69656be428bf3f2a51ad7b02d6ba26bb7445c1f7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 26 Jan 2023 19:27:36 +0530 Subject: [PATCH 0193/2055] clang cant compile the handle_raii class because it thinks INVALID_HANDLE_VALUE is not a constexpr Make a standalone class for it instead of inheriting generic_raii --- src/calibre/utils/windows/common.h | 33 ++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/calibre/utils/windows/common.h b/src/calibre/utils/windows/common.h index ee903c805b..3b5dcdf632 100644 --- a/src/calibre/utils/windows/common.h +++ b/src/calibre/utils/windows/common.h @@ -59,11 +59,36 @@ class scoped_com_initializer { // {{{ #define INITIALIZE_COM_IN_FUNCTION scoped_com_initializer com; if (!com) return com.set_python_error(); static inline void co_task_mem_free(void* m) { CoTaskMemFree(m); } -typedef generic_raii(NULL)> com_wchar_raii; -static inline void handle_destructor(HANDLE p) { CloseHandle(p); } -typedef generic_raii handle_raii; +typedef generic_raii com_wchar_raii; static inline void mapping_destructor(void *p) { UnmapViewOfFile(p); } -typedef generic_raii(NULL)> mapping_raii; +typedef generic_raii mapping_raii; + +class handle_raii { + private: + handle_raii( const handle_raii & ) noexcept; + handle_raii & operator=( const handle_raii & ) noexcept ; + + protected: + HANDLE handle; + + public: + explicit handle_raii(HANDLE h = INVALID_HANDLE_VALUE) noexcept : handle(h) {} + ~handle_raii() noexcept { release(); } + + void release() noexcept { + if (handle != INVALID_HANDLE_VALUE) { + HANDLE temp = handle; + handle = INVALID_HANDLE_VALUE; + CloseHandle(temp); + } + } + + HANDLE ptr() noexcept { return handle; } + HANDLE detach() noexcept { HANDLE ans = handle; handle = INVALID_HANDLE_VALUE; return ans; } + void attach(HANDLE val) noexcept { release(); handle = val; } + explicit operator bool() const noexcept { return handle != INVALID_HANDLE_VALUE; } +}; + struct prop_variant : PROPVARIANT { prop_variant(VARTYPE vt=VT_EMPTY) noexcept : PROPVARIANT{} { PropVariantInit(this); this->vt = vt; } From c895aab7ac927319ffa4dd58e76a07014852cc12 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 26 Jan 2023 19:45:39 +0530 Subject: [PATCH 0194/2055] More fixes for compilation with clang-cl --- src/calibre/utils/windows/winspeech.cpp | 145 +++++++++++++----------- src/calibre/utils/windows/winutil.cpp | 8 +- 2 files changed, 81 insertions(+), 72 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 40fdc45b2a..1b243832bf 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -21,12 +21,12 @@ #include #include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; @@ -320,9 +320,12 @@ public: } else if constexpr (std::is_same_v) { type = DT_BOOL; b = x; - } else { + } +#ifdef _MSVC + else { static_assert(false, "Unknown type T cannot be converted to JSON"); } +#endif } friend std::ostream& operator<<(std::ostream &os, const json_val &self) { @@ -754,24 +757,8 @@ class Synthesizer { Revokers revoker; std::recursive_mutex recursive_lock; - void register_metadata_handler_for_track(uint32_t index, id_type cmd_id) { - TimedMetadataTrack track = current_item.TimedMetadataTracks().GetAt(index); - std::scoped_lock sl(recursive_lock); - if (current_cmd_id.load() != cmd_id) return; - revoker.cue_entered.push_back(track.CueEntered(winrt::auto_revoke, [cmd_id](auto track, const auto& args) { - if (main_loop_is_running.load()) sx.output( - cmd_id, "cue_entered", json_val(track.Label(), args.Cue().as())); - })); - revoker.cue_exited.push_back(track.CueExited(winrt::auto_revoke, [cmd_id](auto track, const auto& args) { - if (main_loop_is_running.load()) sx.output( - cmd_id, "cue_exited", json_val(track.Label(), args.Cue().as())); - })); - revoker.track_failed.push_back(track.TrackFailed(winrt::auto_revoke, [cmd_id](auto, const auto& args) { - if (main_loop_is_running.load()) sx.output( - cmd_id, "track_failed", {}); - })); - current_item.TimedMetadataTracks().SetPresentationMode((unsigned int)index, TimedMetadataTrackPresentationMode::ApplicationPresented); - } + void register_metadata_handler_for_track(uint32_t index, id_type cmd_id); + void load_stream_for_playback(SpeechSynthesisStream const &stream, id_type cmd_id, bool is_cued); void add_cues() { TimedMetadataTrack track(L"mark", L"en-us", TimedMetadataKind::Speech); @@ -786,48 +773,6 @@ class Synthesizer { current_source.ExternalTimedMetadataTracks().Append(track); } - void load_stream_for_playback(SpeechSynthesisStream const &stream, id_type cmd_id, bool is_cued) { - std::scoped_lock sl(recursive_lock); - if (cmd_id != current_cmd_id.load()) return; - current_stream = stream; - current_source = MediaSource::CreateFromStream(current_stream, current_stream.ContentType()); - if (is_cued) add_cues(); - - revoker.playback_state_changed = player.PlaybackSession().PlaybackStateChanged( - winrt::auto_revoke, [cmd_id](auto session, auto const&) { - if (main_loop_is_running.load()) sx.output( - cmd_id, "playback_state_changed", {{"state", session.PlaybackState()}}); - }); - revoker.media_opened = player.MediaOpened(winrt::auto_revoke, [cmd_id](auto player, auto const&) { - if (main_loop_is_running.load()) sx.output( - cmd_id, "media_state_changed", {{"state", "opened"}}); - }); - revoker.media_ended = player.MediaEnded(winrt::auto_revoke, [cmd_id](auto player, auto const&) { - if (main_loop_is_running.load()) sx.output( - cmd_id, "media_state_changed", {{"state", "ended"}}); - }); - revoker.media_failed = player.MediaFailed(winrt::auto_revoke, [cmd_id](auto player, auto const& args) { - if (main_loop_is_running.load()) sx.output( - cmd_id, "media_state_changed", {{"state", "failed"}, {"error", args.ErrorMessage()}, {"code", args.Error()}}); - }); - current_item = MediaPlaybackItem(current_source); - - revoker.timed_metadata_tracks_changed = current_item.TimedMetadataTracksChanged(winrt::auto_revoke, - [cmd_id](auto, auto const &args) { - auto change_type = args.CollectionChange(); - long index; - switch (change_type) { - case CollectionChange::ItemInserted: index = args.Index(); break; - case CollectionChange::Reset: index = -1; break; - default: index = -2; break; - } - if (index > -2 && main_loop_is_running.load()) sx.register_metadata_handler_for_speech(cmd_id, index); - }); - register_metadata_handler_for_speech(cmd_id, -1); - - player.Source(current_item); - } - public: void register_metadata_handler_for_speech(id_type cmd_id, long index) { std::scoped_lock sl(recursive_lock); @@ -905,6 +850,70 @@ class Synthesizer { static Synthesizer sx; +void +Synthesizer::register_metadata_handler_for_track(uint32_t index, id_type cmd_id) { + TimedMetadataTrack track = current_item.TimedMetadataTracks().GetAt(index); + std::scoped_lock sl(recursive_lock); + if (current_cmd_id.load() != cmd_id) return; + revoker.cue_entered.push_back(track.CueEntered(winrt::auto_revoke, [cmd_id](auto track, const auto& args) { + if (main_loop_is_running.load()) sx.output( + cmd_id, "cue_entered", json_val(track.Label(), args.Cue().template as())); + })); + revoker.cue_exited.push_back(track.CueExited(winrt::auto_revoke, [cmd_id](auto track, const auto& args) { + if (main_loop_is_running.load()) sx.output( + cmd_id, "cue_exited", json_val(track.Label(), args.Cue().template as())); + })); + revoker.track_failed.push_back(track.TrackFailed(winrt::auto_revoke, [cmd_id](auto, const auto& args) { + if (main_loop_is_running.load()) sx.output( + cmd_id, "track_failed", {}); + })); + current_item.TimedMetadataTracks().SetPresentationMode((unsigned int)index, TimedMetadataTrackPresentationMode::ApplicationPresented); +} + +void +Synthesizer::load_stream_for_playback(SpeechSynthesisStream const &stream, id_type cmd_id, bool is_cued) { + std::scoped_lock sl(recursive_lock); + if (cmd_id != current_cmd_id.load()) return; + current_stream = stream; + current_source = MediaSource::CreateFromStream(current_stream, current_stream.ContentType()); + if (is_cued) add_cues(); + + revoker.playback_state_changed = player.PlaybackSession().PlaybackStateChanged( + winrt::auto_revoke, [cmd_id](auto session, auto const&) { + if (main_loop_is_running.load()) sx.output( + cmd_id, "playback_state_changed", {{"state", session.PlaybackState()}}); + }); + revoker.media_opened = player.MediaOpened(winrt::auto_revoke, [cmd_id](auto player, auto const&) { + if (main_loop_is_running.load()) sx.output( + cmd_id, "media_state_changed", {{"state", "opened"}}); + }); + revoker.media_ended = player.MediaEnded(winrt::auto_revoke, [cmd_id](auto player, auto const&) { + if (main_loop_is_running.load()) sx.output( + cmd_id, "media_state_changed", {{"state", "ended"}}); + }); + revoker.media_failed = player.MediaFailed(winrt::auto_revoke, [cmd_id](auto player, auto const& args) { + if (main_loop_is_running.load()) sx.output( + cmd_id, "media_state_changed", {{"state", "failed"}, {"error", args.ErrorMessage()}, {"code", args.Error()}}); + }); + current_item = MediaPlaybackItem(current_source); + + revoker.timed_metadata_tracks_changed = current_item.TimedMetadataTracksChanged(winrt::auto_revoke, + [cmd_id](auto, auto const &args) { + auto change_type = args.CollectionChange(); + long index; + switch (change_type) { + case CollectionChange::ItemInserted: index = args.Index(); break; + case CollectionChange::Reset: index = -1; break; + default: index = -2; break; + } + if (index > -2 && main_loop_is_running.load()) sx.register_metadata_handler_for_speech(cmd_id, index); + }); + register_metadata_handler_for_speech(cmd_id, -1); + + player.Source(current_item); +} + + static size_t decode_into(std::string_view src, std::wstring_view dest) { int n = MultiByteToWideChar(CP_UTF8, 0, src.data(), (int)src.size(), (wchar_t*)dest.data(), (int)dest.size()); @@ -1120,7 +1129,7 @@ static PyModuleDef_Slot slots[] = { {Py_mod_exec, (void*)exec_module}, {0, NULL} static struct PyModuleDef module_def = {PyModuleDef_HEAD_INIT}; -CALIBRE_MODINIT_FUNC PyInit_winspeech(void) { +PyMODINIT_FUNC PyInit_winspeech(void) { module_def.m_name = "winspeech"; module_def.m_doc = "Windows Speech API wrapper"; module_def.m_methods = methods; diff --git a/src/calibre/utils/windows/winutil.cpp b/src/calibre/utils/windows/winutil.cpp index 024ddf53f9..757b325ad3 100644 --- a/src/calibre/utils/windows/winutil.cpp +++ b/src/calibre/utils/windows/winutil.cpp @@ -8,7 +8,7 @@ #include "common.h" #include #include -#include +#include #include #include #include @@ -826,10 +826,10 @@ set_handle_information(PyObject *self, PyObject *args) { static PyObject * get_long_path_name(PyObject *self, PyObject *args) { - wchar_raii path; + wchar_raii path, buf; if (!PyArg_ParseTuple(args, "O&", py_to_wchar_no_none, &path)) return NULL; DWORD current_size = 4096; - wchar_raii buf((wchar_t*)PyMem_Malloc(current_size * sizeof(wchar_t))); + buf.attach((wchar_t*)PyMem_Malloc(current_size * sizeof(wchar_t))); if (!buf) return PyErr_NoMemory(); DWORD needed_size; Py_BEGIN_ALLOW_THREADS @@ -1506,7 +1506,7 @@ static PyModuleDef_Slot slots[] = { {Py_mod_exec, (void*)exec_module}, {0, NULL} static struct PyModuleDef module_def = {PyModuleDef_HEAD_INIT}; -CALIBRE_MODINIT_FUNC PyInit_winutil(void) { +PyMODINIT_FUNC PyInit_winutil(void) { module_def.m_name = "winutil"; module_def.m_doc = winutil_doc; module_def.m_methods = winutil_methods; From bf6db247a2710a5c641bc73a60ff97550d929eed Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 26 Jan 2023 20:33:49 +0530 Subject: [PATCH 0195/2055] Command to automatically install the Windows SDK --- bypy/rsync.conf | 2 +- setup/commands.py | 6 +++++- setup/xwin.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 setup/xwin.py diff --git a/bypy/rsync.conf b/bypy/rsync.conf index 8625f7dcaa..64bd448c51 100644 --- a/bypy/rsync.conf +++ b/bypy/rsync.conf @@ -1 +1 @@ -to_vm_excludes '/imgsrc /build /dist /manual /format_docs /translations /.build-cache /tags /Changelog* *.so *.pyd' +to_vm_excludes '/imgsrc /build /dist /manual /format_docs /translations /.build-cache /.cache /tags /Changelog* *.so *.pyd' diff --git a/setup/commands.py b/setup/commands.py index fe414ca0a7..8cc1b1b6eb 100644 --- a/setup/commands.py +++ b/setup/commands.py @@ -20,7 +20,7 @@ __all__ = [ 'upload_user_manual', 'upload_demo', 'reupload', 'stage1', 'stage2', 'stage3', 'stage4', 'stage5', 'publish', 'publish_betas', 'linux', 'linux64', 'linuxarm64', 'win', 'win64', 'osx', 'build_dep', - 'export_packages', 'hyphenation', 'liberation_fonts', 'stylelint' + 'export_packages', 'hyphenation', 'liberation_fonts', 'stylelint', 'xwin', ] from setup.installers import Linux, Win, OSX, Linux64, LinuxArm64, Win64, ExtDev, BuildDep, ExportPackages @@ -101,6 +101,10 @@ upload_to_server = UploadToServer() upload_installers = UploadInstallers() reupload = ReUpload() + +from setup.xwin import XWin +xwin = XWin() + commands = {} for x in __all__: commands[x] = locals()[x] diff --git a/setup/xwin.py b/setup/xwin.py new file mode 100644 index 0000000000..bb140938c0 --- /dev/null +++ b/setup/xwin.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# License: GPLv3 Copyright: 2023, Kovid Goyal + + +import os +import shutil + +from setup import Command + + +class XWin(Command): + description = 'Install the Windows headers for cross compilation' + + def run(self, opts): + import subprocess + cache_dir = '.build-cache/xwin' + output_dir = cache_dir + '/splat' + cmd = f'xwin --include-atl --accept-license --cache-dir {cache_dir}'.split() + for step in 'download unpack'.split(): + try: + subprocess.check_call(cmd + [step]) + except FileNotFoundError: + raise SystemExit('xwin not found install it from https://github.com/Jake-Shadle/xwin/releases') + subprocess.check_call(cmd + ['splat', '--output', output_dir]) + base = f'{output_dir}/sdk/include/um' + for casefix in 'Ole2.h OleCtl.h OAIdl.h OCIdl.h'.split(): + os.link(f'{base}/{casefix.lower()}', f'{base}/{casefix}') + shutil.rmtree(f'{cache_dir}/dl') + shutil.rmtree(f'{cache_dir}/unpack') From 58c9ca9519492af35649b68fd2759d05d524c762 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 26 Jan 2023 20:49:13 +0530 Subject: [PATCH 0196/2055] ... --- setup/build.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup/build.py b/setup/build.py index b545e46ce7..6d8cbf45e1 100644 --- a/setup/build.py +++ b/setup/build.py @@ -300,6 +300,7 @@ class Build(Command): help='Build with sanitization support. Run with LD_PRELOAD=$(gcc -print-file-name=libasan.so)') def dump_db(self, name, db): + os.makedirs('build', exist_ok=True) try: with open(f'build/{name}_commands.json', 'w') as f: json.dump(db, f, indent=2) From d6a0f4bb9dfe01ee8b6fafb2923ff1ca0509282a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 26 Jan 2023 21:06:39 +0530 Subject: [PATCH 0197/2055] Clean up C/C++ std specifications --- setup/build.py | 42 +++++++++++++++++++++--------------------- setup/extensions.json | 17 +++++++++-------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/setup/build.py b/setup/build.py index 6d8cbf45e1..e4978258dd 100644 --- a/setup/build.py +++ b/setup/build.py @@ -49,10 +49,12 @@ class Extension: self.libraries = d['libraries'] = kwargs.get('libraries', []) self.cflags = d['cflags'] = kwargs.get('cflags', []) self.uses_icu = 'icuuc' in self.libraries + if self.needs_cxx and kwargs.get('needs_c++'): + std_prefix = '/std:' if iswindows else '-std=' + self.cflags.insert(0, std_prefix + 'c++' + kwargs['needs_c++']) + if iswindows: self.cflags.append('/DCALIBRE_MODINIT_FUNC=PyMODINIT_FUNC') - if self.needs_cxx and kwargs.get('needs_c++14'): - self.cflags.insert(0, '/std:c++14') else: return_type = 'PyObject*' extern_decl = 'extern "C"' if self.needs_cxx else '' @@ -61,14 +63,8 @@ class Extension: '-DCALIBRE_MODINIT_FUNC=' '{} __attribute__ ((visibility ("default"))) {}'.format(extern_decl, return_type)) - if self.needs_cxx: - if kwargs.get('needs_c++11'): - self.cflags.insert(0, '-std=c++11') - elif kwargs.get('needs_c++14'): - self.cflags.insert(0, '-std=c++14') - else: - if kwargs.get('needs_c99'): - self.cflags.insert(0, '-std=c99') + if kwargs.get('needs_c'): + self.cflags.insert(0, '-std=c' + kwargs['needs_c']) self.ldflags = d['ldflags'] = kwargs.get('ldflags', []) self.optional = d['options'] = kwargs.get('optional', False) @@ -182,6 +178,20 @@ def get_python_include_paths(): is_macos_universal_build = ismacos and 'universal2' in sysconfig.get_platform() +def basic_windows_flags(debug=False): + cflags = '/c /nologo /W3 /EHsc /utf-8'.split() + cflags.append('/Zi' if debug else '/DNDEBUG') + suffix = ('d' if debug else '') + cflags.append('/MD' + suffix) + ldflags = f'/DLL /nologo /INCREMENTAL:NO /NODEFAULTLIB:libcmt{suffix}.lib'.split() + if debug: + ldflags.append('/DEBUG') + # cflags = '/c /nologo /Ox /MD /W3 /EHsc /Zi'.split() + # ldflags = '/DLL /nologo /INCREMENTAL:NO /DEBUG'.split() + cflags.append('/GS-') + return cflags, ldflags + + def init_env(debug=False, sanitize=False): from setup.build_environment import win_ld, win_inc, win_lib, NMAKE, win_cc linker = None @@ -236,17 +246,7 @@ def init_env(debug=False, sanitize=False): if iswindows: cc = cxx = win_cc - cflags = '/c /nologo /W3 /EHsc /utf-8'.split() - cflags.append('/Zi' if debug else '/DNDEBUG') - suffix = ('d' if debug else '') - cflags.append('/MD' + suffix) - ldflags = f'/DLL /nologo /INCREMENTAL:NO /NODEFAULTLIB:libcmt{suffix}.lib'.split() - if debug: - ldflags.append('/DEBUG') - # cflags = '/c /nologo /Ox /MD /W3 /EHsc /Zi'.split() - # ldflags = '/DLL /nologo /INCREMENTAL:NO /DEBUG'.split() - cflags.append('/GS-') - + cflags, ldflags = basic_windows_flags(debug) for p in win_inc: cflags.append('-I'+p) for p in win_lib: diff --git a/setup/extensions.json b/setup/extensions.json index 6eae64d0d3..fc3b7aa55f 100644 --- a/setup/extensions.json +++ b/setup/extensions.json @@ -6,7 +6,7 @@ "lib_dirs": "!hunspell_lib_dirs", "libraries": "hunspell-1.7", "windows_libraries": "libhunspell", - "needs_c++11": true + "needs_c++": "11" }, { "name": "hyphen", @@ -14,7 +14,7 @@ "libraries": "hyphen", "inc_dirs": "!hyphen_inc_dirs", "lib_dirs": "!hyphen_lib_dirs", - "needs_c99": true + "needs_c": "99" }, { "name": "uchardet", @@ -27,7 +27,7 @@ "name": "unicode_names", "headers": "unicode_names/names.h unicode_names/data-types.h", "sources": "unicode_names/unicode_names.c", - "needs_c99": true + "needs_c": "99" }, { "name": "speedup", @@ -82,7 +82,7 @@ "name": "sqlite_extension", "headers": "calibre/utils/cpp_binding.h", "sources": "calibre/db/sqlite_extension.cpp", - "needs_c++14": true, + "needs_c++": "14", "libraries": "icudata icui18n icuuc icuio stemmer", "windows_libraries": "icudt icuin icuuc icuio libstemmer", "lib_dirs": "!icu_lib_dirs", @@ -124,19 +124,19 @@ "lib_dirs": "!podofo_lib", "inc_dirs": "!podofo_inc", "error": "!podofo_error", - "needs_c++11": true + "needs_c++": "11" }, { "name": "html_as_json", "sources": "calibre/srv/html_as_json.cpp", - "needs_c++11": true + "needs_c++": "11" }, { "name": "fast_css_transform", "headers": "calibre/utils/cpp_binding.h calibre/utils/stb_sprintf.h", "sources": "calibre/srv/fast_css_transform.cpp", "inc_dirs": "perfect-hashing", - "needs_c++14": true + "needs_c++": "14" }, { "name": "rcc_backend", @@ -190,7 +190,8 @@ "headers": "calibre/utils/cpp_binding.h calibre/utils/windows/common.h", "sources": "calibre/utils/windows/winspeech.cpp", "libraries": "WindowsApp", - "cflags": "/X /std:c++17 /Zc:__cplusplus /bigobj /await /permissive- /WX /Zc:twoPhase-" + "needs_c++": "17", + "cflags": "/X /Zc:__cplusplus /bigobj /await /permissive- /WX /Zc:twoPhase-" }, { "name": "wpd", From d13404d9ea118f88c3e80378f1609e852b5913be Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 11:38:59 +0530 Subject: [PATCH 0198/2055] Refactor the build system to allow cross compiling windows native code extensions on linux --- setup/build.py | 351 ++++++++++++++++++++++++++----------- setup/build_environment.py | 44 +++-- setup/extensions.json | 22 +-- 3 files changed, 283 insertions(+), 134 deletions(-) diff --git a/setup/build.py b/setup/build.py index e4978258dd..16b5dae723 100644 --- a/setup/build.py +++ b/setup/build.py @@ -4,15 +4,35 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import textwrap, os, shlex, subprocess, glob, shutil, sys, json, errno, sysconfig -from collections import namedtuple +import errno +import glob +import json +import os +import shlex +import shutil +import subprocess +import sys +import sysconfig +import textwrap +from functools import partial +from typing import NamedTuple, List + +from setup import SRC, Command, isbsd, isfreebsd, ishaiku, islinux, ismacos, iswindows -from setup import Command, islinux, isbsd, isfreebsd, ismacos, ishaiku, SRC, iswindows isunix = islinux or ismacos or isbsd or ishaiku py_lib = os.path.join(sys.prefix, 'libs', 'python%d%d.lib' % sys.version_info[:2]) -CompileCommand = namedtuple('CompileCommand', 'cmd src dest') -LinkCommand = namedtuple('LinkCommand', 'cmd objects dest') + +class CompileCommand(NamedTuple): + cmd: List[str] + src: str + dest: str + + +class LinkCommand(NamedTuple): + cmd: List[str] + objects: List[str] + dest: str def walk(path='.'): @@ -75,6 +95,7 @@ class Extension: flag = '/O%d' if iswindows else '-O%d' of = flag % of self.cflags.insert(0, of) + self.only_build_for = kwargs.get('only', '') def lazy_load(name): @@ -87,19 +108,27 @@ def lazy_load(name): raise ImportError('The setup.build_environment module has no symbol named: %s' % name) -def expand_file_list(items, is_paths=True): +def expand_file_list(items, is_paths=True, cross_compile_for='native'): if not items: return [] ans = [] for item in items: if item.startswith('!'): - item = lazy_load(item) - if hasattr(item, 'rjust'): - item = [item] - ans.extend(expand_file_list(item, is_paths=is_paths)) + if cross_compile_for == 'native' or not item.endswith('_dirs'): + item = lazy_load(item) + if hasattr(item, 'rjust'): + item = [item] + items = expand_file_list(item, is_paths=is_paths, cross_compile_for=cross_compile_for) + else: + pkg, category = item[1:].split('_')[:2] + if category == 'inc': + category = 'include' + items = [f'bypy/b/windows/64/{pkg}/{category}'] + items = expand_file_list(item, is_paths=is_paths, cross_compile_for=cross_compile_for) + ans.extend(items) else: if '*' in item: - ans.extend(expand_file_list(sorted(glob.glob(os.path.join(SRC, item))), is_paths=is_paths)) + ans.extend(expand_file_list(sorted(glob.glob(os.path.join(SRC, item))), is_paths=is_paths, cross_compile_for=cross_compile_for)) else: item = [item] if is_paths: @@ -108,35 +137,40 @@ def expand_file_list(items, is_paths=True): return ans -def is_ext_allowed(ext): - only = ext.get('only', '') +def is_ext_allowed(cross_compile_for: str, ext: Extension) -> bool: + only = ext.only_build_for if only: + if islinux and only == cross_compile_for: + return True only = set(only.split()) q = set(filter(lambda x: globals()["is" + x], ["bsd", "freebsd", "haiku", "linux", "macos", "windows"])) return len(q.intersection(only)) > 0 return True -def parse_extension(ext): +def parse_extension(ext, compiling_for='native'): ext = ext.copy() - ext.pop('only', None) + only = ext.pop('only', None) kw = {} name = ext.pop('name') + get_key = 'linux_' + if iswindows: + get_key = 'windows_' + elif ismacos: + get_key = 'macos_' + elif isbsd: + get_key = 'bsd_' + elif isfreebsd: + get_key = 'freebsd_' + elif ishaiku: + get_key = 'haiku_' + if compiling_for == 'windows': + get_key = 'windows_' + def get(k, default=''): ans = ext.pop(k, default) - if iswindows: - ans = ext.pop('windows_' + k, ans) - elif ismacos: - ans = ext.pop('macos_' + k, ans) - elif isbsd: - ans = ext.pop('bsd_' + k, ans) - elif isfreebsd: - ans = ext.pop('freebsd_' + k, ans) - elif ishaiku: - ans = ext.pop('haiku_' + k, ans) - else: - ans = ext.pop('linux_' + k, ans) + ans = ext.pop(get_key + k, ans) return ans for k in 'libraries qt_private ldflags cflags error'.split(): kw[k] = expand_file_list(get(k).split(), is_paths=False) @@ -145,13 +179,14 @@ def parse_extension(ext): if 'cflags' not in kw: kw['cflags'] = [] cflags = kw['cflags'] - prefix = '/D' if iswindows else '-D' + prefix = '/D' if get_key == 'windows_' else '-D' cflags.extend(prefix + x for x in defines.split()) for k in 'inc_dirs lib_dirs sources headers sip_files'.split(): v = get(k) if v: kw[k] = expand_file_list(v.split()) kw.update(ext) + kw['only'] = only return Extension(name, **kw) @@ -192,9 +227,48 @@ def basic_windows_flags(debug=False): return cflags, ldflags -def init_env(debug=False, sanitize=False): - from setup.build_environment import win_ld, win_inc, win_lib, NMAKE, win_cc +class Environment(NamedTuple): + cc: str + cxx: str + linker: str + cflags: List[str] + ldflags: List[str] + make: str + internal_inc_prefix: str + external_inc_prefix: str + libdir_prefix: str + lib_prefix: str + lib_suffix: str + obj_suffix: str + cc_input_c_flag: str + cc_input_cpp_flag: str + cc_output_flag: str + platform_name: str + dest_ext: str + + def inc_dirs_to_cflags(self, dirs) -> List[str]: + return [self.external_inc_prefix+x for x in dirs] + + def lib_dirs_to_ldflags(self, dirs) -> List[str]: + return [self.libdir_prefix+x for x in dirs if x] + + def libraries_to_ldflags(self, dirs): + return [self.lib_prefix+x+self.lib_suffix for x in dirs] + + + +def init_env(debug=False, sanitize=False, compiling_for='native'): + from setup.build_environment import NMAKE, win_cc, win_inc, win_ld, win_lib linker = None + internal_inc_prefix = external_inc_prefix = '-I' + libdir_prefix = '-L' + lib_prefix = '-l' + lib_suffix = '' + obj_suffix = '.o' + cc_input_c_flag = cc_input_cpp_flag = '-c' + cc_output_flag = '-o' + platform_name = 'linux' + dest_ext = '.so' if isunix: cc = os.environ.get('CC', 'gcc') cxx = os.environ.get('CXX', 'g++') @@ -236,6 +310,7 @@ def init_env(debug=False, sanitize=False): ldflags += (sysconfig.get_config_var('LINKFORSHARED') or '').split() if ismacos: + platform_name = 'macos' if is_macos_universal_build: cflags.extend(['-arch', 'x86_64', '-arch', 'arm64']) ldflags.extend(['-arch', 'x86_64', '-arch', 'arm64']) @@ -244,19 +319,50 @@ def init_env(debug=False, sanitize=False): cflags.extend(['-fno-common', '-dynamic']) cflags.extend('-I' + x for x in get_python_include_paths()) - if iswindows: + if iswindows or compiling_for == 'windows': + platform_name = 'windows' cc = cxx = win_cc - cflags, ldflags = basic_windows_flags(debug) - for p in win_inc: - cflags.append('-I'+p) - for p in win_lib: - if p: - ldflags.append('/LIBPATH:'+p) - cflags.extend('-I' + x for x in get_python_include_paths()) - ldflags.append('/LIBPATH:'+os.path.join(sysconfig.get_config_var('prefix'), 'libs')) linker = win_ld - return namedtuple('Environment', 'cc cxx cflags ldflags linker make')( - cc=cc, cxx=cxx, cflags=cflags, ldflags=ldflags, linker=linker, make=NMAKE if iswindows else 'make') + cflags, ldflags = basic_windows_flags(debug) + if compiling_for == 'windows': + cc = 'clang-cl' + linker = 'lld-link' + splat = '.build-cache/xwin/splat' + for I in 'sdk/include/um sdk/include/cppwinrt sdk/include/shared sdk/include/ucrt crt/include': + cflags.append('/external:I') + cflags.append(f'{splat}/{I}') + for L in './sdk/lib/um/x86_64 crt/lib/x86_64': + ldflags.append(f'/libpath:{splat}/{L}') + else: + for p in win_inc: + cflags.append('-I'+p) + for p in win_lib: + if p: + ldflags.append('/LIBPATH:'+p) + internal_inc_prefix = external_inc_prefix = '/I' + libdir_prefix = '/libpath:' + lib_prefix = '' + lib_suffix = '.lib' + cc_input_c_flag = '/Tc' + cc_input_cpp_flag = '/Tp' + cc_output_flag = '/Fo' + obj_suffix = '.obj' + dest_ext = '.pyd' + if compiling_for == 'windows': + external_inc_prefix = '/external:I' + dest_ext = '.cross-windows-x64' + dest_ext + obj_suffix = '.cross-windows-x64' + obj_suffix + cflags.append('/external:I') + cflags.append('bypy/b/windows/64/pkg/python/private/python/include') + ldflags.append('/libpath:' + 'bypy/b/windows/64/pkg/python/private/python/libs') + else: + cflags.extend('-I' + x for x in get_python_include_paths()) + ldflags.append('/LIBPATH:'+os.path.join(sysconfig.get_config_var('prefix'), 'libs')) + return Environment( + platform_name=platform_name, dest_ext=dest_ext, + cc=cc, cxx=cxx, cflags=cflags, ldflags=ldflags, linker=linker, make=NMAKE if iswindows else 'make', lib_prefix=lib_prefix, + obj_suffix=obj_suffix, cc_input_c_flag=cc_input_c_flag, cc_input_cpp_flag=cc_input_cpp_flag, cc_output_flag=cc_output_flag, + internal_inc_prefix=internal_inc_prefix, external_inc_prefix=external_inc_prefix, libdir_prefix=libdir_prefix, lib_suffix=lib_suffix) class Build(Command): @@ -285,7 +391,7 @@ class Build(Command): ''') def add_options(self, parser): - choices = [e['name'] for e in read_extensions() if is_ext_allowed(e)]+['all', 'headless'] + choices = [e['name'] for e in read_extensions()]+['all', 'headless'] parser.add_option('-1', '--only', choices=choices, default='all', help=('Build only the named extension. Available: '+ ', '.join(choices)+'. Default:%default')) parser.add_option('--no-compile', default=False, action='store_true', @@ -298,6 +404,9 @@ class Build(Command): help='Build in debug mode') parser.add_option('--sanitize', default=False, action='store_true', help='Build with sanitization support. Run with LD_PRELOAD=$(gcc -print-file-name=libasan.so)') + parser.add_option('--cross-compile-extensions', choices='windows disabled'.split(), default='disabled', + help=('Cross compile extensions for other platforms. Useful for development.' + ' Currently supports of windows extensions on Linux. Remember to run ./setup.py xwin first to install the Windows SDK locally. ')) def dump_db(self, name, db): os.makedirs('build', exist_ok=True) @@ -309,12 +418,18 @@ class Build(Command): raise def run(self, opts): - from setup.parallel_build import parallel_build, create_job + from setup.parallel_build import create_job, parallel_build if opts.no_compile: self.info('--no-compile specified, skipping compilation') return + self.compiling_for = 'native' + if islinux and opts.cross_compile_extensions == 'windows': + self.compiling_for = 'windows' + if not os.path.exists('.build-cache/xwin/splat'): + subprocess.check_call([sys.executable, 'setup.py', 'xwin']) self.env = init_env(debug=opts.debug) - all_extensions = map(parse_extension, filter(is_ext_allowed, read_extensions())) + self.windows_cross_env = init_env(debug=opts.debug, compiling_for='windows') + all_extensions = tuple(map(partial(parse_extension, compiling_for=self.compiling_for), read_extensions())) self.build_dir = os.path.abspath(opts.build_dir or self.DEFAULT_BUILDDIR) self.output_dir = os.path.abspath(opts.output_dir or self.DEFAULT_OUTPUTDIR) self.obj_dir = os.path.join(self.build_dir, 'objects') @@ -324,35 +439,40 @@ class Build(Command): for ext in all_extensions: if opts.only != 'all' and opts.only != ext.name: continue + if not is_ext_allowed(self.compiling_for, ext): + continue if ext.error: if ext.optional: self.warn(ext.error) continue else: raise Exception(ext.error) - dest = self.dest(ext) - os.makedirs(self.d(dest), exist_ok=True) - (pyqt_extensions if ext.sip_files else extensions).append((ext, dest)) + (pyqt_extensions if ext.sip_files else extensions).append(ext) jobs = [] objects_map = {} self.info(f'Building {len(extensions)+len(pyqt_extensions)} extensions') ccdb = [] - for (ext, dest) in extensions: - cmds, objects = self.get_compile_commands(ext, dest, ccdb) + for ext in all_extensions: + if ext in pyqt_extensions: + continue + cmds, objects = self.get_compile_commands(ext, ccdb) objects_map[id(ext)] = objects - for cmd in cmds: - jobs.append(create_job(cmd.cmd)) + if ext in extensions: + for cmd in cmds: + jobs.append(create_job(cmd.cmd)) self.dump_db('compile', ccdb) if jobs: self.info(f'Compiling {len(jobs)} files...') if not parallel_build(jobs, self.info): raise SystemExit(1) jobs, link_commands, lddb = [], [], [] - for (ext, dest) in extensions: + for ext in all_extensions: + if ext in pyqt_extensions: + continue objects = objects_map[id(ext)] - cmd = self.get_link_command(ext, dest, objects, lddb) - if cmd is not None: + cmd = self.get_link_command(ext, objects, lddb) + if ext in extensions and cmd is not None: link_commands.append(cmd) jobs.append(create_job(cmd.cmd)) self.dump_db('link', lddb) @@ -365,7 +485,7 @@ class Build(Command): jobs = [] sbf_map = {} - for (ext, dest) in pyqt_extensions: + for ext in pyqt_extensions: cmd, sbf, cwd = self.get_sip_commands(ext) sbf_map[id(ext)] = sbf if cmd is not None: @@ -374,71 +494,87 @@ class Build(Command): self.info(f'SIPing {len(jobs)} files...') if not parallel_build(jobs, self.info): raise SystemExit(1) - for (ext, dest) in pyqt_extensions: + for ext in pyqt_extensions: sbf = sbf_map[id(ext)] if not os.path.exists(sbf): - self.build_pyqt_extension(ext, dest, sbf) + self.build_pyqt_extension(ext, sbf) if opts.only in {'all', 'headless'}: self.build_headless() - def dest(self, ext): - ex = '.pyd' if iswindows else '.so' - return os.path.join(self.output_dir, getattr(ext, 'name', ext))+ex + def dest(self, ext, env): + return os.path.join(self.output_dir, getattr(ext, 'name', ext))+env.dest_ext - def inc_dirs_to_cflags(self, dirs): - return ['-I'+x for x in dirs] + def env_for_compilation_db(self, ext): + if is_ext_allowed('native', ext): + return self.env + if ext.only_build_for == 'windows': + return self.windows_cross_env - def lib_dirs_to_ldflags(self, dirs): - pref = '/LIBPATH:' if iswindows else '-L' - return [pref+x for x in dirs if x] + def get_compile_commands(self, ext, db): + obj_dir = self.j(self.obj_dir, ext.name) - def libraries_to_ldflags(self, dirs): - pref = '' if iswindows else '-l' - suff = '.lib' if iswindows else '' - return [pref+x+suff for x in dirs] + def get(src: str, env: Environment) -> CompileCommand: + compiler = env.cxx if ext.needs_cxx else env.cc + obj = self.j(obj_dir, os.path.splitext(self.b(src))[0]+env.obj_suffix) + inf = env.cc_input_cpp_flag if src.endswith('.cpp') or src.endswith('.cxx') else env.cc_input_c_flag + sinc = [inf, src] + if env.cc_output_flag.startswith('/'): + oinc = [env.cc_output_flag + obj] + sinc = [inf + src] + else: + oinc = [env.cc_output_flag, obj] + einc = env.inc_dirs_to_cflags(ext.inc_dirs) + cmd = [compiler] + env.cflags + ext.cflags + einc + sinc + oinc + return CompileCommand(cmd, src, obj) - def get_compile_commands(self, ext, dest, db): - compiler = self.env.cxx if ext.needs_cxx else self.env.cc objects = [] ans = [] - obj_dir = self.j(self.obj_dir, ext.name) - einc = self.inc_dirs_to_cflags(ext.inc_dirs) os.makedirs(obj_dir, exist_ok=True) for src in ext.sources: - obj = self.j(obj_dir, os.path.splitext(self.b(src))[0]+'.o') - objects.append(obj) - inf = '/Tp' if src.endswith('.cpp') or src.endswith('.cxx') else '/Tc' - sinc = [inf+src] if iswindows else ['-c', src] - oinc = ['/Fo'+obj] if iswindows else ['-o', obj] - cmd = [compiler] + self.env.cflags + ext.cflags + einc + sinc + oinc - db.append({'arguments': cmd, 'directory': os.getcwd(), 'file': os.path.relpath(src, os.getcwd()), 'output': os.path.relpath(obj, os.getcwd())}) - if self.newer(obj, [src]+ext.headers): - ans.append(CompileCommand(cmd, src, obj)) + cc = get(src, self.windows_cross_env if self.compiling_for == 'windows' else self.env) + objects.append(cc.dest) + if self.newer(cc.dest, [src]+ext.headers): + ans.append(cc) + env = self.env_for_compilation_db(ext) + if env is None: + continue + db.append({ + 'arguments': get(src, env).cmd, 'directory': os.getcwd(), 'file': os.path.relpath(src, os.getcwd()), + 'output': os.path.relpath(cc.dest, os.getcwd())}) return ans, objects - def get_link_command(self, ext, dest, objects, lddb): - compiler = self.env.cxx if ext.needs_cxx else self.env.cc - linker = self.env.linker if iswindows else compiler - dest = self.dest(ext) - elib = self.lib_dirs_to_ldflags(ext.lib_dirs) - xlib = self.libraries_to_ldflags(ext.libraries) - cmd = [linker] - if iswindows: - pre_ld_flags = [] - if ext.uses_icu: - # windows has its own ICU libs that dont work - pre_ld_flags = elib - cmd += pre_ld_flags + self.env.ldflags + ext.ldflags + elib + xlib + \ - ['/EXPORT:' + init_symbol_name(ext.name)] + objects + ext.extra_objs + ['/OUT:'+dest] - else: - cmd += objects + ext.extra_objs + ['-o', dest] + self.env.ldflags + ext.ldflags + elib + xlib - lddb.append({'arguments': cmd, 'directory': os.getcwd(), 'output': os.path.relpath(dest, os.getcwd())}) + def get_link_command(self, ext, objects, lddb): - if self.newer(dest, objects+ext.extra_objs): + def get(env: Environment) -> LinkCommand: + dest = self.dest(ext, env) + compiler = env.cxx if ext.needs_cxx else env.cc + linker = self.env.linker or compiler + cmd = [linker] + elib = env.lib_dirs_to_ldflags(ext.lib_dirs) + xlib = env.libraries_to_ldflags(ext.libraries) + if iswindows or env is self.windows_cross_env: + pre_ld_flags = [] + if ext.uses_icu: + # windows has its own ICU libs that dont work + pre_ld_flags = elib + cmd += pre_ld_flags + self.env.ldflags + ext.ldflags + elib + xlib + \ + ['/EXPORT:' + init_symbol_name(ext.name)] + objects + ext.extra_objs + ['/OUT:'+dest] + else: + cmd += objects + ext.extra_objs + ['-o', dest] + self.env.ldflags + ext.ldflags + elib + xlib return LinkCommand(cmd, objects, dest) + env = self.env_for_compilation_db(ext) + if env is not None: + ld = get(env) + lddb.append({'arguments': ld.cmd, 'directory': os.getcwd(), 'output': os.path.relpath(self.dest(ext, env), os.getcwd())}) + + env = self.windows_cross_env if self.compiling_for == 'windows' else self.env + lc = get(env) + if self.newer(lc.dest, objects+ext.extra_objs): + return lc + def post_link_cleanup(self, link_command): if iswindows: dest = link_command.dest @@ -478,7 +614,7 @@ class Build(Command): 'calibre/headless/headless_integration.cpp', ]) others = a(['calibre/headless/headless.json']) - target = self.dest('headless') + target = self.dest('headless', self.env) if not ismacos: target = target.replace('headless', 'libheadless') if not self.newer(target, headers + sources + others): @@ -557,7 +693,7 @@ sip-file = "{os.path.basename(sipf)}" ] return cmd, sbf, cwd - def build_pyqt_extension(self, ext, dest, sbf): + def build_pyqt_extension(self, ext, sbf): self.info(f'\n####### Building {ext.name} extension', '#'*7) src_dir = os.path.dirname(sbf) cwd = os.getcwd() @@ -570,7 +706,7 @@ sip-file = "{os.path.basename(sipf)}" raise SystemExit(f'No built PyQt extension file in {os.path.join(os.getcwd(), ext.name)}') if len(m) != 1: raise SystemExit(f'Found extra PyQt extension files: {m}') - shutil.copy2(m[0], dest) + shutil.copy2(m[0], self.dest(ext, self.env)) with open(sbf, 'w') as f: f.write('done') finally: @@ -578,10 +714,13 @@ sip-file = "{os.path.basename(sipf)}" def clean(self): self.output_dir = self.DEFAULT_OUTPUTDIR - extensions = map(parse_extension, filter(is_ext_allowed, read_extensions())) + extensions = map(parse_extension, read_extensions()) + env = init_env() for ext in extensions: - dest = self.dest(ext) - for x in (dest, dest+'.manifest'): + dest = self.dest(ext, env) + b, d = os.path.basename(dest), os.path.dirname(dest) + b = b.split('.')[0] + '.*' + for x in glob.glob(os.path.join(d, b)): if os.path.exists(x): os.remove(x) build_dir = self.DEFAULT_BUILDDIR diff --git a/setup/build_environment.py b/setup/build_environment.py index eb0a87a7c6..dc0027bb72 100644 --- a/setup/build_environment.py +++ b/setup/build_environment.py @@ -5,10 +5,13 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, subprocess, re, shutil +import os +import re +import shutil +import subprocess from functools import lru_cache -from setup import ismacos, iswindows, islinux, ishaiku +from setup import isfreebsd, ishaiku, islinux, ismacos, iswindows NMAKE = RC = msvc = MT = win_inc = win_lib = win_cc = win_ld = None @@ -115,23 +118,35 @@ def readvar(name): qt = {x:readvar(y) for x, y in {'libs':'QT_INSTALL_LIBS', 'plugins':'QT_INSTALL_PLUGINS'}.items()} qmakespec = readvar('QMAKE_SPEC') if iswindows else None -ft_lib_dirs = [] -ft_libs = [] -ft_inc_dirs = [] +freetype_lib_dirs = [] +freetype_libs = [] +freetype_inc_dirs = [] + podofo_inc = '/usr/include/podofo' podofo_lib = '/usr/lib' + +usb_library = 'usb' if isfreebsd else 'usb-1.0' + chmlib_inc_dirs = chmlib_lib_dirs = [] + sqlite_inc_dirs = [] + icu_inc_dirs = [] icu_lib_dirs = [] + zlib_inc_dirs = [] zlib_lib_dirs = [] + hunspell_inc_dirs = [] hunspell_lib_dirs = [] + hyphen_inc_dirs = [] hyphen_lib_dirs = [] + uchardet_inc_dirs, uchardet_lib_dirs, uchardet_libs = [], [], ['uchardet'] + openssl_inc_dirs, openssl_lib_dirs = [], [] + ICU = sw = '' if iswindows: @@ -149,9 +164,9 @@ if iswindows: sqlite_inc_dirs = [sw_inc_dir] chmlib_inc_dirs = [sw_inc_dir] chmlib_lib_dirs = [sw_lib_dir] - ft_lib_dirs = [sw_lib_dir] - ft_libs = ['freetype'] - ft_inc_dirs = [os.path.join(sw_inc_dir, 'freetype2'), sw_inc_dir] + freetype_lib_dirs = [sw_lib_dir] + freetype_libs = ['freetype'] + freetype_inc_dirs = [os.path.join(sw_inc_dir, 'freetype2'), sw_inc_dir] hunspell_inc_dirs = [os.path.join(sw_inc_dir, 'hunspell')] hunspell_lib_dirs = [sw_lib_dir] zlib_inc_dirs = [sw_inc_dir] @@ -166,8 +181,8 @@ elif ismacos: podofo_inc = os.path.join(sw_inc_dir, 'podofo') hunspell_inc_dirs = [os.path.join(sw_inc_dir, 'hunspell')] podofo_lib = sw_lib_dir - ft_libs = ['freetype'] - ft_inc_dirs = [sw + '/include/freetype2'] + freetype_libs = ['freetype'] + freetype_inc_dirs = [sw + '/include/freetype2'] uchardet_inc_dirs = [sw + '/include/uchardet'] SSL = os.environ.get('OPENSSL_DIR', os.path.join(sw, 'private', 'ssl')) openssl_inc_dirs = [os.path.join(SSL, 'include')] @@ -175,10 +190,10 @@ elif ismacos: if os.path.exists(os.path.join(sw_bin_dir, 'cmake')): CMAKE = os.path.join(sw_bin_dir, 'cmake') else: - ft_inc_dirs = pkgconfig_include_dirs('freetype2', 'FT_INC_DIR', + freetype_inc_dirs = pkgconfig_include_dirs('freetype2', 'FT_INC_DIR', '/usr/include/freetype2') - ft_lib_dirs = pkgconfig_lib_dirs('freetype2', 'FT_LIB_DIR', '/usr/lib') - ft_libs = pkgconfig_libs('freetype2', '', '') + freetype_lib_dirs = pkgconfig_lib_dirs('freetype2', 'FT_LIB_DIR', '/usr/lib') + freetype_libs = pkgconfig_libs('freetype2', '', '') hunspell_inc_dirs = pkgconfig_include_dirs('hunspell', 'HUNSPELL_INC_DIR', '/usr/include/hunspell') hunspell_lib_dirs = pkgconfig_lib_dirs('hunspell', 'HUNSPELL_LIB_DIR', '/usr/lib') sw = os.environ.get('SW', os.path.expanduser('~/sw')) @@ -198,4 +213,5 @@ podofo_error = None if os.path.exists(os.path.join(podofo_inc, 'podofo.h')) else ('PoDoFo not found on your system. Various PDF related', ' functionality will not work. Use the PODOFO_INC_DIR and', ' PODOFO_LIB_DIR environment variables.') -podofo_inc = [podofo_inc, os.path.dirname(podofo_inc)] +podofo_inc_dirs = [podofo_inc, os.path.dirname(podofo_inc)] +podofo_lib_dirs = [podofo_lib] diff --git a/setup/extensions.json b/setup/extensions.json index fc3b7aa55f..b17c57c113 100644 --- a/setup/extensions.json +++ b/setup/extensions.json @@ -97,9 +97,9 @@ { "name": "freetype", "sources": "calibre/utils/fonts/freetype.cpp", - "libraries": "!ft_libs", - "inc_dirs": "!ft_inc_dirs", - "lib_dirs": "!ft_lib_dirs" + "libraries": "!freetype_libs", + "inc_dirs": "!freetype_inc_dirs", + "lib_dirs": "!freetype_lib_dirs" }, { "name": "msdes", @@ -121,8 +121,8 @@ "sources": "calibre/utils/podofo/utils.cpp calibre/utils/podofo/output.cpp calibre/utils/podofo/doc.cpp calibre/utils/podofo/outline.cpp calibre/utils/podofo/fonts.cpp calibre/utils/podofo/impose.cpp calibre/utils/podofo/images.cpp calibre/utils/podofo/outlines.cpp calibre/utils/podofo/podofo.cpp", "headers": "calibre/utils/podofo/global.h", "libraries": "podofo", - "lib_dirs": "!podofo_lib", - "inc_dirs": "!podofo_inc", + "lib_dirs": "!podofo_lib_dirs", + "inc_dirs": "!podofo_inc_dirs", "error": "!podofo_error", "needs_c++": "11" }, @@ -190,7 +190,7 @@ "headers": "calibre/utils/cpp_binding.h calibre/utils/windows/common.h", "sources": "calibre/utils/windows/winspeech.cpp", "libraries": "WindowsApp", - "needs_c++": "17", + "needs_c++": "20", "cflags": "/X /Zc:__cplusplus /bigobj /await /permissive- /WX /Zc:twoPhase-" }, { @@ -222,15 +222,9 @@ }, { "name": "libusb", - "only": "macos linux haiku", + "only": "macos linux haiku freebsd", "sources": "calibre/devices/libusb/libusb.c", - "libraries": "usb-1.0" - }, - { - "name": "libusb", - "only": "freebsd", - "sources": "calibre/devices/libusb/libusb.c", - "libraries": "usb" + "libraries": "!usb_library" }, { "name": "libmtp", From ef9e669ef9ba8d5d3adeec13c717b1921678272f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 12:47:46 +0530 Subject: [PATCH 0199/2055] Cross compiling now actually works At least for non-PyQt based extensions --- setup/build.py | 81 +++++++++++++------------ setup/extensions.json | 2 +- src/calibre/utils/windows/winspeech.cpp | 8 --- 3 files changed, 44 insertions(+), 47 deletions(-) diff --git a/setup/build.py b/setup/build.py index 16b5dae723..4d931e228d 100644 --- a/setup/build.py +++ b/setup/build.py @@ -69,32 +69,10 @@ class Extension: self.libraries = d['libraries'] = kwargs.get('libraries', []) self.cflags = d['cflags'] = kwargs.get('cflags', []) self.uses_icu = 'icuuc' in self.libraries - if self.needs_cxx and kwargs.get('needs_c++'): - std_prefix = '/std:' if iswindows else '-std=' - self.cflags.insert(0, std_prefix + 'c++' + kwargs['needs_c++']) - - if iswindows: - self.cflags.append('/DCALIBRE_MODINIT_FUNC=PyMODINIT_FUNC') - else: - return_type = 'PyObject*' - extern_decl = 'extern "C"' if self.needs_cxx else '' - - self.cflags.append( - '-DCALIBRE_MODINIT_FUNC=' - '{} __attribute__ ((visibility ("default"))) {}'.format(extern_decl, return_type)) - - if kwargs.get('needs_c'): - self.cflags.insert(0, '-std=c' + kwargs['needs_c']) - self.ldflags = d['ldflags'] = kwargs.get('ldflags', []) self.optional = d['options'] = kwargs.get('optional', False) - of = kwargs.get('optimize_level', None) - if of is None: - of = '/Ox' if iswindows else '-O3' - else: - flag = '/O%d' if iswindows else '-O%d' - of = flag % of - self.cflags.insert(0, of) + self.needs_cxx_std = kwargs.get('needs_c++') + self.needs_c_std = kwargs.get('needs_c') self.only_build_for = kwargs.get('only', '') @@ -245,6 +223,7 @@ class Environment(NamedTuple): cc_output_flag: str platform_name: str dest_ext: str + std_prefix: str def inc_dirs_to_cflags(self, dirs) -> List[str]: return [self.external_inc_prefix+x for x in dirs] @@ -264,6 +243,7 @@ def init_env(debug=False, sanitize=False, compiling_for='native'): libdir_prefix = '-L' lib_prefix = '-l' lib_suffix = '' + std_prefix = '-std=' obj_suffix = '.o' cc_input_c_flag = cc_input_cpp_flag = '-c' cc_output_flag = '-o' @@ -321,17 +301,18 @@ def init_env(debug=False, sanitize=False, compiling_for='native'): if iswindows or compiling_for == 'windows': platform_name = 'windows' + std_prefix = '/std:' cc = cxx = win_cc linker = win_ld cflags, ldflags = basic_windows_flags(debug) if compiling_for == 'windows': - cc = 'clang-cl' + cc = cxx = 'clang-cl' linker = 'lld-link' splat = '.build-cache/xwin/splat' - for I in 'sdk/include/um sdk/include/cppwinrt sdk/include/shared sdk/include/ucrt crt/include': + for I in 'sdk/include/um sdk/include/cppwinrt sdk/include/shared sdk/include/ucrt crt/include'.split(): cflags.append('/external:I') cflags.append(f'{splat}/{I}') - for L in './sdk/lib/um/x86_64 crt/lib/x86_64': + for L in 'sdk/lib/um/x86_64 crt/lib/x86_64 sdk/lib/ucrt/x86_64'.split(): ldflags.append(f'/libpath:{splat}/{L}') else: for p in win_inc: @@ -359,7 +340,7 @@ def init_env(debug=False, sanitize=False, compiling_for='native'): cflags.extend('-I' + x for x in get_python_include_paths()) ldflags.append('/LIBPATH:'+os.path.join(sysconfig.get_config_var('prefix'), 'libs')) return Environment( - platform_name=platform_name, dest_ext=dest_ext, + platform_name=platform_name, dest_ext=dest_ext, std_prefix=std_prefix, cc=cc, cxx=cxx, cflags=cflags, ldflags=ldflags, linker=linker, make=NMAKE if iswindows else 'make', lib_prefix=lib_prefix, obj_suffix=obj_suffix, cc_input_c_flag=cc_input_c_flag, cc_input_cpp_flag=cc_input_cpp_flag, cc_output_flag=cc_output_flag, internal_inc_prefix=internal_inc_prefix, external_inc_prefix=external_inc_prefix, libdir_prefix=libdir_prefix, lib_suffix=lib_suffix) @@ -410,9 +391,18 @@ class Build(Command): def dump_db(self, name, db): os.makedirs('build', exist_ok=True) + existing = [] + try: + with open(f'build/{name}_commands.json', 'rb') as f: + existing = json.load(f) + except FileNotFoundError: + pass + combined = {x['output']: x for x in existing} + for x in db: + combined[x['output']] = x try: with open(f'build/{name}_commands.json', 'w') as f: - json.dump(db, f, indent=2) + json.dump(tuple(combined.values()), f, indent=2) except OSError as err: if err.errno != errno.EROFS: raise @@ -525,7 +515,23 @@ class Build(Command): else: oinc = [env.cc_output_flag, obj] einc = env.inc_dirs_to_cflags(ext.inc_dirs) - cmd = [compiler] + env.cflags + ext.cflags + einc + sinc + oinc + if env.cc_output_flag.startswith('/'): + cflags = ['/DCALIBRE_MODINIT_FUNC=PyMODINIT_FUNC'] + else: + return_type = 'PyObject*' + extern_decl = 'extern "C"' if ext.needs_cxx else '' + cflags = [ + '-DCALIBRE_MODINIT_FUNC=' + '{} __attribute__ ((visibility ("default"))) {}'.format(extern_decl, return_type)] + if ext.needs_cxx and ext.needs_cxx_std: + cflags.append(env.std_prefix + 'c++' + ext.needs_cxx_std) + + if ext.needs_c_std and not env.std_prefix.startswith('/'): + cflags.append(env.std_prefix + 'c' + ext.needs_c_std) + if env is self.windows_cross_env: + cflags.append('-Wno-deprecated-experimental-coroutine') + + cmd = [compiler] + env.cflags + cflags + ext.cflags + einc + sinc + oinc return CompileCommand(cmd, src, obj) objects = [] @@ -538,11 +544,10 @@ class Build(Command): if self.newer(cc.dest, [src]+ext.headers): ans.append(cc) env = self.env_for_compilation_db(ext) - if env is None: - continue - db.append({ - 'arguments': get(src, env).cmd, 'directory': os.getcwd(), 'file': os.path.relpath(src, os.getcwd()), - 'output': os.path.relpath(cc.dest, os.getcwd())}) + if env is not None: + db.append({ + 'arguments': get(src, env).cmd, 'directory': os.getcwd(), 'file': os.path.relpath(src, os.getcwd()), + 'output': os.path.relpath(cc.dest, os.getcwd())}) return ans, objects def get_link_command(self, ext, objects, lddb): @@ -550,7 +555,7 @@ class Build(Command): def get(env: Environment) -> LinkCommand: dest = self.dest(ext, env) compiler = env.cxx if ext.needs_cxx else env.cc - linker = self.env.linker or compiler + linker = env.linker or compiler cmd = [linker] elib = env.lib_dirs_to_ldflags(ext.lib_dirs) xlib = env.libraries_to_ldflags(ext.libraries) @@ -559,10 +564,10 @@ class Build(Command): if ext.uses_icu: # windows has its own ICU libs that dont work pre_ld_flags = elib - cmd += pre_ld_flags + self.env.ldflags + ext.ldflags + elib + xlib + \ + cmd += pre_ld_flags + env.ldflags + ext.ldflags + elib + xlib + \ ['/EXPORT:' + init_symbol_name(ext.name)] + objects + ext.extra_objs + ['/OUT:'+dest] else: - cmd += objects + ext.extra_objs + ['-o', dest] + self.env.ldflags + ext.ldflags + elib + xlib + cmd += objects + ext.extra_objs + ['-o', dest] + env.ldflags + ext.ldflags + elib + xlib return LinkCommand(cmd, objects, dest) env = self.env_for_compilation_db(ext) diff --git a/setup/extensions.json b/setup/extensions.json index b17c57c113..c7260f8d51 100644 --- a/setup/extensions.json +++ b/setup/extensions.json @@ -191,7 +191,7 @@ "sources": "calibre/utils/windows/winspeech.cpp", "libraries": "WindowsApp", "needs_c++": "20", - "cflags": "/X /Zc:__cplusplus /bigobj /await /permissive- /WX /Zc:twoPhase-" + "cflags": "/X /Zc:__cplusplus /bigobj /permissive- /WX /Zc:twoPhase-" }, { "name": "wpd", diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 1b243832bf..3c5f6dcfd4 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -66,14 +66,6 @@ enum { EXIT_REQUESTED }; -// trim from start (in place) -static inline void -ltrim(std::string &s) { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { - return !std::isspace(ch); - })); -} - // trim from end (in place) static inline void rtrim(std::string &s) { From 39f5192c99bf321aa32dd3cdf747c62f9abf4d38 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 13:05:34 +0530 Subject: [PATCH 0200/2055] Use cross compilation for extdev --- setup/installers.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/setup/installers.py b/setup/installers.py index ba64955633..6d63edaf46 100644 --- a/setup/installers.py +++ b/setup/installers.py @@ -238,13 +238,15 @@ class ExtDev(Command): def run(self, opts): which, ext = opts.cli_args[:2] cmd = opts.cli_args[2:] or ['calibre-debug', '--test-build'] - bitness = '64' if which == 'windows' else '' - ext_dir = build_only(which, bitness, ext) if which == 'windows': + subprocess.check_call([sys.executable, 'setup.py', 'build', '--cross-compile-extensions=windows', '--only=winspeech']) + src = 'src/calibre/plugins/winspeech.cross-windows-x64.pyd' host = 'win' path = '/cygdrive/c/Program Files/Calibre2/app/bin/{}.pyd' bin_dir = '/cygdrive/c/Program Files/Calibre2' elif which == 'macos': + ext_dir = build_only(which, '', ext) + src = os.path.join(ext_dir, os.path.basename(path)) print( "\n\n\x1b[33;1mWARNING: This does not work on macOS, unless you use un-signed builds with ", ' ./update-on-ox develop\x1b[m', @@ -252,6 +254,8 @@ class ExtDev(Command): host = 'ox' path = '/Applications/calibre.app/Contents/Frameworks/plugins/{}.so' bin_dir = '/Applications/calibre.app/Contents/MacOS' + else: + raise SystemExit(f'Unknown OS {which}') control_path = os.path.expanduser('~/.ssh/extdev-master-%C') if subprocess.Popen([ 'ssh', '-o', 'ControlMaster=auto', '-o', 'ControlPath=' + control_path, '-o', 'ControlPersist=yes', host, @@ -260,7 +264,6 @@ class ExtDev(Command): raise SystemExit(1) try: path = path.format(ext) - src = os.path.join(ext_dir, os.path.basename(path)) subprocess.check_call(['ssh', '-S', control_path, host, 'chmod', '+wx', f'"{path}"']) with open(src, 'rb') as f: p = subprocess.Popen(['ssh', '-S', control_path, host, f'cat - > "{path}"'], stdin=subprocess.PIPE) From e145f42fdc9eb09a9bceaa7e63ed6d488cdfb3f5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 13:25:08 +0530 Subject: [PATCH 0201/2055] Fix generation of compile_commands.json for windows specific extensions --- setup/build.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/setup/build.py b/setup/build.py index 4d931e228d..cf7dac88a9 100644 --- a/setup/build.py +++ b/setup/build.py @@ -504,14 +504,17 @@ class Build(Command): def get_compile_commands(self, ext, db): obj_dir = self.j(self.obj_dir, ext.name) - def get(src: str, env: Environment) -> CompileCommand: + def get(src: str, env: Environment, for_tooling: bool = False) -> CompileCommand: compiler = env.cxx if ext.needs_cxx else env.cc obj = self.j(obj_dir, os.path.splitext(self.b(src))[0]+env.obj_suffix) inf = env.cc_input_cpp_flag if src.endswith('.cpp') or src.endswith('.cxx') else env.cc_input_c_flag sinc = [inf, src] if env.cc_output_flag.startswith('/'): - oinc = [env.cc_output_flag + obj] - sinc = [inf + src] + if for_tooling: # clangd gets confused by cl.exe style source and output flags + oinc = ['-o', obj] + else: + oinc = [env.cc_output_flag + obj] + sinc = [inf + src] else: oinc = [env.cc_output_flag, obj] einc = env.inc_dirs_to_cflags(ext.inc_dirs) @@ -545,8 +548,9 @@ class Build(Command): ans.append(cc) env = self.env_for_compilation_db(ext) if env is not None: + cc = get(src, env, for_tooling=True) db.append({ - 'arguments': get(src, env).cmd, 'directory': os.getcwd(), 'file': os.path.relpath(src, os.getcwd()), + 'arguments': cc.cmd, 'directory': os.getcwd(), 'file': os.path.relpath(src, os.getcwd()), 'output': os.path.relpath(cc.dest, os.getcwd())}) return ans, objects @@ -573,7 +577,7 @@ class Build(Command): env = self.env_for_compilation_db(ext) if env is not None: ld = get(env) - lddb.append({'arguments': ld.cmd, 'directory': os.getcwd(), 'output': os.path.relpath(self.dest(ext, env), os.getcwd())}) + lddb.append({'arguments': ld.cmd, 'directory': os.getcwd(), 'output': os.path.relpath(ld.dest, os.getcwd())}) env = self.windows_cross_env if self.compiling_for == 'windows' else self.env lc = get(env) From 65885f505e76ebecd65ce8c4a52443702c0d3c09 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 13:54:03 +0530 Subject: [PATCH 0202/2055] Use lambdas for template specialization --- src/calibre/utils/cpp_binding.h | 11 ++++------- src/calibre/utils/windows/common.h | 6 ++---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/calibre/utils/cpp_binding.h b/src/calibre/utils/cpp_binding.h index 31bbc1dcd5..dd6c4a869a 100644 --- a/src/calibre/utils/cpp_binding.h +++ b/src/calibre/utils/cpp_binding.h @@ -17,7 +17,7 @@ #define arraysz(x) (sizeof(x)/sizeof(x[0])) -template(NULL)> +template(NULL)> class generic_raii { private: generic_raii( const generic_raii & ) noexcept; @@ -45,8 +45,8 @@ class generic_raii { explicit operator bool() const noexcept { return handle != null; } }; +class wchar_raii : public generic_raii { #if __cplusplus >= 201703L -class wchar_raii : public generic_raii { private: Py_ssize_t sz; public: @@ -58,13 +58,10 @@ class wchar_raii : public generic_raii { } std::wstring_view as_view() const { return std::wstring_view(handle, sz); } std::wstring as_copy() const { return std::wstring(handle, sz); } -}; -#else -typedef generic_raii wchar_raii; #endif +}; -static inline void python_object_destructor(void *p) { PyObject *x = reinterpret_cast(p); Py_XDECREF(x); } -typedef generic_raii pyobject_raii; +typedef generic_raii pyobject_raii; template(NULL)> class generic_raii_array { diff --git a/src/calibre/utils/windows/common.h b/src/calibre/utils/windows/common.h index 3b5dcdf632..068bae1657 100644 --- a/src/calibre/utils/windows/common.h +++ b/src/calibre/utils/windows/common.h @@ -58,10 +58,8 @@ class scoped_com_initializer { // {{{ #define INITIALIZE_COM_IN_FUNCTION scoped_com_initializer com; if (!com) return com.set_python_error(); -static inline void co_task_mem_free(void* m) { CoTaskMemFree(m); } -typedef generic_raii com_wchar_raii; -static inline void mapping_destructor(void *p) { UnmapViewOfFile(p); } -typedef generic_raii mapping_raii; +typedef generic_raii com_wchar_raii; +typedef generic_raii mapping_raii; class handle_raii { private: From 17450ddc1c8eebb366423889eafac89c37232ecb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 14:44:23 +0530 Subject: [PATCH 0203/2055] Silence spurious warning from gcc --- src/calibre/ebooks/djvu/bzzdecoder.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/djvu/bzzdecoder.c b/src/calibre/ebooks/djvu/bzzdecoder.c index 5b4641e585..d74a8ddc02 100644 --- a/src/calibre/ebooks/djvu/bzzdecoder.c +++ b/src/calibre/ebooks/djvu/bzzdecoder.c @@ -655,12 +655,13 @@ bzz_decompress(PyObject *self, PyObject *args) { if (state.xsize > 0) { while (buflen - (pos - buf) <= state.xsize) { + size_t xpos = pos - buf; tmp = (char*) realloc(buf, buflen + (buflen * sizeof(char))); if (tmp == NULL) { PyErr_NoMemory(); goto end; } buflen += buflen * sizeof(char); - pos = tmp + (pos - buf); + pos = tmp + xpos; buf = tmp; tmp = NULL; } memcpy(pos, state.buf, state.xsize); From 039b684269fb40fe87ca428c5c1da965adaa7a67 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 15:14:01 +0530 Subject: [PATCH 0204/2055] Sadly the linux build VMs dont support c++20 so we cant use lambdas as template arguments --- src/calibre/utils/cpp_binding.h | 61 +++++++++++++++++++----------- src/calibre/utils/windows/common.h | 7 +++- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/calibre/utils/cpp_binding.h b/src/calibre/utils/cpp_binding.h index dd6c4a869a..f35555840b 100644 --- a/src/calibre/utils/cpp_binding.h +++ b/src/calibre/utils/cpp_binding.h @@ -17,7 +17,7 @@ #define arraysz(x) (sizeof(x)/sizeof(x[0])) -template(NULL)> +template(NULL)> class generic_raii { private: generic_raii( const generic_raii & ) noexcept; @@ -43,23 +43,37 @@ class generic_raii { void attach(T val) noexcept { release(); handle = val; } T* unsafe_address() noexcept { return &handle; } explicit operator bool() const noexcept { return handle != null; } + }; -class wchar_raii : public generic_raii { -#if __cplusplus >= 201703L +static inline void wchar_raii_free(wchar_t *x) { PyMem_Free(x); } + +#if (defined(__GNUC__) && !defined(__clang__)) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsubobject-linkage" +#endif +class wchar_raii : public generic_raii(NULL)> { private: Py_ssize_t sz; public: + wchar_raii(PyObject *obj) { + from_unicode(obj); + } + wchar_raii(wchar_t *obj, size_t sz) : generic_raii(obj), sz(sz) { } int from_unicode(PyObject *obj) { wchar_t *buf = PyUnicode_AsWideCharString(obj, &sz); if (!buf) return 0; attach(buf); return 1; } +#if __cplusplus >= 201703L std::wstring_view as_view() const { return std::wstring_view(handle, sz); } - std::wstring as_copy() const { return std::wstring(handle, sz); } #endif + std::wstring as_copy() const { return std::wstring(handle, sz); } }; +#if (defined(__GNUC__) && !defined(__clang__)) +#pragma GCC diagnostic pop +#endif typedef generic_raii pyobject_raii; @@ -91,26 +105,8 @@ class generic_raii_array { const T operator[](size_t i) const noexcept { return array[i]; } }; - static inline int -py_to_wchar(PyObject *obj, wchar_raii *output) { - if (!PyUnicode_Check(obj)) { - if (obj == Py_None) { output->release(); return 1; } - PyErr_SetString(PyExc_TypeError, "unicode object expected"); - return 0; - } - wchar_t *buf = PyUnicode_AsWideCharString(obj, NULL); - if (!buf) { PyErr_NoMemory(); return 0; } - output->attach(buf); - return 1; -} - -static inline int -py_to_wchar_no_none(PyObject *obj, wchar_raii *output) { - if (!PyUnicode_Check(obj)) { - PyErr_SetString(PyExc_TypeError, "unicode object expected"); - return 0; - } +py_to_wchar_(PyObject *obj, wchar_raii *output) { #if __cplusplus >= 201703L return output->from_unicode(obj); #else @@ -120,3 +116,22 @@ py_to_wchar_no_none(PyObject *obj, wchar_raii *output) { return 1; #endif } + +static inline int +py_to_wchar(PyObject *obj, wchar_raii *output) { + if (!PyUnicode_Check(obj)) { + if (obj == Py_None) { output->release(); return 1; } + PyErr_SetString(PyExc_TypeError, "unicode object expected"); + return 0; + } + return py_to_wchar_(obj, output); +} + +static inline int +py_to_wchar_no_none(PyObject *obj, wchar_raii *output) { + if (!PyUnicode_Check(obj)) { + PyErr_SetString(PyExc_TypeError, "unicode object expected"); + return 0; + } + return py_to_wchar_(obj, output); +} diff --git a/src/calibre/utils/windows/common.h b/src/calibre/utils/windows/common.h index 068bae1657..1ad3190ef3 100644 --- a/src/calibre/utils/windows/common.h +++ b/src/calibre/utils/windows/common.h @@ -58,8 +58,11 @@ class scoped_com_initializer { // {{{ #define INITIALIZE_COM_IN_FUNCTION scoped_com_initializer com; if (!com) return com.set_python_error(); -typedef generic_raii com_wchar_raii; -typedef generic_raii mapping_raii; +static inline void com_wchar_raii_free(wchar_t *x) { CoTaskMemFree(x); } +typedef generic_raii com_wchar_raii; + +static inline void mapping_raii_free(void *x) { UnmapViewOfFile(x); } +typedef generic_raii mapping_raii; class handle_raii { private: From 649baf052f5a2738b068cba825a4b1cf7845090f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 15:20:02 +0530 Subject: [PATCH 0205/2055] Nicer error formatting when cross compiling --- setup/build.py | 2 ++ setup/installers.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/setup/build.py b/setup/build.py index cf7dac88a9..6a3641577c 100644 --- a/setup/build.py +++ b/setup/build.py @@ -309,6 +309,8 @@ def init_env(debug=False, sanitize=False, compiling_for='native'): cc = cxx = 'clang-cl' linker = 'lld-link' splat = '.build-cache/xwin/splat' + cflags.append('-fcolor-diagnostics') + cflags.append('-fansi-escape-codes') for I in 'sdk/include/um sdk/include/cppwinrt sdk/include/shared sdk/include/ucrt crt/include'.split(): cflags.append('/external:I') cflags.append(f'{splat}/{I}') diff --git a/setup/installers.py b/setup/installers.py index 6d63edaf46..6a13e9491c 100644 --- a/setup/installers.py +++ b/setup/installers.py @@ -239,7 +239,9 @@ class ExtDev(Command): which, ext = opts.cli_args[:2] cmd = opts.cli_args[2:] or ['calibre-debug', '--test-build'] if which == 'windows': - subprocess.check_call([sys.executable, 'setup.py', 'build', '--cross-compile-extensions=windows', '--only=winspeech']) + cp = subprocess.run([sys.executable, 'setup.py', 'build', '--cross-compile-extensions=windows', '--only=winspeech']) + if cp.returncode != 0: + raise SystemExit(cp.returncode) src = 'src/calibre/plugins/winspeech.cross-windows-x64.pyd' host = 'win' path = '/cygdrive/c/Program Files/Calibre2/app/bin/{}.pyd' From 0a95d52f9eec170fd02dbd10f33fb6e184714535 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 15:36:32 +0530 Subject: [PATCH 0206/2055] Fix MSVC compile error --- src/calibre/utils/cpp_binding.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/cpp_binding.h b/src/calibre/utils/cpp_binding.h index f35555840b..bf5d4ebc44 100644 --- a/src/calibre/utils/cpp_binding.h +++ b/src/calibre/utils/cpp_binding.h @@ -68,8 +68,8 @@ class wchar_raii : public generic_raii= 201703L std::wstring_view as_view() const { return std::wstring_view(handle, sz); } -#endif std::wstring as_copy() const { return std::wstring(handle, sz); } +#endif }; #if (defined(__GNUC__) && !defined(__clang__)) #pragma GCC diagnostic pop From 22cea8d90a0032384dfba5046a78b3ed0bc5b750 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 15:36:49 +0530 Subject: [PATCH 0207/2055] MSVC does not like std:c++11 --- setup/build.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup/build.py b/setup/build.py index 6a3641577c..b67803b90f 100644 --- a/setup/build.py +++ b/setup/build.py @@ -529,6 +529,8 @@ class Build(Command): '-DCALIBRE_MODINIT_FUNC=' '{} __attribute__ ((visibility ("default"))) {}'.format(extern_decl, return_type)] if ext.needs_cxx and ext.needs_cxx_std: + if env.cc_output_flag.startswith('/') and ext.needs_cxx == "11": + ext.needs_cxx = "14" cflags.append(env.std_prefix + 'c++' + ext.needs_cxx_std) if ext.needs_c_std and not env.std_prefix.startswith('/'): From 2b87fa400a6a8a3677c09f9b0e690d97eff8d560 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 15:49:48 +0530 Subject: [PATCH 0208/2055] Cleanup wchar_raii constructors --- src/calibre/utils/cpp_binding.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/calibre/utils/cpp_binding.h b/src/calibre/utils/cpp_binding.h index bf5d4ebc44..5877640931 100644 --- a/src/calibre/utils/cpp_binding.h +++ b/src/calibre/utils/cpp_binding.h @@ -6,6 +6,7 @@ #pragma once +#include #define PY_SSIZE_T_CLEAN #define UNICODE #define _UNICODE @@ -56,8 +57,9 @@ class wchar_raii : public generic_raii Date: Fri, 27 Jan 2023 15:50:35 +0530 Subject: [PATCH 0209/2055] Narrow the scope of ignoring the warning --- src/calibre/utils/cpp_binding.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calibre/utils/cpp_binding.h b/src/calibre/utils/cpp_binding.h index 5877640931..469b998eeb 100644 --- a/src/calibre/utils/cpp_binding.h +++ b/src/calibre/utils/cpp_binding.h @@ -54,6 +54,9 @@ static inline void wchar_raii_free(wchar_t *x) { PyMem_Free(x); } #pragma GCC diagnostic ignored "-Wsubobject-linkage" #endif class wchar_raii : public generic_raii(NULL)> { +#if (defined(__GNUC__) && !defined(__clang__)) +#pragma GCC diagnostic pop +#endif private: Py_ssize_t sz; public: @@ -73,9 +76,6 @@ class wchar_raii : public generic_raii pyobject_raii; From 9b4f63c100dada73b63c8cae20488bf215af0397 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 15:59:12 +0530 Subject: [PATCH 0210/2055] Cleanup some warnings from clang --- src/calibre/utils/windows/winutil.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/calibre/utils/windows/winutil.cpp b/src/calibre/utils/windows/winutil.cpp index 757b325ad3..d4a38b5ef1 100644 --- a/src/calibre/utils/windows/winutil.cpp +++ b/src/calibre/utils/windows/winutil.cpp @@ -311,7 +311,7 @@ known_folder_path(PyObject *self, PyObject *args) { DWORD flags = KF_FLAG_DEFAULT; if (!PyArg_ParseTuple(args, "O!|k", &PyGUIDType, &id, &flags)) return NULL; com_wchar_raii path; - HRESULT hr = SHGetKnownFolderPath(id->guid, flags, NULL, path.unsafe_address()); + SHGetKnownFolderPath(id->guid, flags, NULL, path.unsafe_address()); return PyUnicode_FromWideChar(path.ptr(), -1); } @@ -473,7 +473,7 @@ static PyObject* winutil_get_file_size(PyObject *self, PyObject *args) { HANDLE handle; if (!PyArg_ParseTuple(args, "O&", convert_handle, &handle)) return NULL; - LARGE_INTEGER ans = {0}; + LARGE_INTEGER ans = {{0}}; if (!GetFileSizeEx(handle, &ans)) return set_error_from_handle(args); return PyLong_FromLongLong(ans.QuadPart); } @@ -482,9 +482,9 @@ static PyObject* winutil_set_file_pointer(PyObject *self, PyObject *args) { unsigned long move_method = FILE_BEGIN; HANDLE handle; - LARGE_INTEGER pos = {0}; + LARGE_INTEGER pos = {{0}}; if (!PyArg_ParseTuple(args, "O&L|k", convert_handle, &handle, &pos.QuadPart, &move_method)) return NULL; - LARGE_INTEGER ans = {0}; + LARGE_INTEGER ans = {{0}}; if (!SetFilePointerEx(handle, pos, &ans, move_method)) return set_error_from_handle(args); return PyLong_FromLongLong(ans.QuadPart); } @@ -1019,7 +1019,6 @@ EnumResProc(HMODULE handle, LPWSTR type, LPWSTR name, ResourceData *data) { static const wchar_t* get_resource_id_for_index(HMODULE handle, const int index, LPCWSTR type = RT_GROUP_ICON) { ResourceData data = {index, NULL}; - int count = index; EnumResourceNamesW(handle, type, reinterpret_cast(EnumResProc), reinterpret_cast(&data)); return data.resource_id; } From d4a8629db8cde7354e35d06aec855d6a340d7a75 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 16:00:11 +0530 Subject: [PATCH 0211/2055] ... --- src/calibre/utils/windows/common.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/utils/windows/common.h b/src/calibre/utils/windows/common.h index 1ad3190ef3..2d60c8b366 100644 --- a/src/calibre/utils/windows/common.h +++ b/src/calibre/utils/windows/common.h @@ -58,8 +58,8 @@ class scoped_com_initializer { // {{{ #define INITIALIZE_COM_IN_FUNCTION scoped_com_initializer com; if (!com) return com.set_python_error(); -static inline void com_wchar_raii_free(wchar_t *x) { CoTaskMemFree(x); } -typedef generic_raii com_wchar_raii; +static inline void co_task_mem_free(wchar_t *x) { CoTaskMemFree(x); } +typedef generic_raii com_wchar_raii; static inline void mapping_raii_free(void *x) { UnmapViewOfFile(x); } typedef generic_raii mapping_raii; From 31fe61569fc8762f7f6822139c9b4e269af12a78 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 17:28:01 +0530 Subject: [PATCH 0212/2055] ... --- setup/installers.py | 4 ++-- src/calibre/utils/cpp_binding.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/setup/installers.py b/setup/installers.py index 6a13e9491c..0ead78c82e 100644 --- a/setup/installers.py +++ b/setup/installers.py @@ -239,10 +239,10 @@ class ExtDev(Command): which, ext = opts.cli_args[:2] cmd = opts.cli_args[2:] or ['calibre-debug', '--test-build'] if which == 'windows': - cp = subprocess.run([sys.executable, 'setup.py', 'build', '--cross-compile-extensions=windows', '--only=winspeech']) + cp = subprocess.run([sys.executable, 'setup.py', 'build', '--cross-compile-extensions=windows', f'--only={ext}']) if cp.returncode != 0: raise SystemExit(cp.returncode) - src = 'src/calibre/plugins/winspeech.cross-windows-x64.pyd' + src = f'src/calibre/plugins/{ext}.cross-windows-x64.pyd' host = 'win' path = '/cygdrive/c/Program Files/Calibre2/app/bin/{}.pyd' bin_dir = '/cygdrive/c/Program Files/Calibre2' diff --git a/src/calibre/utils/cpp_binding.h b/src/calibre/utils/cpp_binding.h index 469b998eeb..8a3ee47c65 100644 --- a/src/calibre/utils/cpp_binding.h +++ b/src/calibre/utils/cpp_binding.h @@ -79,7 +79,7 @@ class wchar_raii : public generic_raii pyobject_raii; -template(NULL)> +template(NULL)> class generic_raii_array { private: generic_raii_array( const generic_raii_array & ) noexcept; From fb95b532bdf6538802f456638372d74980d66c8c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 17:44:58 +0530 Subject: [PATCH 0213/2055] Make MSVC happy --- src/calibre/devices/mtp/windows/content_enumeration.cpp | 2 +- src/calibre/devices/mtp/windows/global.h | 4 ++-- src/calibre/utils/cpp_binding.h | 5 +++-- src/calibre/utils/windows/common.h | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/calibre/devices/mtp/windows/content_enumeration.cpp b/src/calibre/devices/mtp/windows/content_enumeration.cpp index 3c1b8503eb..729b6910c2 100644 --- a/src/calibre/devices/mtp/windows/content_enumeration.cpp +++ b/src/calibre/devices/mtp/windows/content_enumeration.cpp @@ -555,7 +555,7 @@ wpd::get_file(IPortableDevice *device, const wchar_t *object_id, PyObject *dest, return NULL; } - generic_raii buf(reinterpret_cast(PyMem_Malloc(bufsize))); + generic_raii buf(reinterpret_cast(PyMem_Malloc(bufsize))); if (!buf) return PyErr_NoMemory(); while (total_read < filesize) { diff --git a/src/calibre/devices/mtp/windows/global.h b/src/calibre/devices/mtp/windows/global.h index 9660b21c40..1c2fb779b6 100644 --- a/src/calibre/devices/mtp/windows/global.h +++ b/src/calibre/devices/mtp/windows/global.h @@ -8,11 +8,11 @@ #pragma once #define UNICODE #define PY_SSIZE_T_CLEAN -#include +#include #include #include -#include +#include #include #include #include "../../../utils/windows/common.h" diff --git a/src/calibre/utils/cpp_binding.h b/src/calibre/utils/cpp_binding.h index 8a3ee47c65..1fd9e52932 100644 --- a/src/calibre/utils/cpp_binding.h +++ b/src/calibre/utils/cpp_binding.h @@ -47,13 +47,14 @@ class generic_raii { }; -static inline void wchar_raii_free(wchar_t *x) { PyMem_Free(x); } +template +static inline void pymem_free(T *x) { PyMem_Free(x); } #if (defined(__GNUC__) && !defined(__clang__)) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsubobject-linkage" #endif -class wchar_raii : public generic_raii(NULL)> { +class wchar_raii : public generic_raii { #if (defined(__GNUC__) && !defined(__clang__)) #pragma GCC diagnostic pop #endif diff --git a/src/calibre/utils/windows/common.h b/src/calibre/utils/windows/common.h index 2d60c8b366..1ae3b57e99 100644 --- a/src/calibre/utils/windows/common.h +++ b/src/calibre/utils/windows/common.h @@ -58,7 +58,8 @@ class scoped_com_initializer { // {{{ #define INITIALIZE_COM_IN_FUNCTION scoped_com_initializer com; if (!com) return com.set_python_error(); -static inline void co_task_mem_free(wchar_t *x) { CoTaskMemFree(x); } +template +static inline void co_task_mem_free(T *x) { CoTaskMemFree(x); } typedef generic_raii com_wchar_raii; static inline void mapping_raii_free(void *x) { UnmapViewOfFile(x); } From 55ebdf049f08d820094faf54ec6ef12355390d03 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 17:49:10 +0530 Subject: [PATCH 0214/2055] Silence various warnings from clang --- .../devices/mtp/windows/content_enumeration.cpp | 15 +++++++-------- src/calibre/devices/mtp/windows/wpd.cpp | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/calibre/devices/mtp/windows/content_enumeration.cpp b/src/calibre/devices/mtp/windows/content_enumeration.cpp index 729b6910c2..7c2e926585 100644 --- a/src/calibre/devices/mtp/windows/content_enumeration.cpp +++ b/src/calibre/devices/mtp/windows/content_enumeration.cpp @@ -188,7 +188,7 @@ private: public: GetBulkPropertiesCallback() : items(NULL), subfolders(NULL), level(0), complete(INVALID_HANDLE_VALUE), self_ref(0), callback(NULL) {} - ~GetBulkPropertiesCallback() { if (complete != INVALID_HANDLE_VALUE) CloseHandle(complete); complete = INVALID_HANDLE_VALUE; } + virtual ~GetBulkPropertiesCallback() { if (complete != INVALID_HANDLE_VALUE) CloseHandle(complete); complete = INVALID_HANDLE_VALUE; } bool start_processing(PyObject *items, PyObject *subfolders, unsigned int level, PyObject *callback) { complete = CreateEvent(NULL, FALSE, FALSE, NULL); @@ -226,7 +226,7 @@ public: } return hr; } - HRESULT __stdcall GetBulkPropertiesCallback::OnProgress(REFGUID Context, IPortableDeviceValuesCollection* values) { + HRESULT __stdcall OnProgress(REFGUID Context, IPortableDeviceValuesCollection* values) { handle_values(values); return S_OK; } @@ -420,7 +420,6 @@ static IPortableDeviceValues* create_object_properties(const wchar_t *parent_id, const wchar_t *name, const GUID content_type, unsigned PY_LONG_LONG size) { // {{{ CComPtr values; HRESULT hr; - bool ok = false; prop_variant timestamp(VT_DATE); SYSTEMTIME systemtime; GetLocalTime(&systemtime); @@ -486,7 +485,7 @@ get_files_and_folders(unsigned int level, IPortableDevice *device, CComPtr content; HRESULT hr; @@ -503,7 +502,7 @@ wpd::get_filesystem(IPortableDevice *device, const wchar_t *storage_id, IPortabl } // }}} PyObject* -wpd::get_file(IPortableDevice *device, const wchar_t *object_id, PyObject *dest, PyObject *callback) { // {{{ +get_file(IPortableDevice *device, const wchar_t *object_id, PyObject *dest, PyObject *callback) { // {{{ CComPtr content; CComPtr resources; CComPtr devprops; @@ -592,7 +591,7 @@ wpd::get_file(IPortableDevice *device, const wchar_t *object_id, PyObject *dest, } // }}} PyObject* -wpd::create_folder(IPortableDevice *device, const wchar_t *parent_id, const wchar_t *name) { // {{{ +create_folder(IPortableDevice *device, const wchar_t *parent_id, const wchar_t *name) { // {{{ CComPtr content; CComPtr values; CComPtr devprops; @@ -623,7 +622,7 @@ wpd::create_folder(IPortableDevice *device, const wchar_t *parent_id, const wcha } // }}} PyObject* -wpd::delete_object(IPortableDevice *device, const wchar_t *object_id) { // {{{ +delete_object(IPortableDevice *device, const wchar_t *object_id) { // {{{ CComPtr content; CComPtr object_ids; HRESULT hr; @@ -656,7 +655,7 @@ wpd::delete_object(IPortableDevice *device, const wchar_t *object_id) { // {{{ } // }}} PyObject* -wpd::put_file(IPortableDevice *device, const wchar_t *parent_id, const wchar_t *name, PyObject *src, unsigned PY_LONG_LONG size, PyObject *callback) { // {{{ +put_file(IPortableDevice *device, const wchar_t *parent_id, const wchar_t *name, PyObject *src, unsigned PY_LONG_LONG size, PyObject *callback) { // {{{ CComPtr content; CComPtr values; CComPtr devprops; diff --git a/src/calibre/devices/mtp/windows/wpd.cpp b/src/calibre/devices/mtp/windows/wpd.cpp index 1cc90c3d50..ee1d98f83b 100644 --- a/src/calibre/devices/mtp/windows/wpd.cpp +++ b/src/calibre/devices/mtp/windows/wpd.cpp @@ -120,7 +120,7 @@ wpd_uninit(PyObject *self, PyObject *args) { // enumerate_devices() {{{ static PyObject * wpd_enumerate_devices(PyObject *self, PyObject *args) { - PyObject *refresh = NULL, *ans = NULL, *temp; + PyObject *ans = NULL, *temp; HRESULT hr; DWORD num_of_devices, i; PWSTR *pnp_device_ids; From ccd5fd83f407fd24193f0f703fe8753bd25f8817 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 18:00:05 +0530 Subject: [PATCH 0215/2055] Fix warning from clang --- src/calibre/utils/windows/winutil.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/utils/windows/winutil.cpp b/src/calibre/utils/windows/winutil.cpp index d4a38b5ef1..143e168a19 100644 --- a/src/calibre/utils/windows/winutil.cpp +++ b/src/calibre/utils/windows/winutil.cpp @@ -222,6 +222,7 @@ static PyMethodDef Handle_methods[] = { class DeleteFileProgressSink : public IFileOperationProgressSink { // {{{ public: DeleteFileProgressSink() : m_cRef(0) {} + virtual ~DeleteFileProgressSink() = default; private: ULONG STDMETHODCALLTYPE AddRef(void) { InterlockedIncrement(&m_cRef); return m_cRef; } From ce78a29fc810907cbc1debd8537fedbe84b9094b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 18:11:26 +0530 Subject: [PATCH 0216/2055] Outsmart clang clang insists INVALID_HANDLE_VALUE is not a constexpr because it is defined as (void*)-1 So it wont let us use it a template paramter. Make the template paramter a function instead that returns the constant value. Hopefully compilers are smart enough to just inline the function call so there is no performance impact. Dont have the patience to check the assembly to make sure. --- src/calibre/utils/cpp_binding.h | 17 ++++++++++------- src/calibre/utils/windows/common.h | 30 +++--------------------------- 2 files changed, 13 insertions(+), 34 deletions(-) diff --git a/src/calibre/utils/cpp_binding.h b/src/calibre/utils/cpp_binding.h index 1fd9e52932..d5e8712c3e 100644 --- a/src/calibre/utils/cpp_binding.h +++ b/src/calibre/utils/cpp_binding.h @@ -18,7 +18,10 @@ #define arraysz(x) (sizeof(x)/sizeof(x[0])) -template(NULL)> +template +static inline T generic_null_getter(void) { return T{}; } + +template class generic_raii { private: generic_raii( const generic_raii & ) noexcept; @@ -28,23 +31,23 @@ class generic_raii { T handle; public: - explicit generic_raii(T h = null) noexcept : handle(h) {} + generic_raii() noexcept { handle = null_getter(); } + explicit generic_raii(T h) noexcept : handle(h) {} ~generic_raii() noexcept { release(); } void release() noexcept { - if (handle != null) { + if (handle != null_getter()) { T temp = handle; - handle = null; + handle = null_getter(); free_T(temp); } } T ptr() noexcept { return handle; } - T detach() noexcept { T ans = handle; handle = null; return ans; } + T detach() noexcept { T ans = handle; handle = null_getter(); return ans; } void attach(T val) noexcept { release(); handle = val; } T* unsafe_address() noexcept { return &handle; } - explicit operator bool() const noexcept { return handle != null; } - + explicit operator bool() const noexcept { return handle != null_getter(); } }; template diff --git a/src/calibre/utils/windows/common.h b/src/calibre/utils/windows/common.h index 1ae3b57e99..e37feafaa6 100644 --- a/src/calibre/utils/windows/common.h +++ b/src/calibre/utils/windows/common.h @@ -64,33 +64,9 @@ typedef generic_raii com_wchar_raii; static inline void mapping_raii_free(void *x) { UnmapViewOfFile(x); } typedef generic_raii mapping_raii; - -class handle_raii { - private: - handle_raii( const handle_raii & ) noexcept; - handle_raii & operator=( const handle_raii & ) noexcept ; - - protected: - HANDLE handle; - - public: - explicit handle_raii(HANDLE h = INVALID_HANDLE_VALUE) noexcept : handle(h) {} - ~handle_raii() noexcept { release(); } - - void release() noexcept { - if (handle != INVALID_HANDLE_VALUE) { - HANDLE temp = handle; - handle = INVALID_HANDLE_VALUE; - CloseHandle(temp); - } - } - - HANDLE ptr() noexcept { return handle; } - HANDLE detach() noexcept { HANDLE ans = handle; handle = INVALID_HANDLE_VALUE; return ans; } - void attach(HANDLE val) noexcept { release(); handle = val; } - explicit operator bool() const noexcept { return handle != INVALID_HANDLE_VALUE; } -}; - +static inline HANDLE invalid_handle_value_getter(void) { return INVALID_HANDLE_VALUE; } +static inline void close_handle(HANDLE x) { CloseHandle(x); } +typedef generic_raii handle_raii; struct prop_variant : PROPVARIANT { prop_variant(VARTYPE vt=VT_EMPTY) noexcept : PROPVARIANT{} { PropVariantInit(this); this->vt = vt; } From 8fa523dd709a146096d8d789439d1bb666a7cc5f Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Fri, 27 Jan 2023 13:03:03 +0000 Subject: [PATCH 0217/2055] Two minor improvements: 1) Clear the temporary VL if the search box is empty instead of giving an error. 2) Escape regexp special characters in tag browser first letter searches. Use super-quoting to make escaping work reliably. --- src/calibre/gui2/search_restriction_mixin.py | 32 ++++++++++---------- src/calibre/gui2/tag_browser/model.py | 11 ++++--- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/calibre/gui2/search_restriction_mixin.py b/src/calibre/gui2/search_restriction_mixin.py index 96f9f9eae7..5f35b92cbd 100644 --- a/src/calibre/gui2/search_restriction_mixin.py +++ b/src/calibre/gui2/search_restriction_mixin.py @@ -453,23 +453,23 @@ class SearchRestrictionMixin: db.data.set_base_restriction_name('') elif library == '*': if not self.search.current_text: - error_dialog(self, _('No search'), - _('There is no current search to use'), show=True) - return + # Clear the temporary VL if the search box is empty + db.data.set_base_restriction('') + db.data.set_base_restriction_name('') + else: + txt = _build_full_search_string(self) + try: + db.data.search_getting_ids('', txt, use_virtual_library=False) + except ParseException as e: + error_dialog(self, _('Invalid search'), + _('The search in the search box is not valid'), + det_msg=e.msg, show=True) + return - txt = _build_full_search_string(self) - try: - db.data.search_getting_ids('', txt, use_virtual_library=False) - except ParseException as e: - error_dialog(self, _('Invalid search'), - _('The search in the search box is not valid'), - det_msg=e.msg, show=True) - return - - self.search_based_vl = txt - db.data.set_base_restriction(txt) - self.search_based_vl_name = self._trim_restriction_name('*' + txt) - db.data.set_base_restriction_name(self.search_based_vl_name) + self.search_based_vl = txt + db.data.set_base_restriction(txt) + self.search_based_vl_name = self._trim_restriction_name('*' + txt) + db.data.set_base_restriction_name(self.search_based_vl_name) elif library == self.search_based_vl_name: db.data.set_base_restriction(self.search_based_vl) db.data.set_base_restriction_name(self.search_based_vl_name) diff --git a/src/calibre/gui2/tag_browser/model.py b/src/calibre/gui2/tag_browser/model.py index 34b20627af..87a0ff48c6 100644 --- a/src/calibre/gui2/tag_browser/model.py +++ b/src/calibre/gui2/tag_browser/model.py @@ -1717,15 +1717,18 @@ class TagsModel(QAbstractItemModel): # {{{ letters_seen = {} for subnode in tag_item.children: if subnode.tag.sort: - letters_seen[subnode.tag.sort[0]] = True + c = subnode.tag.sort[0] + if c in r'\.^$[]|()': + c = f'\\{c}' + letters_seen[c] = True if letters_seen: charclass = ''.join(letters_seen) if k == 'author_sort': - expr = r'%s:"~(^[%s])|(&\s*[%s])"'%(k, charclass, charclass) + expr = r'%s:"""~(^[%s])|(&\s*[%s])"""'%(k, charclass, charclass) elif k == 'series': - expr = r'series_sort:"~^[%s]"'%(charclass) + expr = r'series_sort:"""~^[%s]"""'%(charclass) else: - expr = r'%s:"~^[%s]"'%(k, charclass) + expr = r'%s:"""~^[%s]"""'%(k, charclass) else: expr = r'%s:false'%(k) if node_searches[tag_item.tag.state] == 'true': From 88e2331f634a93a2f1609c1054c4abcfeb126d6f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 21:06:20 +0530 Subject: [PATCH 0218/2055] Hack to get mark reporting working Since Microsoft dont seem to have implemented support for SSML bookmarks or at least I cant get it to work, use the word cue events. When it fires report any surpassed or closeby mark. --- src/calibre/utils/windows/winspeech.cpp | 43 ++++++++++++++++--------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 3c5f6dcfd4..17c933f777 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -6,6 +6,7 @@ */ #include "common.h" +#include #include #include #include @@ -28,6 +29,9 @@ #include #include +#ifdef max +#undef max +#endif using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::Media::SpeechSynthesis; @@ -744,6 +748,7 @@ class Synthesizer { MediaPlaybackItem current_item{nullptr}; std::vector current_text_storage; Marks current_marks; + int32_t last_reported_mark_index; std::atomic current_cmd_id; Revokers revoker; @@ -752,19 +757,6 @@ class Synthesizer { void register_metadata_handler_for_track(uint32_t index, id_type cmd_id); void load_stream_for_playback(SpeechSynthesisStream const &stream, id_type cmd_id, bool is_cued); - void add_cues() { - TimedMetadataTrack track(L"mark", L"en-us", TimedMetadataKind::Speech); - track.Label(L"mark"); - for (const Mark &mark : current_marks) { - SpeechCue cue; - cue.StartPositionInInput(IReference{(int)mark.pos_in_text}); - cue.EndPositionInInput(IReference{(int)mark.pos_in_text + 1}); - cue.Text(winrt::to_hstring(mark.id)); - track.AddCue(cue); - } - current_source.ExternalTimedMetadataTracks().Append(track); - } - public: void register_metadata_handler_for_speech(id_type cmd_id, long index) { std::scoped_lock sl(recursive_lock); @@ -785,6 +777,26 @@ class Synthesizer { if (cmd_id_is_current(cmd_id)) ::output(cmd_id, type, std::move(x)); } + void on_cue_entered(id_type cmd_id, const winrt::hstring &label, const SpeechCue &cue) { + std::scoped_lock sl(recursive_lock); + if (!cmd_id_is_current(cmd_id)) return; + output(cmd_id, "cue_entered", json_val(label, cue)); + if (label != L"SpeechWord") return; + int32_t pos = cue.StartPositionInInput().Value(); + for (int32_t i = std::max(0, last_reported_mark_index); i < (int32_t)current_marks.size(); i++) { + int32_t idx = -1; + if (current_marks[i].pos_in_text > pos) { + idx = i-1; + if (idx == last_reported_mark_index && current_marks[i].pos_in_text - pos < 3) idx = i; + } else if (current_marks[i].pos_in_text == pos) idx = i; + if (idx > -1) { + output(cmd_id, "mark_reached", {{"id", current_marks[idx].id}}); + last_reported_mark_index = idx; + break; + } + } + } + void initialize() { synth = SpeechSynthesizer(); player = MediaPlayer(); @@ -803,6 +815,7 @@ class Synthesizer { player.Pause(); current_text_storage = std::vector(); current_marks = Marks(); + last_reported_mark_index = -1; } } @@ -848,8 +861,7 @@ Synthesizer::register_metadata_handler_for_track(uint32_t index, id_type cmd_id) std::scoped_lock sl(recursive_lock); if (current_cmd_id.load() != cmd_id) return; revoker.cue_entered.push_back(track.CueEntered(winrt::auto_revoke, [cmd_id](auto track, const auto& args) { - if (main_loop_is_running.load()) sx.output( - cmd_id, "cue_entered", json_val(track.Label(), args.Cue().template as())); + if (main_loop_is_running.load()) sx.on_cue_entered(cmd_id, track.Label(), args.Cue().template as()); })); revoker.cue_exited.push_back(track.CueExited(winrt::auto_revoke, [cmd_id](auto track, const auto& args) { if (main_loop_is_running.load()) sx.output( @@ -868,7 +880,6 @@ Synthesizer::load_stream_for_playback(SpeechSynthesisStream const &stream, id_ty if (cmd_id != current_cmd_id.load()) return; current_stream = stream; current_source = MediaSource::CreateFromStream(current_stream, current_stream.ContentType()); - if (is_cued) add_cues(); revoker.playback_state_changed = player.PlaybackSession().PlaybackStateChanged( winrt::auto_revoke, [cmd_id](auto session, auto const&) { From f0ee3506d785945f4e130ae119a2bb2863312ef7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 21:18:19 +0530 Subject: [PATCH 0219/2055] Keep all the speak related functions together in a block --- src/calibre/utils/windows/winspeech.cpp | 135 ++++++++++++------------ 1 file changed, 70 insertions(+), 65 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 17c933f777..9f550a7122 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -754,48 +754,15 @@ class Synthesizer { Revokers revoker; std::recursive_mutex recursive_lock; + public: + // Speak {{{ void register_metadata_handler_for_track(uint32_t index, id_type cmd_id); void load_stream_for_playback(SpeechSynthesisStream const &stream, id_type cmd_id, bool is_cued); - - public: - void register_metadata_handler_for_speech(id_type cmd_id, long index) { - std::scoped_lock sl(recursive_lock); - if (!cmd_id_is_current(cmd_id)) return; - if (index < 0) { - for (uint32_t i = 0; i < current_item.TimedMetadataTracks().Size(); i++) { - register_metadata_handler_for_track(i, cmd_id); - } - } else { - register_metadata_handler_for_track(index, cmd_id); - } - } - + winrt::fire_and_forget speak(id_type cmd_id, std::wstring_view const &text, bool is_ssml, bool is_cued, std::vector &&buf, Marks const && marks); + void register_metadata_handler_for_speech(id_type cmd_id, long index); bool cmd_id_is_current(id_type cmd_id) const noexcept { return current_cmd_id.load() == cmd_id; } - - void output(id_type cmd_id, std::string_view const& type, json_val const && x) { - std::scoped_lock sl(recursive_lock); - if (cmd_id_is_current(cmd_id)) ::output(cmd_id, type, std::move(x)); - } - - void on_cue_entered(id_type cmd_id, const winrt::hstring &label, const SpeechCue &cue) { - std::scoped_lock sl(recursive_lock); - if (!cmd_id_is_current(cmd_id)) return; - output(cmd_id, "cue_entered", json_val(label, cue)); - if (label != L"SpeechWord") return; - int32_t pos = cue.StartPositionInInput().Value(); - for (int32_t i = std::max(0, last_reported_mark_index); i < (int32_t)current_marks.size(); i++) { - int32_t idx = -1; - if (current_marks[i].pos_in_text > pos) { - idx = i-1; - if (idx == last_reported_mark_index && current_marks[i].pos_in_text - pos < 3) idx = i; - } else if (current_marks[i].pos_in_text == pos) idx = i; - if (idx > -1) { - output(cmd_id, "mark_reached", {{"id", current_marks[idx].id}}); - last_reported_mark_index = idx; - break; - } - } - } + void on_cue_entered(id_type cmd_id, const winrt::hstring &label, const SpeechCue &cue); + // }}} void initialize() { synth = SpeechSynthesizer(); @@ -804,6 +771,11 @@ class Synthesizer { player.AutoPlay(true); } + void output(id_type cmd_id, std::string_view const& type, json_val const && x) { + std::scoped_lock sl(recursive_lock); + if (cmd_id_is_current(cmd_id)) ::output(cmd_id, type, std::move(x)); + } + void stop_current_activity() { std::scoped_lock sl(recursive_lock); if (current_cmd_id.load()) { @@ -819,32 +791,6 @@ class Synthesizer { } } - winrt::fire_and_forget speak(id_type cmd_id, std::wstring_view const &text, bool is_ssml, bool is_cued, std::vector &&buf, Marks const && marks) { - SpeechSynthesisStream stream{nullptr}; - { std::scoped_lock sl(recursive_lock); - stop_current_activity(); - current_cmd_id.store(cmd_id); - current_text_storage = std::move(buf); - current_marks = std::move(marks); - synth.Options().IncludeSentenceBoundaryMetadata(true); - synth.Options().IncludeWordBoundaryMetadata(true); - } - output(cmd_id, "synthesizing", {{"ssml", is_ssml}, {"num_marks", current_marks.size()}, {"text_length", text.size()}}); - bool ok = false; - try { - if (is_ssml) stream = co_await synth.SynthesizeSsmlToStreamAsync(text); - else stream = co_await synth.SynthesizeTextToStreamAsync(text); - ok = true; - } CATCH_ALL_EXCEPTIONS("Failed to synthesize speech", cmd_id); - if (ok) { - if (main_loop_is_running.load()) { - try { - load_stream_for_playback(stream, cmd_id, is_cued); - } CATCH_ALL_EXCEPTIONS("Failed to load synthesized stream for playback", cmd_id); - } - } - } - double volume() const { return player.Volume(); } void volume(double val) { if (val < 0 || val > 1) throw std::out_of_range("Invalid volume value must be between 0 and 1"); @@ -855,6 +801,39 @@ class Synthesizer { static Synthesizer sx; +// Speak {{{ +void Synthesizer::on_cue_entered(id_type cmd_id, const winrt::hstring &label, const SpeechCue &cue) { + std::scoped_lock sl(recursive_lock); + if (!cmd_id_is_current(cmd_id)) return; + output(cmd_id, "cue_entered", json_val(label, cue)); + if (label != L"SpeechWord") return; + uint32_t pos = cue.StartPositionInInput().Value(); + for (int32_t i = std::max(0, last_reported_mark_index); i < (int32_t)current_marks.size(); i++) { + int32_t idx = -1; + if (current_marks[i].pos_in_text > pos) { + idx = i-1; + if (idx == last_reported_mark_index && current_marks[i].pos_in_text - pos < 3) idx = i; + } else if (current_marks[i].pos_in_text == pos) idx = i; + if (idx > -1) { + output(cmd_id, "mark_reached", {{"id", current_marks[idx].id}}); + last_reported_mark_index = idx; + break; + } + } +} + +void Synthesizer::register_metadata_handler_for_speech(id_type cmd_id, long index) { + std::scoped_lock sl(recursive_lock); + if (!cmd_id_is_current(cmd_id)) return; + if (index < 0) { + for (uint32_t i = 0; i < current_item.TimedMetadataTracks().Size(); i++) { + register_metadata_handler_for_track(i, cmd_id); + } + } else { + register_metadata_handler_for_track(index, cmd_id); + } +} + void Synthesizer::register_metadata_handler_for_track(uint32_t index, id_type cmd_id) { TimedMetadataTrack track = current_item.TimedMetadataTracks().GetAt(index); @@ -916,6 +895,31 @@ Synthesizer::load_stream_for_playback(SpeechSynthesisStream const &stream, id_ty player.Source(current_item); } +winrt::fire_and_forget Synthesizer::speak(id_type cmd_id, std::wstring_view const &text, bool is_ssml, bool is_cued, std::vector &&buf, Marks const && marks) { + SpeechSynthesisStream stream{nullptr}; + { std::scoped_lock sl(recursive_lock); + stop_current_activity(); + current_cmd_id.store(cmd_id); + current_text_storage = std::move(buf); + current_marks = std::move(marks); + synth.Options().IncludeSentenceBoundaryMetadata(true); + synth.Options().IncludeWordBoundaryMetadata(true); + } + output(cmd_id, "synthesizing", {{"ssml", is_ssml}, {"num_marks", current_marks.size()}, {"text_length", text.size()}}); + bool ok = false; + try { + if (is_ssml) stream = co_await synth.SynthesizeSsmlToStreamAsync(text); + else stream = co_await synth.SynthesizeTextToStreamAsync(text); + ok = true; + } CATCH_ALL_EXCEPTIONS("Failed to synthesize speech", cmd_id); + if (ok) { + if (main_loop_is_running.load()) { + try { + load_stream_for_playback(stream, cmd_id, is_cued); + } CATCH_ALL_EXCEPTIONS("Failed to load synthesized stream for playback", cmd_id); + } + } +} static size_t decode_into(std::string_view src, std::wstring_view dest) { @@ -1008,6 +1012,7 @@ handle_speak(id_type cmd_id, std::vector &parts) { *((wchar_t*)text.data() + text.size()) = 0; // ensure NULL termination sx.speak(cmd_id, text, is_ssml, is_cued, std::move(buf), std::move(marks)); } +// }}} static int64_t handle_stdin_message(winrt::hstring const &&msg) { From 795c4c60616a414dfeda5a3bb04415ae309c5c82 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Jan 2023 22:16:01 +0530 Subject: [PATCH 0220/2055] Work on saving speech to WAV --- src/calibre/utils/cpp_binding.h | 4 + src/calibre/utils/windows/winspeech.cpp | 115 ++++++++++++++++++------ 2 files changed, 92 insertions(+), 27 deletions(-) diff --git a/src/calibre/utils/cpp_binding.h b/src/calibre/utils/cpp_binding.h index d5e8712c3e..865ec5702d 100644 --- a/src/calibre/utils/cpp_binding.h +++ b/src/calibre/utils/cpp_binding.h @@ -12,10 +12,14 @@ #define _UNICODE #include #include +#include #if __cplusplus >= 201703L #include #endif +#if __cplusplus >= 201402L +using namespace std::string_literals; +#endif #define arraysz(x) (sizeof(x)/sizeof(x[0])) template diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 9f550a7122..291eee17d6 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -750,6 +751,7 @@ class Synthesizer { Marks current_marks; int32_t last_reported_mark_index; std::atomic current_cmd_id; + std::ofstream outfile; Revokers revoker; std::recursive_mutex recursive_lock; @@ -757,15 +759,20 @@ class Synthesizer { public: // Speak {{{ void register_metadata_handler_for_track(uint32_t index, id_type cmd_id); - void load_stream_for_playback(SpeechSynthesisStream const &stream, id_type cmd_id, bool is_cued); + void load_stream_for_playback(SpeechSynthesisStream const &&stream, id_type cmd_id, bool is_cued); winrt::fire_and_forget speak(id_type cmd_id, std::wstring_view const &text, bool is_ssml, bool is_cued, std::vector &&buf, Marks const && marks); void register_metadata_handler_for_speech(id_type cmd_id, long index); bool cmd_id_is_current(id_type cmd_id) const noexcept { return current_cmd_id.load() == cmd_id; } void on_cue_entered(id_type cmd_id, const winrt::hstring &label, const SpeechCue &cue); // }}} + winrt::fire_and_forget save(id_type cmd_id, std::wstring_view const &text, bool is_ssml, std::vector &&buf, std::ofstream &&outfile); + void load_stream_for_save(SpeechSynthesisStream const &&stream, id_type cmd_id); + void initialize() { synth = SpeechSynthesizer(); + synth.Options().IncludeSentenceBoundaryMetadata(true); + synth.Options().IncludeWordBoundaryMetadata(true); player = MediaPlayer(); player.AudioCategory(MediaPlayerAudioCategory::Speech); player.AutoPlay(true); @@ -788,6 +795,7 @@ class Synthesizer { current_text_storage = std::vector(); current_marks = Marks(); last_reported_mark_index = -1; + if (outfile.is_open()) outfile.close(); } } @@ -854,7 +862,7 @@ Synthesizer::register_metadata_handler_for_track(uint32_t index, id_type cmd_id) } void -Synthesizer::load_stream_for_playback(SpeechSynthesisStream const &stream, id_type cmd_id, bool is_cued) { +Synthesizer::load_stream_for_playback(SpeechSynthesisStream const &&stream, id_type cmd_id, bool is_cued) { std::scoped_lock sl(recursive_lock); if (cmd_id != current_cmd_id.load()) return; current_stream = stream; @@ -902,8 +910,6 @@ winrt::fire_and_forget Synthesizer::speak(id_type cmd_id, std::wstring_view cons current_cmd_id.store(cmd_id); current_text_storage = std::move(buf); current_marks = std::move(marks); - synth.Options().IncludeSentenceBoundaryMetadata(true); - synth.Options().IncludeWordBoundaryMetadata(true); } output(cmd_id, "synthesizing", {{"ssml", is_ssml}, {"num_marks", current_marks.size()}, {"text_length", text.size()}}); bool ok = false; @@ -915,7 +921,7 @@ winrt::fire_and_forget Synthesizer::speak(id_type cmd_id, std::wstring_view cons if (ok) { if (main_loop_is_running.load()) { try { - load_stream_for_playback(stream, cmd_id, is_cued); + load_stream_for_playback(std::move(stream), cmd_id, is_cued); } CATCH_ALL_EXCEPTIONS("Failed to load synthesized stream for playback", cmd_id); } } @@ -964,6 +970,26 @@ parse_cued_text(std::string_view src, Marks &marks, std::wstring_view dest) { return dest.substr(0, dest_pos); } +static std::wstring_view +read_from_shm(id_type cmd_id, const std::wstring_view size, const std::wstring_view address, std::vector &buf, Marks &marks, bool is_cued=false) { + id_type shm_size = parse_id(size); + handle_raii handle(OpenFileMappingW(FILE_MAP_READ, false, address.data())); + if (handle.ptr() == INVALID_HANDLE_VALUE) { + output_error(cmd_id, "Could not open shared memory at", winrt::to_string(address), __LINE__); + return {}; + } + mapping_raii mapping(MapViewOfFile(handle.ptr(), FILE_MAP_READ, 0, 0, (SIZE_T)shm_size)); + if (!mapping) { + output_error(cmd_id, "Could not map shared memory with error", std::to_string(GetLastError()), __LINE__); + return {}; + } + buf.reserve(shm_size + 2); + std::string_view src((const char*)mapping.ptr(), shm_size); + std::wstring_view dest(buf.data(), buf.capacity()); + if (is_cued) return parse_cued_text(src, marks, dest); + return std::wstring_view(buf.data(), decode_into(src, dest)); +} + static void handle_speak(id_type cmd_id, std::vector &parts) { bool is_ssml = false, is_shm = false, is_cued = false; @@ -976,32 +1002,12 @@ handle_speak(id_type cmd_id, std::vector &parts) { } parts.erase(parts.begin(), parts.begin() + 2); std::wstring address; - id_type shm_size = 0; Marks marks; std::vector buf; std::wstring_view text; if (is_shm) { - shm_size = parse_id(parts.at(0)); - address = parts.at(1); - handle_raii handle(OpenFileMappingW(FILE_MAP_READ, false, address.data())); - if (handle.ptr() == INVALID_HANDLE_VALUE) { - output_error(cmd_id, "Could not open shared memory at", winrt::to_string(address), __LINE__); - return; - } - mapping_raii mapping(MapViewOfFile(handle.ptr(), FILE_MAP_READ, 0, 0, (SIZE_T)shm_size)); - if (mapping.ptr() == NULL) { - output_error(cmd_id, "Could not map shared memory with error", std::to_string(GetLastError()), __LINE__); - return; - } - buf.reserve(shm_size + 2); - std::string_view src((const char*)mapping.ptr(), shm_size); - std::wstring_view dest(buf.data(), buf.capacity()); - if (is_cued) { - text = parse_cued_text(src, marks, dest); - } else { - size_t n = decode_into(src, dest); - text = std::wstring_view(buf.data(), n); - } + text = read_from_shm(cmd_id, parts.at(0), parts.at(1), buf, marks, is_cued); + if (text.size() == 0) return; } else { address = join(parts); if (address.size() == 0) throw std::string("Address missing"); @@ -1014,6 +1020,58 @@ handle_speak(id_type cmd_id, std::vector &parts) { } // }}} +// Save {{{ +void +Synthesizer::load_stream_for_save(SpeechSynthesisStream const &&stream, id_type cmd_id) { + std::scoped_lock sl(recursive_lock); + if (cmd_id != current_cmd_id.load()) return; + current_stream = stream; +} + +winrt::fire_and_forget Synthesizer::save(id_type cmd_id, std::wstring_view const &text, bool is_ssml, std::vector &&buf, std::ofstream &&out) { + SpeechSynthesisStream stream{nullptr}; + { std::scoped_lock sl(recursive_lock); + stop_current_activity(); + current_cmd_id.store(cmd_id); + current_text_storage = std::move(buf); + outfile = std::move(out); + } + output(cmd_id, "saving", {{"ssml", is_ssml}}); + bool ok = false; + try { + if (is_ssml) stream = co_await synth.SynthesizeSsmlToStreamAsync(text); + else stream = co_await synth.SynthesizeTextToStreamAsync(text); + ok = true; + } CATCH_ALL_EXCEPTIONS("Failed to synthesize speech", cmd_id); + if (ok) { + if (main_loop_is_running.load()) { + try { + load_stream_for_save(std::move(stream), cmd_id); + } CATCH_ALL_EXCEPTIONS("Failed to load synthesized stream for save", cmd_id); + } + } +} + +static void +handle_save(id_type cmd_id, std::vector &parts) { + bool is_ssml; + try { + is_ssml = parts.at(0) == L"ssml"; + } catch (std::exception const&) { + throw std::string("Not a well formed save command"); + } + std::vector buf; + std::wstring_view text; + std::wstring address; + Marks marks; + text = read_from_shm(cmd_id, parts.at(2), parts.at(3), buf, marks); + if (text.size() == 0) return; + *((wchar_t*)text.data() + text.size()) = 0; // ensure NULL termination + std::ofstream outfile(parts.at(1), std::ios::out | std::ios::trunc); + sx.save(cmd_id, text, is_ssml, std::move(buf), std::move(outfile)); +} +// }}} + static int64_t handle_stdin_message(winrt::hstring const &&msg) { if (msg == L"exit") { @@ -1059,6 +1117,9 @@ handle_stdin_message(winrt::hstring const &&msg) { } output(cmd_id, "volume", {{"value", sx.volume()}}); } + else if (command == L"save") { + handle_save(cmd_id, parts); + } else throw std::string("Unknown command: ") + winrt::to_string(command); } CATCH_ALL_EXCEPTIONS("Error handling input message", cmd_id); return -1; From 467af44edb8449809f8863faf87a2c1385ec860d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 28 Jan 2023 12:21:34 +0530 Subject: [PATCH 0221/2055] Niceties for getting the last error on windows --- src/calibre/utils/windows/common.h | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/calibre/utils/windows/common.h b/src/calibre/utils/windows/common.h index e37feafaa6..98fae364b5 100644 --- a/src/calibre/utils/windows/common.h +++ b/src/calibre/utils/windows/common.h @@ -5,12 +5,15 @@ */ #pragma once +#include #define PY_SSIZE_T_CLEAN #define UNICODE #define _UNICODE #include #include +#include #include +#include #include "../cpp_binding.h" static inline PyObject* @@ -24,6 +27,46 @@ set_error_from_hresult(PyObject *exc_type, const char *file, const int line, con } #define error_from_hresult(hr, ...) set_error_from_hresult(PyExc_OSError, __FILE__, __LINE__, hr, __VA_ARGS__) +// trim from end (in place) +template +static inline void +rtrim(std::basic_string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](T ch) { + switch (ch) { case ' ': case '\t': case '\n': case '\r': case '\v': case '\f': return false; } + return true; + }).base(), s.end()); +} + +static inline std::wstring +get_last_error(std::wstring const & prefix = L"") { + auto ec = GetLastError(); + LPWSTR buf; + DWORD n; + + if ((n = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + ec, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&buf), + 0, NULL)) == 0) { + auto error_code{ ::GetLastError() }; + throw std::system_error(error_code, std::system_category(), "Failed to retrieve error message string."); + } + auto deleter = [](void* p) { ::LocalFree(p); }; + std::unique_ptr ptrBuffer(buf, deleter); + auto msg = std::wstring(buf, n); + std::wstring ans = prefix; + if (prefix.size() > 0) { + ans += L": "; + } + rtrim(msg); + ans += L"Code: " + std::to_wstring(ec) + L" Message: " + msg; + return ans; +} + class scoped_com_initializer { // {{{ public: scoped_com_initializer() : m_succeded(false), hr(0) { @@ -67,6 +110,7 @@ typedef generic_raii mapping_raii; static inline HANDLE invalid_handle_value_getter(void) { return INVALID_HANDLE_VALUE; } static inline void close_handle(HANDLE x) { CloseHandle(x); } typedef generic_raii handle_raii; +typedef generic_raii handle_raii_null; struct prop_variant : PROPVARIANT { prop_variant(VARTYPE vt=VT_EMPTY) noexcept : PROPVARIANT{} { PropVariantInit(this); this->vt = vt; } From 6e9c4e0bb9cebd43d547c1e38b446ce6cf6bfc5d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 28 Jan 2023 12:32:40 +0530 Subject: [PATCH 0222/2055] Saving to WAV kinda works though audio is very distorted --- src/calibre/utils/windows/winspeech.cpp | 579 ++++++------------------ src/calibre/utils/windows/winspeech.py | 68 ++- 2 files changed, 171 insertions(+), 476 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 291eee17d6..edd8b0c2e8 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -8,7 +8,10 @@ #include #include +#include #include +#include +#include #include #include #include @@ -44,15 +47,26 @@ typedef uint64_t id_type; static std::mutex output_lock; static DWORD main_thread_id; -template static void -__debug_multiple(T x, Args... args) { - std::cerr << x << " "; - __debug_multiple(args...); +template static void +__debug_multiple_impl(T x) { + if constexpr (std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v) { + std::cerr << winrt::to_string(x); + } else { + std::cerr << x; + } } template static void __debug_multiple(T x) { - std::cerr << x << std::endl; + __debug_multiple_impl(x); + std::cerr << std::endl; +} + +template static void +__debug_multiple(T x, Args... args) { + __debug_multiple_impl(x); + std::cerr << " "; + __debug_multiple(args...); } template static void @@ -71,14 +85,6 @@ enum { EXIT_REQUESTED }; -// trim from end (in place) -static inline void -rtrim(std::string &s) { - s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { - return !std::isspace(ch); - }).base(), s.end()); -} - static std::vector split(std::wstring_view const &src, std::wstring const &delim = L" ") { size_t pos; @@ -350,6 +356,8 @@ output_error(id_type cmd_id, std::string_view const &msg, std::string_view const #define CATCH_ALL_EXCEPTIONS(msg, cmd_id) \ catch(winrt::hresult_error const& ex) { \ output_error(cmd_id, msg, winrt::to_string(ex.message()), __LINE__, ex.to_abi()); \ +} catch(const std::system_error& ex) { \ + output_error(cmd_id, msg, "system_error with code: " + std::to_string(ex.code().value()) + " and meaning: " + ex.what(), __LINE__); \ } catch (std::exception const &ex) { \ output_error(cmd_id, msg, ex.what(), __LINE__); \ } catch (std::string const &ex) { \ @@ -360,369 +368,6 @@ output_error(id_type cmd_id, std::string_view const &msg, std::string_view const output_error(cmd_id, msg, "Unknown exception type was raised", __LINE__); \ } -/* Legacy code {{{ - -template -class WeakRefs { - private: - std::mutex weak_ref_lock; - std::unordered_map refs; - id_type counter; - public: - id_type register_ref(T *self) { - std::scoped_lock lock(weak_ref_lock); - auto id = ++counter; - refs[id] = self; - return id; - } - void unregister_ref(T* self) { - std::scoped_lock lock(weak_ref_lock); - auto id = self->clear_id(); - refs.erase(id); - self->~T(); - } - void use_ref(id_type id, std::function callback) { - std::scoped_lock lock(weak_ref_lock); - try { - callback(refs.at(id)); - } catch (std::out_of_range) { - callback(NULL); - } - } -}; - -enum class EventType { - playback_state_changed = 1, media_opened, media_failed, media_ended, source_changed, cue_entered, cue_exited, track_failed -}; - -class Event { - private: - EventType type; - public: - Event(EventType type) : type(type) {} - Event(const Event &source) : type(source.type) {} -}; - -class SynthesizerImplementation { - private: - id_type id; - DWORD creation_thread_id; - SpeechSynthesizer synth{nullptr}; - MediaPlayer player{nullptr}; - MediaSource current_source{nullptr}; - SpeechSynthesisStream current_stream{nullptr}; - MediaPlaybackItem currently_playing{nullptr}; - - struct { - MediaPlaybackSession::PlaybackStateChanged_revoker playback_state_changed; - MediaPlayer::MediaEnded_revoker media_ended; MediaPlayer::MediaOpened_revoker media_opened; - MediaPlayer::MediaFailed_revoker media_failed; MediaPlayer::SourceChanged_revoker source_changed; - - MediaPlaybackItem::TimedMetadataTracksChanged_revoker timed_metadata_tracks_changed; - std::vector cue_entered; - std::vector cue_exited; - std::vector track_failed; - } revoker; - - std::vector events; - std::mutex events_lock; - - public: - SynthesizerImplementation(); - - void add_simple_event(EventType type) { - try { - std::scoped_lock lock(events_lock); - events.emplace_back(type); - } catch(...) {} - } - - SpeechSynthesisStream synthesize(const std::wstring_view &text, bool is_ssml = false) { - if (is_ssml) return synth.SynthesizeSsmlToStreamAsync(text).get(); - return synth.SynthesizeTextToStreamAsync(text).get(); - } - - void speak(const std::wstring_view &text, bool is_ssml = false) { - revoker.cue_entered.clear(); - revoker.cue_exited.clear(); - revoker.track_failed.clear(); - current_stream = synthesize(text, is_ssml); - current_source = MediaSource::CreateFromStream(current_stream, current_stream.ContentType()); - currently_playing = MediaPlaybackItem(current_source); - auto self_id = id; - revoker.timed_metadata_tracks_changed = currently_playing.TimedMetadataTracksChanged(winrt::auto_revoke, [self_id](auto, auto const &args) { - auto change_type = args.CollectionChange(); - auto index = args.Index(); - synthesizer_weakrefs.use_ref(self_id, [change_type, index](auto s) { - if (!s) return; - switch (change_type) { - case CollectionChange::ItemInserted: { - s->register_metadata_handler_for_speech(s->currently_playing.TimedMetadataTracks().GetAt(index)); - } break; - case CollectionChange::Reset: - for (auto const& track : s->currently_playing.TimedMetadataTracks()) { - s->register_metadata_handler_for_speech(track); - } - break; - }}); - }); - player.Source(currently_playing); - for (auto const &track : currently_playing.TimedMetadataTracks()) { - register_metadata_handler_for_speech(track); - } - } - - bool is_creation_thread() const noexcept { - return creation_thread_id == GetCurrentThreadId(); - } - - id_type clear_id() noexcept { - auto ans = id; - id = 0; - return ans; - } - - void register_metadata_handler_for_speech(TimedMetadataTrack const& track) { - fprintf(stderr, "99999999999 registering metadata handler\n"); - auto self_id = id; -#define simple_event_listener(method, event_type) \ - revoker.event_type.push_back(method(winrt::auto_revoke, [self_id](auto, const auto&) { \ - fprintf(stderr, "111111111 %s %u\n", #event_type, GetCurrentThreadId()); fflush(stderr); \ - synthesizer_weakrefs.use_ref(self_id, [](auto s) { \ - if (!s) return; \ - s->add_simple_event(EventType::event_type); \ - fprintf(stderr, "2222222222 %d\n", s->player.PlaybackSession().PlaybackState()); \ - }); \ - })); - simple_event_listener(track.CueEntered, cue_entered); - simple_event_listener(track.CueExited, cue_exited); - simple_event_listener(track.TrackFailed, track_failed); -#undef simple_event_listener - track.CueEntered([](auto, const auto&) { - fprintf(stderr, "cue entered\n"); fflush(stderr); - }); -} - - -}; - -struct Synthesizer { - PyObject_HEAD - SynthesizerImplementation impl; -}; - -static PyTypeObject SynthesizerType = { - PyVarObject_HEAD_INIT(NULL, 0) -}; - -static WeakRefs synthesizer_weakrefs; - -SynthesizerImplementation::SynthesizerImplementation() { - events.reserve(128); - synth = SpeechSynthesizer(); - synth.Options().IncludeSentenceBoundaryMetadata(true); - synth.Options().IncludeWordBoundaryMetadata(true); - player = MediaPlayer(); - player.AudioCategory(MediaPlayerAudioCategory::Speech); - player.AutoPlay(true); - creation_thread_id = GetCurrentThreadId(); - id = synthesizer_weakrefs.register_ref(this); - auto self_id = id; -#define simple_event_listener(method, event_type) \ - revoker.event_type = method(winrt::auto_revoke, [self_id](auto, const auto&) { \ - fprintf(stderr, "111111111 %s %u\n", #event_type, GetCurrentThreadId()); fflush(stderr); \ - synthesizer_weakrefs.use_ref(self_id, [](auto s) { \ - if (!s) return; \ - s->add_simple_event(EventType::event_type); \ - fprintf(stderr, "2222222222 %d\n", s->player.PlaybackSession().PlaybackState()); \ - }); \ - }); - simple_event_listener(player.PlaybackSession().PlaybackStateChanged, playback_state_changed); - simple_event_listener(player.MediaOpened, media_opened); - simple_event_listener(player.MediaFailed, media_failed); - simple_event_listener(player.MediaEnded, media_ended); - simple_event_listener(player.SourceChanged, source_changed); -#undef simple_event_listener - player.PlaybackSession().PlaybackStateChanged([](auto, const auto&) { - fprintf(stderr, "111111111 %s %u\n", "playback state changed", GetCurrentThreadId()); fflush(stderr); \ - }); - player.MediaOpened([](auto, const auto&) { - fprintf(stderr, "111111111 %s %u\n", "media opened", GetCurrentThreadId()); fflush(stderr); \ - }); - player.MediaFailed([](auto, const auto&) { - fprintf(stderr, "111111111 %s %u\n", "media failed", GetCurrentThreadId()); fflush(stderr); \ - }); - player.MediaEnded([](auto, const auto&) { - fprintf(stderr, "111111111 %s %u\n", "media ended", GetCurrentThreadId()); fflush(stderr); \ - }); -} - -static PyObject* -Synthesizer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { INITIALIZE_COM_IN_FUNCTION - Synthesizer *self = (Synthesizer *) type->tp_alloc(type, 0); - if (self) { - auto i = &self->impl; - try { - new (i) SynthesizerImplementation(); - } CATCH_ALL_EXCEPTIONS("Failed to create SynthesizerImplementation object"); - if (PyErr_Occurred()) { Py_CLEAR(self); } - } - if (self) com.detach(); - return (PyObject*)self; -} - -static void -Synthesizer_dealloc(Synthesizer *self) { - auto *i = &self->impl; - try { - synthesizer_weakrefs.unregister_ref(i); - } CATCH_ALL_EXCEPTIONS("Failed to destruct SynthesizerImplementation"); - if (PyErr_Occurred()) { PyErr_Print(); } - Py_TYPE(self)->tp_free((PyObject*)self); - CoUninitialize(); -} - -static void -ensure_current_thread_has_message_queue(void) { - MSG msg; - PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); -} - -#define PREPARE_METHOD_CALL ensure_current_thread_has_message_queue(); if (!self->impl.is_creation_thread()) { PyErr_SetString(PyExc_RuntimeError, "Cannot use a Synthesizer object from a thread other than the thread it was created in"); return NULL; } - -static PyObject* -Synthesizer_speak(Synthesizer *self, PyObject *args) { - PREPARE_METHOD_CALL; - wchar_raii pytext; - int is_ssml = 0; - if (!PyArg_ParseTuple(args, "O&|p", py_to_wchar_no_none, &pytext, &is_ssml)) return NULL; - try { - self->impl.speak(pytext.as_view(), (bool)is_ssml); - } CATCH_ALL_EXCEPTIONS("Failed to start speaking text"); - if (PyErr_Occurred()) return NULL; - Py_RETURN_NONE; -} - - -static PyObject* -Synthesizer_create_recording(Synthesizer *self, PyObject *args) { - PREPARE_METHOD_CALL; - wchar_raii pytext; - PyObject *callback; - int is_ssml = 0; - if (!PyArg_ParseTuple(args, "O&O|p", py_to_wchar_no_none, &pytext, &callback, &is_ssml)) return NULL; - if (!PyCallable_Check(callback)) { PyErr_SetString(PyExc_TypeError, "callback must be callable"); return NULL; } - - SpeechSynthesisStream stream{nullptr}; - try { - stream = self->impl.synthesize(pytext.as_view(), (bool)is_ssml); - } CATCH_ALL_EXCEPTIONS( "Failed to get SpeechSynthesisStream from text"); - if (PyErr_Occurred()) return NULL; - unsigned long long stream_size = stream.Size(), bytes_read = 0; - DataReader reader(stream); - unsigned int n; - const static unsigned int chunk_size = 16 * 1024; - while (bytes_read < stream_size) { - try { - n = reader.LoadAsync(chunk_size).get(); - } CATCH_ALL_EXCEPTIONS("Failed to load data from DataReader"); - if (PyErr_Occurred()) return NULL; - if (n > 0) { - bytes_read += n; - pyobject_raii b(PyBytes_FromStringAndSize(NULL, n)); - if (!b) return NULL; - unsigned char *p = reinterpret_cast(PyBytes_AS_STRING(b.ptr())); - reader.ReadBytes(winrt::array_view(p, p + n)); - pyobject_raii ret(PyObject_CallFunctionObjArgs(callback, b.ptr(), NULL)); - } - } - - if (PyErr_Occurred()) return NULL; - Py_RETURN_NONE; -} - - -static PyObject* -voice_as_dict(VoiceInformation const& voice) { - try { - const char *gender = ""; - switch (voice.Gender()) { - case VoiceGender::Male: gender = "male"; break; - case VoiceGender::Female: gender = "female"; break; - } - return Py_BuildValue("{su su su su ss}", - "display_name", voice.DisplayName().c_str(), - "description", voice.Description().c_str(), - "id", voice.Id().c_str(), - "language", voice.Language().c_str(), - "gender", gender - ); - } CATCH_ALL_EXCEPTIONS("Could not convert Voice to dict"); - return NULL; -} - - -static PyObject* -all_voices(PyObject*, PyObject*) { INITIALIZE_COM_IN_FUNCTION - try { - auto voices = SpeechSynthesizer::AllVoices(); - pyobject_raii ans(PyTuple_New(voices.Size())); - if (!ans) return NULL; - Py_ssize_t i = 0; - for(auto const& voice : voices) { - PyObject *v = voice_as_dict(voice); - if (v) { - PyTuple_SET_ITEM(ans.ptr(), i++, v); - } else { - return NULL; - } - } - return ans.detach(); - } CATCH_ALL_EXCEPTIONS("Could not get all voices"); - return NULL; -} - -static PyObject* -default_voice(PyObject*, PyObject*) { INITIALIZE_COM_IN_FUNCTION - try { - return voice_as_dict(SpeechSynthesizer::DefaultVoice()); - } CATCH_ALL_EXCEPTIONS("Could not get default voice"); - return NULL; -} - -#define M(name, args) { #name, (PyCFunction)Synthesizer_##name, args, ""} -static PyMethodDef Synthesizer_methods[] = { - M(create_recording, METH_VARARGS), - M(speak, METH_VARARGS), - {NULL, NULL, 0, NULL} -}; -#undef M - -static PyObject* -pump_waiting_messages(PyObject*, PyObject*) { - UINT firstMsg = 0, lastMsg = 0; - MSG msg; - bool found = false; - // Read all of the messages in this next loop, - // removing each message as we read it. - while (PeekMessage(&msg, NULL, firstMsg, lastMsg, PM_REMOVE)) { - // If it's a quit message, we're out of here. - if (msg.message == WM_QUIT) { - Py_RETURN_NONE; - } - found = true; - // Otherwise, dispatch the message. - DispatchMessage(&msg); - } // End of PeekMessage while loop - - if (found) Py_RETURN_TRUE; - Py_RETURN_FALSE; -} - - -}}} */ - - struct Revokers { MediaPlaybackSession::PlaybackStateChanged_revoker playback_state_changed; MediaPlayer::MediaEnded_revoker media_ended; MediaPlayer::MediaOpened_revoker media_opened; @@ -767,12 +412,10 @@ class Synthesizer { // }}} winrt::fire_and_forget save(id_type cmd_id, std::wstring_view const &text, bool is_ssml, std::vector &&buf, std::ofstream &&outfile); - void load_stream_for_save(SpeechSynthesisStream const &&stream, id_type cmd_id); + void start_save_stream(SpeechSynthesisStream const &&stream, id_type cmd_id); void initialize() { synth = SpeechSynthesizer(); - synth.Options().IncludeSentenceBoundaryMetadata(true); - synth.Options().IncludeWordBoundaryMetadata(true); player = MediaPlayer(); player.AudioCategory(MediaPlayerAudioCategory::Speech); player.AutoPlay(true); @@ -809,6 +452,59 @@ class Synthesizer { static Synthesizer sx; +static size_t +decode_into(std::string_view src, std::wstring_view dest) { + int n = MultiByteToWideChar(CP_UTF8, 0, src.data(), (int)src.size(), (wchar_t*)dest.data(), (int)dest.size()); + if (n == 0 && src.size() > 0) { + throw std::system_error(GetLastError(), std::system_category(), "Failed to decode cued text"); + } + return n; +} + +static std::wstring_view +parse_cued_text(std::string_view src, Marks &marks, std::wstring_view dest) { + size_t dest_pos = 0; + if (dest.size() < src.size()) throw std::exception("Destination buffer for parse_cued_text() too small"); + while (src.size()) { + auto pos = src.find('\0'); + size_t limit = pos == std::string_view::npos ? src.size() : pos; + if (limit) { + dest_pos += decode_into(src.substr(0, limit), dest.substr(dest_pos, dest.size() - dest_pos)); + src = src.substr(limit, src.size() - limit); + } + if (pos != std::string_view::npos) { + src = src.substr(1, src.size() - 1); + if (src.size() >= 4) { + uint32_t mark = *((uint32_t*)src.data()); + marks.emplace_back(mark, (uint32_t)dest_pos); + src = src.substr(4, src.size() - 4); + } + } + } + return dest.substr(0, dest_pos); +} + +static std::wstring_view +read_from_shm(id_type cmd_id, const std::wstring_view size, const std::wstring &address, std::vector &buf, Marks &marks, bool is_cued=false) { + id_type shm_size = parse_id(size); + handle_raii_null handle(OpenFileMappingW(FILE_MAP_READ, false, address.data())); + if (!handle) { + output_error(cmd_id, "Could not open shared memory at: " + winrt::to_string(address), winrt::to_string(get_last_error()), __LINE__); + return {}; + } + mapping_raii mapping(MapViewOfFile(handle.ptr(), FILE_MAP_READ, 0, 0, (SIZE_T)shm_size)); + if (!mapping) { + output_error(cmd_id, "Could not map shared memory", winrt::to_string(get_last_error()), __LINE__); + return {}; + } + buf.reserve(shm_size + 2); + std::string_view src((const char*)mapping.ptr(), shm_size); + std::wstring_view dest(buf.data(), buf.capacity()); + if (is_cued) return parse_cued_text(src, marks, dest); + return std::wstring_view(buf.data(), decode_into(src, dest)); +} + + // Speak {{{ void Synthesizer::on_cue_entered(id_type cmd_id, const winrt::hstring &label, const SpeechCue &cue) { std::scoped_lock sl(recursive_lock); @@ -910,6 +606,8 @@ winrt::fire_and_forget Synthesizer::speak(id_type cmd_id, std::wstring_view cons current_cmd_id.store(cmd_id); current_text_storage = std::move(buf); current_marks = std::move(marks); + synth.Options().IncludeSentenceBoundaryMetadata(true); + synth.Options().IncludeWordBoundaryMetadata(true); } output(cmd_id, "synthesizing", {{"ssml", is_ssml}, {"num_marks", current_marks.size()}, {"text_length", text.size()}}); bool ok = false; @@ -927,69 +625,6 @@ winrt::fire_and_forget Synthesizer::speak(id_type cmd_id, std::wstring_view cons } } -static size_t -decode_into(std::string_view src, std::wstring_view dest) { - int n = MultiByteToWideChar(CP_UTF8, 0, src.data(), (int)src.size(), (wchar_t*)dest.data(), (int)dest.size()); - if (n == 0 && src.size() > 0) { - switch (GetLastError()) { - case ERROR_INSUFFICIENT_BUFFER: - throw std::exception("Output buffer too small while decoding cued text"); - case ERROR_INVALID_FLAGS: - throw std::exception("Invalid flags while decoding cued text"); - case ERROR_INVALID_PARAMETER: - throw std::exception("Invalid parameters while decoding cued text"); - case ERROR_NO_UNICODE_TRANSLATION: - throw std::exception("Invalid UTF-8 found while decoding cued text"); - default: - throw std::exception("Unknown error while decoding cued text"); - } - } - return n; -} - -static std::wstring_view -parse_cued_text(std::string_view src, Marks &marks, std::wstring_view dest) { - size_t dest_pos = 0; - if (dest.size() < src.size()) throw std::exception("Destination buffer for parse_cued_text() too small"); - while (src.size()) { - auto pos = src.find('\0'); - size_t limit = pos == std::string_view::npos ? src.size() : pos; - if (limit) { - dest_pos += decode_into(src.substr(0, limit), dest.substr(dest_pos, dest.size() - dest_pos)); - src = src.substr(limit, src.size() - limit); - } - if (pos != std::string_view::npos) { - src = src.substr(1, src.size() - 1); - if (src.size() >= 4) { - uint32_t mark = *((uint32_t*)src.data()); - marks.emplace_back(mark, (uint32_t)dest_pos); - src = src.substr(4, src.size() - 4); - } - } - } - return dest.substr(0, dest_pos); -} - -static std::wstring_view -read_from_shm(id_type cmd_id, const std::wstring_view size, const std::wstring_view address, std::vector &buf, Marks &marks, bool is_cued=false) { - id_type shm_size = parse_id(size); - handle_raii handle(OpenFileMappingW(FILE_MAP_READ, false, address.data())); - if (handle.ptr() == INVALID_HANDLE_VALUE) { - output_error(cmd_id, "Could not open shared memory at", winrt::to_string(address), __LINE__); - return {}; - } - mapping_raii mapping(MapViewOfFile(handle.ptr(), FILE_MAP_READ, 0, 0, (SIZE_T)shm_size)); - if (!mapping) { - output_error(cmd_id, "Could not map shared memory with error", std::to_string(GetLastError()), __LINE__); - return {}; - } - buf.reserve(shm_size + 2); - std::string_view src((const char*)mapping.ptr(), shm_size); - std::wstring_view dest(buf.data(), buf.capacity()); - if (is_cued) return parse_cued_text(src, marks, dest); - return std::wstring_view(buf.data(), decode_into(src, dest)); -} - static void handle_speak(id_type cmd_id, std::vector &parts) { bool is_ssml = false, is_shm = false, is_cued = false; @@ -1006,7 +641,7 @@ handle_speak(id_type cmd_id, std::vector &parts) { std::vector buf; std::wstring_view text; if (is_shm) { - text = read_from_shm(cmd_id, parts.at(0), parts.at(1), buf, marks, is_cued); + text = read_from_shm(cmd_id, parts.at(0), std::wstring(parts.at(1)), buf, marks, is_cued); if (text.size() == 0) return; } else { address = join(parts); @@ -1021,11 +656,43 @@ handle_speak(id_type cmd_id, std::vector &parts) { // }}} // Save {{{ +static winrt::fire_and_forget +save_stream(SpeechSynthesisStream const &&stream, std::ofstream &&outfile, id_type cmd_id) { + unsigned long long stream_size = stream.Size(), bytes_read = 0; + DataReader reader(stream); + unsigned int n; + const static unsigned int chunk_size = 16 * 1024; + uint8_t buf[chunk_size]; + while (bytes_read < stream_size) { + bool ok = false; + try { + n = co_await reader.LoadAsync(chunk_size); + ok = true; + } CATCH_ALL_EXCEPTIONS("Failed to load data from DataReader", cmd_id); + if (!ok) break; + if (n > 0) { + bytes_read += n; + ok = false; + try { + reader.ReadBytes(winrt::array_view(buf, buf + n)); + outfile.write((const char*)buf, n); + if (!outfile.good()) throw "Failed to write to output file"; + ok = true; + } CATCH_ALL_EXCEPTIONS("Failed to save bytes from DataReader to file", cmd_id); + if (!ok) break; + } + } + outfile.close(); + output(cmd_id, "saved", {{"size", bytes_read}}); +} + void -Synthesizer::load_stream_for_save(SpeechSynthesisStream const &&stream, id_type cmd_id) { +Synthesizer::start_save_stream(SpeechSynthesisStream const &&stream, id_type cmd_id) { std::scoped_lock sl(recursive_lock); - if (cmd_id != current_cmd_id.load()) return; - current_stream = stream; + try { + save_stream(std::move(stream), std::move(outfile), cmd_id); + } CATCH_ALL_EXCEPTIONS("Failed to save loaded stream", cmd_id); + stop_current_activity(); } winrt::fire_and_forget Synthesizer::save(id_type cmd_id, std::wstring_view const &text, bool is_ssml, std::vector &&buf, std::ofstream &&out) { @@ -1035,8 +702,9 @@ winrt::fire_and_forget Synthesizer::save(id_type cmd_id, std::wstring_view const current_cmd_id.store(cmd_id); current_text_storage = std::move(buf); outfile = std::move(out); + synth.Options().IncludeSentenceBoundaryMetadata(false); + synth.Options().IncludeWordBoundaryMetadata(false); } - output(cmd_id, "saving", {{"ssml", is_ssml}}); bool ok = false; try { if (is_ssml) stream = co_await synth.SynthesizeSsmlToStreamAsync(text); @@ -1046,7 +714,7 @@ winrt::fire_and_forget Synthesizer::save(id_type cmd_id, std::wstring_view const if (ok) { if (main_loop_is_running.load()) { try { - load_stream_for_save(std::move(stream), cmd_id); + sx.start_save_stream(std::move(stream), cmd_id); } CATCH_ALL_EXCEPTIONS("Failed to load synthesized stream for save", cmd_id); } } @@ -1058,16 +726,20 @@ handle_save(id_type cmd_id, std::vector &parts) { try { is_ssml = parts.at(0) == L"ssml"; } catch (std::exception const&) { - throw std::string("Not a well formed save command"); + throw "Not a well formed save command"s; } std::vector buf; - std::wstring_view text; std::wstring address; Marks marks; - text = read_from_shm(cmd_id, parts.at(2), parts.at(3), buf, marks); + std::wstring_view text = read_from_shm(cmd_id, parts.at(1), std::wstring(parts.at(2)), buf, marks); if (text.size() == 0) return; + parts.erase(parts.begin(), parts.begin() + 3); *((wchar_t*)text.data() + text.size()) = 0; // ensure NULL termination - std::ofstream outfile(parts.at(1), std::ios::out | std::ios::trunc); + auto filename = join(parts); + auto path = std::filesystem::absolute(filename); + std::ofstream outfile(path.string(), std::ios::out | std::ios::trunc); + if (!outfile.good()) throw "Failed to create: " + path.string(); + output(cmd_id, "saving", {{"ssml", is_ssml}, {"output_path", path.string()}}); sx.save(cmd_id, text, is_ssml, std::move(buf), std::move(outfile)); } // }}} @@ -1132,6 +804,9 @@ run_main_loop(PyObject*, PyObject*) { std::cout.imbue(std::locale("C")); std::cin.imbue(std::locale("C")); std::cerr.imbue(std::locale("C")); + std::wcin.imbue(std::locale("C")); + std::wcout.imbue(std::locale("C")); + std::wcerr.imbue(std::locale("C")); } CATCH_ALL_EXCEPTIONS("Failed to set stdio locales to C", 0); winrt::init_apartment(); // MTA (multi-threaded apartment) main_thread_id = GetCurrentThreadId(); diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index ad2c762884..bdd4e4eddf 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -3,14 +3,15 @@ import json +import os import struct import sys from contextlib import closing from queue import Queue from threading import Thread -from calibre.utils.shm import SharedMemory from calibre.utils.ipc.simple_worker import start_pipe_worker +from calibre.utils.shm import SharedMemory SSML_SAMPLE = ''' @@ -61,9 +62,8 @@ def encode_to_file_object(text, output) -> int: return sz -def develop_speech(text='Lucca Brazzi sleeps with the fishes.', mark_words=True): +def develop_loop(wait_for, *commands): p = start_worker() - print('\x1b[32mSpeaking', text, '\x1b[39m]]'[:-2], flush=True) q = Queue() def echo_output(p): @@ -79,6 +79,36 @@ def develop_speech(text='Lucca Brazzi sleeps with the fishes.', mark_words=True) Thread(name='Echo', target=echo_output, args=(p,), daemon=True).start() exit_code = 0 + with closing(p.stdin), closing(p.stdout): + try: + send('1 echo Synthesizer started') + send('1 volume 0.1') + for command in commands: + send(command) + while True: + m = q.get() + if m['related_to'] != wait_for: + continue + if m['payload_type'] == 'media_state_changed' and m['state'] == 'ended': + break + if m['payload_type'] == 'saved': + break + if m['payload_type'] == 'error': + exit_code = 1 + break + send(f'333 echo Synthesizer exiting with exit code: {exit_code}') + send(f'334 exit {exit_code}') + ec = p.wait(1) + print(f'Worker exited with code: {os.waitstatus_to_exitcode(p.wait(1))}', file=sys.stderr, flush=True) + raise SystemExit(ec) + finally: + if p.poll() is None: + p.kill() + raise SystemExit(1) + + +def develop_speech(text='Lucca Brazzi sleeps with the fishes.', mark_words=True): + print('\x1b[32mSpeaking', text, '\x1b[39m]]'[:-2], flush=True) st = 'ssml' if ' Date: Sat, 28 Jan 2023 12:44:42 +0530 Subject: [PATCH 0223/2055] Set volume on synthesizer rather than player so it works for save as well --- src/calibre/utils/windows/winspeech.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index edd8b0c2e8..eea73795be 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -442,10 +442,10 @@ class Synthesizer { } } - double volume() const { return player.Volume(); } + double volume() const { return synth.Options().AudioVolume(); } void volume(double val) { if (val < 0 || val > 1) throw std::out_of_range("Invalid volume value must be between 0 and 1"); - player.Volume(val); + synth.Options().AudioVolume(val); } }; From ae3c33ec8cb9ad8e08f1609def6653f85e7596c0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 28 Jan 2023 13:01:21 +0530 Subject: [PATCH 0224/2055] Implement rate control --- src/calibre/utils/windows/winspeech.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index eea73795be..4dd7a1ddb2 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -442,12 +442,27 @@ class Synthesizer { } } - double volume() const { return synth.Options().AudioVolume(); } + double volume() const { + return synth.Options().AudioVolume(); + } + void volume(double val) { if (val < 0 || val > 1) throw std::out_of_range("Invalid volume value must be between 0 and 1"); + std::scoped_lock sl(recursive_lock); synth.Options().AudioVolume(val); } + double rate() const { + return synth.Options().SpeakingRate(); + } + + void rate(double val) { + if (val < 0.5 || val > 6.0) throw std::out_of_range("Invalid rate value must be between 0.5 and 6"); + std::scoped_lock sl(recursive_lock); + synth.Options().SpeakingRate(val); + } + + }; static Synthesizer sx; @@ -789,6 +804,13 @@ handle_stdin_message(winrt::hstring const &&msg) { } output(cmd_id, "volume", {{"value", sx.volume()}}); } + else if (command == L"rate") { + if (parts.size()) { + auto rate = parse_double(parts[0].data()); + sx.rate(rate); + } + output(cmd_id, "rate", {{"value", sx.rate()}}); + } else if (command == L"save") { handle_save(cmd_id, parts); } From 8b3129360019574f8f450e6e3ae4a692d1ad6fdb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 28 Jan 2023 13:22:54 +0530 Subject: [PATCH 0225/2055] ... --- src/calibre/utils/windows/winspeech.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 4dd7a1ddb2..f4b5273ce3 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -830,7 +830,7 @@ run_main_loop(PyObject*, PyObject*) { std::wcout.imbue(std::locale("C")); std::wcerr.imbue(std::locale("C")); } CATCH_ALL_EXCEPTIONS("Failed to set stdio locales to C", 0); - winrt::init_apartment(); // MTA (multi-threaded apartment) + winrt::init_apartment(winrt::apartment_type::multi_threaded); main_thread_id = GetCurrentThreadId(); MSG msg; int64_t exit_code = 0; From dd13c03bfce554f92aed71e74ccb94828fae42de Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 28 Jan 2023 13:31:36 +0530 Subject: [PATCH 0226/2055] No need to store outfile just open it when needed --- src/calibre/utils/windows/winspeech.cpp | 31 ++++++++++++------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index f4b5273ce3..92e0ac2a3b 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -396,7 +396,6 @@ class Synthesizer { Marks current_marks; int32_t last_reported_mark_index; std::atomic current_cmd_id; - std::ofstream outfile; Revokers revoker; std::recursive_mutex recursive_lock; @@ -411,8 +410,8 @@ class Synthesizer { void on_cue_entered(id_type cmd_id, const winrt::hstring &label, const SpeechCue &cue); // }}} - winrt::fire_and_forget save(id_type cmd_id, std::wstring_view const &text, bool is_ssml, std::vector &&buf, std::ofstream &&outfile); - void start_save_stream(SpeechSynthesisStream const &&stream, id_type cmd_id); + winrt::fire_and_forget save(id_type cmd_id, std::wstring_view const &text, bool is_ssml, std::vector &&buf, std::filesystem::path path); + void start_save_stream(SpeechSynthesisStream const &&stream, std::filesystem::path path, id_type cmd_id); void initialize() { synth = SpeechSynthesizer(); @@ -438,7 +437,6 @@ class Synthesizer { current_text_storage = std::vector(); current_marks = Marks(); last_reported_mark_index = -1; - if (outfile.is_open()) outfile.close(); } } @@ -672,14 +670,19 @@ handle_speak(id_type cmd_id, std::vector &parts) { // Save {{{ static winrt::fire_and_forget -save_stream(SpeechSynthesisStream const &&stream, std::ofstream &&outfile, id_type cmd_id) { +save_stream(SpeechSynthesisStream const &&stream, std::filesystem::path path, id_type cmd_id) { unsigned long long stream_size = stream.Size(), bytes_read = 0; DataReader reader(stream); unsigned int n; const static unsigned int chunk_size = 16 * 1024; uint8_t buf[chunk_size]; - while (bytes_read < stream_size) { - bool ok = false; + std::ofstream outfile; + bool ok = false; + try { + outfile.open(path.string(), std::ios::out | std::ios::trunc); + ok = true; + } CATCH_ALL_EXCEPTIONS("Failed to create file: " + path.string(), cmd_id); + while (ok && bytes_read < stream_size) { try { n = co_await reader.LoadAsync(chunk_size); ok = true; @@ -697,26 +700,24 @@ save_stream(SpeechSynthesisStream const &&stream, std::ofstream &&outfile, id_ty if (!ok) break; } } - outfile.close(); output(cmd_id, "saved", {{"size", bytes_read}}); } void -Synthesizer::start_save_stream(SpeechSynthesisStream const &&stream, id_type cmd_id) { +Synthesizer::start_save_stream(SpeechSynthesisStream const &&stream, std::filesystem::path path, id_type cmd_id) { std::scoped_lock sl(recursive_lock); try { - save_stream(std::move(stream), std::move(outfile), cmd_id); + save_stream(std::move(stream), path, cmd_id); } CATCH_ALL_EXCEPTIONS("Failed to save loaded stream", cmd_id); stop_current_activity(); } -winrt::fire_and_forget Synthesizer::save(id_type cmd_id, std::wstring_view const &text, bool is_ssml, std::vector &&buf, std::ofstream &&out) { +winrt::fire_and_forget Synthesizer::save(id_type cmd_id, std::wstring_view const &text, bool is_ssml, std::vector &&buf, std::filesystem::path path) { SpeechSynthesisStream stream{nullptr}; { std::scoped_lock sl(recursive_lock); stop_current_activity(); current_cmd_id.store(cmd_id); current_text_storage = std::move(buf); - outfile = std::move(out); synth.Options().IncludeSentenceBoundaryMetadata(false); synth.Options().IncludeWordBoundaryMetadata(false); } @@ -729,7 +730,7 @@ winrt::fire_and_forget Synthesizer::save(id_type cmd_id, std::wstring_view const if (ok) { if (main_loop_is_running.load()) { try { - sx.start_save_stream(std::move(stream), cmd_id); + sx.start_save_stream(std::move(stream), path, cmd_id); } CATCH_ALL_EXCEPTIONS("Failed to load synthesized stream for save", cmd_id); } } @@ -752,10 +753,8 @@ handle_save(id_type cmd_id, std::vector &parts) { *((wchar_t*)text.data() + text.size()) = 0; // ensure NULL termination auto filename = join(parts); auto path = std::filesystem::absolute(filename); - std::ofstream outfile(path.string(), std::ios::out | std::ios::trunc); - if (!outfile.good()) throw "Failed to create: " + path.string(); output(cmd_id, "saving", {{"ssml", is_ssml}, {"output_path", path.string()}}); - sx.save(cmd_id, text, is_ssml, std::move(buf), std::move(outfile)); + sx.save(cmd_id, text, is_ssml, std::move(buf), path); } // }}} From 46731246c6c1cbaaefcd2a836b4c9197cfb92e3b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 28 Jan 2023 13:33:53 +0530 Subject: [PATCH 0227/2055] ... --- src/calibre/utils/windows/winspeech.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 92e0ac2a3b..4df82ae9a6 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -682,12 +682,13 @@ save_stream(SpeechSynthesisStream const &&stream, std::filesystem::path path, id outfile.open(path.string(), std::ios::out | std::ios::trunc); ok = true; } CATCH_ALL_EXCEPTIONS("Failed to create file: " + path.string(), cmd_id); - while (ok && bytes_read < stream_size) { + if (!ok) co_return; + while (bytes_read < stream_size) { try { n = co_await reader.LoadAsync(chunk_size); ok = true; } CATCH_ALL_EXCEPTIONS("Failed to load data from DataReader", cmd_id); - if (!ok) break; + if (!ok) co_return; if (n > 0) { bytes_read += n; ok = false; @@ -697,7 +698,7 @@ save_stream(SpeechSynthesisStream const &&stream, std::filesystem::path path, id if (!outfile.good()) throw "Failed to write to output file"; ok = true; } CATCH_ALL_EXCEPTIONS("Failed to save bytes from DataReader to file", cmd_id); - if (!ok) break; + if (!ok) co_return; } } output(cmd_id, "saved", {{"size", bytes_read}}); From 32155c172ccbae2115951e8a52b4fa65388bbbc2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 28 Jan 2023 13:39:08 +0530 Subject: [PATCH 0228/2055] Allow interleaved waits in develop_loop --- src/calibre/utils/windows/winspeech.py | 32 ++++++++++++++------------ 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index bdd4e4eddf..086222606c 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -62,7 +62,7 @@ def encode_to_file_object(text, output) -> int: return sz -def develop_loop(wait_for, *commands): +def develop_loop(*commands): p = start_worker() q = Queue() @@ -84,18 +84,20 @@ def develop_loop(wait_for, *commands): send('1 echo Synthesizer started') send('1 volume 0.1') for command in commands: - send(command) - while True: - m = q.get() - if m['related_to'] != wait_for: - continue - if m['payload_type'] == 'media_state_changed' and m['state'] == 'ended': - break - if m['payload_type'] == 'saved': - break - if m['payload_type'] == 'error': - exit_code = 1 - break + if isinstance(command, str): + send(command) + else: + while True: + m = q.get() + if m['related_to'] != command: + continue + if m['payload_type'] == 'media_state_changed' and m['state'] == 'ended': + break + if m['payload_type'] == 'saved': + break + if m['payload_type'] == 'error': + exit_code = 1 + break send(f'333 echo Synthesizer exiting with exit code: {exit_code}') send(f'334 exit {exit_code}') ec = p.wait(1) @@ -122,7 +124,7 @@ def develop_speech(text='Lucca Brazzi sleeps with the fishes.', mark_words=True) with SharedMemory(size=max_buffer_size(text)) as shm: sz = encode_to_file_object(text, shm) - develop_loop(2, f'2 speak {st} shm {sz} {shm.name}') + develop_loop(f'2 speak {st} shm {sz} {shm.name}', 2) def develop_save(text='Lucca Brazzi sleeps with the fishes.', filename="speech.wav"): @@ -130,4 +132,4 @@ def develop_save(text='Lucca Brazzi sleeps with the fishes.', filename="speech.w st = 'ssml' if ' Date: Sat, 28 Jan 2023 13:52:15 +0530 Subject: [PATCH 0229/2055] Use std::array instead of C array --- src/calibre/utils/windows/winspeech.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 4df82ae9a6..68cd78a640 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -675,7 +675,7 @@ save_stream(SpeechSynthesisStream const &&stream, std::filesystem::path path, id DataReader reader(stream); unsigned int n; const static unsigned int chunk_size = 16 * 1024; - uint8_t buf[chunk_size]; + std::array buf; std::ofstream outfile; bool ok = false; try { @@ -693,8 +693,8 @@ save_stream(SpeechSynthesisStream const &&stream, std::filesystem::path path, id bytes_read += n; ok = false; try { - reader.ReadBytes(winrt::array_view(buf, buf + n)); - outfile.write((const char*)buf, n); + reader.ReadBytes(winrt::array_view(buf.data(), buf.data() + n)); + outfile.write((const char*)buf.data(), n); if (!outfile.good()) throw "Failed to write to output file"; ok = true; } CATCH_ALL_EXCEPTIONS("Failed to save bytes from DataReader to file", cmd_id); From 356af928ac9c2b383621e783d0f1ccdcc50ac5a8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 28 Jan 2023 14:07:46 +0530 Subject: [PATCH 0230/2055] Implement control for pitch --- src/calibre/utils/windows/winspeech.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 68cd78a640..38d08f2282 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -460,6 +460,16 @@ class Synthesizer { synth.Options().SpeakingRate(val); } + double pitch() const { + return synth.Options().AudioPitch(); + } + + void pitch(double val) { + if (val < 0 || val > 2) throw std::out_of_range("Invalid pitch value must be between 0 and 2"); + std::scoped_lock sl(recursive_lock); + synth.Options().AudioPitch(val); + } + }; @@ -811,6 +821,13 @@ handle_stdin_message(winrt::hstring const &&msg) { } output(cmd_id, "rate", {{"value", sx.rate()}}); } + else if (command == L"pitch") { + if (parts.size()) { + auto rate = parse_double(parts[0].data()); + sx.rate(rate); + } + output(cmd_id, "pitch", {{"pitch", sx.rate()}}); + } else if (command == L"save") { handle_save(cmd_id, parts); } From e006a23b70eabc2909cacabfa6792e7971b4ca68 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 29 Jan 2023 10:40:22 +0530 Subject: [PATCH 0231/2055] Controls for playback state --- src/calibre/utils/windows/winspeech.cpp | 31 +++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 38d08f2282..365ab38bd0 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -470,6 +470,25 @@ class Synthesizer { synth.Options().AudioPitch(val); } + void pause() const { + player.Pause(); + } + + void play() const { + player.Play(); + } + + bool toggle() const { + switch (player.PlaybackSession().PlaybackState()) { + case MediaPlaybackState::Playing: pause(); return true; + case MediaPlaybackState::Paused: play(); return true; + default: return false; + } + } + + MediaPlaybackState playback_state() const { + return player.PlaybackSession().PlaybackState(); + } }; @@ -795,6 +814,18 @@ handle_stdin_message(winrt::hstring const &&msg) { } catch(...) { } return 0; } + else if (command == L"play") { + sx.play(); + output(cmd_id, "play", {{"playback_state", sx.playback_state()}}); + } + else if (command == L"pause") { + sx.play(); + output(cmd_id, "pause", {{"playback_state", sx.playback_state()}}); + } + else if (command == L"state") { + sx.play(); + output(cmd_id, "state", {{"playback_state", sx.playback_state()}}); + } else if (command == L"echo") { output(cmd_id, "echo", {{"msg", join(parts)}}); } From 5eb1e106a87ae9584be4b7bb665051ca2d296119 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 29 Jan 2023 12:05:05 +0530 Subject: [PATCH 0232/2055] Command to output all audio devices --- src/calibre/utils/windows/winspeech.cpp | 108 +++++++++++++++++++----- 1 file changed, 85 insertions(+), 23 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 365ab38bd0..fca6815e6e 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -32,6 +32,8 @@ #include #include #include +#include +#include #ifdef max #undef max @@ -41,6 +43,8 @@ using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::Media::SpeechSynthesis; using namespace winrt::Windows::Media::Playback; using namespace winrt::Windows::Media::Core; +using namespace winrt::Windows::Media::Devices; +using namespace winrt::Windows::Devices::Enumeration; using namespace winrt::Windows::Storage::Streams; typedef uint64_t id_type; @@ -257,18 +261,58 @@ public: case VoiceGender::Female: gender = "female"; break; } object = { - {"display_name", json_val(voice.DisplayName())}, - {"description", json_val(voice.Description())}, - {"id", json_val(voice.Id())}, - {"language", json_val(voice.Language())}, - {"gender", json_val(gender)}, + {"display_name", voice.DisplayName()}, + {"description", voice.Description()}, + {"id", voice.Id()}, + {"language", voice.Language()}, + {"gender", gender}, }; } json_val(IVectorView const& voices) : type(DT_LIST) { list.reserve(voices.Size()); for(auto const& voice : voices) { - list.emplace_back(json_val(voice)); + list.emplace_back(voice); + } + } + + json_val(DeviceInformationKind const dev) : type(DT_STRING) { + switch(dev) { + case DeviceInformationKind::Unknown: + s = "unknown"; break; + case DeviceInformationKind::AssociationEndpoint: + s = "association_endpoint"; break; + case DeviceInformationKind::AssociationEndpointContainer: + s = "association_endpoint_container"; break; + case DeviceInformationKind::AssociationEndpointService: + s = "association_endpoint_service"; break; + case DeviceInformationKind::Device: + s = "device"; break; + case DeviceInformationKind::DevicePanel: + s = "device_panel"; break; + case DeviceInformationKind::DeviceInterface: + s = "device_interface"; break; + case DeviceInformationKind::DeviceInterfaceClass: + s = "device_interface_class"; break; + case DeviceInformationKind::DeviceContainer: + s = "device_container"; break; + } + } + + json_val(DeviceInformation const& dev) : type(DT_OBJECT) { + object = { + {"id", dev.Id()}, + {"name", dev.Name()}, + {"kind", dev.Kind()}, + {"is_default", dev.IsDefault()}, + {"is_enabled", dev.IsEnabled()}, + }; + } + + json_val(DeviceInformationCollection const& devices) : type(DT_LIST) { + list.reserve(devices.Size()); + for(auto const& dev : devices) { + list.emplace_back(json_val(dev)); } } @@ -309,26 +353,22 @@ public: }; } - template json_val(T x) { - static_assert(sizeof(bool) < sizeof(int16_t), "The bool type on this machine is more than one byte"); - if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { - type = DT_INT; - i = x; - } else if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { - type = DT_UINT; - u = x; - } else if constexpr (std::is_same_v || std::is_same_v) { - type = DT_FLOAT; - f = x; - } else if constexpr (std::is_same_v) { + template json_val(T const x) { + if constexpr (std::is_same_v) { type = DT_BOOL; b = x; + } else if constexpr (std::is_unsigned_v) { + type = DT_UINT; + u = x; + } else if constexpr (std::is_integral_v) { + type = DT_INT; + i = x; + } else if constexpr (std::is_floating_point_v) { + type = DT_FLOAT; + f = x; + } else { + static_assert(!sizeof(T), "Unknown type T cannot be converted to JSON"); } -#ifdef _MSVC - else { - static_assert(false, "Unknown type T cannot be converted to JSON"); - } -#endif } friend std::ostream& operator<<(std::ostream &os, const json_val &self) { @@ -383,6 +423,7 @@ struct Mark { uint32_t id, pos_in_text; Mark(uint32_t id, uint32_t pos) : id(id), pos_in_text(pos) {} }; + typedef std::vector Marks; class Synthesizer { @@ -490,6 +531,22 @@ class Synthesizer { return player.PlaybackSession().PlaybackState(); } + DeviceInformation audio_device() const { + return player.AudioDevice(); + } + + void audio_device(DeviceInformation const &di) const { + player.AudioDevice(di); + } + + VoiceInformation voice() const { + return synth.Voice(); + } + + void voice(VoiceInformation const &v) const { + return synth.Voice(v); + } + }; static Synthesizer sx; @@ -835,6 +892,11 @@ handle_stdin_message(winrt::hstring const &&msg) { else if (command == L"all_voices") { output(cmd_id, "all_voices", SpeechSynthesizer::AllVoices()); } + else if (command == L"all_audio_devices") { + try { + output(cmd_id, "all_audio_devices", DeviceInformation::FindAllAsync(MediaDevice::GetAudioRenderSelector()).get()); + } CATCH_ALL_EXCEPTIONS("Failed to list audio devices", cmd_id) { } + } else if (command == L"speak") { handle_speak(cmd_id, parts); } From 356091be76b07e0dac34f9735c7f94da834a3a6f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 29 Jan 2023 12:56:53 +0530 Subject: [PATCH 0233/2055] Use a handler dict to dispatch commands --- src/calibre/utils/windows/winspeech.cpp | 154 ++++++++++++++---------- 1 file changed, 91 insertions(+), 63 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index fca6815e6e..68169803b3 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -845,6 +845,83 @@ handle_save(id_type cmd_id, std::vector &parts) { } // }}} + +typedef std::function, int64_t*)> handler_function; + +static const std::unordered_map handlers = { + + {"exit", [](id_type cmd_id, std::vector parts, int64_t* exit_code) { + try { + *exit_code = parse_id(parts.at(0)); + } catch(...) { } + *exit_code = 0; + }}, + + {"echo", [](id_type cmd_id, std::vector parts, int64_t*) { + output(cmd_id, "echo", {{"msg", join(parts)}}); + }}, + + {"play", [](id_type cmd_id, std::vector parts, int64_t*) { + sx.play(); + output(cmd_id, "play", {{"playback_state", sx.playback_state()}}); + }}, + + {"pause", [](id_type cmd_id, std::vector parts, int64_t*) { + sx.play(); + output(cmd_id, "pause", {{"playback_state", sx.playback_state()}}); + }}, + + {"state", [](id_type cmd_id, std::vector parts, int64_t*) { + sx.play(); + output(cmd_id, "state", {{"playback_state", sx.playback_state()}}); + }}, + + {"default_voice", [](id_type cmd_id, std::vector parts, int64_t*) { + output(cmd_id, "default_voice", {{"voice", SpeechSynthesizer::DefaultVoice()}}); + }}, + + {"all_voices", [](id_type cmd_id, std::vector parts, int64_t*) { + output(cmd_id, "all_voices", {{"voices", SpeechSynthesizer::AllVoices()}}); + }}, + + {"all_audio_devices", [](id_type cmd_id, std::vector parts, int64_t*) { + output(cmd_id, "all_audio_devices", {{"devices", DeviceInformation::FindAllAsync(MediaDevice::GetAudioRenderSelector()).get()}}); + }}, + + {"speak", [](id_type cmd_id, std::vector parts, int64_t*) { + handle_speak(cmd_id, parts); + }}, + + {"volume", [](id_type cmd_id, std::vector parts, int64_t*) { + if (parts.size()) { + auto vol = parse_double(parts[0].data()); + sx.volume(vol); + } + output(cmd_id, "volume", {{"value", sx.volume()}}); + }}, + + {"rate", [](id_type cmd_id, std::vector parts, int64_t*) { + if (parts.size()) { + auto rate = parse_double(parts[0].data()); + sx.rate(rate); + } + output(cmd_id, "rate", {{"value", sx.rate()}}); + }}, + + {"pitch", [](id_type cmd_id, std::vector parts, int64_t*) { + if (parts.size()) { + auto rate = parse_double(parts[0].data()); + sx.rate(rate); + } + output(cmd_id, "pitch", {{"pitch", sx.rate()}}); + }}, + + {"save", [](id_type cmd_id, std::vector parts, int64_t*) { + handle_save(cmd_id, parts); + }}, +}; + + static int64_t handle_stdin_message(winrt::hstring const &&msg) { if (msg == L"exit") { @@ -854,6 +931,7 @@ handle_stdin_message(winrt::hstring const &&msg) { std::wstring_view command; bool ok = false; std::vector parts; + int64_t exit_code = -1; try { parts = split(msg); command = parts.at(1); cmd_id = parse_id(parts.at(0)); @@ -863,70 +941,20 @@ handle_stdin_message(winrt::hstring const &&msg) { parts.erase(parts.begin(), parts.begin() + 2); ok = true; } CATCH_ALL_EXCEPTIONS((std::string("Invalid input message: ") + winrt::to_string(msg)), 0); - if (!ok) return -1; - try { - if (command == L"exit") { - try { - return parse_id(parts.at(0)); - } catch(...) { } - return 0; + if (ok) { + handler_function handler; + std::string cmd(winrt::to_string(command)); + try { + handler = handlers.at(cmd.c_str()); + } catch (std::out_of_range) { + output_error(cmd_id, "Unknown command", cmd, __LINE__); + return exit_code; } - else if (command == L"play") { - sx.play(); - output(cmd_id, "play", {{"playback_state", sx.playback_state()}}); - } - else if (command == L"pause") { - sx.play(); - output(cmd_id, "pause", {{"playback_state", sx.playback_state()}}); - } - else if (command == L"state") { - sx.play(); - output(cmd_id, "state", {{"playback_state", sx.playback_state()}}); - } - else if (command == L"echo") { - output(cmd_id, "echo", {{"msg", join(parts)}}); - } - else if (command == L"default_voice") { - output(cmd_id, "default_voice", SpeechSynthesizer::DefaultVoice()); - } - else if (command == L"all_voices") { - output(cmd_id, "all_voices", SpeechSynthesizer::AllVoices()); - } - else if (command == L"all_audio_devices") { - try { - output(cmd_id, "all_audio_devices", DeviceInformation::FindAllAsync(MediaDevice::GetAudioRenderSelector()).get()); - } CATCH_ALL_EXCEPTIONS("Failed to list audio devices", cmd_id) { } - } - else if (command == L"speak") { - handle_speak(cmd_id, parts); - } - else if (command == L"volume") { - if (parts.size()) { - auto vol = parse_double(parts[0].data()); - sx.volume(vol); - } - output(cmd_id, "volume", {{"value", sx.volume()}}); - } - else if (command == L"rate") { - if (parts.size()) { - auto rate = parse_double(parts[0].data()); - sx.rate(rate); - } - output(cmd_id, "rate", {{"value", sx.rate()}}); - } - else if (command == L"pitch") { - if (parts.size()) { - auto rate = parse_double(parts[0].data()); - sx.rate(rate); - } - output(cmd_id, "pitch", {{"pitch", sx.rate()}}); - } - else if (command == L"save") { - handle_save(cmd_id, parts); - } - else throw std::string("Unknown command: ") + winrt::to_string(command); - } CATCH_ALL_EXCEPTIONS("Error handling input message", cmd_id); - return -1; + try { + handler(cmd_id, parts, &exit_code); + } CATCH_ALL_EXCEPTIONS("Error handling input message", cmd_id); + } + return exit_code; } From cf0cc595b65e32bd7bad55d79883dfd102c840c1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 29 Jan 2023 14:58:54 +0530 Subject: [PATCH 0234/2055] Refactor speech code to make it simpler and hopefully more robust Also get rid of the catch macro --- src/calibre/utils/windows/winspeech.cpp | 539 +++++++++--------------- 1 file changed, 188 insertions(+), 351 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 68169803b3..78f5f6639c 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -393,21 +393,29 @@ output_error(id_type cmd_id, std::string_view const &msg, std::string_view const output(cmd_id, "error", std::move(m)); } -#define CATCH_ALL_EXCEPTIONS(msg, cmd_id) \ - catch(winrt::hresult_error const& ex) { \ - output_error(cmd_id, msg, winrt::to_string(ex.message()), __LINE__, ex.to_abi()); \ -} catch(const std::system_error& ex) { \ - output_error(cmd_id, msg, "system_error with code: " + std::to_string(ex.code().value()) + " and meaning: " + ex.what(), __LINE__); \ -} catch (std::exception const &ex) { \ - output_error(cmd_id, msg, ex.what(), __LINE__); \ -} catch (std::string const &ex) { \ - output_error(cmd_id, msg, ex, __LINE__); \ -} catch (std::wstring const &ex) { \ - output_error(cmd_id, msg, winrt::to_string(ex), __LINE__); \ -} catch (...) { \ - output_error(cmd_id, msg, "Unknown exception type was raised", __LINE__); \ +static bool +run_catching_exceptions(std::function f, std::string_view const &msg, int64_t line, id_type cmd_id=0) { + bool ok = false; + try { + f(); + ok = true; + } catch(winrt::hresult_error const& ex) { + output_error(cmd_id, msg, winrt::to_string(ex.message()), line, ex.to_abi()); + } catch(const std::system_error& ex) { + output_error(cmd_id, msg, "system_error with code: " + std::to_string(ex.code().value()) + " and meaning: " + ex.what(), line); + } catch (std::exception const &ex) { + output_error(cmd_id, msg, ex.what(), line); + } catch (std::string const &ex) { + output_error(cmd_id, msg, ex, line); + } catch (std::wstring const &ex) { + output_error(cmd_id, msg, winrt::to_string(ex), line); + } catch (...) { + output_error(cmd_id, msg, "Unknown exception type was raised", line); + } + return ok; } + struct Revokers { MediaPlaybackSession::PlaybackStateChanged_revoker playback_state_changed; MediaPlayer::MediaEnded_revoker media_ended; MediaPlayer::MediaOpened_revoker media_opened; @@ -424,132 +432,14 @@ struct Mark { Mark(uint32_t id, uint32_t pos) : id(id), pos_in_text(pos) {} }; -typedef std::vector Marks; - -class Synthesizer { - private: - SpeechSynthesizer synth{nullptr}; - MediaPlayer player{nullptr}; - MediaSource current_source{nullptr}; - SpeechSynthesisStream current_stream{nullptr}; - MediaPlaybackItem current_item{nullptr}; - std::vector current_text_storage; - Marks current_marks; +struct Marks { + std::vector entries; int32_t last_reported_mark_index; - std::atomic current_cmd_id; - - Revokers revoker; - std::recursive_mutex recursive_lock; - - public: - // Speak {{{ - void register_metadata_handler_for_track(uint32_t index, id_type cmd_id); - void load_stream_for_playback(SpeechSynthesisStream const &&stream, id_type cmd_id, bool is_cued); - winrt::fire_and_forget speak(id_type cmd_id, std::wstring_view const &text, bool is_ssml, bool is_cued, std::vector &&buf, Marks const && marks); - void register_metadata_handler_for_speech(id_type cmd_id, long index); - bool cmd_id_is_current(id_type cmd_id) const noexcept { return current_cmd_id.load() == cmd_id; } - void on_cue_entered(id_type cmd_id, const winrt::hstring &label, const SpeechCue &cue); - // }}} - - winrt::fire_and_forget save(id_type cmd_id, std::wstring_view const &text, bool is_ssml, std::vector &&buf, std::filesystem::path path); - void start_save_stream(SpeechSynthesisStream const &&stream, std::filesystem::path path, id_type cmd_id); - - void initialize() { - synth = SpeechSynthesizer(); - player = MediaPlayer(); - player.AudioCategory(MediaPlayerAudioCategory::Speech); - player.AutoPlay(true); - } - - void output(id_type cmd_id, std::string_view const& type, json_val const && x) { - std::scoped_lock sl(recursive_lock); - if (cmd_id_is_current(cmd_id)) ::output(cmd_id, type, std::move(x)); - } - - void stop_current_activity() { - std::scoped_lock sl(recursive_lock); - if (current_cmd_id.load()) { - current_cmd_id.store(0); - revoker = {}; - current_source = MediaSource{nullptr}; - current_stream = SpeechSynthesisStream{nullptr}; - current_item = MediaPlaybackItem{nullptr}; - player.Pause(); - current_text_storage = std::vector(); - current_marks = Marks(); - last_reported_mark_index = -1; - } - } - - double volume() const { - return synth.Options().AudioVolume(); - } - - void volume(double val) { - if (val < 0 || val > 1) throw std::out_of_range("Invalid volume value must be between 0 and 1"); - std::scoped_lock sl(recursive_lock); - synth.Options().AudioVolume(val); - } - - double rate() const { - return synth.Options().SpeakingRate(); - } - - void rate(double val) { - if (val < 0.5 || val > 6.0) throw std::out_of_range("Invalid rate value must be between 0.5 and 6"); - std::scoped_lock sl(recursive_lock); - synth.Options().SpeakingRate(val); - } - - double pitch() const { - return synth.Options().AudioPitch(); - } - - void pitch(double val) { - if (val < 0 || val > 2) throw std::out_of_range("Invalid pitch value must be between 0 and 2"); - std::scoped_lock sl(recursive_lock); - synth.Options().AudioPitch(val); - } - - void pause() const { - player.Pause(); - } - - void play() const { - player.Play(); - } - - bool toggle() const { - switch (player.PlaybackSession().PlaybackState()) { - case MediaPlaybackState::Playing: pause(); return true; - case MediaPlaybackState::Paused: play(); return true; - default: return false; - } - } - - MediaPlaybackState playback_state() const { - return player.PlaybackSession().PlaybackState(); - } - - DeviceInformation audio_device() const { - return player.AudioDevice(); - } - - void audio_device(DeviceInformation const &di) const { - player.AudioDevice(di); - } - - VoiceInformation voice() const { - return synth.Voice(); - } - - void voice(VoiceInformation const &v) const { - return synth.Voice(v); - } - + Marks() : entries(), last_reported_mark_index(-1) {} }; -static Synthesizer sx; +static SpeechSynthesizer speech_synthesizer{nullptr}; +static MediaPlayer media_player{nullptr}; static size_t decode_into(std::string_view src, std::wstring_view dest) { @@ -575,7 +465,7 @@ parse_cued_text(std::string_view src, Marks &marks, std::wstring_view dest) { src = src.substr(1, src.size() - 1); if (src.size() >= 4) { uint32_t mark = *((uint32_t*)src.data()); - marks.emplace_back(mark, (uint32_t)dest_pos); + marks.entries.emplace_back(mark, (uint32_t)dest_pos); src = src.substr(4, src.size() - 4); } } @@ -605,124 +495,46 @@ read_from_shm(id_type cmd_id, const std::wstring_view size, const std::wstring & // Speak {{{ -void Synthesizer::on_cue_entered(id_type cmd_id, const winrt::hstring &label, const SpeechCue &cue) { - std::scoped_lock sl(recursive_lock); - if (!cmd_id_is_current(cmd_id)) return; - output(cmd_id, "cue_entered", json_val(label, cue)); - if (label != L"SpeechWord") return; - uint32_t pos = cue.StartPositionInInput().Value(); - for (int32_t i = std::max(0, last_reported_mark_index); i < (int32_t)current_marks.size(); i++) { - int32_t idx = -1; - if (current_marks[i].pos_in_text > pos) { - idx = i-1; - if (idx == last_reported_mark_index && current_marks[i].pos_in_text - pos < 3) idx = i; - } else if (current_marks[i].pos_in_text == pos) idx = i; - if (idx > -1) { - output(cmd_id, "mark_reached", {{"id", current_marks[idx].id}}); - last_reported_mark_index = idx; - break; - } - } -} +static Revokers speak_revoker = {}; -void Synthesizer::register_metadata_handler_for_speech(id_type cmd_id, long index) { - std::scoped_lock sl(recursive_lock); - if (!cmd_id_is_current(cmd_id)) return; - if (index < 0) { - for (uint32_t i = 0; i < current_item.TimedMetadataTracks().Size(); i++) { - register_metadata_handler_for_track(i, cmd_id); - } - } else { - register_metadata_handler_for_track(index, cmd_id); - } -} +static void +register_metadata_handler_for_track(MediaPlaybackTimedMetadataTrackList const &tracks, uint32_t index, id_type cmd_id, std::shared_ptr marks) { + TimedMetadataTrack track = tracks.GetAt(index); + tracks.SetPresentationMode((unsigned int)index, TimedMetadataTrackPresentationMode::ApplicationPresented); -void -Synthesizer::register_metadata_handler_for_track(uint32_t index, id_type cmd_id) { - TimedMetadataTrack track = current_item.TimedMetadataTracks().GetAt(index); - std::scoped_lock sl(recursive_lock); - if (current_cmd_id.load() != cmd_id) return; - revoker.cue_entered.push_back(track.CueEntered(winrt::auto_revoke, [cmd_id](auto track, const auto& args) { - if (main_loop_is_running.load()) sx.on_cue_entered(cmd_id, track.Label(), args.Cue().template as()); + speak_revoker.cue_entered.push_back(track.CueEntered(winrt::auto_revoke, [cmd_id, marks](auto track, const auto& args) { + if (main_loop_is_running.load()) { + auto label = track.Label(); + auto cue = args.Cue().template as(); + output(cmd_id, "cue_entered", {label, cue}); + if (label != L"SpeechWord") return; + uint32_t pos = cue.StartPositionInInput().Value(); + for (int32_t i = std::max(0, marks->last_reported_mark_index); i < (int32_t)marks->entries.size(); i++) { + int32_t idx = -1; + if (marks->entries[i].pos_in_text > pos) { + idx = i-1; + if (idx == marks->last_reported_mark_index && marks->entries[i].pos_in_text - pos < 3) idx = i; + } else if (marks->entries[i].pos_in_text == pos) idx = i; + if (idx > -1) { + output(cmd_id, "mark_reached", {{"id", marks->entries[idx].id}}); + marks->last_reported_mark_index = idx; + break; + } + } + } })); - revoker.cue_exited.push_back(track.CueExited(winrt::auto_revoke, [cmd_id](auto track, const auto& args) { - if (main_loop_is_running.load()) sx.output( + + speak_revoker.cue_exited.push_back(track.CueExited(winrt::auto_revoke, [cmd_id](auto track, const auto& args) { + if (main_loop_is_running.load()) output( cmd_id, "cue_exited", json_val(track.Label(), args.Cue().template as())); })); - revoker.track_failed.push_back(track.TrackFailed(winrt::auto_revoke, [cmd_id](auto, const auto& args) { - if (main_loop_is_running.load()) sx.output( + + speak_revoker.track_failed.push_back(track.TrackFailed(winrt::auto_revoke, [cmd_id](auto, const auto& args) { + if (main_loop_is_running.load()) output( cmd_id, "track_failed", {}); })); - current_item.TimedMetadataTracks().SetPresentationMode((unsigned int)index, TimedMetadataTrackPresentationMode::ApplicationPresented); -} +}; -void -Synthesizer::load_stream_for_playback(SpeechSynthesisStream const &&stream, id_type cmd_id, bool is_cued) { - std::scoped_lock sl(recursive_lock); - if (cmd_id != current_cmd_id.load()) return; - current_stream = stream; - current_source = MediaSource::CreateFromStream(current_stream, current_stream.ContentType()); - - revoker.playback_state_changed = player.PlaybackSession().PlaybackStateChanged( - winrt::auto_revoke, [cmd_id](auto session, auto const&) { - if (main_loop_is_running.load()) sx.output( - cmd_id, "playback_state_changed", {{"state", session.PlaybackState()}}); - }); - revoker.media_opened = player.MediaOpened(winrt::auto_revoke, [cmd_id](auto player, auto const&) { - if (main_loop_is_running.load()) sx.output( - cmd_id, "media_state_changed", {{"state", "opened"}}); - }); - revoker.media_ended = player.MediaEnded(winrt::auto_revoke, [cmd_id](auto player, auto const&) { - if (main_loop_is_running.load()) sx.output( - cmd_id, "media_state_changed", {{"state", "ended"}}); - }); - revoker.media_failed = player.MediaFailed(winrt::auto_revoke, [cmd_id](auto player, auto const& args) { - if (main_loop_is_running.load()) sx.output( - cmd_id, "media_state_changed", {{"state", "failed"}, {"error", args.ErrorMessage()}, {"code", args.Error()}}); - }); - current_item = MediaPlaybackItem(current_source); - - revoker.timed_metadata_tracks_changed = current_item.TimedMetadataTracksChanged(winrt::auto_revoke, - [cmd_id](auto, auto const &args) { - auto change_type = args.CollectionChange(); - long index; - switch (change_type) { - case CollectionChange::ItemInserted: index = args.Index(); break; - case CollectionChange::Reset: index = -1; break; - default: index = -2; break; - } - if (index > -2 && main_loop_is_running.load()) sx.register_metadata_handler_for_speech(cmd_id, index); - }); - register_metadata_handler_for_speech(cmd_id, -1); - - player.Source(current_item); -} - -winrt::fire_and_forget Synthesizer::speak(id_type cmd_id, std::wstring_view const &text, bool is_ssml, bool is_cued, std::vector &&buf, Marks const && marks) { - SpeechSynthesisStream stream{nullptr}; - { std::scoped_lock sl(recursive_lock); - stop_current_activity(); - current_cmd_id.store(cmd_id); - current_text_storage = std::move(buf); - current_marks = std::move(marks); - synth.Options().IncludeSentenceBoundaryMetadata(true); - synth.Options().IncludeWordBoundaryMetadata(true); - } - output(cmd_id, "synthesizing", {{"ssml", is_ssml}, {"num_marks", current_marks.size()}, {"text_length", text.size()}}); - bool ok = false; - try { - if (is_ssml) stream = co_await synth.SynthesizeSsmlToStreamAsync(text); - else stream = co_await synth.SynthesizeTextToStreamAsync(text); - ok = true; - } CATCH_ALL_EXCEPTIONS("Failed to synthesize speech", cmd_id); - if (ok) { - if (main_loop_is_running.load()) { - try { - load_stream_for_playback(std::move(stream), cmd_id, is_cued); - } CATCH_ALL_EXCEPTIONS("Failed to load synthesized stream for playback", cmd_id); - } - } -} static void handle_speak(id_type cmd_id, std::vector &parts) { @@ -736,11 +548,11 @@ handle_speak(id_type cmd_id, std::vector &parts) { } parts.erase(parts.begin(), parts.begin() + 2); std::wstring address; - Marks marks; + auto marks = std::make_shared(); std::vector buf; std::wstring_view text; if (is_shm) { - text = read_from_shm(cmd_id, parts.at(0), std::wstring(parts.at(1)), buf, marks, is_cued); + text = read_from_shm(cmd_id, parts.at(0), std::wstring(parts.at(1)), buf, *marks, is_cued); if (text.size() == 0) return; } else { address = join(parts); @@ -750,12 +562,62 @@ handle_speak(id_type cmd_id, std::vector &parts) { address.copy(buf.data(), address.size()); } *((wchar_t*)text.data() + text.size()) = 0; // ensure NULL termination - sx.speak(cmd_id, text, is_ssml, is_cued, std::move(buf), std::move(marks)); + + output(cmd_id, "synthesizing", {{"ssml", is_ssml}, {"num_marks", marks->entries.size()}, {"text_length", text.size()}}); + bool ok = false; + SpeechSynthesisStream stream{nullptr}; + if (!run_catching_exceptions([&]() { + speech_synthesizer.Options().IncludeSentenceBoundaryMetadata(true); + speech_synthesizer.Options().IncludeWordBoundaryMetadata(true); + if (is_ssml) stream = speech_synthesizer.SynthesizeSsmlToStreamAsync(text).get(); + else stream = speech_synthesizer.SynthesizeTextToStreamAsync(text).get(); + ok = true; + }, "Failed to synthesize speech", __LINE__, cmd_id)) return; + + speak_revoker = {}; // delete any revokers previously installed + MediaSource source(MediaSource::CreateFromStream(stream, stream.ContentType())); + + speak_revoker.playback_state_changed = media_player.PlaybackSession().PlaybackStateChanged( + winrt::auto_revoke, [cmd_id](auto session, auto const&) { + if (main_loop_is_running.load()) output( + cmd_id, "playback_state_changed", {{"state", session.PlaybackState()}}); + }); + speak_revoker.media_opened = media_player.MediaOpened(winrt::auto_revoke, [cmd_id](auto player, auto const&) { + if (main_loop_is_running.load()) output( + cmd_id, "media_state_changed", {{"state", "opened"}}); + }); + speak_revoker.media_ended = media_player.MediaEnded(winrt::auto_revoke, [cmd_id](auto player, auto const&) { + if (main_loop_is_running.load()) output( + cmd_id, "media_state_changed", {{"state", "ended"}}); + }); + speak_revoker.media_failed = media_player.MediaFailed(winrt::auto_revoke, [cmd_id](auto player, auto const& args) { + if (main_loop_is_running.load()) output( + cmd_id, "media_state_changed", {{"state", "failed"}, {"error", args.ErrorMessage()}, {"code", args.Error()}}); + }); + auto playback_item = std::make_shared(source); + + speak_revoker.timed_metadata_tracks_changed = playback_item->TimedMetadataTracksChanged(winrt::auto_revoke, + [cmd_id, playback_item_weak_ref = std::weak_ptr(playback_item), marks](auto, auto const &args) { + auto change_type = args.CollectionChange(); + long index; + switch (change_type) { + case CollectionChange::ItemInserted: index = args.Index(); break; + case CollectionChange::Reset: index = -1; break; + default: index = -2; break; + } + auto pi{ playback_item_weak_ref.lock() }; + if (index > -2 && pi && main_loop_is_running.load()) register_metadata_handler_for_track(pi->TimedMetadataTracks(), index, cmd_id, marks); + }); + + for (uint32_t i = 0; i < playback_item->TimedMetadataTracks().Size(); i++) { + register_metadata_handler_for_track(playback_item->TimedMetadataTracks(), i, cmd_id, marks); + } + media_player.Source(*playback_item); } // }}} // Save {{{ -static winrt::fire_and_forget +static void save_stream(SpeechSynthesisStream const &&stream, std::filesystem::path path, id_type cmd_id) { unsigned long long stream_size = stream.Size(), bytes_read = 0; DataReader reader(stream); @@ -763,66 +625,26 @@ save_stream(SpeechSynthesisStream const &&stream, std::filesystem::path path, id const static unsigned int chunk_size = 16 * 1024; std::array buf; std::ofstream outfile; - bool ok = false; - try { + if (!run_catching_exceptions([&](){ outfile.open(path.string(), std::ios::out | std::ios::trunc); - ok = true; - } CATCH_ALL_EXCEPTIONS("Failed to create file: " + path.string(), cmd_id); - if (!ok) co_return; + }, "Failed to create file: " + path.string(), __LINE__, cmd_id)) return; + while (bytes_read < stream_size) { - try { - n = co_await reader.LoadAsync(chunk_size); - ok = true; - } CATCH_ALL_EXCEPTIONS("Failed to load data from DataReader", cmd_id); - if (!ok) co_return; + if (!run_catching_exceptions([&]() { + n = reader.LoadAsync(chunk_size).get(); + }, "Failed to load data from DataReader", __LINE__, cmd_id)) return; if (n > 0) { bytes_read += n; - ok = false; - try { + if (!run_catching_exceptions([&]() { reader.ReadBytes(winrt::array_view(buf.data(), buf.data() + n)); outfile.write((const char*)buf.data(), n); if (!outfile.good()) throw "Failed to write to output file"; - ok = true; - } CATCH_ALL_EXCEPTIONS("Failed to save bytes from DataReader to file", cmd_id); - if (!ok) co_return; + }, "Failed to save bytes from DataReader to file", __LINE__, cmd_id)) return; } } output(cmd_id, "saved", {{"size", bytes_read}}); } -void -Synthesizer::start_save_stream(SpeechSynthesisStream const &&stream, std::filesystem::path path, id_type cmd_id) { - std::scoped_lock sl(recursive_lock); - try { - save_stream(std::move(stream), path, cmd_id); - } CATCH_ALL_EXCEPTIONS("Failed to save loaded stream", cmd_id); - stop_current_activity(); -} - -winrt::fire_and_forget Synthesizer::save(id_type cmd_id, std::wstring_view const &text, bool is_ssml, std::vector &&buf, std::filesystem::path path) { - SpeechSynthesisStream stream{nullptr}; - { std::scoped_lock sl(recursive_lock); - stop_current_activity(); - current_cmd_id.store(cmd_id); - current_text_storage = std::move(buf); - synth.Options().IncludeSentenceBoundaryMetadata(false); - synth.Options().IncludeWordBoundaryMetadata(false); - } - bool ok = false; - try { - if (is_ssml) stream = co_await synth.SynthesizeSsmlToStreamAsync(text); - else stream = co_await synth.SynthesizeTextToStreamAsync(text); - ok = true; - } CATCH_ALL_EXCEPTIONS("Failed to synthesize speech", cmd_id); - if (ok) { - if (main_loop_is_running.load()) { - try { - sx.start_save_stream(std::move(stream), path, cmd_id); - } CATCH_ALL_EXCEPTIONS("Failed to load synthesized stream for save", cmd_id); - } - } -} - static void handle_save(id_type cmd_id, std::vector &parts) { bool is_ssml; @@ -841,7 +663,14 @@ handle_save(id_type cmd_id, std::vector &parts) { auto filename = join(parts); auto path = std::filesystem::absolute(filename); output(cmd_id, "saving", {{"ssml", is_ssml}, {"output_path", path.string()}}); - sx.save(cmd_id, text, is_ssml, std::move(buf), path); + SpeechSynthesisStream stream{nullptr}; + speech_synthesizer.Options().IncludeSentenceBoundaryMetadata(false); + speech_synthesizer.Options().IncludeWordBoundaryMetadata(false); + if (!run_catching_exceptions([&]() { + if (is_ssml) stream = speech_synthesizer.SynthesizeSsmlToStreamAsync(text).get(); + else stream = speech_synthesizer.SynthesizeTextToStreamAsync(text).get(); + }, "Failed to synthesize speech", __LINE__, cmd_id)) return; + save_stream(std::move(stream), path, cmd_id); } // }}} @@ -862,18 +691,17 @@ static const std::unordered_map handlers = { }}, {"play", [](id_type cmd_id, std::vector parts, int64_t*) { - sx.play(); - output(cmd_id, "play", {{"playback_state", sx.playback_state()}}); + media_player.Play(); + output(cmd_id, "play", {{"playback_state", media_player.PlaybackSession().PlaybackState()}}); }}, {"pause", [](id_type cmd_id, std::vector parts, int64_t*) { - sx.play(); - output(cmd_id, "pause", {{"playback_state", sx.playback_state()}}); + media_player.Pause(); + output(cmd_id, "pause", {{"playback_state", media_player.PlaybackSession().PlaybackState()}}); }}, {"state", [](id_type cmd_id, std::vector parts, int64_t*) { - sx.play(); - output(cmd_id, "state", {{"playback_state", sx.playback_state()}}); + output(cmd_id, "state", {{"playback_state", media_player.PlaybackSession().PlaybackState()}}); }}, {"default_voice", [](id_type cmd_id, std::vector parts, int64_t*) { @@ -895,25 +723,28 @@ static const std::unordered_map handlers = { {"volume", [](id_type cmd_id, std::vector parts, int64_t*) { if (parts.size()) { auto vol = parse_double(parts[0].data()); - sx.volume(vol); + if (vol < 0 || vol > 1) throw std::out_of_range("Invalid volume value must be between 0 and 1"); + speech_synthesizer.Options().AudioVolume(vol); } - output(cmd_id, "volume", {{"value", sx.volume()}}); + output(cmd_id, "volume", {{"value", speech_synthesizer.Options().AudioVolume()}}); }}, {"rate", [](id_type cmd_id, std::vector parts, int64_t*) { if (parts.size()) { auto rate = parse_double(parts[0].data()); - sx.rate(rate); + if (rate < 0.5 || rate > 6.0) throw std::out_of_range("Invalid rate value must be between 0.5 and 6"); + speech_synthesizer.Options().SpeakingRate(rate); } - output(cmd_id, "rate", {{"value", sx.rate()}}); + output(cmd_id, "rate", {{"value", speech_synthesizer.Options().SpeakingRate()}}); }}, {"pitch", [](id_type cmd_id, std::vector parts, int64_t*) { if (parts.size()) { - auto rate = parse_double(parts[0].data()); - sx.rate(rate); + auto pitch = parse_double(parts[0].data()); + if (pitch < 0 || pitch > 2) throw std::out_of_range("Invalid pitch value must be between 0 and 2"); + speech_synthesizer.Options().AudioPitch(pitch); } - output(cmd_id, "pitch", {{"pitch", sx.rate()}}); + output(cmd_id, "pitch", {{"pitch", speech_synthesizer.Options().AudioPitch()}}); }}, {"save", [](id_type cmd_id, std::vector parts, int64_t*) { @@ -932,7 +763,7 @@ handle_stdin_message(winrt::hstring const &&msg) { bool ok = false; std::vector parts; int64_t exit_code = -1; - try { + if (!run_catching_exceptions([&]() { parts = split(msg); command = parts.at(1); cmd_id = parse_id(parts.at(0)); if (cmd_id == 0) { @@ -940,56 +771,60 @@ handle_stdin_message(winrt::hstring const &&msg) { } parts.erase(parts.begin(), parts.begin() + 2); ok = true; - } CATCH_ALL_EXCEPTIONS((std::string("Invalid input message: ") + winrt::to_string(msg)), 0); - if (ok) { - handler_function handler; - std::string cmd(winrt::to_string(command)); - try { - handler = handlers.at(cmd.c_str()); - } catch (std::out_of_range) { - output_error(cmd_id, "Unknown command", cmd, __LINE__); - return exit_code; - } - try { - handler(cmd_id, parts, &exit_code); - } CATCH_ALL_EXCEPTIONS("Error handling input message", cmd_id); + }, "Invalid input message: " + winrt::to_string(msg), __LINE__)) return exit_code; + handler_function handler; + std::string cmd(winrt::to_string(command)); + try { + handler = handlers.at(cmd.c_str()); + } catch (std::out_of_range) { + output_error(cmd_id, "Unknown command", cmd, __LINE__); + return exit_code; } + run_catching_exceptions([&]() { + handler(cmd_id, parts, &exit_code); + }, "Error handling input message", __LINE__, cmd_id); return exit_code; } - static PyObject* run_main_loop(PyObject*, PyObject*) { - try { + if (!run_catching_exceptions([]() { std::cout.imbue(std::locale("C")); std::cin.imbue(std::locale("C")); std::cerr.imbue(std::locale("C")); std::wcin.imbue(std::locale("C")); std::wcout.imbue(std::locale("C")); std::wcerr.imbue(std::locale("C")); - } CATCH_ALL_EXCEPTIONS("Failed to set stdio locales to C", 0); - winrt::init_apartment(winrt::apartment_type::multi_threaded); - main_thread_id = GetCurrentThreadId(); - MSG msg; - int64_t exit_code = 0; - bool ok = false; - try { - new (&sx) Synthesizer(); - sx.initialize(); - ok = true; - } CATCH_ALL_EXCEPTIONS("Error initializing Synthesizer", 0); - if (!ok) return PyLong_FromUnsignedLongLong(1); + }, "Failed to set stdio locales to C", __LINE__)) { + return PyLong_FromLongLong(1); + } - Py_BEGIN_ALLOW_THREADS; - main_loop_is_running.store(true); - PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); // ensure we have a message queue + if (!run_catching_exceptions([]() { + winrt::init_apartment(winrt::apartment_type::multi_threaded); + }, "Failed to initialize COM", __LINE__)) { + return PyLong_FromLongLong(1); + } + + main_thread_id = GetCurrentThreadId(); + + if (!run_catching_exceptions([]() { + speech_synthesizer = SpeechSynthesizer(); + media_player = MediaPlayer(); + media_player.AudioCategory(MediaPlayerAudioCategory::Speech); + media_player.AutoPlay(true); + }, "Failed to initialize SpeechSynthesizer and MediaPlayer", __LINE__)) { + return PyLong_FromLongLong(1); + } if (_isatty(_fileno(stdin))) { std::cout << "Welcome to winspeech. Type exit to quit." << std::endl; } + int64_t exit_code = -1; + main_loop_is_running.store(true); + Py_BEGIN_ALLOW_THREADS; std::string input_buffer; - while (true) { + while (exit_code < 0) { try { if (!std::getline(std::cin, input_buffer)) { if (!std::cin.eof()) exit_code = 1; @@ -997,7 +832,10 @@ run_main_loop(PyObject*, PyObject*) { } rtrim(input_buffer); if (input_buffer.size() > 0) { - if ((exit_code = handle_stdin_message(std::move(winrt::to_hstring(input_buffer)))) >= 0) break; + run_catching_exceptions([&]() { + exit_code = handle_stdin_message(std::move(winrt::to_hstring(input_buffer))); + }, "Error handling STDIN message", __LINE__); + if (exit_code >= 0) break; } } catch(...) { exit_code = 1; @@ -1005,14 +843,13 @@ run_main_loop(PyObject*, PyObject*) { break; } } - - main_loop_is_running.store(false); Py_END_ALLOW_THREADS; + main_loop_is_running.store(false); try { - sx.stop_current_activity(); - (&sx)->~Synthesizer(); - } CATCH_ALL_EXCEPTIONS("Error stopping all activity", 0); + speech_synthesizer = SpeechSynthesizer{nullptr}; + media_player = MediaPlayer{nullptr}; + } catch(...) {} return PyLong_FromLongLong(exit_code); } From 20d3b0798de3aac0b27a99f525915c1fdfb6c6ed Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 29 Jan 2023 15:04:05 +0530 Subject: [PATCH 0235/2055] Remove redundant includes --- src/calibre/utils/windows/winspeech.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 78f5f6639c..31561a9e25 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -6,19 +6,10 @@ */ #include "common.h" -#include #include #include -#include -#include #include -#include -#include -#include -#include -#include #include -#include #include #include #include From e8f07e273b54234d2a30c188b70864a77365c72c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 29 Jan 2023 15:08:43 +0530 Subject: [PATCH 0236/2055] Revoke on exit --- src/calibre/utils/windows/winspeech.cpp | 40 ++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 31561a9e25..063349fd77 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -493,34 +493,33 @@ register_metadata_handler_for_track(MediaPlaybackTimedMetadataTrackList const &t TimedMetadataTrack track = tracks.GetAt(index); tracks.SetPresentationMode((unsigned int)index, TimedMetadataTrackPresentationMode::ApplicationPresented); - speak_revoker.cue_entered.push_back(track.CueEntered(winrt::auto_revoke, [cmd_id, marks](auto track, const auto& args) { - if (main_loop_is_running.load()) { - auto label = track.Label(); - auto cue = args.Cue().template as(); - output(cmd_id, "cue_entered", {label, cue}); - if (label != L"SpeechWord") return; - uint32_t pos = cue.StartPositionInInput().Value(); - for (int32_t i = std::max(0, marks->last_reported_mark_index); i < (int32_t)marks->entries.size(); i++) { - int32_t idx = -1; - if (marks->entries[i].pos_in_text > pos) { - idx = i-1; - if (idx == marks->last_reported_mark_index && marks->entries[i].pos_in_text - pos < 3) idx = i; - } else if (marks->entries[i].pos_in_text == pos) idx = i; - if (idx > -1) { - output(cmd_id, "mark_reached", {{"id", marks->entries[idx].id}}); - marks->last_reported_mark_index = idx; - break; - } + speak_revoker.cue_entered.emplace_back(track.CueEntered(winrt::auto_revoke, [cmd_id, marks](auto track, const auto& args) { + if (!main_loop_is_running.load()) return; + auto label = track.Label(); + auto cue = args.Cue().template as(); + output(cmd_id, "cue_entered", {label, cue}); + if (label != L"SpeechWord") return; + uint32_t pos = cue.StartPositionInInput().Value(); + for (int32_t i = std::max(0, marks->last_reported_mark_index); i < (int32_t)marks->entries.size(); i++) { + int32_t idx = -1; + if (marks->entries[i].pos_in_text > pos) { + idx = i-1; + if (idx == marks->last_reported_mark_index && marks->entries[i].pos_in_text - pos < 3) idx = i; + } else if (marks->entries[i].pos_in_text == pos) idx = i; + if (idx > -1) { + output(cmd_id, "mark_reached", {{"id", marks->entries[idx].id}}); + marks->last_reported_mark_index = idx; + break; } } })); - speak_revoker.cue_exited.push_back(track.CueExited(winrt::auto_revoke, [cmd_id](auto track, const auto& args) { + speak_revoker.cue_exited.emplace_back(track.CueExited(winrt::auto_revoke, [cmd_id](auto track, const auto& args) { if (main_loop_is_running.load()) output( cmd_id, "cue_exited", json_val(track.Label(), args.Cue().template as())); })); - speak_revoker.track_failed.push_back(track.TrackFailed(winrt::auto_revoke, [cmd_id](auto, const auto& args) { + speak_revoker.track_failed.emplace_back(track.TrackFailed(winrt::auto_revoke, [cmd_id](auto, const auto& args) { if (main_loop_is_running.load()) output( cmd_id, "track_failed", {}); })); @@ -838,6 +837,7 @@ run_main_loop(PyObject*, PyObject*) { main_loop_is_running.store(false); try { + speak_revoker = {}; speech_synthesizer = SpeechSynthesizer{nullptr}; media_player = MediaPlayer{nullptr}; } catch(...) {} From 28b49c7e3ee0f5f48b6d898eccb10ec06d23a52e Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 29 Jan 2023 09:47:36 +0000 Subject: [PATCH 0237/2055] Enhancement #2004083: wireless device connection waiting time is too short Removed the timer. --- src/calibre/devices/smart_device_app/driver.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/calibre/devices/smart_device_app/driver.py b/src/calibre/devices/smart_device_app/driver.py index 26ff658aa8..87608f305b 100644 --- a/src/calibre/devices/smart_device_app/driver.py +++ b/src/calibre/devices/smart_device_app/driver.py @@ -232,7 +232,6 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): # Some network protocol constants BASE_PACKET_LEN = 4096 PROTOCOL_VERSION = 1 - MAX_CLIENT_COMM_TIMEOUT = 300.0 # Wait at most N seconds for an answer MAX_UNSUCCESSFUL_CONNECTS = 5 SEND_NOOP_EVERY_NTH_PROBE = 5 @@ -575,9 +574,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): def _read_binary_from_net(self, length): try: - self.device_socket.settimeout(self.MAX_CLIENT_COMM_TIMEOUT) v = self.device_socket.recv(length) - self.device_socket.settimeout(None) return v except: self._close_device_socket() @@ -615,12 +612,10 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin): total_len = len(s) while sent_len < total_len: try: - sock.settimeout(self.MAX_CLIENT_COMM_TIMEOUT) if sent_len == 0: amt_sent = sock.send(s) else: amt_sent = sock.send(s[sent_len:]) - sock.settimeout(None) if amt_sent <= 0: raise OSError('Bad write on socket') sent_len += amt_sent From ad9cf1b0f60a0b8b03e2ef8f7c1b12fbb6c2758b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 29 Jan 2023 15:23:23 +0530 Subject: [PATCH 0238/2055] Code to set the voice and audio device --- src/calibre/utils/windows/winspeech.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 063349fd77..df5a688e7c 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -554,14 +554,12 @@ handle_speak(id_type cmd_id, std::vector &parts) { *((wchar_t*)text.data() + text.size()) = 0; // ensure NULL termination output(cmd_id, "synthesizing", {{"ssml", is_ssml}, {"num_marks", marks->entries.size()}, {"text_length", text.size()}}); - bool ok = false; SpeechSynthesisStream stream{nullptr}; if (!run_catching_exceptions([&]() { speech_synthesizer.Options().IncludeSentenceBoundaryMetadata(true); speech_synthesizer.Options().IncludeWordBoundaryMetadata(true); if (is_ssml) stream = speech_synthesizer.SynthesizeSsmlToStreamAsync(text).get(); else stream = speech_synthesizer.SynthesizeTextToStreamAsync(text).get(); - ok = true; }, "Failed to synthesize speech", __LINE__, cmd_id)) return; speak_revoker = {}; // delete any revokers previously installed @@ -710,6 +708,29 @@ static const std::unordered_map handlers = { handle_speak(cmd_id, parts); }}, + {"audio_device", [](id_type cmd_id, std::vector parts, int64_t*) { + if (parts.size()) { + auto di = DeviceInformation::CreateFromIdAsync(parts[0]).get(); + media_player.AudioDevice(di); + } + output(cmd_id, "audio_device", {{"value", media_player.AudioDevice()}}); + }}, + + {"voice", [](id_type cmd_id, std::vector parts, int64_t*) { + bool found = false; + if (parts.size()) { + auto voice_id = winrt::hstring(parts[0]); + for (auto const &candidate : SpeechSynthesizer::AllVoices()) { + if (candidate.Id() == voice_id) { + speech_synthesizer.Voice(candidate); + found = true; + break; + } + } + } + output(cmd_id, "voice", {{"value", speech_synthesizer.Voice()}, {"found", found}}); + }}, + {"volume", [](id_type cmd_id, std::vector parts, int64_t*) { if (parts.size()) { auto vol = parse_double(parts[0].data()); From 8939c813998ade4f6d1b6db99c860d3bc4edf51a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 29 Jan 2023 19:25:28 +0530 Subject: [PATCH 0239/2055] Implement audio device control --- src/calibre/utils/windows/winspeech.cpp | 44 ++++++++++++++++++++----- src/calibre/utils/windows/winspeech.py | 20 +++++++++++ 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index df5a688e7c..f5333169f1 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -665,6 +665,19 @@ handle_save(id_type cmd_id, std::vector &parts) { typedef std::function, int64_t*)> handler_function; +static DeviceInformationKind +get_device_kind(const std::wstring x) { + if (x == L"device") return DeviceInformationKind::Device; + if (x == L"association_endpoint") return DeviceInformationKind::AssociationEndpoint; + if (x == L"association_endpoint_container") return DeviceInformationKind::AssociationEndpointContainer; + if (x == L"association_endpoint_service") return DeviceInformationKind::AssociationEndpointService; + if (x == L"device_container") return DeviceInformationKind::DeviceContainer; + if (x == L"device_interface") return DeviceInformationKind::DeviceInterface; + if (x == L"device_interface_class") return DeviceInformationKind::DeviceInterfaceClass; + if (x == L"device_panel") return DeviceInformationKind::DevicePanel; + return DeviceInformationKind::Unknown; +} + static const std::unordered_map handlers = { {"exit", [](id_type cmd_id, std::vector parts, int64_t* exit_code) { @@ -689,7 +702,9 @@ static const std::unordered_map handlers = { }}, {"state", [](id_type cmd_id, std::vector parts, int64_t*) { - output(cmd_id, "state", {{"playback_state", media_player.PlaybackSession().PlaybackState()}}); + auto ps = media_player.PlaybackSession(); + if (ps) output(cmd_id, "state", {{"playback_state", ps.PlaybackState()}}); + else output(cmd_id, "state", {{"playback_state", ""}}); }}, {"default_voice", [](id_type cmd_id, std::vector parts, int64_t*) { @@ -709,17 +724,26 @@ static const std::unordered_map handlers = { }}, {"audio_device", [](id_type cmd_id, std::vector parts, int64_t*) { + bool found = false; if (parts.size()) { - auto di = DeviceInformation::CreateFromIdAsync(parts[0]).get(); - media_player.AudioDevice(di); + auto device_kind = std::wstring(parts.at(0)); + parts.erase(parts.begin(), parts.begin() + 1); + auto device_id = join(parts); + auto di = DeviceInformation::CreateFromIdAsync(device_id, {}, get_device_kind(device_kind)).get(); + if (di) { + media_player.AudioDevice(di); + found = true; + } } - output(cmd_id, "audio_device", {{"value", media_player.AudioDevice()}}); + auto x = media_player.AudioDevice(); + if (x) output(cmd_id, "audio_device", {{"value", x}, {"found", found}}); + else output(cmd_id, "audio_device", {{"value", ""}, {"found", found}}); }}, {"voice", [](id_type cmd_id, std::vector parts, int64_t*) { bool found = false; if (parts.size()) { - auto voice_id = winrt::hstring(parts[0]); + auto voice_id = winrt::hstring(parts.at(0)); for (auto const &candidate : SpeechSynthesizer::AllVoices()) { if (candidate.Id() == voice_id) { speech_synthesizer.Voice(candidate); @@ -728,12 +752,14 @@ static const std::unordered_map handlers = { } } } - output(cmd_id, "voice", {{"value", speech_synthesizer.Voice()}, {"found", found}}); + auto x = speech_synthesizer.Voice(); + if (x) output(cmd_id, "voice", {{"value", speech_synthesizer.Voice()}, {"found", found}}); + else output(cmd_id, "voice", {{"value", ""}, {"found", found}}); }}, {"volume", [](id_type cmd_id, std::vector parts, int64_t*) { if (parts.size()) { - auto vol = parse_double(parts[0].data()); + auto vol = parse_double(parts.at(0).data()); if (vol < 0 || vol > 1) throw std::out_of_range("Invalid volume value must be between 0 and 1"); speech_synthesizer.Options().AudioVolume(vol); } @@ -742,7 +768,7 @@ static const std::unordered_map handlers = { {"rate", [](id_type cmd_id, std::vector parts, int64_t*) { if (parts.size()) { - auto rate = parse_double(parts[0].data()); + auto rate = parse_double(parts.at(0).data()); if (rate < 0.5 || rate > 6.0) throw std::out_of_range("Invalid rate value must be between 0.5 and 6"); speech_synthesizer.Options().SpeakingRate(rate); } @@ -751,7 +777,7 @@ static const std::unordered_map handlers = { {"pitch", [](id_type cmd_id, std::vector parts, int64_t*) { if (parts.size()) { - auto pitch = parse_double(parts[0].data()); + auto pitch = parse_double(parts.at(0).data()); if (pitch < 0 || pitch > 2) throw std::out_of_range("Invalid pitch value must be between 0 and 2"); speech_synthesizer.Options().AudioPitch(pitch); } diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index 086222606c..d58fcb936e 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -133,3 +133,23 @@ def develop_save(text='Lucca Brazzi sleeps with the fishes.', filename="speech.w with SharedMemory(size=max_buffer_size(text)) as shm: sz = encode_to_file_object(text, shm) develop_loop(f'2 save {st} {sz} {shm.name} {filename}', 2) + + +def develop_interactive(): + import subprocess + from calibre.debug import run_calibre_debug + print('\x1b[32mInteractive winspeech', '\x1b[39m]]'[:-2], flush=True) + p = run_calibre_debug('-c', 'from calibre_extensions.winspeech import run_main_loop; raise SystemExit(run_main_loop())', + stdin=subprocess.PIPE) + try: + while True: + line = input() + if p.poll() is not None: + raise SystemExit(p.returncode) + p.stdin.write((line + '\n').encode()) + p.stdin.flush() + except KeyboardInterrupt: + print('Exiting on interrupt', flush=True) + finally: + if p.poll() is None: + p.kill() From 479f9fe9ee20c9b313707f5b39342622b4fd783e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 30 Jan 2023 14:13:31 +0530 Subject: [PATCH 0240/2055] Update LiveMint --- recipes/livemint.recipe | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/recipes/livemint.recipe b/recipes/livemint.recipe index 520d9bd54b..72b7a7b307 100644 --- a/recipes/livemint.recipe +++ b/recipes/livemint.recipe @@ -23,6 +23,7 @@ class LiveMint(BasicNewsRecipe): masthead_url = 'https://images.livemint.com/static/livemint-logo-v1.svg' remove_empty_feeds = True + resolve_internal_links = True def get_cover_url(self): soup = self.index_to_soup( @@ -61,21 +62,23 @@ class LiveMint(BasicNewsRecipe): else: extra_css = ''' + img {display:block; margin:0 auto;} #img-cap {font-size:small; text-align:center;} #auth-info {font-size:small; text-align:center;} .highlights {font-style:italic;} - .summary{font-style:italic; color:#404040;} + .summary{font-style:italic; color:#202020;} + .author-widget{font-size:small; font-style:italic; color:#404040; text-align:center;} + em, blockquote {color:#202020;} ''' keep_only_tags = [ - dict(name='h1'), - dict(name='figure', attrs={'data-vars-mediatype':'image'}), - classes('articleInfo FirstEle summary highlights paywall'), + dict(name='article'), + classes('contentSec') ] remove_tags = [ classes( 'trendingSimilarHeight moreNews mobAppDownload label msgError msgOk taboolaHeight' - ' socialHolder imgbig disclamerText disqus-comment-count' + ' socialHolder imgbig disclamerText disqus-comment-count openinApp2 lastAdSlot' ) ] From ff0766bda0d4a081b44329d662fba897cc84ed6f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 30 Jan 2023 14:15:28 +0530 Subject: [PATCH 0241/2055] Boston Globe Print Edition by unkn0wn --- recipes/boston_globe_print_edition.recipe | 113 ++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 recipes/boston_globe_print_edition.recipe diff --git a/recipes/boston_globe_print_edition.recipe b/recipes/boston_globe_print_edition.recipe new file mode 100644 index 0000000000..86aa99bcf5 --- /dev/null +++ b/recipes/boston_globe_print_edition.recipe @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# License: GPLv3 Copyright: 2016, Kovid Goyal +from calibre.web.feeds.news import BasicNewsRecipe, classes +from collections import defaultdict +from datetime import date + +def class_as_string(x): + if isinstance(x, (list, tuple)): + x = ' '.join(x) + return x + +def class_startswith(*prefixes): + + def q(x): + if x: + x = class_as_string(x) + for prefix in prefixes: + if x.startswith(prefix): + return True + return False + + return dict(attrs={'class': q}) + +def absolutize_url(url): + if url.startswith("//"): + return "https:" + url + if url.startswith('/'): + url = "https://www.bostonglobe.com" + url + return url + + +class BostonGlobePrint(BasicNewsRecipe): + title = "Boston Globe | Print Edition" + __author__ = 'Kovid Goyal, unkn0wn' + description = 'The Boston Globe - Today\'s Paper' + language = 'en' + + keep_only_tags = [ + class_startswith('headline |', 'subheader |', 'byline |', 'image |', 'lead |', 'body |', 'comic-debug'), + ] + remove_tags = [ + classes('inline-newsletter ad skip-nav article-footer sharebar arc_ad'), + dict(id='continue_button'), + dict(name=['meta', 'link']) + ] + remove_tags_after = dict(attrs={'class': lambda x:x and x.startswith('body |')}) + remove_attributes = ['style', 'height', 'width'] + no_stylesheets = True + scale_news_images = 1600, 1200 + ignore_duplicate_articles = {'url'} + # simultaneous_downloads = 1 + + def image_url_processor(self, baseurl, url): + return absolutize_url(url) + + def get_cover_url(self): + cover = 'https://img.kiosko.net/' + str( + date.today().year + ) + '/' + date.today().strftime('%m') + '/' + date.today( + ).strftime('%d') + '/us/boston_globe.750.jpg' + br = BasicNewsRecipe.get_browser(self) + try: + br.open(cover) + except: + index = 'https://en.kiosko.net/us/np/boston_globe.html' + soup = self.index_to_soup(index) + for image in soup.findAll('img', src=True): + if image['src'].endswith('750.jpg'): + return 'https:' + image['src'] + self.log("\nCover unavailable") + cover = None + return cover + + def parse_index(self): + + soup = self.index_to_soup('https://www.bostonglobe.com/todays-paper/') + if timefmt := soup.find(**classes('todays-date')): + self.timefmt = ' [' + self.tag_to_string(timefmt) + ']' + + feeds_dict = defaultdict(list) + + for div in soup.findAll('section', attrs={'id':['sp-top-main', 'sp-middle-main']}): + for a in div.findAll('a', href=lambda x: x and x.startswith('/' + str(date.today().year) + '/')): + section = 'Front Page' + if bar := a.findParent(**classes('container')).find_previous_sibling(**classes('title_bar')): + section = self.tag_to_string(bar) + url = absolutize_url(a['href']) + title = self.tag_to_string(a.find('h2')) + desc = '' + if d := a.find(**classes('deck')): + desc = self.tag_to_string(d) + + self.log(section, '\n\t', title, '\n\t', desc, '\n\t\t', url) + feeds_dict[section].append({"title": title, "url": url, "description": desc}) + return [(section, articles) for section, articles in feeds_dict.items()] + + def preprocess_raw_html(self, raw_html, url): + soup = self.index_to_soup(raw_html) + meta = soup.find(attrs={'name': 'description'}, content=True) + if meta is not None and meta['content'].startswith('Comics: '): + meta = soup.find(property='og:image', content=True) + img_url = 'https://cloudfront-us-east-1.images.arcpublishing.com/bostonglobe/' + meta['content'].split('/')[-1] + title = self.tag_to_string(soup.find('title')) + raw_html = '

{}

'.format(title, img_url) + return raw_html + + def preprocess_html(self, soup): + for img in soup.findAll('img'): + fs = img.get('data-src') + if fs: + img['src'] = fs + return soup From f0f4d952020c8d6eeb87aef11d69e5d6b0ce34b9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 30 Jan 2023 20:21:46 +0530 Subject: [PATCH 0242/2055] Parse winspeech messages into python tuples and enums --- src/calibre/utils/windows/winspeech.cpp | 26 ++- src/calibre/utils/windows/winspeech.py | 273 ++++++++++++++++++++++-- 2 files changed, 279 insertions(+), 20 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index f5333169f1..97d23d2534 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -267,6 +267,19 @@ public: } } + json_val(TimedMetadataTrackErrorCode const ec) : type(DT_STRING) { + switch(ec) { + case TimedMetadataTrackErrorCode::DataFormatError: + s = "data_format_error"; break; + case TimedMetadataTrackErrorCode::NetworkError: + s = "network_error"; break; + case TimedMetadataTrackErrorCode::InternalError: + s = "internal_error"; break; + case TimedMetadataTrackErrorCode::None: + s = "none"; break; + } + } + json_val(DeviceInformationKind const dev) : type(DT_STRING) { switch(dev) { case DeviceInformationKind::Unknown: @@ -314,7 +327,6 @@ public: case MediaPlaybackState::Buffering: s = "buffering"; break; case MediaPlaybackState::Playing: s = "playing"; break; case MediaPlaybackState::Paused: s = "paused"; break; - default: s = "unknown"; break; } } @@ -326,7 +338,6 @@ public: case MediaPlayerError::NetworkError: s = "network_error"; break; case MediaPlayerError::DecodingError: s = "decoding_error"; break; case MediaPlayerError::SourceNotSupported: s = "source_not_supported"; break; - default: s = "unknown"; break; } } @@ -520,8 +531,9 @@ register_metadata_handler_for_track(MediaPlaybackTimedMetadataTrackList const &t })); speak_revoker.track_failed.emplace_back(track.TrackFailed(winrt::auto_revoke, [cmd_id](auto, const auto& args) { + auto error = args.Error(); if (main_loop_is_running.load()) output( - cmd_id, "track_failed", {}); + cmd_id, "track_failed", {{"code", error.ErrorCode()}, {"hr", json_val::from_hresult(error.ExtendedError())}}); })); }; @@ -580,7 +592,7 @@ handle_speak(id_type cmd_id, std::vector &parts) { }); speak_revoker.media_failed = media_player.MediaFailed(winrt::auto_revoke, [cmd_id](auto player, auto const& args) { if (main_loop_is_running.load()) output( - cmd_id, "media_state_changed", {{"state", "failed"}, {"error", args.ErrorMessage()}, {"code", args.Error()}}); + cmd_id, "media_state_changed", {{"state", "failed"}, {"error", args.ErrorMessage()}, {"hr", json_val::from_hresult(args.ExtendedErrorCode())}, {"code", args.Error()}}); }); auto playback_item = std::make_shared(source); @@ -736,8 +748,8 @@ static const std::unordered_map handlers = { } } auto x = media_player.AudioDevice(); - if (x) output(cmd_id, "audio_device", {{"value", x}, {"found", found}}); - else output(cmd_id, "audio_device", {{"value", ""}, {"found", found}}); + if (x) output(cmd_id, "audio_device", {{"device", x}, {"found", found}}); + else output(cmd_id, "audio_device", {{"device", ""}, {"found", found}}); }}, {"voice", [](id_type cmd_id, std::vector parts, int64_t*) { @@ -781,7 +793,7 @@ static const std::unordered_map handlers = { if (pitch < 0 || pitch > 2) throw std::out_of_range("Invalid pitch value must be between 0 and 2"); speech_synthesizer.Options().AudioPitch(pitch); } - output(cmd_id, "pitch", {{"pitch", speech_synthesizer.Options().AudioPitch()}}); + output(cmd_id, "pitch", {{"value", speech_synthesizer.Options().AudioPitch()}}); }}, {"save", [](id_type cmd_id, std::vector parts, int64_t*) { diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index d58fcb936e..9e2635a612 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -7,8 +7,11 @@ import os import struct import sys from contextlib import closing +from enum import Enum, auto +from itertools import count from queue import Queue from threading import Thread +from typing import NamedTuple, Tuple from calibre.utils.ipc.simple_worker import start_pipe_worker from calibre.utils.shm import SharedMemory @@ -21,14 +24,6 @@ SSML_SAMPLE = ''' ''' -def decode_msg(line: bytes) -> dict: - parts = line.strip().split(b' ', 2) - msg_id, msg_type, ans = int(parts[0]), parts[1].decode(), json.loads(parts[2]) - ans['related_to'] = msg_id - ans['payload_type'] = msg_type - return ans - - def start_worker(): return start_pipe_worker('from calibre_extensions.winspeech import run_main_loop; raise SystemExit(run_main_loop())') @@ -62,6 +57,257 @@ def encode_to_file_object(text, output) -> int: return sz +# message decoding {{{ +class Saving(NamedTuple): + related_to: int + ssml: bool + output_path: str + + +class Saved(NamedTuple): + related_to: int + size: int + + +class CueEntered(NamedTuple): + related_to: int + start_pos_in_text: int + end_pos_in_text: int + start_time: int + type: str + text: str + + +class CueExited(CueEntered): + related_to: int + start_pos_in_text: int + end_pos_in_text: int + start_time: int + type: str + + +class MarkReached(NamedTuple): + related_to: int + id: int + + +class Error(NamedTuple): + msg: str + error: str = '' + line: int = 0 + file: str = 'winspeech.py' + hr: str = '' + related_to: int = 0 + + +class Synthesizing(NamedTuple): + related_to: int + ssml: bool + num_marks: int + text_length: int + + +class TrackFailed(NamedTuple): + related_to: int + code: str + hr: str + + +class PlaybackState(Enum): + none = auto() + opening = auto() + buffering = auto() + playing = auto() + paused = auto() + + +class PlaybackStateChanged(NamedTuple): + related_to: int + state: PlaybackState + + +class MediaState(Enum): + opened = auto() + ended = auto() + failed = auto() + + +class MediaPlayerError(Enum): + unknown = auto() + aborted = auto() + network_error = auto() + decoding_error = auto() + source_not_supported = auto() + + +class MediaStateChanged(NamedTuple): + related_to: int + state: MediaState + error: str = "" + code: MediaPlayerError = MediaPlayerError.unknown + hr: str = "" + + +class Echo(NamedTuple): + related_to: int + msg: str + + +class Play(NamedTuple): + related_to: int + playback_state: PlaybackState + + +class Pause(NamedTuple): + related_to: int + playback_state: PlaybackState + + +class State(NamedTuple): + related_to: int + playback_state: PlaybackState + + +class VoiceInformation(NamedTuple): + display_name: str + description: str + id: str + language: str + gender: str + + +class DefaultVoice(NamedTuple): + related_to: int + voice: VoiceInformation + + +class Voice(NamedTuple): + related_to: int + voice: VoiceInformation + found: bool = True + + +class DeviceInformation(NamedTuple): + id: str + name: str + kind: str + is_default: bool + is_enabled: bool + + +class AudioDevice(NamedTuple): + related_to: int + device: DeviceInformation + found: bool = True + + +class AllVoices(NamedTuple): + related_to: int + voices: Tuple[VoiceInformation, ...] + + +class Volume(NamedTuple): + related_to: int + value: float + + +class Rate(NamedTuple): + related_to: int + value: float + + +class Pitch(NamedTuple): + related_to: int + value: float + + +def parse_message(line): + parts = line.strip().split(b' ', 2) + msg_id, msg_type, ans = int(parts[0]), parts[1].decode(), json.loads(parts[2]) + ans['related_to'] = msg_id + if msg_type == 'cue_entered': + return CueEntered(**ans) + if msg_type == 'cue_exited': + return CueExited(**ans) + if msg_type == 'mark_reached': + return MarkReached(**ans) + if msg_type == 'playback_state_changed': + ans['state'] = getattr(PlaybackState, ans['state']) + return PlaybackStateChanged(**ans) + if msg_type == 'media_state_changed': + ans['state'] = getattr(MediaState, ans['state']) + if 'code' in ans: + ans['code'] = MediaPlayerError(ans['code']) + return MediaStateChanged(**ans) + if msg_type == 'error': + return Error(**ans) + if msg_type == 'synthesizing': + return Synthesizing(**ans) + if msg_type == 'track_failed': + return TrackFailed(**ans) + if msg_type == 'saving': + return Saving(**ans) + if msg_type == 'saved': + return Saved(**ans) + if msg_type == 'echo': + return Echo(**ans) + if msg_type == 'play': + ans['playback_state'] = getattr(PlaybackState, ans['playback_state']) + return Play(**ans) + if msg_type == 'pause': + ans['playback_state'] = getattr(PlaybackState, ans['playback_state']) + return Pause(**ans) + if msg_type == 'state': + ans['playback_state'] = getattr(PlaybackState, ans['playback_state']) + return State(**ans) + if msg_type == 'default_voice': + ans['voice'] = VoiceInformation(**ans['voice']) + return DefaultVoice(**ans) + if msg_type == 'all_voices': + ans['voices'] = tuple(VoiceInformation(**x) for x in ans['voices']) + return AllVoices(**ans) + if msg_type == 'all_audio_devices': + ans['devices'] = tuple(DeviceInformation(**x) for x in ans['devices']) + return AudioDevice(**ans) + if msg_type == 'audio_device': + return AudioDevice(**ans) + if msg_type == 'voice': + ans['voice'] = VoiceInformation(**ans['voice']) + return Voice(**ans) + if msg_type == 'volume': + return Volume(**ans) + if msg_type == 'rate': + return Rate(**ans) + if msg_type == 'Pitch': + return Pitch(**ans) + return Error(f'Unknown message type: {msg_type}') +# }}} + + +class WinSpeech: + + def __init__(self): + self._worker = None + self.queue = Queue() + self.msg_id_counter = count() + next(self.msg_id_counter) + + @property + def worker(self): + if self._worker is None: + self._worker = start_worker() + Thread(name='WinspeechQueue', target=self._get_messages, args=(self._worker, self.queue), daemon=True).start() + return self._worker + + def _get_messages(self, worker, queue): + try: + for line in worker.stdout: + queue.put(line.decode('utf-8')) + except OSError as e: + line = ('0 error ' + json.dumps({"msg": "Failed to read from worker", "error": str(e), "file": "winspeech.py", "line": 0})) + queue.put(line) + + def develop_loop(*commands): p = start_worker() q = Queue() @@ -70,7 +316,7 @@ def develop_loop(*commands): for line in p.stdout: sys.stdout.buffer.write(b'\x1b[33m' + line + b'\x1b[39m]]'[:-2]) sys.stdout.buffer.flush() - q.put(decode_msg(line)) + q.put(parse_message(line)) def send(*a): cmd = ' '.join(map(str, a)) + '\n' @@ -89,13 +335,13 @@ def develop_loop(*commands): else: while True: m = q.get() - if m['related_to'] != command: + if m.related_to != command: continue - if m['payload_type'] == 'media_state_changed' and m['state'] == 'ended': + if isinstance(m, MediaStateChanged) and m.state in (MediaState.ended, MediaState.failed): break - if m['payload_type'] == 'saved': + if isinstance(m, Saved): break - if m['payload_type'] == 'error': + if isinstance(m, Error): exit_code = 1 break send(f'333 echo Synthesizer exiting with exit code: {exit_code}') @@ -137,6 +383,7 @@ def develop_save(text='Lucca Brazzi sleeps with the fishes.', filename="speech.w def develop_interactive(): import subprocess + from calibre.debug import run_calibre_debug print('\x1b[32mInteractive winspeech', '\x1b[39m]]'[:-2], flush=True) p = run_calibre_debug('-c', 'from calibre_extensions.winspeech import run_main_loop; raise SystemExit(run_main_loop())', From 878d93327e5aede51ea600d609ca32f8e4fbb6cb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 30 Jan 2023 20:39:37 +0530 Subject: [PATCH 0243/2055] Fix #2004167 [Bug in calibre.db.cache when run from source code](https://bugs.launchpad.net/calibre/+bug/2004167) --- src/calibre/db/cache.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 0b5e0eda77..fd74dd723a 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2651,7 +2651,10 @@ class Cache: fmts = field.table.book_col_map.get(book_id, ()) if not fmts: continue - mi = self._get_metadata(book_id, get_cover=True, cover_as_data=True) + mi = self._get_metadata(book_id) + cdata = self.cover(book_id) + if cdata: + mi.cover_data = ('jpeg', cdata) try: path = self._field_for('path', book_id).replace('/', os.sep) except: From 8095b33d09852a2d0cce3360f2a20646c047233b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 30 Jan 2023 20:41:31 +0530 Subject: [PATCH 0244/2055] Also avoid the lock when getting the cover --- src/calibre/db/cache.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index fd74dd723a..7e05d40677 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2652,7 +2652,10 @@ class Cache: if not fmts: continue mi = self._get_metadata(book_id) - cdata = self.cover(book_id) + buf = BytesIO() + if not self._copy_cover_to(book_id, buf): + return + cdata = buf.getvalue() if cdata: mi.cover_data = ('jpeg', cdata) try: From d2158d75b99fc11190f5f6428a08f57e4b21da52 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 31 Jan 2023 12:24:18 +0530 Subject: [PATCH 0245/2055] string changes --- manual/edit.rst | 2 +- manual/function_mode.rst | 6 +++--- manual/template_lang.rst | 10 +++++----- src/calibre/db/cache.py | 2 +- src/calibre/ebooks/metadata/sources/base.py | 2 +- src/calibre/gui2/preferences/create_custom_column.py | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/manual/edit.rst b/manual/edit.rst index ff3a8c669f..18d070c97b 100644 --- a/manual/edit.rst +++ b/manual/edit.rst @@ -133,7 +133,7 @@ Changing text file order ^^^^^^^^^^^^^^^^^^^^^^^^^^ You can re-arrange the order in which text (HTML) files are opened when reading -the book by simply dragging and dropping them in the Files browser or clicking +the book by simply dragging and dropping them in the :guilabel:`File browser` or clicking on the file to move and then pressing the :kbd:`Ctrl+Shift` modifiers with the :kbd:`Up`, :kbd:`Down`, :kbd:`Home` or :kbd:`End` keys. For the technically inclined, this is called re-ordering the book spine. diff --git a/manual/function_mode.rst b/manual/function_mode.rst index f2852d4c00..844bb53474 100644 --- a/manual/function_mode.rst +++ b/manual/function_mode.rst @@ -207,7 +207,7 @@ HTML Table of Contents, ready to be pasted into :file:`toc.html`. The function above is heavily commented, so it should be easy to follow. The key new feature is the use of another useful extra argument to the ``replace()`` function, the ``data`` object. The ``data`` object is a Python -*dict* that persists between all successive invocations of ``replace()`` during +*dictionary* that persists between all successive invocations of ``replace()`` during a single :guilabel:`Replace All` operation. Another new feature is the use of ``call_after_last_match`` -- setting that to @@ -278,9 +278,9 @@ for the current book's language. The ``data`` argument ^^^^^^^^^^^^^^^^^^^^^ -This a simple Python ``dict``. When you run +This a simple Python ``dictionary``. When you run :guilabel:`Replace all`, every successive match will cause ``replace()`` to be -called with the same ``dict`` as data. You can thus use it to store arbitrary +called with the same ``dictionary`` as data. You can thus use it to store arbitrary data between invocations of ``replace()`` during a :guilabel:`Replace all` operation. diff --git a/manual/template_lang.rst b/manual/template_lang.rst index a7f325b65a..93d7880447 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -737,9 +737,9 @@ A developer can choose to pass additional information to the template processor, **Developer: how to pass additional information** -The additional information is a Python dictionary containing pairs ``variable_name: variable_value`` where the values must be strings. The template can access the dict, creating template local variables named ``variable_name`` containing the value ``variable_value``. The user cannot change the name so it is best to use names that won't collide with other template local variables, for example by prefixing the name with an underscore. +The additional information is a Python dictionary containing pairs ``variable_name: variable_value`` where the values must be strings. The template can access the dictionary, creating template local variables named ``variable_name`` containing the value ``variable_value``. The user cannot change the name so it is best to use names that won't collide with other template local variables, for example by prefixing the name with an underscore. -This dict is passed to the template processor (the ``formatter``) using the named parameter ``global_vars=your_dict``. The full method signature is:: +This dictionary is passed to the template processor (the ``formatter``) using the named parameter ``global_vars=your_dict``. The full method signature is:: def safe_format(self, fmt, kwargs, error_value, book, column_name=None, template_cache=None, @@ -749,17 +749,17 @@ This dict is passed to the template processor (the ``formatter``) using the name **Template writer: how to access the additional information** -You access the additional information (the ``globals`` dict) in a template using the template function:: +You access the additional information (the ``globals`` dictionary) in a template using the template function:: globals(id[=expression] [, id[=expression]]*) where ``id`` is any legal variable name. This function checks whether the additional information provided by the developer contains the name. If it does then the function assigns the provided value to a template local variable with that name. If the name is not in the additional information and if an ``expression`` is provided, the ``expression`` is evaluated and the result is assigned to the local variable. If neither a value nor an expression is provided, the function assigns the empty string (``''``) to the local variable. -A template can set a value in the ``globals`` dict using the template function:: +A template can set a value in the ``globals`` dictionary using the template function:: set_globals(id[=expression] [, id[=expression]]*) -This function sets the ``globals`` dict key:value pair ``id:value`` where ``value`` is the value of the template local variable ``id``. If that local variable doesn't exist then ``value`` is set to the result of evaluating ``expression``. +This function sets the ``globals`` dictionary key:value pair ``id:value`` where ``value`` is the value of the template local variable ``id``. If that local variable doesn't exist then ``value`` is set to the result of evaluating ``expression``. Notes on the difference between modes ----------------------------------------- diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 7e05d40677..d7ec79f238 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -681,7 +681,7 @@ class Cache: The returned value for is_multiple fields are always tuples, even when no values are found (in other words, default_value is ignored). The - exception is identifiers for which the returned value is always a dict. + exception is identifiers for which the returned value is always a dictionary. The returned tuples are always in link order, that is, the order in which they were created. ''' diff --git a/src/calibre/ebooks/metadata/sources/base.py b/src/calibre/ebooks/metadata/sources/base.py index 805c932e85..ea659906a9 100644 --- a/src/calibre/ebooks/metadata/sources/base.py +++ b/src/calibre/ebooks/metadata/sources/base.py @@ -508,7 +508,7 @@ class Source(Plugin): def get_cached_cover_url(self, identifiers): ''' Return cached cover URL for the book identified by - the identifiers dict or None if no such URL exists. + the identifiers dictionary or None if no such URL exists. Note that this method must only return validated URLs, i.e. not URLS that could result in a generic cover image or a not found error. diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index aa3a83ea50..11d47e453c 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -892,7 +892,7 @@ class CreateNewCustomColumn: _("You cannot specify is_multiple for the datatype %s") % datatype) if not isinstance(display, dict): return (self.Result.INVALID_DISPLAY, - _("The display parameter must be a Python dict")) + _("The display parameter must be a Python dictionary")) self.created_count += 1 self.custcols[lookup_name] = { 'label': lookup_name, From bd960fa84d0ec45765b18eb51b2a50fcd9a564b5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 31 Jan 2023 12:25:14 +0530 Subject: [PATCH 0246/2055] ... --- src/calibre/utils/windows/winspeech.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index 9e2635a612..ec43e02616 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -302,9 +302,9 @@ class WinSpeech: def _get_messages(self, worker, queue): try: for line in worker.stdout: - queue.put(line.decode('utf-8')) + queue.put(line.decode('utf-8', 'replace')) except OSError as e: - line = ('0 error ' + json.dumps({"msg": "Failed to read from worker", "error": str(e), "file": "winspeech.py", "line": 0})) + line = '0 error ' + json.dumps({"msg": "Failed to read from worker", "error": str(e), "file": "winspeech.py", "line": 0}) queue.put(line) From 5cc00e504abe835421a8265fedc3f63d8dfeba13 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 31 Jan 2023 15:23:00 +0530 Subject: [PATCH 0247/2055] postadd plugins should not be called with None book_id when a dupe is found --- src/calibre/db/cache.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index d7ec79f238..17a7676a7e 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2002,17 +2002,17 @@ class Cache: as per the simple duplicate detection heuristic used by :meth:`has_book`. ''' duplicates, ids = [], [] - fmt_map = {} for mi, format_map in books: book_id = self.create_book_entry(mi, add_duplicates=add_duplicates, apply_import_tags=apply_import_tags, preserve_uuid=preserve_uuid) if book_id is None: duplicates.append((mi, format_map)) else: + fmt_map = {} ids.append(book_id) - for fmt, stream_or_path in iteritems(format_map): + for fmt, stream_or_path in format_map.items(): if self.add_format(book_id, fmt, stream_or_path, dbapi=dbapi, run_hooks=run_hooks): fmt_map[fmt.lower()] = getattr(stream_or_path, 'name', stream_or_path) or '' - run_plugins_on_postadd(dbapi or self, book_id, fmt_map) + run_plugins_on_postadd(dbapi or self, book_id, fmt_map) return ids, duplicates @write_api From 33b7c80e29725c2912fb7137b9ddd70fa2920e4f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 31 Jan 2023 15:29:41 +0530 Subject: [PATCH 0248/2055] Fix #2003906 [Auto-add with duplicate detection messes with FileTypePlugins](https://bugs.launchpad.net/calibre/+bug/2003906) --- src/calibre/gui2/auto_add.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/auto_add.py b/src/calibre/gui2/auto_add.py index 8a543dbb35..22a8c6badb 100644 --- a/src/calibre/gui2/auto_add.py +++ b/src/calibre/gui2/auto_add.py @@ -233,6 +233,7 @@ class AutoAdder(QObject): if os.path.exists(fpath): with open(fpath) as f: paths[0] = f.read() + book_fmt = os.path.splitext(os.path.basename(paths[0]))[1][1:].upper() sz = os.path.join(tdir, 'size.txt') try: with open(sz, 'rb') as f: @@ -265,10 +266,7 @@ class AutoAdder(QObject): mi.authors = new_authors mi.author_sort = gui.current_db.new_api.author_sort_from_authors(mi.authors) mi = [mi] - dups, ids = m.add_books(paths, - [os.path.splitext(fname)[1][1:].upper()], mi, - add_duplicates=not gprefs['auto_add_check_for_duplicates'], - return_ids=True) + dups, ids = m.add_books(paths, [book_fmt], mi, add_duplicates=not gprefs['auto_add_check_for_duplicates'], return_ids=True) added_ids |= set(ids) num = len(ids) if dups: From 31b0c321fc256fc293adef2e6d2abfd014acab15 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 31 Jan 2023 19:26:42 +0530 Subject: [PATCH 0249/2055] DRYer --- src/pyj/read_book/paged_mode.pyj | 15 ++------------- src/pyj/read_book/viewport.pyj | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/pyj/read_book/paged_mode.pyj b/src/pyj/read_book/paged_mode.pyj index 3b417a6b2e..707355d273 100644 --- a/src/pyj/read_book/paged_mode.pyj +++ b/src/pyj/read_book/paged_mode.pyj @@ -33,7 +33,7 @@ from read_book.cfi import ( ) from read_book.globals import current_spine_item, get_boss, rtl_page_progression from read_book.settings import opts -from read_book.viewport import scroll_viewport, line_height, rem_size +from read_book.viewport import scroll_viewport, line_height, rem_size, get_unit_size_in_pixels from utils import ( get_elem_data, set_elem_data ) @@ -155,18 +155,7 @@ def cps_by_em_size(): if fs is 0: ans = cps_by_em_size.ans = 16 else: - d = document.createElement('span') - d.style.position = 'absolute' - d.style.visibility = 'hidden' - d.style.width = '1rem' - d.style.fontSize = '1rem' - d.style.paddingTop = d.style.paddingBottom = d.style.paddingLeft = d.style.paddingRight = '0' - d.style.marginTop = d.style.marginBottom = d.style.marginLeft = d.style.marginRight = '0' - d.style.borderStyle = 'none' - document.body.appendChild(d) - w = d.clientWidth - document.body.removeChild(d) - ans = cps_by_em_size.ans = max(2, w) + ans = cps_by_em_size.ans = max(2, get_unit_size_in_pixels('rem')) cps_by_em_size.at_font_size = fs return ans diff --git a/src/pyj/read_book/viewport.pyj b/src/pyj/read_book/viewport.pyj index d4d73d4452..ecc7dfbfd9 100644 --- a/src/pyj/read_book/viewport.pyj +++ b/src/pyj/read_book/viewport.pyj @@ -330,6 +330,22 @@ for attr in FUNCTIONS: scroll_viewport['paged_' + attr] = scroll_viewport[attr] viewport_mode_changer(scroll_viewport.set_mode) + +def get_unit_size_in_pixels(unit): + d = document.createElement('span') + d.style.position = 'absolute' + d.style.visibility = 'hidden' + d.style.width = f'1{unit}' + d.style.fontSize = f'1{unit}' + d.style.paddingTop = d.style.paddingBottom = d.style.paddingLeft = d.style.paddingRight = '0' + d.style.marginTop = d.style.marginBottom = d.style.marginLeft = d.style.marginRight = '0' + d.style.borderStyle = 'none' + document.body.appendChild(d) + ans = d.clientWidth + document.body.removeChild(d) + return ans + + def rem_size(reset): if reset: rem_size.ans = None @@ -344,7 +360,7 @@ def rem_size(reset): d.style.marginTop = d.style.marginBottom = d.style.marginLeft = d.style.marginRight = '0' d.style.borderStyle = 'none' document.body.appendChild(d) - rem_size.ans = d.clientWidth + rem_size.ans = max(2, d.clientWidth) document.body.removeChild(d) return rem_size.ans From 5e758211f13f393e7a966ab139a8c42f50b39839 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 31 Jan 2023 19:59:35 +0530 Subject: [PATCH 0250/2055] Viewer: use an inch as the limit for the back tap zone rather than 100px --- src/pyj/read_book/touch.pyj | 23 ++++++++++++++++------- src/pyj/read_book/view.pyj | 11 +++++++---- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/pyj/read_book/touch.pyj b/src/pyj/read_book/touch.pyj index 0c208dd076..b26c11f642 100644 --- a/src/pyj/read_book/touch.pyj +++ b/src/pyj/read_book/touch.pyj @@ -2,9 +2,9 @@ # License: GPL v3 Copyright: 2016, Kovid Goyal from __python__ import bound_methods, hash_literals -from read_book.globals import get_boss, ui_operations, ltr_page_progression -from read_book.viewport import scroll_viewport +from read_book.globals import get_boss, ltr_page_progression, ui_operations from read_book.settings import opts +from read_book.viewport import get_unit_size_in_pixels, scroll_viewport HOLD_THRESHOLD = 750 # milliseconds TAP_THRESHOLD = 8 # pixels @@ -233,6 +233,13 @@ class TouchHandler: return self.handle_gesture(gesture) +def inch_in_pixels(): + ans = inch_in_pixels().ans + if not ans: + ans = inch_in_pixels.ans = max(2, get_unit_size_in_pixels('in')) + return ans + + class BookTouchHandler(TouchHandler): def __init__(self, for_side_margin=None): @@ -250,21 +257,23 @@ class BookTouchHandler(TouchHandler): return if not gesture.active: if self.for_side_margin or not tap_on_link(gesture): + inch = inch_in_pixels() if gesture.viewport_y < min(100, scroll_viewport.height() / 4): gesture.type = 'show-chrome' else: + limit = inch # default, books that go left to right. if ltr_page_progression() and not opts.reverse_page_turn_zones: - if gesture.viewport_x < min(100, scroll_viewport.width() / 4): + if gesture.viewport_x < min(limit, scroll_viewport.width() / 4): gesture.type = 'prev-page' else: gesture.type = 'next-page' # We swap the sizes in RTL mode, so that going to the next page is always the bigger touch region. else: - # The "going back" area should not be more than 100 units big, - # even if 1/4 of the scroll viewport is more than 100 units. - # Checking against the larger of the width minus the 100 units and 3/4 of the width will accomplish that. - if gesture.viewport_x > max(scroll_viewport.width() - 100, scroll_viewport.width() * (3/4)): + # The "going back" area should not be more than limit units big, + # even if 1/4 of the scroll viewport is more than limit units. + # Checking against the larger of the width minus the limit units and 3/4 of the width will accomplish that. + if gesture.viewport_x > max(scroll_viewport.width() - limit, scroll_viewport.width() * (3/4)): gesture.type = 'prev-page' else: gesture.type = 'next-page' diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index 87232bc756..4aec340452 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -116,12 +116,15 @@ def show_controls_help(): return set_css(E.div(txt), padding='1ex 1em', text_align='center', margin='auto') left_msg = msg(_('Tap to turn back')) - left_width = '25vw' + left_width = 'min(25vw, 1in)' right_msg = msg(_('Tap to turn page')) - right_width = '75vw' + right_width = 'auto' + left_grow = 0 + right_grow = 1 if rtl_page_progression(): left_msg, right_msg = right_msg, left_msg left_width, right_width = right_width, left_width + left_grow, right_grow = right_grow, left_grow # Clear it out if this is not the first time it's created. # Needed to correctly show it again in a different page progression direction. @@ -139,11 +142,11 @@ def show_controls_help(): style="display: flex; align-items: stretch; flex-grow: 10", E.div( left_msg, - style=f'width: {left_width}; display:flex; align-items: center; border-right: solid 2px currentColor', + style=f'width: {left_width}; flex-grow: {left_grow}; display:flex; align-items: center; border-right: solid 2px currentColor', ), E.div( right_msg, - style=f'width: {right_width}; display:flex; align-items: center', + style=f'width: {right_width}; display:flex; flex-grow: {right_grow}; align-items: center', ) ) )) From 6ad92cbf11a2bd1be3fb992a904b11445a8cd620 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 1 Feb 2023 08:44:58 +0530 Subject: [PATCH 0251/2055] Only run the HTML file size check on EPUB files --- src/calibre/ebooks/oeb/polish/check/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/oeb/polish/check/main.py b/src/calibre/ebooks/oeb/polish/check/main.py index fe6e5b1e28..dd7fdc6092 100644 --- a/src/calibre/ebooks/oeb/polish/check/main.py +++ b/src/calibre/ebooks/oeb/polish/check/main.py @@ -61,7 +61,8 @@ def run_checks(container): items = raster_images if items is not None: items.append((name, mt, container.raw_data(name, decode=decode))) - errors.extend(run_checkers(check_html_size, html_items)) + if container.book_type == 'epub': + errors.extend(run_checkers(check_html_size, html_items)) errors.extend(run_checkers(check_xml_parsing, xml_items)) errors.extend(run_checkers(check_xml_parsing, html_items)) errors.extend(run_checkers(check_raster_images, raster_images)) From 438cf020e3de17186cf8d90a1e96436a11c4b0c6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 1 Feb 2023 13:08:18 +0530 Subject: [PATCH 0252/2055] Rename old SAPI implementation --- src/calibre/gui2/tts/{windows.py => windows_sapi.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/calibre/gui2/tts/{windows.py => windows_sapi.py} (100%) diff --git a/src/calibre/gui2/tts/windows.py b/src/calibre/gui2/tts/windows_sapi.py similarity index 100% rename from src/calibre/gui2/tts/windows.py rename to src/calibre/gui2/tts/windows_sapi.py From 78d890c925e4fa36e7d59edbf613c9f969f0ea24 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 1 Feb 2023 20:29:46 +0530 Subject: [PATCH 0253/2055] Work on integrating winspeech --- src/calibre/gui2/tts/windows.py | 156 +++++++++++++++++++++++++ src/calibre/utils/windows/winspeech.py | 124 ++++++++++++++++++-- 2 files changed, 271 insertions(+), 9 deletions(-) create mode 100644 src/calibre/gui2/tts/windows.py diff --git a/src/calibre/gui2/tts/windows.py b/src/calibre/gui2/tts/windows.py new file mode 100644 index 0000000000..19e6395871 --- /dev/null +++ b/src/calibre/gui2/tts/windows.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python +# License: GPL v3 Copyright: 2020, Kovid Goyal + +from functools import partial + +from calibre.utils.windows.winspeech import WinSpeech, Error, MarkReached, MediaStateChanged, MediaState + +from .common import Event, EventType + +def split_into_chunks(marked_text, chunk_size): + chunk = [] + tlen = 0 + for x in marked_text: + if isinstance(x, int): + chunk.append(x) + else: + sz = len(x) + if tlen + sz > chunk_size: + mark = None + if chunk and isinstance(chunk[-1], int): + mark = chunk[-1] + del chunk[-1] + yield chunk + chunk = [] if mark is None else [mark] + tlen = sz + chunk.append(x) + else: + chunk.append(x) + tlen += sz + if chunk: + yield chunk + + +class Client: + + mark_template = '' + name = 'winspeech' + min_rate = 0.5 + max_rate = 6.0 + default_system_rate = 1.0 + chunk_size = 128 * 1024 + + @classmethod + def escape_marked_text(cls, text): + return text + + def __init__(self, settings=None, dispatch_on_main_thread=lambda f: f()): + self.backend = WinSpeech(self.dispatch_msg) + self.last_mark = -1 + self.current_callback = None + self.dispatch_on_main_thread = dispatch_on_main_thread + self.synthesizing = False + self.settings = settings or {} + self.apply_settings() + + def __del__(self): + if self.backend is not None: + self.backend.shutdown() + self.backend = None + shutdown = __del__ + + def dispatch_msg(self, msg): + self.dispatch_on_main_thread(partial(self.handle_event, msg)) + + def handle_event(self, x): + if isinstance(x, MarkReached): + self.last_mark = x.id + elif isinstance(x, MediaStateChanged) and self.current_chunks: + if x.state is MediaState.opened: + if self.current_chunk == 0: + self.callback_ignoring_errors(Event(EventType.begin)) + elif x.state is MediaState.ended: + if self.current_chunk >= len(self.chunks) - 1: + self.clear_chunks() + self.callback_ignoring_errors(Event(EventType.end)) + else: + self.current_chunk += 1 + self.backend.speak(self.chunks[self.current_chunk], is_cued=True) + elif x.state is MediaState.failed: + raise x.as_exception() + elif isinstance(x, Error): + raise x.as_exception(check_for_no_audio_devices=True) + else: + raise KeyError(f'Unknown event type: {x}') + + def speak_simple_text(self, text): + self.current_callback = None + self.clear_chunks() + self.backend.speak(text) + + def speak_marked_text(self, text, callback): + self.backend.pause() + self.clear_chunks() + self.current_callback = callback + self.chunks = tuple(split_into_chunks(text, self.chunk_size)) + self.current_chunk = 0 + if self.chunks: + self.backend.speak(self.chunks[self.current_chunk], is_cued=True) + self.synthesizing = True + + def callback_ignoring_errors(self, ev): + if self.current_callback is not None: + try: + self.current_callback(ev) + except Exception: + import traceback + traceback.print_exc() + + def clear_chunks(self): + self.synthesizing = False + self.current_chunk = 0 + self.current_chunks = [] + self.last_mark = -1 + + def stop(self): + self.backend.pause() + self.clear_chunks() + if self.current_callback is not None: + self.current_callback(Event(EventType.cancel)) + + def pause(self): + self.backend.pause() + self.synthesizing = False + if self.current_callback is not None: + self.current_callback(Event(EventType.pause)) + + def resume(self): + self.backend.play() + self.synthesizing = True + if self.current_callback is not None: + self.current_callback(Event(EventType.resume)) + + def apply_settings(self, new_settings=None): + pass + + def config_widget(self, backend_settings, parent): + from calibre.gui2.tts.windows_config import Widget + return Widget(self, backend_settings, parent) + + def change_rate(self, steps=1): + rate = current_rate = self.settings.get('rate', self.default_system_rate) + if rate < 1: + step_size = 0.1 + else: + step_size = 0.5 + rate += steps * step_size + rate = max(self.min_rate, min(rate, self.max_rate)) + if rate != current_rate: + self.settings['rate'] = rate + was_synthesizing = self.synthesizing + self.pause() + self.apply_settings() + if was_synthesizing: + self.synthesizing = True + self.resume_after_configure() + return self.settings diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index ec43e02616..0a3bf52802 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -6,13 +6,15 @@ import json import os import struct import sys -from contextlib import closing +from contextlib import closing, suppress from enum import Enum, auto from itertools import count -from queue import Queue +from queue import Empty, Queue from threading import Thread +from time import monotonic from typing import NamedTuple, Tuple +from calibre.constants import DEBUG from calibre.utils.ipc.simple_worker import start_pipe_worker from calibre.utils.shm import SharedMemory @@ -91,14 +93,36 @@ class MarkReached(NamedTuple): id: int +class SpeechError(OSError): + + def __init__(self, err, msg=''): + val = 'There was an error in the Windows Speech subsystem. ' + if msg: + val += f'{msg}. ' + val += err.msg + ': ' + err.error + f'\nFile: {err.file} Line: {err.line}' + if err.hr: + val += f' HRESULT: 0x{err.hr:x}' + super().__init__(val) + + +class NoAudioDevices(Exception): + def __init__(self): + super().__init__(_('No active audio output devices found. Connect headphones or speakers.')) + + class Error(NamedTuple): msg: str error: str = '' line: int = 0 file: str = 'winspeech.py' - hr: str = '' + hr: str = 0 related_to: int = 0 + def as_exception(self, msg='', check_for_no_audio_devices=False): + if check_for_no_audio_devices and self.hr == 0x8004503a: + raise NoAudioDevices(_('No active audio output devices found. Connect headphones or speakers.')) + raise SpeechError(self, msg) + class Synthesizing(NamedTuple): related_to: int @@ -145,7 +169,11 @@ class MediaStateChanged(NamedTuple): state: MediaState error: str = "" code: MediaPlayerError = MediaPlayerError.unknown - hr: str = "" + hr: int = 0 + + def as_exception(self): + err = Error("Playback of speech stream failed", self.error + f' ({self.code})', hr=self.hr) + return err.as_exception(check_for_no_audio_devices=True) class Echo(NamedTuple): @@ -237,9 +265,13 @@ def parse_message(line): if msg_type == 'media_state_changed': ans['state'] = getattr(MediaState, ans['state']) if 'code' in ans: - ans['code'] = MediaPlayerError(ans['code']) + ans['code'] = getattr(MediaPlayerError, ans['code']) + if 'hr' in ans: + ans['hr'] = int(ans['hr'], 16) return MediaStateChanged(**ans) if msg_type == 'error': + if 'hr' in ans: + ans['hr'] = int(ans['hr'], 16) return Error(**ans) if msg_type == 'synthesizing': return Synthesizing(**ans) @@ -286,11 +318,15 @@ def parse_message(line): class WinSpeech: - def __init__(self): + def __init__(self, event_dispatcher=print): self._worker = None self.queue = Queue() self.msg_id_counter = count() next(self.msg_id_counter) + self.pending_messages = [] + self.current_speak_cmd_id = 0 + self.waiting_for = -1 + self.event_dispatcher = event_dispatcher @property def worker(self): @@ -299,15 +335,84 @@ class WinSpeech: Thread(name='WinspeechQueue', target=self._get_messages, args=(self._worker, self.queue), daemon=True).start() return self._worker + def __del__(self): + if self._worker is not None: + self.send_command('exit') + with suppress(Exception): + self._worker.wait(0.3) + if self._worker.poll() is None: + self._worker.kill() + self._worker = None + shutdown = __del__ + def _get_messages(self, worker, queue): + def send_msg(msg): + if self.waiting_for == msg.related_to: + self.queue.put(msg) + else: + self.dispatch_message(msg) try: for line in worker.stdout: - queue.put(line.decode('utf-8', 'replace')) + line = line.strip() + if DEBUG: + with suppress(Exception): + print('winspeech:', line.decode('utf-8', 'replace'), flush=True) + send_msg(parse_message(line)) except OSError as e: - line = '0 error ' + json.dumps({"msg": "Failed to read from worker", "error": str(e), "file": "winspeech.py", "line": 0}) - queue.put(line) + send_msg(Error('Failed to read from worker', str(e))) + except Exception as e: + send_msg(Error('Failed to parse message from worker', str(e))) + + def send_command(self, cmd): + cmd_id = next(self.msg_id_counter) + w = self.worker + w.stdin.write(f'{cmd_id} {cmd}\n'.encode('utf-8')) + w.stdin.flush() + return cmd_id + + def wait_for(self, error_msg, *classes, related_to=-1, timeout=4): + orig, self.waiting_for = self.waiting_for, related_to + try: + limit = monotonic() + timeout + while True: + left = limit - monotonic() + if left <= 0: + break + try: + x = self.queue.get(True, left) + except Empty: + break + if (not classes or isinstance(x, *classes)) and (not related_to or x.related_to == related_to): + return x + if isinstance(x, Error) and (not related_to or x.related_to == related_to): + raise x.as_exception(error_msg) + raise TimeoutError('Timed out waiting for: ' + error_msg) + finally: + self.waiting_for = orig + + def speak(self, text, is_cued=False, is_xml=False): + with SharedMemory(size=max_buffer_size(text)) as shm: + st = 'cued' if is_cued else ('ssml' if is_xml else 'text') + sz = encode_to_file_object(text, shm) + self.current_speak_cmd_id = self.send_command(f'speak {st} shm {sz} {shm.name}') + x = self.wait_for('speech synthesis to start', MediaStateChanged, related_to=self.current_speak_cmd_id, timeout=8) + if x.state is MediaState.failed: + raise x.as_exception() + return self.current_speak_cmd_id + + def dispatch_message(self, x): + if x.related_to == self.current_speak_cmd_id: + if isinstance(x, (Error, MediaStateChanged, MarkReached)): + self.event_dispatcher(x) + + def pause(self): + self.wait_for('pause', Pause, related_to=self.send_command('pause')) + + def play(self): + self.wait_for('play', Play, related_to=self.send_command('play')) +# develop {{{ def develop_loop(*commands): p = start_worker() q = Queue() @@ -400,3 +505,4 @@ def develop_interactive(): finally: if p.poll() is None: p.kill() +# }}} From eef892a90fd20435db1f2dc07583f8bf1bd8db45 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 1 Feb 2023 21:30:06 +0530 Subject: [PATCH 0254/2055] Basic speech seems to work --- src/calibre/gui2/tts/windows.py | 27 +++++++++++++------------- src/calibre/utils/windows/winspeech.py | 7 ++++--- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/calibre/gui2/tts/windows.py b/src/calibre/gui2/tts/windows.py index 19e6395871..c9957e5fcb 100644 --- a/src/calibre/gui2/tts/windows.py +++ b/src/calibre/gui2/tts/windows.py @@ -51,6 +51,7 @@ class Client: self.dispatch_on_main_thread = dispatch_on_main_thread self.synthesizing = False self.settings = settings or {} + self.clear_chunks() self.apply_settings() def __del__(self): @@ -63,19 +64,17 @@ class Client: self.dispatch_on_main_thread(partial(self.handle_event, msg)) def handle_event(self, x): - if isinstance(x, MarkReached): + if isinstance(x, MarkReached) and self.current_chunks: self.last_mark = x.id + self.callback_ignoring_errors(Event(EventType.mark, x.id)) elif isinstance(x, MediaStateChanged) and self.current_chunks: - if x.state is MediaState.opened: - if self.current_chunk == 0: - self.callback_ignoring_errors(Event(EventType.begin)) - elif x.state is MediaState.ended: - if self.current_chunk >= len(self.chunks) - 1: + if x.state is MediaState.ended: + if self.current_chunk_idx >= len(self.current_chunks) - 1: self.clear_chunks() self.callback_ignoring_errors(Event(EventType.end)) else: - self.current_chunk += 1 - self.backend.speak(self.chunks[self.current_chunk], is_cued=True) + self.current_chunk_idx += 1 + self.backend.speak(self.current_chunks[self.current_chunk_idx], is_cued=True) elif x.state is MediaState.failed: raise x.as_exception() elif isinstance(x, Error): @@ -92,11 +91,13 @@ class Client: self.backend.pause() self.clear_chunks() self.current_callback = callback - self.chunks = tuple(split_into_chunks(text, self.chunk_size)) - self.current_chunk = 0 - if self.chunks: - self.backend.speak(self.chunks[self.current_chunk], is_cued=True) + self.current_chunks = tuple(split_into_chunks(text, self.chunk_size)) + self.current_chunk_idx = 0 + if self.current_chunks: + self.backend.speak(self.current_chunks[self.current_chunk_idx], is_cued=True) self.synthesizing = True + if self.current_callback is not None: + self.current_callback(Event(EventType.begin)) def callback_ignoring_errors(self, ev): if self.current_callback is not None: @@ -108,7 +109,7 @@ class Client: def clear_chunks(self): self.synthesizing = False - self.current_chunk = 0 + self.current_chunk_idx = -100 self.current_chunks = [] self.last_mark = -1 diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index 0a3bf52802..e612bf890f 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -107,7 +107,8 @@ class SpeechError(OSError): class NoAudioDevices(Exception): def __init__(self): - super().__init__(_('No active audio output devices found. Connect headphones or speakers.')) + super().__init__(_('No active audio output devices found.' + ' Connect headphones or speakers. If you are using Remote Desktop then enable Remote Audio for it.')) class Error(NamedTuple): @@ -119,8 +120,8 @@ class Error(NamedTuple): related_to: int = 0 def as_exception(self, msg='', check_for_no_audio_devices=False): - if check_for_no_audio_devices and self.hr == 0x8004503a: - raise NoAudioDevices(_('No active audio output devices found. Connect headphones or speakers.')) + if check_for_no_audio_devices and self.hr == 0xc00d36fa: + raise NoAudioDevices() raise SpeechError(self, msg) From 0b9d30a725fcf7c382fd32df4c1edc2b81484dff Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 1 Feb 2023 21:33:21 +0530 Subject: [PATCH 0255/2055] Rename the windows sapi config module as well --- src/calibre/gui2/tts/windows_sapi.py | 2 +- .../gui2/tts/{windows_config.py => windows_sapi_config.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/calibre/gui2/tts/{windows_config.py => windows_sapi_config.py} (100%) diff --git a/src/calibre/gui2/tts/windows_sapi.py b/src/calibre/gui2/tts/windows_sapi.py index 51cc574749..68444df44d 100644 --- a/src/calibre/gui2/tts/windows_sapi.py +++ b/src/calibre/gui2/tts/windows_sapi.py @@ -270,7 +270,7 @@ class Client: return ans def config_widget(self, backend_settings, parent): - from calibre.gui2.tts.windows_config import Widget + from calibre.gui2.tts.windows_sapi_config import Widget return Widget(self, backend_settings, parent) def change_rate(self, steps=1): diff --git a/src/calibre/gui2/tts/windows_config.py b/src/calibre/gui2/tts/windows_sapi_config.py similarity index 100% rename from src/calibre/gui2/tts/windows_config.py rename to src/calibre/gui2/tts/windows_sapi_config.py From f64b9e3e2c5270be19d5b7166e587e406f1c0516 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 1 Feb 2023 22:39:53 +0530 Subject: [PATCH 0256/2055] Dont block UI while waiting for speech to start --- src/calibre/gui2/tts/windows.py | 8 +++++++- src/calibre/gui2/viewer/tts.py | 4 +++- src/calibre/utils/windows/winspeech.py | 8 +++----- src/pyj/read_book/read_aloud.pyj | 2 ++ 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/tts/windows.py b/src/calibre/gui2/tts/windows.py index c9957e5fcb..2709a9d8e2 100644 --- a/src/calibre/gui2/tts/windows.py +++ b/src/calibre/gui2/tts/windows.py @@ -76,7 +76,13 @@ class Client: self.current_chunk_idx += 1 self.backend.speak(self.current_chunks[self.current_chunk_idx], is_cued=True) elif x.state is MediaState.failed: - raise x.as_exception() + self.clear_chunks() + self.callback_ignoring_errors(Event(EventType.cancel)) + e = x.as_exception() + e.display_to_user = True + raise e + elif x.state is MediaState.opened: + self.callback_ignoring_errors(Event(EventType.begin)) elif isinstance(x, Error): raise x.as_exception(check_for_no_audio_devices=True) else: diff --git a/src/calibre/gui2/viewer/tts.py b/src/calibre/gui2/viewer/tts.py index 3e2f77ddb2..31e7174be8 100644 --- a/src/calibre/gui2/viewer/tts.py +++ b/src/calibre/gui2/viewer/tts.py @@ -57,9 +57,11 @@ class TTS(QObject): def dispatch_on_main_thread(self, func): try: func() - except Exception: + except Exception as e: import traceback traceback.print_exc() + if getattr(e, 'display_to_user', False): + error_dialog(self.parent(), _('Error in speech subsystem'), str(e), det_msg=traceback.format_exc(), show=True) @property def tts_client_class(self): diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index e612bf890f..c7b65c4495 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -121,8 +121,8 @@ class Error(NamedTuple): def as_exception(self, msg='', check_for_no_audio_devices=False): if check_for_no_audio_devices and self.hr == 0xc00d36fa: - raise NoAudioDevices() - raise SpeechError(self, msg) + return NoAudioDevices() + return SpeechError(self, msg) class Synthesizing(NamedTuple): @@ -396,9 +396,7 @@ class WinSpeech: st = 'cued' if is_cued else ('ssml' if is_xml else 'text') sz = encode_to_file_object(text, shm) self.current_speak_cmd_id = self.send_command(f'speak {st} shm {sz} {shm.name}') - x = self.wait_for('speech synthesis to start', MediaStateChanged, related_to=self.current_speak_cmd_id, timeout=8) - if x.state is MediaState.failed: - raise x.as_exception() + self.wait_for('speech synthesis to start', Synthesizing, related_to=self.current_speak_cmd_id, timeout=8) return self.current_speak_cmd_id def dispatch_message(self, x): diff --git a/src/pyj/read_book/read_aloud.pyj b/src/pyj/read_book/read_aloud.pyj index 6c5ede5a41..dcfb99ad2a 100644 --- a/src/pyj/read_book/read_aloud.pyj +++ b/src/pyj/read_book/read_aloud.pyj @@ -243,6 +243,8 @@ class ReadAloud: self.play() if data is not None: pass + elif which is 'cancel': + self.state = STOPPED def send_message(self, type, **kw): self.view.iframe_wrapper.send_message('tts', type=type, **kw) From c8e9f337363639f518c13dfd548d168b5cf8a1b0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 11:22:46 +0530 Subject: [PATCH 0257/2055] Port config to winspeech --- src/calibre/gui2/tts/windows.py | 68 +++++++- src/calibre/gui2/tts/windows_config.py | 196 ++++++++++++++++++++++++ src/calibre/utils/windows/winspeech.cpp | 7 +- src/calibre/utils/windows/winspeech.py | 68 +++++++- 4 files changed, 321 insertions(+), 18 deletions(-) create mode 100644 src/calibre/gui2/tts/windows_config.py diff --git a/src/calibre/gui2/tts/windows.py b/src/calibre/gui2/tts/windows.py index 2709a9d8e2..d430cbe3db 100644 --- a/src/calibre/gui2/tts/windows.py +++ b/src/calibre/gui2/tts/windows.py @@ -52,8 +52,16 @@ class Client: self.synthesizing = False self.settings = settings or {} self.clear_chunks() + self.default_system_audio_device = self.backend.get_audio_device().device + self.default_system_voice = self.backend.default_voice().voice self.apply_settings() + def get_all_voices(self): + return self.backend.all_voices().voices + + def get_all_audio_devices(self): + return self.backend.all_audio_devices().devices + def __del__(self): if self.backend is not None: self.backend.shutdown() @@ -63,6 +71,9 @@ class Client: def dispatch_msg(self, msg): self.dispatch_on_main_thread(partial(self.handle_event, msg)) + def speak_current_chunk(self): + self.backend.speak(self.current_chunks[self.current_chunk_idx], is_cued=True) + def handle_event(self, x): if isinstance(x, MarkReached) and self.current_chunks: self.last_mark = x.id @@ -74,7 +85,7 @@ class Client: self.callback_ignoring_errors(Event(EventType.end)) else: self.current_chunk_idx += 1 - self.backend.speak(self.current_chunks[self.current_chunk_idx], is_cued=True) + self.speak_current_chunk() elif x.state is MediaState.failed: self.clear_chunks() self.callback_ignoring_errors(Event(EventType.cancel)) @@ -82,7 +93,8 @@ class Client: e.display_to_user = True raise e elif x.state is MediaState.opened: - self.callback_ignoring_errors(Event(EventType.begin)) + self.callback_ignoring_errors(Event(EventType.resume if self.next_start_is_resume else EventType.begin)) + self.next_start_is_resume = False elif isinstance(x, Error): raise x.as_exception(check_for_no_audio_devices=True) else: @@ -98,12 +110,11 @@ class Client: self.clear_chunks() self.current_callback = callback self.current_chunks = tuple(split_into_chunks(text, self.chunk_size)) - self.current_chunk_idx = 0 + self.current_chunk_idx = -100 if self.current_chunks: - self.backend.speak(self.current_chunks[self.current_chunk_idx], is_cued=True) + self.current_chunk_idx = 0 + self.speak_current_chunk() self.synthesizing = True - if self.current_callback is not None: - self.current_callback(Event(EventType.begin)) def callback_ignoring_errors(self, ev): if self.current_callback is not None: @@ -115,8 +126,9 @@ class Client: def clear_chunks(self): self.synthesizing = False + self.next_start_is_resume = False self.current_chunk_idx = -100 - self.current_chunks = [] + self.current_chunks = () self.last_mark = -1 def stop(self): @@ -138,12 +150,52 @@ class Client: self.current_callback(Event(EventType.resume)) def apply_settings(self, new_settings=None): - pass + if self.synthesizing: + self.stop() + if new_settings is not None: + self.settings = new_settings + try: + self.backend.set_rate(self.settings.get('rate', self.default_system_rate)) + except OSError: + self.settings.pop('rate', None) + try: + self.backend.set_voice(self.settings.get('voice'), self.default_system_voice) + except OSError: + self.settings.pop('voice', None) + try: + self.backend.set_audio_device(self.settings.get('sound_output'), self.default_system_audio_device) + except OSError: + self.settings.pop('sound_output', None) def config_widget(self, backend_settings, parent): from calibre.gui2.tts.windows_config import Widget return Widget(self, backend_settings, parent) + def chunks_from_last_mark(self): + for i, chunk in enumerate(self.current_chunks): + for ci, x in enumerate(chunk): + if x == self.last_mark: + chunks = self.current_chunks[i:] + chunk = chunk[ci + 1:] + if chunk: + chunks = (chunk,) + chunks[1:] + else: + chunks = chunks[1:] + return chunks + return () + + def resume_after_configure(self): + if not self.synthesizing: + return + self.current_chunk_idx = -100 + self.last_mark = -1 + self.current_chunks = self.chunks_from_last_mark() + self.next_start_is_resume = True + self.synthesizing = bool(self.current_chunks) + if self.current_chunks: + self.current_chunk_idx = 0 + self.speak_current_chunk() + def change_rate(self, steps=1): rate = current_rate = self.settings.get('rate', self.default_system_rate) if rate < 1: diff --git a/src/calibre/gui2/tts/windows_config.py b/src/calibre/gui2/tts/windows_config.py new file mode 100644 index 0000000000..727f17e18f --- /dev/null +++ b/src/calibre/gui2/tts/windows_config.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python +# License: GPL v3 Copyright: 2020, Kovid Goyal + +from contextlib import suppress +from qt.core import ( + QAbstractItemView, QAbstractTableModel, QByteArray, QComboBox, QFontMetrics, + QFormLayout, QItemSelectionModel, QSlider, QSortFilterProxyModel, Qt, QTableView, + QWidget +) + +from calibre.gui2.widgets import BusyCursor + + +class VoicesModel(QAbstractTableModel): + + system_default_voice = '__default__' + + def __init__(self, voice_data, parent=None): + super().__init__(parent) + self.voice_data = voice_data + self.current_voices = tuple((x.display_name, x.language, x.gender, x.id) for x in voice_data) + self.column_headers = _('Name'), _('Language'), _('Gender') + + def rowCount(self, parent=None): + return len(self.current_voices) + 1 + + def columnCount(self, parent=None): + return len(self.column_headers) + + def headerData(self, section, orientation, role=Qt.ItemDataRole.DisplayRole): + if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: + return self.column_headers[section] + return super().headerData(section, orientation, role) + + def data(self, index, role=Qt.ItemDataRole.DisplayRole): + if role == Qt.ItemDataRole.DisplayRole: + row = index.row() + with suppress(IndexError): + if row == 0: + return (_('System default'), '', '', '')[index.column()] + data = self.current_voices[row - 1] + col = index.column() + ans = data[col] or '' + return ans + if role == Qt.ItemDataRole.UserRole: + row = index.row() + with suppress(IndexError): + if row == 0: + return self.system_default_voice + return self.current_voices[row - 1][3] + + def index_for_voice(self, v): + r = 0 + if v != self.system_default_voice: + for i, x in enumerate(self.current_voices): + if x[3] == v: + r = i + 1 + break + else: + return + return self.index(r, 0) + + +class Widget(QWidget): + + def __init__(self, tts_client, initial_backend_settings=None, parent=None): + QWidget.__init__(self, parent) + self.l = l = QFormLayout(self) + self.tts_client = tts_client + + with BusyCursor(): + self.voice_data = self.tts_client.get_all_voices() + self.default_system_rate = self.tts_client.default_system_rate + self.all_sound_outputs = self.tts_client.get_all_audio_devices() + self.default_system_audio_device = self.tts_client.default_system_audio_device + + self.speed = s = QSlider(Qt.Orientation.Horizontal, self) + s.setMinimumWidth(200) + l.addRow(_('&Speed of speech:'), s) + s.setRange(int(self.tts_client.min_rate * 100), int(100 * self.tts_client.max_rate)) + s.setSingleStep(10) + s.setPageStep(40) + + self.voices = v = QTableView(self) + self.voices_model = VoicesModel(self.voice_data, parent=v) + self.proxy_model = p = QSortFilterProxyModel(self) + p.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) + p.setSourceModel(self.voices_model) + v.setModel(p) + v.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + v.setSortingEnabled(True) + v.horizontalHeader().resizeSection(0, QFontMetrics(self.font()).averageCharWidth() * 25) + v.horizontalHeader().resizeSection(1, QFontMetrics(self.font()).averageCharWidth() * 30) + v.verticalHeader().close() + v.verticalHeader().close() + v.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) + v.sortByColumn(0, Qt.SortOrder.AscendingOrder) + l.addRow(v) + + self.sound_outputs = so = QComboBox(self) + so.addItem(_('System default'), ()) + for x in self.all_sound_outputs: + so.addItem(x.name, x.spec()) + l.addRow(_('Sound output:'), so) + + self.backend_settings = initial_backend_settings or {} + + def restore_state(self, prefs): + data = prefs.get(f'{self.tts_client.name}-voice-table-state') + if data is not None: + self.voices.horizontalHeader().restoreState(QByteArray(data)) + + def save_state(self, prefs): + data = bytearray(self.voices.horizontalHeader().saveState()) + prefs.set(f'{self.tts_client.name}-voice-table-state', data) + + def restore_to_defaults(self): + self.backend_settings = {} + + def sizeHint(self): + ans = super().sizeHint() + ans.setHeight(max(ans.height(), 600)) + ans.setWidth(max(ans.width(), 500)) + return ans + + @property + def selected_voice(self): + for x in self.voices.selectedIndexes(): + return x.data(Qt.ItemDataRole.UserRole) + + @selected_voice.setter + def selected_voice(self, val): + val = val or VoicesModel.system_default_voice + idx = self.voices_model.index_for_voice(val) + if idx is not None: + idx = self.proxy_model.mapFromSource(idx) + self.voices.selectionModel().select(idx, QItemSelectionModel.SelectionFlag.ClearAndSelect | QItemSelectionModel.SelectionFlag.Rows) + self.voices.scrollTo(idx) + + @property + def rate(self): + return self.speed.value() / 100 + + @rate.setter + def rate(self, val): + val = int((val or self.default_system_rate) * 100) + self.speed.setValue(val) + + @property + def sound_output(self): + return self.sound_outputs.currentData() + + @sound_output.setter + def sound_output(self, val): + idx = 0 + if val: + q = self.sound_outputs.findData(val) + if q > -1: + idx = q + self.sound_outputs.setCurrentIndex(idx) + + @property + def backend_settings(self): + ans = {} + voice = self.selected_voice + if voice and voice != VoicesModel.system_default_voice: + ans['voice'] = voice + rate = self.rate + if rate and rate != self.default_system_rate: + ans['rate'] = rate + so = self.sound_output + if so: + ans['sound_output'] = so + return ans + + @backend_settings.setter + def backend_settings(self, val): + voice = val.get('voice') or VoicesModel.system_default_voice + self.selected_voice = voice + self.rate = val.get('rate', self.default_system_rate) + self.sound_output = val.get('sound_output') or () + + +def develop(): + from calibre.gui2 import Application + from calibre.gui2.tts.implementation import Client + app = Application([]) + c = Client() + w = Widget(c, {}) + w.show() + app.exec() + print(w.backend_settings) + + +if __name__ == '__main__': + develop() diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index 97d23d2534..b86020eb34 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -756,6 +756,9 @@ static const std::unordered_map handlers = { bool found = false; if (parts.size()) { auto voice_id = winrt::hstring(parts.at(0)); + if (voice_id == L"__default__") { + voice_id = SpeechSynthesizer::DefaultVoice().Id(); + } for (auto const &candidate : SpeechSynthesizer::AllVoices()) { if (candidate.Id() == voice_id) { speech_synthesizer.Voice(candidate); @@ -765,8 +768,8 @@ static const std::unordered_map handlers = { } } auto x = speech_synthesizer.Voice(); - if (x) output(cmd_id, "voice", {{"value", speech_synthesizer.Voice()}, {"found", found}}); - else output(cmd_id, "voice", {{"value", ""}, {"found", found}}); + if (x) output(cmd_id, "voice", {{"voice", speech_synthesizer.Voice()}, {"found", found}}); + else output(cmd_id, "voice", {{"voice", ""}, {"found", found}}); }}, {"volume", [](id_type cmd_id, std::vector parts, int64_t*) { diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index c7b65c4495..f9cfef41fe 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -12,7 +12,7 @@ from itertools import count from queue import Empty, Queue from threading import Thread from time import monotonic -from typing import NamedTuple, Tuple +from typing import NamedTuple, Tuple, Optional from calibre.constants import DEBUG from calibre.utils.ipc.simple_worker import start_pipe_worker @@ -101,11 +101,12 @@ class SpeechError(OSError): val += f'{msg}. ' val += err.msg + ': ' + err.error + f'\nFile: {err.file} Line: {err.line}' if err.hr: + # List of mediaserver errors is here: https://www.hresult.info/FACILITY_MEDIASERVER val += f' HRESULT: 0x{err.hr:x}' super().__init__(val) -class NoAudioDevices(Exception): +class NoAudioDevices(OSError): def __init__(self): super().__init__(_('No active audio output devices found.' ' Connect headphones or speakers. If you are using Remote Desktop then enable Remote Audio for it.')) @@ -212,7 +213,7 @@ class DefaultVoice(NamedTuple): class Voice(NamedTuple): related_to: int - voice: VoiceInformation + voice: Optional[VoiceInformation] found: bool = True @@ -223,13 +224,21 @@ class DeviceInformation(NamedTuple): is_default: bool is_enabled: bool + def spec(self) -> Tuple[str, str]: + return self.kind, self.id + class AudioDevice(NamedTuple): related_to: int - device: DeviceInformation + device: Optional[DeviceInformation] found: bool = True +class AllAudioDevices(NamedTuple): + related_to: int + devices: Tuple[DeviceInformation, ...] + + class AllVoices(NamedTuple): related_to: int voices: Tuple[VoiceInformation, ...] @@ -301,11 +310,18 @@ def parse_message(line): return AllVoices(**ans) if msg_type == 'all_audio_devices': ans['devices'] = tuple(DeviceInformation(**x) for x in ans['devices']) - return AudioDevice(**ans) + return AllAudioDevices(**ans) if msg_type == 'audio_device': + if ans['device']: + ans['device'] = DeviceInformation(ans['device']) + else: + ans['device'] = None return AudioDevice(**ans) if msg_type == 'voice': - ans['voice'] = VoiceInformation(**ans['voice']) + if ans['voice']: + ans['voice'] = VoiceInformation(**ans['voice']) + else: + ans['voice'] = None return Voice(**ans) if msg_type == 'volume': return Volume(**ans) @@ -357,7 +373,7 @@ class WinSpeech: line = line.strip() if DEBUG: with suppress(Exception): - print('winspeech:', line.decode('utf-8', 'replace'), flush=True) + print('winspeech:\x1b[32m<-\x1b[39m', line.decode('utf-8', 'replace'), flush=True) send_msg(parse_message(line)) except OSError as e: send_msg(Error('Failed to read from worker', str(e))) @@ -367,7 +383,11 @@ class WinSpeech: def send_command(self, cmd): cmd_id = next(self.msg_id_counter) w = self.worker - w.stdin.write(f'{cmd_id} {cmd}\n'.encode('utf-8')) + cmd = f'{cmd_id} {cmd}' + if DEBUG: + with suppress(Exception): + print('winspeech:\x1b[31m->\x1b[39m', cmd, flush=True) + w.stdin.write(f'{cmd}\n'.encode('utf-8')) w.stdin.flush() return cmd_id @@ -410,6 +430,38 @@ class WinSpeech: def play(self): self.wait_for('play', Play, related_to=self.send_command('play')) + def set_rate(self, val): + val = float(val) + self.wait_for('Setting the rate', Rate, related_to=self.send_command(f'rate {val}')) + + def set_voice(self, spec, default_system_voice): + val = spec or getattr(default_system_voice, 'id', '__default__') + x = self.wait_for('Setting the voice', Voice, related_to=self.send_command(f'voice {val}')) + if not x.found: + raise KeyError(f'Failed to find the voice: {val}') + + def set_audio_device(self, spec, default_system_audio_device): + if not spec and not default_system_audio_device: + return + if not spec: + spec = default_system_audio_device.spec() + x = self.wait_for('Setting the audio device', AudioDevice, related_to=self.send_command(f'audio_device {spec[0]} {spec[1]}')) + if not x.found: + raise KeyError(f'Failed to find the audio device: {spec}') + + def get_audio_device(self): + return self.wait_for('Audio device', AudioDevice, related_to=self.send_command('audio_device')) + + def default_voice(self): + return self.wait_for('Default voice', DefaultVoice, related_to=self.send_command('default_voice')) + + def all_voices(self): + return self.wait_for('All voices', AllVoices, related_to=self.send_command('all_voices')) + + def all_audio_devices(self): + return self.wait_for('All audio devices', AllAudioDevices, related_to=self.send_command('all_audio_devices')) + + # develop {{{ def develop_loop(*commands): From e872e7588ba9a239af54347e9a4c821ba28e25b7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 11:51:36 +0530 Subject: [PATCH 0258/2055] Get speaking rate changes working --- src/calibre/gui2/tts/windows.py | 38 +++++++++++++++++---------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/calibre/gui2/tts/windows.py b/src/calibre/gui2/tts/windows.py index d430cbe3db..4885fc3f1a 100644 --- a/src/calibre/gui2/tts/windows.py +++ b/src/calibre/gui2/tts/windows.py @@ -133,6 +133,7 @@ class Client: def stop(self): self.backend.pause() + self.synthesizing = False self.clear_chunks() if self.current_callback is not None: self.current_callback(Event(EventType.cancel)) @@ -151,17 +152,17 @@ class Client: def apply_settings(self, new_settings=None): if self.synthesizing: - self.stop() + self.pause() if new_settings is not None: self.settings = new_settings - try: - self.backend.set_rate(self.settings.get('rate', self.default_system_rate)) - except OSError: - self.settings.pop('rate', None) try: self.backend.set_voice(self.settings.get('voice'), self.default_system_voice) except OSError: self.settings.pop('voice', None) + try: + self.backend.set_rate(self.settings.get('rate', self.default_system_rate)) + except OSError: + self.settings.pop('rate', None) try: self.backend.set_audio_device(self.settings.get('sound_output'), self.default_system_audio_device) except OSError: @@ -172,27 +173,29 @@ class Client: return Widget(self, backend_settings, parent) def chunks_from_last_mark(self): - for i, chunk in enumerate(self.current_chunks): - for ci, x in enumerate(chunk): - if x == self.last_mark: - chunks = self.current_chunks[i:] - chunk = chunk[ci + 1:] - if chunk: - chunks = (chunk,) + chunks[1:] - else: - chunks = chunks[1:] - return chunks + print(222222222, self.last_mark) + if self.last_mark > -1: + for i, chunk in enumerate(self.current_chunks): + for ci, x in enumerate(chunk): + if x == self.last_mark: + chunks = self.current_chunks[i:] + chunk = chunk[ci + 1:] + if chunk: + chunks = (chunk,) + chunks[1:] + else: + chunks = chunks[1:] + return chunks return () def resume_after_configure(self): if not self.synthesizing: return + self.current_chunks = self.chunks_from_last_mark() self.current_chunk_idx = -100 self.last_mark = -1 - self.current_chunks = self.chunks_from_last_mark() self.next_start_is_resume = True self.synthesizing = bool(self.current_chunks) - if self.current_chunks: + if self.synthesizing: self.current_chunk_idx = 0 self.speak_current_chunk() @@ -207,7 +210,6 @@ class Client: if rate != current_rate: self.settings['rate'] = rate was_synthesizing = self.synthesizing - self.pause() self.apply_settings() if was_synthesizing: self.synthesizing = True From eec97a8ec963f3e9997095f20be4d3377b8199ca Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 12:09:33 +0530 Subject: [PATCH 0259/2055] DRYer --- src/calibre/gui2/tts/windows.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/tts/windows.py b/src/calibre/gui2/tts/windows.py index 4885fc3f1a..18e8644079 100644 --- a/src/calibre/gui2/tts/windows.py +++ b/src/calibre/gui2/tts/windows.py @@ -151,6 +151,7 @@ class Client: self.current_callback(Event(EventType.resume)) def apply_settings(self, new_settings=None): + was_synthesizing = self.synthesizing if self.synthesizing: self.pause() if new_settings is not None: @@ -167,13 +168,14 @@ class Client: self.backend.set_audio_device(self.settings.get('sound_output'), self.default_system_audio_device) except OSError: self.settings.pop('sound_output', None) + if was_synthesizing: + self.resume_after_configure() def config_widget(self, backend_settings, parent): from calibre.gui2.tts.windows_config import Widget return Widget(self, backend_settings, parent) def chunks_from_last_mark(self): - print(222222222, self.last_mark) if self.last_mark > -1: for i, chunk in enumerate(self.current_chunks): for ci, x in enumerate(chunk): @@ -188,8 +190,6 @@ class Client: return () def resume_after_configure(self): - if not self.synthesizing: - return self.current_chunks = self.chunks_from_last_mark() self.current_chunk_idx = -100 self.last_mark = -1 @@ -209,9 +209,5 @@ class Client: rate = max(self.min_rate, min(rate, self.max_rate)) if rate != current_rate: self.settings['rate'] = rate - was_synthesizing = self.synthesizing self.apply_settings() - if was_synthesizing: - self.synthesizing = True - self.resume_after_configure() return self.settings From c19ce27025b305fa23b306fc5db1696b82f19f56 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 12:36:56 +0530 Subject: [PATCH 0260/2055] Fix sound output retention in config dialog --- src/calibre/gui2/tts/windows_config.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/tts/windows_config.py b/src/calibre/gui2/tts/windows_config.py index 727f17e18f..a3157cd06e 100644 --- a/src/calibre/gui2/tts/windows_config.py +++ b/src/calibre/gui2/tts/windows_config.py @@ -154,9 +154,12 @@ class Widget(QWidget): def sound_output(self, val): idx = 0 if val: - q = self.sound_outputs.findData(val) - if q > -1: - idx = q + val = tuple(val) + for q in range(self.sound_outputs.count()): + x = self.sound_outputs.itemData(q) + if x == val: + idx = q + break self.sound_outputs.setCurrentIndex(idx) @property @@ -184,11 +187,16 @@ class Widget(QWidget): def develop(): from calibre.gui2 import Application from calibre.gui2.tts.implementation import Client + from calibre.gui2.viewer.config import vprefs + s = vprefs.get('tts_winspeech') or {} + print(s) + print(flush=True) app = Application([]) c = Client() - w = Widget(c, {}) + w = Widget(c, s) w.show() app.exec() + print(flush=True) print(w.backend_settings) From f464c71d61f9fdde9cdbc87a820717f96f6465d8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 12:38:47 +0530 Subject: [PATCH 0261/2055] Use SpeechError not KeyError for failure to find device/voice --- src/calibre/utils/windows/winspeech.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index f9cfef41fe..0727508ac9 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -438,7 +438,7 @@ class WinSpeech: val = spec or getattr(default_system_voice, 'id', '__default__') x = self.wait_for('Setting the voice', Voice, related_to=self.send_command(f'voice {val}')) if not x.found: - raise KeyError(f'Failed to find the voice: {val}') + raise SpeechError(f'Failed to find the voice: {val}') def set_audio_device(self, spec, default_system_audio_device): if not spec and not default_system_audio_device: @@ -447,7 +447,7 @@ class WinSpeech: spec = default_system_audio_device.spec() x = self.wait_for('Setting the audio device', AudioDevice, related_to=self.send_command(f'audio_device {spec[0]} {spec[1]}')) if not x.found: - raise KeyError(f'Failed to find the audio device: {spec}') + raise SpeechError(f'Failed to find the audio device: {spec}') def get_audio_device(self): return self.wait_for('Audio device', AudioDevice, related_to=self.send_command('audio_device')) From 6bd4c506bb22c98a81331fb5a22b8a9055904149 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 12:43:10 +0530 Subject: [PATCH 0262/2055] Fix changing sound output not working --- src/calibre/gui2/tts/windows.py | 6 ++++++ src/calibre/utils/windows/winspeech.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/tts/windows.py b/src/calibre/gui2/tts/windows.py index 18e8644079..a8ad597075 100644 --- a/src/calibre/gui2/tts/windows.py +++ b/src/calibre/gui2/tts/windows.py @@ -159,14 +159,20 @@ class Client: try: self.backend.set_voice(self.settings.get('voice'), self.default_system_voice) except OSError: + import traceback + traceback.print_exc() self.settings.pop('voice', None) try: self.backend.set_rate(self.settings.get('rate', self.default_system_rate)) except OSError: + import traceback + traceback.print_exc() self.settings.pop('rate', None) try: self.backend.set_audio_device(self.settings.get('sound_output'), self.default_system_audio_device) except OSError: + import traceback + traceback.print_exc() self.settings.pop('sound_output', None) if was_synthesizing: self.resume_after_configure() diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index 0727508ac9..476f56a070 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -313,7 +313,7 @@ def parse_message(line): return AllAudioDevices(**ans) if msg_type == 'audio_device': if ans['device']: - ans['device'] = DeviceInformation(ans['device']) + ans['device'] = DeviceInformation(**ans['device']) else: ans['device'] = None return AudioDevice(**ans) From 4684c874975c0b3010674f2a9ddc834630281477 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 12:56:38 +0530 Subject: [PATCH 0263/2055] Fix chunking --- src/calibre/gui2/tts/windows.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/tts/windows.py b/src/calibre/gui2/tts/windows.py index a8ad597075..c540ce3cbf 100644 --- a/src/calibre/gui2/tts/windows.py +++ b/src/calibre/gui2/tts/windows.py @@ -17,9 +17,10 @@ def split_into_chunks(marked_text, chunk_size): sz = len(x) if tlen + sz > chunk_size: mark = None - if chunk and isinstance(chunk[-1], int): - mark = chunk[-1] - del chunk[-1] + if chunk: + if isinstance(chunk[-1], int): + mark = chunk[-1] + del chunk[-1] yield chunk chunk = [] if mark is None else [mark] tlen = sz @@ -38,7 +39,7 @@ class Client: min_rate = 0.5 max_rate = 6.0 default_system_rate = 1.0 - chunk_size = 128 * 1024 + chunk_size = 64 * 1024 @classmethod def escape_marked_text(cls, text): From 3b9fd595b4150593619a6cfab6d216b50d303e46 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 15:01:34 +0530 Subject: [PATCH 0264/2055] When updating metadata in EPUB 2 files and no language is specified, do not remove the tag as this causes epubcheck to complain. Instead set the language to "und". Fixes #2004522 [sendtokindle fails after editing](https://bugs.launchpad.net/calibre/+bug/2004522) --- src/calibre/ebooks/metadata/opf2.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index d3a7a6744a..50dc3384a9 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -1106,9 +1106,14 @@ class OPF: # {{{ for x in matches: x.getparent().remove(x) + num_done = 0 for lang in val: l = self.create_metadata_element('language') self.set_text(l, str(lang)) + num_done += 1 + if num_done == 0: + l = self.create_metadata_element('language') + self.set_text(l, 'und') @property def raw_languages(self): From be4e875706ed66f94456337dd754f4e69eeefece Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 15:23:20 +0530 Subject: [PATCH 0265/2055] Improve the highlight deletion confirmation message to show some text from the highlight and mention if the highlight has associated notes --- src/pyj/read_book/annotations.pyj | 5 +++++ src/pyj/read_book/selection_bar.pyj | 12 +++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/pyj/read_book/annotations.pyj b/src/pyj/read_book/annotations.pyj index 55197ecc3a..59a214e0de 100644 --- a/src/pyj/read_book/annotations.pyj +++ b/src/pyj/read_book/annotations.pyj @@ -215,6 +215,11 @@ class AnnotationsManager: # {{{ if h: return h.notes + def text_for_highlight(self, uuid): + h = self.highlights[uuid] if uuid else None + if h: + return h.highlighted_text + def set_notes_for_highlight(self, uuid, notes): h = self.highlights[uuid] if h: diff --git a/src/pyj/read_book/selection_bar.pyj b/src/pyj/read_book/selection_bar.pyj index 5b97f0b3f3..eddc740822 100644 --- a/src/pyj/read_book/selection_bar.pyj +++ b/src/pyj/read_book/selection_bar.pyj @@ -1074,8 +1074,18 @@ class SelectionBar: def remove_highlight(self): annot_id = self.view.currently_showing.selection.annot_id if annot_id: + q = _('Are you sure you want to delete this highlight permanently?') + text = self.annotations_manager.text_for_highlight(annot_id) + if text and text.length: + if text.length > 20: + text = text[:20] + '…' + notes = self.annotations_manager.notes_for_highlight(annot_id) + if notes and notes.length: + q = _('Are you sure you want to delete the highlighting of "{}" and the associated notes permanently?').format(text) + else: + q = _('Are you sure you want to delete the highlighting of "{}" permanently?').format(text) question_dialog( - _('Are you sure?'), _('Are you sure you want to delete this highlight permanently?'), + _('Are you sure?'), q, def (yes): if yes: self.remove_highlight_with_id(annot_id) From 40ca12ff397157e6acb3bb3d97fa82c5aa6009bc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 15:33:54 +0530 Subject: [PATCH 0266/2055] Fix detection of selected highlights when all text is selected. See #2003908 (Clear selection when several selections are selected only deletes one) --- src/pyj/range_utils.pyj | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pyj/range_utils.pyj b/src/pyj/range_utils.pyj index 5f6341cfd6..1217f3db9f 100644 --- a/src/pyj/range_utils.pyj +++ b/src/pyj/range_utils.pyj @@ -31,8 +31,9 @@ def text_nodes_in_range(r): def first_annot_in_range(r, annot_id_uuid_map): parent = r.commonAncestorContainer doc = parent.ownerDocument or document + is_full_tree = parent is doc.documentElement + in_range = not is_full_tree iterator = doc.createNodeIterator(parent) - in_range = False while True: node = iterator.nextNode() if not node: @@ -44,7 +45,7 @@ def first_annot_in_range(r, annot_id_uuid_map): annot_id = annot_id_uuid_map[node.dataset.calibreRangeWrapper] if annot_id: return annot_id - if node.isSameNode(r.endContainer): + if not is_full_tree and node.isSameNode(r.endContainer): break @@ -52,7 +53,8 @@ def all_annots_in_range(r, annot_id_uuid_map, ans): parent = r.commonAncestorContainer doc = parent.ownerDocument or document iterator = doc.createNodeIterator(parent) - in_range = False + is_full_tree = parent is doc.documentElement + in_range = not is_full_tree while True: node = iterator.nextNode() if not node: @@ -64,7 +66,7 @@ def all_annots_in_range(r, annot_id_uuid_map, ans): annot_id = annot_id_uuid_map[node.dataset.calibreRangeWrapper] if annot_id: ans.push(annot_id) - if node.isSameNode(r.endContainer): + if not is_full_tree and node.isSameNode(r.endContainer): break return ans From 1fb7f72fd078b27d34bbce05ba3c1ddcef49504e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 18:07:34 +0530 Subject: [PATCH 0267/2055] E-book viewer: Fix occasional false warning about highlight being overwritten. Fixes #2003916 [Overlapping highlights not detected properly (inconsistent)](https://bugs.launchpad.net/calibre/+bug/2003916) all_annots_in_selection should return a list of unique uuids. --- src/pyj/range_utils.pyj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pyj/range_utils.pyj b/src/pyj/range_utils.pyj index 1217f3db9f..1f6c697fd6 100644 --- a/src/pyj/range_utils.pyj +++ b/src/pyj/range_utils.pyj @@ -65,17 +65,17 @@ def all_annots_in_range(r, annot_id_uuid_map, ans): if node.dataset and node.dataset.calibreRangeWrapper: annot_id = annot_id_uuid_map[node.dataset.calibreRangeWrapper] if annot_id: - ans.push(annot_id) + ans[annot_id] = True if not is_full_tree and node.isSameNode(r.endContainer): break return ans def all_annots_in_selection(sel, annot_id_uuid_map): - ans = v'[]' + ans = v'{}' for i in range(sel.rangeCount): all_annots_in_range(sel.getRangeAt(i), annot_id_uuid_map, ans) - return ans + return Object.keys(ans) def remove(node): From 6a9e9d9fe5005616f2cdeb5ece634e8a006da3ac Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 18:13:53 +0530 Subject: [PATCH 0268/2055] Update Jerusalem Post --- recipes/jpost.recipe | 3 +++ 1 file changed, 3 insertions(+) diff --git a/recipes/jpost.recipe b/recipes/jpost.recipe index 9d434d0e8e..aa9c005db7 100644 --- a/recipes/jpost.recipe +++ b/recipes/jpost.recipe @@ -27,6 +27,9 @@ class JerusalemPost(BasicNewsRecipe): max_articles_per_feed = 10 no_stylesheets = True + def get_browser(self): + return BasicNewsRecipe.get_browser(self, user_agent='common_words/based') + feeds = [ ('Arab Israeli Conflict', 'https://www.jpost.com/rss/rssfeedsarabisraeliconflict.aspx'), ('Jerusalem', 'https://www.jpost.com/rss/rssfeedsjerusalem.aspx'), From f11a907a940a8fca38f4f325ccde9b27b7fcff2d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 18:14:27 +0530 Subject: [PATCH 0269/2055] ... --- recipes/jpost.recipe | 1 - 1 file changed, 1 deletion(-) diff --git a/recipes/jpost.recipe b/recipes/jpost.recipe index aa9c005db7..611f6fd771 100644 --- a/recipes/jpost.recipe +++ b/recipes/jpost.recipe @@ -32,7 +32,6 @@ class JerusalemPost(BasicNewsRecipe): feeds = [ ('Arab Israeli Conflict', 'https://www.jpost.com/rss/rssfeedsarabisraeliconflict.aspx'), - ('Jerusalem', 'https://www.jpost.com/rss/rssfeedsjerusalem.aspx'), ('US Politics', 'https://www.jpost.com/rss/rssfeedsamerican-politics'), ('Israel News', 'https://www.jpost.com/rss/rssfeedsisraelnews.aspx'), ( From 97d80078b4ba27386523643e1c09b96a6c3eb89b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 20:03:40 +0530 Subject: [PATCH 0270/2055] In honor of ChatGPT See https://www.mobileread.com/forums/showthread.php?t=351886 --- recipes/jpost.recipe | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/recipes/jpost.recipe b/recipes/jpost.recipe index 611f6fd771..2c94daead2 100644 --- a/recipes/jpost.recipe +++ b/recipes/jpost.recipe @@ -15,7 +15,7 @@ class JerusalemPost(BasicNewsRecipe): use_embedded_content = False language = 'en' keep_only_tags = [ - classes('margin-container-body'), + classes('margin-container-body article-title article-subline article-inner-content-breaking-news'), ] remove_tags = [ classes('share-buttons hide-for-premium'), @@ -31,6 +31,7 @@ class JerusalemPost(BasicNewsRecipe): return BasicNewsRecipe.get_browser(self, user_agent='common_words/based') feeds = [ + ('Top Stories', 'https://www.jpost.com/Rss/RssFeedsHeadlines.aspx'), ('Arab Israeli Conflict', 'https://www.jpost.com/rss/rssfeedsarabisraeliconflict.aspx'), ('US Politics', 'https://www.jpost.com/rss/rssfeedsamerican-politics'), ('Israel News', 'https://www.jpost.com/rss/rssfeedsisraelnews.aspx'), @@ -39,4 +40,5 @@ class JerusalemPost(BasicNewsRecipe): 'https://www.jpost.com/rss/rssfeedsmiddleeastnews.aspx' ), ('International News', 'https://www.jpost.com/rss/rssfeedsinternational'), + ('Opinion', 'https://www.jpost.com/Rss/RssFeedsOpinion.aspx'), ] From 517d229e081d3574d9f5d7a75e22f239ebc39c81 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 20:50:55 +0530 Subject: [PATCH 0271/2055] Content server viewer: Fix reload book not actually reloading until the browser is also refreshed. Fixes #2004197 [content server - viewer does not show updated book/format](https://bugs.launchpad.net/calibre/+bug/2004197) --- src/pyj/read_book/overlay.pyj | 4 ++-- src/pyj/read_book/view.pyj | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/pyj/read_book/overlay.pyj b/src/pyj/read_book/overlay.pyj index a7563ca603..0890e18a6c 100644 --- a/src/pyj/read_book/overlay.pyj +++ b/src/pyj/read_book/overlay.pyj @@ -120,7 +120,7 @@ class DeleteBook: # {{{ def delete_book(self): if runtime.is_standalone_viewer: if self.reload_book: - ui_operations.reload_book() + self.overlay.view.reload_book() return self.show_working() view = self.overlay.view @@ -130,7 +130,7 @@ class DeleteBook: # {{{ view.ui.show_error(_('Failed to delete book'), _('Failed to delete book from local storage, click "Show details" for more information.'), errmsg) else: if self.reload_book: - ui_operations.reload_book() + self.overlay.view.reload_book() else: home() ) diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index 4aec340452..293887d55f 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -527,7 +527,7 @@ class View: elif data.name is 'toggle_read_aloud': self.toggle_read_aloud() elif data.name is 'reload_book': - ui_operations.reload_book() + self.reload_book() elif data.name is 'sync_book': self.overlay.sync_book() elif data.name is 'next_section': @@ -893,6 +893,14 @@ class View: 'library_id': self.book.key[0], 'book_id': self.book.key[1] + '', 'close_action': 'book_list', }, 'book_details')) + def clear_book_resource_caches(self): + self.loaded_resources = {} + self.content_popup_overlay.loaded_resources = {} + + def reload_book(self): + self.clear_book_resource_caches() + ui_operations.reload_book() + def display_book(self, book, initial_position, is_redisplay): self.hide_overlays() self.iframe.focus() @@ -902,10 +910,9 @@ class View: if self.book: self.iframe_wrapper.reset() self.content_popup_overlay.reset() - self.loaded_resources = {} - self.content_popup_overlay.loaded_resources = {} + self.clear_book_resource_caches() self.timers.start_book(book) - self.search_overlay.clear_caches(book) + self.search_overlay.clear_caches(book) # could be a reload unkey = username_key(get_interface_data().username) self.book = current_book.book = book hl = None From 3eed23619e41fb8c00c42ba8ea7a8155e0908aa7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 20:57:11 +0530 Subject: [PATCH 0272/2055] dont clear search caches on a redisplay --- src/pyj/read_book/view.pyj | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index 293887d55f..d17bc911fc 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -906,13 +906,16 @@ class View: self.iframe.focus() is_current_book = self.book and self.book.key == book.key self.book_load_started = True - if not is_current_book: + if is_current_book: + if not is_redisplay: + self.search_overlay.clear_caches(book) # could be a reload + else: if self.book: self.iframe_wrapper.reset() self.content_popup_overlay.reset() self.clear_book_resource_caches() self.timers.start_book(book) - self.search_overlay.clear_caches(book) # could be a reload + self.search_overlay.clear_caches(book) unkey = username_key(get_interface_data().username) self.book = current_book.book = book hl = None From 3121517b510598b233983d45bf4dacb619c06ea6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 21:18:26 +0530 Subject: [PATCH 0273/2055] Allow switching back to SAPI speech client via a "plugin tweak" --- src/calibre/gui2/tts/implementation.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/tts/implementation.py b/src/calibre/gui2/tts/implementation.py index deba410c80..f72d6ace18 100644 --- a/src/calibre/gui2/tts/implementation.py +++ b/src/calibre/gui2/tts/implementation.py @@ -4,7 +4,11 @@ from calibre.constants import iswindows, ismacos if iswindows: - from .windows import Client + from calibre.utils.config_base import tweaks + if tweaks.get('prefer_winsapi'): + from .windows_sapi import Client + else: + from .windows import Client elif ismacos: from .macos import Client else: From 3b924ecb04de8f382302a6fb031af70a36f6b1dd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 21:29:32 +0530 Subject: [PATCH 0274/2055] Dont try to cancel non cancelable event --- src/pyj/read_book/touch.pyj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyj/read_book/touch.pyj b/src/pyj/read_book/touch.pyj index b26c11f642..4265d0a846 100644 --- a/src/pyj/read_book/touch.pyj +++ b/src/pyj/read_book/touch.pyj @@ -213,7 +213,8 @@ class TouchHandler: self.reset_handlers() def handle_touchcancel(self, ev): - ev.preventDefault(), ev.stopPropagation() + if ev.cancelable: + ev.preventDefault(), ev.stopPropagation() for touch in ev.changedTouches: tid = touch_id(touch) # noqa: unused-local v'delete self.ongoing_touches[tid]' From bd24c88f8cf3a21bd6336ee7307b17eff0757e2c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 21:31:07 +0530 Subject: [PATCH 0275/2055] ... --- src/pyj/read_book/touch.pyj | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pyj/read_book/touch.pyj b/src/pyj/read_book/touch.pyj index 4265d0a846..11b01e8888 100644 --- a/src/pyj/read_book/touch.pyj +++ b/src/pyj/read_book/touch.pyj @@ -180,7 +180,9 @@ class TouchHandler: self.start_hold_timer() def handle_touchstart(self, ev): - ev.preventDefault(), ev.stopPropagation() + if ev.cancelable: + ev.preventDefault() + ev.stopPropagation() self.prune_expired_touches() for touch in ev.changedTouches: self.ongoing_touches[touch_id(touch)] = copy_touch(touch) @@ -193,7 +195,9 @@ class TouchHandler: self.start_hold_timer() def handle_touchmove(self, ev): - ev.preventDefault(), ev.stopPropagation() + if ev.cancelable: + ev.preventDefault() + ev.stopPropagation() for touch in ev.changedTouches: t = self.ongoing_touches[touch_id(touch)] if t: @@ -201,7 +205,9 @@ class TouchHandler: self.dispatch_gesture() def handle_touchend(self, ev): - ev.preventDefault(), ev.stopPropagation() + if ev.cancelable: + ev.preventDefault() + ev.stopPropagation() for touch in ev.changedTouches: t = self.ongoing_touches[touch_id(touch)] if t: @@ -214,7 +220,8 @@ class TouchHandler: def handle_touchcancel(self, ev): if ev.cancelable: - ev.preventDefault(), ev.stopPropagation() + ev.preventDefault() + ev.stopPropagation() for touch in ev.changedTouches: tid = touch_id(touch) # noqa: unused-local v'delete self.ongoing_touches[tid]' From ae5dbbe78619c10278932254edd29e9518ccdb04 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 21:33:35 +0530 Subject: [PATCH 0276/2055] I hate browsers --- src/pyj/read_book/touch.pyj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pyj/read_book/touch.pyj b/src/pyj/read_book/touch.pyj index 11b01e8888..8551e2a4b4 100644 --- a/src/pyj/read_book/touch.pyj +++ b/src/pyj/read_book/touch.pyj @@ -180,7 +180,7 @@ class TouchHandler: self.start_hold_timer() def handle_touchstart(self, ev): - if ev.cancelable: + if jstype(ev.cancelable) is not 'boolean' or ev.cancelable: ev.preventDefault() ev.stopPropagation() self.prune_expired_touches() @@ -195,7 +195,7 @@ class TouchHandler: self.start_hold_timer() def handle_touchmove(self, ev): - if ev.cancelable: + if jstype(ev.cancelable) is not 'boolean' or ev.cancelable: ev.preventDefault() ev.stopPropagation() for touch in ev.changedTouches: @@ -205,7 +205,7 @@ class TouchHandler: self.dispatch_gesture() def handle_touchend(self, ev): - if ev.cancelable: + if jstype(ev.cancelable) is not 'boolean' or ev.cancelable: ev.preventDefault() ev.stopPropagation() for touch in ev.changedTouches: @@ -219,7 +219,7 @@ class TouchHandler: self.reset_handlers() def handle_touchcancel(self, ev): - if ev.cancelable: + if jstype(ev.cancelable) is not 'boolean' or ev.cancelable: ev.preventDefault() ev.stopPropagation() for touch in ev.changedTouches: From c8cbe7a4968b7c19d02be9ae93612b8c0fa52708 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Feb 2023 21:54:15 +0530 Subject: [PATCH 0277/2055] ... --- src/pyj/read_book/touch.pyj | 2 +- src/pyj/read_book/ui.pyj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyj/read_book/touch.pyj b/src/pyj/read_book/touch.pyj index 8551e2a4b4..6b8612feac 100644 --- a/src/pyj/read_book/touch.pyj +++ b/src/pyj/read_book/touch.pyj @@ -242,7 +242,7 @@ class TouchHandler: self.handle_gesture(gesture) def inch_in_pixels(): - ans = inch_in_pixels().ans + ans = inch_in_pixels.ans if not ans: ans = inch_in_pixels.ans = max(2, get_unit_size_in_pixels('in')) return ans diff --git a/src/pyj/read_book/ui.pyj b/src/pyj/read_book/ui.pyj index 01c0aa6c57..50c13eccee 100644 --- a/src/pyj/read_book/ui.pyj +++ b/src/pyj/read_book/ui.pyj @@ -162,7 +162,7 @@ class ReadUI: div.appendChild(E.div(style='padding: 2ex 2rem; display:table; margin: auto')) div = div.lastChild dp = E.div() - create_simple_dialog_markup(title, _('Could not open {}. {}').format(book, msg), details, 'bug', '', dp) + create_simple_dialog_markup(title, _('Could not open {}. {}').format(book, msg), details, 'bug', '', 'red', dp) div.appendChild(dp) div.appendChild(E.div( style='margin-top: 1ex; padding-top: 1ex; border-top: solid 1px currentColor', From 08ccf8ad4b50546b6b73d41d3619e11eb4372df9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 3 Feb 2023 07:29:48 +0530 Subject: [PATCH 0278/2055] Change the default shortcuts for reload book as they conflict with browser reload shortcuts --- src/pyj/read_book/shortcuts.pyj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyj/read_book/shortcuts.pyj b/src/pyj/read_book/shortcuts.pyj index 3d9e00e415..1ed91960d4 100644 --- a/src/pyj/read_book/shortcuts.pyj +++ b/src/pyj/read_book/shortcuts.pyj @@ -315,7 +315,7 @@ def common_shortcuts(): # {{{ ), 'reload_book': desc( - v"['F5', 'Ctrl+r']", + v"['Ctrl+Alt+F5', 'Ctrl+Alt+r']", 'ui', _('Reload book'), ), From 839f977566b6246b6d80eff7088fa1a5a56a9be4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 3 Feb 2023 07:41:49 +0530 Subject: [PATCH 0279/2055] version 6.12.0 --- Changelog.txt | 62 ++++++++++++++++++++++++++++++++++++++++ src/calibre/constants.py | 2 +- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/Changelog.txt b/Changelog.txt index c5cbba21dd..e1bdb05a31 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -23,6 +23,68 @@ # - title by author # }}} +{{{ 6.12.0 2023-02-03 + +:: new features + +- E-book viewer: Read aloud: On Windows switch to using the new Microsoft speech subsystem with access to more voices + + Note that this means that old voice, speed and audio devices setting will not be used so a reconfiguration might be needed. + +- [2003712] calibre:// URL scheme: allow specifying a Virtual library for show_book URLs + +- [2003227] Add by ISBN: Allow adding using identifiers other than ISBN as well + +- Update bundled Qt to 6.4 this means calibre on macOS is now only supported on Big Sur and newer + +- Spell check dialog: Allow up and down arrow keys to work regardless of focus + +- [2002257] Allow multiple Template tester dialogs + +:: bug fixes + +- Windows MTP device driver: Ignore failure to enumerate objects inside non-root folders + + There are apparently a lot of devices out there that fail in this way. + So rather than aborting the scan simply ignore the folder. + +- Book list: Fix a regression in the previous release that broke dragging to select multiple books + +- [2004197] Content server viewer: Fix reload book not actually reloading until the browser is also refreshed + +- [2003916] E-book viewer: Fix occasional false warning about highlight being overwritten + +- [2003908] E-book viewer: Fix detection of selected highlights when all text is selected + +- [2003729] Fix an error when embedding metadata into a large number of books + +- [2004522] When updating metadata in EPUB 2 files and no language is specified, do not remove the tag as this causes epubcheck to complain. Instead set the language to "und" + +- [2004083] Wireless device driver: Remove the timeout for initial connection + +- [2003652] Use an icon rather than a color to report errors in fields and the search box + +- Conversion dialog: Regex builder: Workaround bug in Qt that prevented searching for non breaking spaces in the wizard used to test search expressions + +- [2002864] Spell check dialog: move down after correcting word, not up + +- [2002534] Get books: Fix Mobileread store plugin not working + +:: improved recipes +- Jerusalem Post +- LiveMint +- The Seattle Times +- India Today +- Outlook Magazine +- Live Mint +- Irish Independent +- Irish Times + +:: new recipes +- Boston Globe Print Edition by unkn0wn +- Observer Reach Foundation by unkn0wn +}}} + {{{ 6.11.0 2023-01-06 :: new features diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 9691910808..450a1078d4 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -5,7 +5,7 @@ from functools import lru_cache import sys, locale, codecs, os, collections, collections.abc __appname__ = 'calibre' -numeric_version = (6, 11, 0) +numeric_version = (6, 12, 0) __version__ = '.'.join(map(str, numeric_version)) git_version = None __author__ = "Kovid Goyal " From fed1d1b31397feff8bf1d9b021f22fc3724b13b0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 3 Feb 2023 16:58:13 +0530 Subject: [PATCH 0280/2055] Make newer() robust against missing source files --- setup/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/setup/__init__.py b/setup/__init__.py index a1476b4f3b..4d353a4ac9 100644 --- a/setup/__init__.py +++ b/setup/__init__.py @@ -47,8 +47,12 @@ def newer(targets, sources): if not os.path.exists(f): return True ttimes = map(lambda x: os.stat(x).st_mtime, targets) - stimes = map(lambda x: os.stat(x).st_mtime, sources) - newest_source, oldest_target = max(stimes), min(ttimes) + oldest_target = max(ttimes) + try: + stimes = map(lambda x: os.stat(x).st_mtime, sources) + newest_source = max(stimes) + except FileNotFoundError: + newest_source = oldest_target +1 return newest_source > oldest_target From 118ba067006726af31f9adcc61a05bbadadccadd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 3 Feb 2023 17:46:08 +0530 Subject: [PATCH 0281/2055] Improve hover highlight color in tree views. Fixes #2004621 [Dark Mode: Invisible arrow when hovering in Tag Browser](https://bugs.launchpad.net/calibre/+bug/2004621) --- src/calibre/gui2/palette.py | 16 ++++++++++++++++ .../gui2/progress_indicator/CalibreStyle.cpp | 2 +- src/calibre/gui2/tag_browser/view.py | 8 ++------ src/calibre/gui2/viewer/toc.py | 8 +------- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/calibre/gui2/palette.py b/src/calibre/gui2/palette.py index a592404ff3..9a1d0cb0de 100644 --- a/src/calibre/gui2/palette.py +++ b/src/calibre/gui2/palette.py @@ -445,3 +445,19 @@ class PaletteManager(QObject): print('Detected a spontaneous palette change by Qt, reverting it', file=sys.stderr) self.set_palette(pal) self.on_palette_change() + + def tree_view_hover_style(self): + g1, g2 = '#e7effd', '#cbdaf1' + border_size = '1px' + if self.is_dark_theme: + c = QApplication.instance().palette().color(QPalette.ColorRole.Highlight) + c = c.lighter(180) + g1 = g2 = c.name() + border_size = '0px' + return f''' + QTreeView::item:hover {{ + background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 {g1}, stop: 1 {g2}); + border: {border_size} solid #bfcde4; + border-radius: 6px; + }} + ''' diff --git a/src/calibre/gui2/progress_indicator/CalibreStyle.cpp b/src/calibre/gui2/progress_indicator/CalibreStyle.cpp index c72adf1eef..e7727c3622 100644 --- a/src/calibre/gui2/progress_indicator/CalibreStyle.cpp +++ b/src/calibre/gui2/progress_indicator/CalibreStyle.cpp @@ -203,7 +203,7 @@ void CalibreStyle::drawPrimitive(PrimitiveElement element, const QStyleOption * QColor color = vopt->palette.color(QPalette::Normal, QPalette::Highlight); QStyleOptionViewItem opt = QStyleOptionViewItem(*vopt); if (is_color_dark(option->palette.color(QPalette::Window))) { - color = color.lighter(190); + color = color.lighter(180); } else { color = color.lighter(125); } diff --git a/src/calibre/gui2/tag_browser/view.py b/src/calibre/gui2/tag_browser/view.py index 326bb5949c..9da187b594 100644 --- a/src/calibre/gui2/tag_browser/view.py +++ b/src/calibre/gui2/tag_browser/view.py @@ -238,13 +238,9 @@ class TagsView(QTreeView): # {{{ padding-bottom:PADex; } - QTreeView::item:hover { - background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #e7effd, stop: 1 #cbdaf1); - border: 1px solid #bfcde4; - border-radius: 6px; - } '''.replace('PAD', str(gprefs['tag_browser_item_padding'])) + ( - '' if gprefs['tag_browser_old_look'] else stylish_tb)) + '' if gprefs['tag_browser_old_look'] else stylish_tb) + QApplication.instance().palette_manager.tree_view_hover_style() + ) def set_look_and_feel(self, first=False): self.set_style_sheet() diff --git a/src/calibre/gui2/viewer/toc.py b/src/calibre/gui2/viewer/toc.py index e105464e07..05c7373993 100644 --- a/src/calibre/gui2/viewer/toc.py +++ b/src/calibre/gui2/viewer/toc.py @@ -88,13 +88,7 @@ class TOCView(QTreeView): padding-bottom:0.5ex; } - QTreeView::item:hover { - background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #e7effd, stop: 1 #cbdaf1); - color: black; - border: 1px solid #bfcde4; - border-radius: 6px; - } - ''') + ''' + QApplication.instance().palette_manager.tree_view_hover_style()) def mouseMoveEvent(self, ev): if self.indexAt(ev.pos()).isValid(): From d6068e031609c8e0aa69203116fc865afb7a2097 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 3 Feb 2023 19:46:15 +0530 Subject: [PATCH 0282/2055] Also draw the arrow dark when in dark theme with highlight --- .../gui2/progress_indicator/CalibreStyle.cpp | 85 +++++++++++++++++++ src/calibre/gui2/tag_browser/view.py | 1 + src/calibre/gui2/viewer/toc.py | 1 + 3 files changed, 87 insertions(+) diff --git a/src/calibre/gui2/progress_indicator/CalibreStyle.cpp b/src/calibre/gui2/progress_indicator/CalibreStyle.cpp index e7727c3622..b54c6de5b1 100644 --- a/src/calibre/gui2/progress_indicator/CalibreStyle.cpp +++ b/src/calibre/gui2/progress_indicator/CalibreStyle.cpp @@ -7,6 +7,7 @@ #include "QProgressIndicator.h" #include +#include #include #include #include @@ -61,6 +62,82 @@ static inline QByteArray detectDesktopEnvironment() return QByteArrayLiteral("UNKNOWN"); } +#ifdef Q_OS_DARWIN +static const qreal qstyleBaseDpi = 72; +#else +static const qreal qstyleBaseDpi = 96; +#endif + +qreal dpiScaled(qreal value, qreal dpi) +{ + return value * dpi / qstyleBaseDpi; +} + +static QPixmap styleCachePixmap(const QSize &size) +{ + const qreal pixelRatio = qApp->devicePixelRatio(); + QPixmap cachePixmap = QPixmap(size * pixelRatio); + cachePixmap.setDevicePixelRatio(pixelRatio); + return cachePixmap; +} + + +static void draw_arrow(Qt::ArrowType type, QPainter *painter, const QStyleOption *option, const QRect &rect, const QColor &color) +{ + if (rect.isEmpty()) + return; + + const qreal dpi = option->fontMetrics.fontDpi(); + const int arrowWidth = int(dpiScaled(14, dpi)); + const int arrowHeight = int(dpiScaled(8, dpi)); + + const int arrowMax = qMin(arrowHeight, arrowWidth); + const int rectMax = qMin(rect.height(), rect.width()); + const int size = qMin(arrowMax, rectMax); + + QPixmap cachePixmap; + QString cacheKey = QString("calibre-tree-view-arrow-%1-%2-%3").arg((unsigned long)color.rgba()).arg((unsigned long)type).arg(size); + if (!QPixmapCache::find(cacheKey, &cachePixmap)) { + cachePixmap = styleCachePixmap(rect.size()); + cachePixmap.fill(Qt::transparent); + QPainter cachePainter(&cachePixmap); + + QRectF arrowRect; + arrowRect.setWidth(size); + arrowRect.setHeight(arrowHeight * size / arrowWidth); + if (type == Qt::LeftArrow || type == Qt::RightArrow) + arrowRect = arrowRect.transposed(); + arrowRect.moveTo((rect.width() - arrowRect.width()) / 2.0, + (rect.height() - arrowRect.height()) / 2.0); + + QPolygonF triangle; + triangle.reserve(3); + switch (type) { + case Qt::DownArrow: + triangle << arrowRect.topLeft() << arrowRect.topRight() << QPointF(arrowRect.center().x(), arrowRect.bottom()); + break; + case Qt::RightArrow: + triangle << arrowRect.topLeft() << arrowRect.bottomLeft() << QPointF(arrowRect.right(), arrowRect.center().y()); + break; + case Qt::LeftArrow: + triangle << arrowRect.topRight() << arrowRect.bottomRight() << QPointF(arrowRect.left(), arrowRect.center().y()); + break; + default: + triangle << arrowRect.bottomLeft() << arrowRect.bottomRight() << QPointF(arrowRect.center().x(), arrowRect.top()); + break; + } + + cachePainter.setPen(Qt::NoPen); + cachePainter.setBrush(color); + cachePainter.setRenderHint(QPainter::Antialiasing); + cachePainter.drawPolygon(triangle); + + QPixmapCache::insert(cacheKey, cachePixmap); + } + + painter->drawPixmap(rect, cachePixmap); +} + CalibreStyle::CalibreStyle(int transient_scroller) : QProxyStyle(QString::fromUtf8("Fusion")), transient_scroller(transient_scroller) { setObjectName(QString("calibre")); @@ -212,6 +289,14 @@ void CalibreStyle::drawPrimitive(PrimitiveElement element, const QStyleOption * } break; // }}} + case PE_IndicatorBranch: // {{{ + if (option->state & State_MouseOver && option->state & State_Children && widget && widget->property("hovered_item_is_highlighted").toBool() && is_color_dark(option->palette.color(QPalette::Window))) {; + if (option->rect.width() <= 1 || option->rect.height() <= 1) return; + Qt::ArrowType arrow = Qt::ArrowType::DownArrow; + if (!(option->state & State_Open)) arrow = Qt::ArrowType::RightArrow; + draw_arrow(arrow, painter, option, option->rect, QColor(Qt::black)); + return; + } break; // }}} case PE_IndicatorToolBarSeparator: // {{{ // Make toolbar separators stand out a bit more in dark themes { diff --git a/src/calibre/gui2/tag_browser/view.py b/src/calibre/gui2/tag_browser/view.py index 9da187b594..2e0d22a4a0 100644 --- a/src/calibre/gui2/tag_browser/view.py +++ b/src/calibre/gui2/tag_browser/view.py @@ -241,6 +241,7 @@ class TagsView(QTreeView): # {{{ '''.replace('PAD', str(gprefs['tag_browser_item_padding'])) + ( '' if gprefs['tag_browser_old_look'] else stylish_tb) + QApplication.instance().palette_manager.tree_view_hover_style() ) + self.setProperty('hovered_item_is_highlighted', True) def set_look_and_feel(self, first=False): self.set_style_sheet() diff --git a/src/calibre/gui2/viewer/toc.py b/src/calibre/gui2/viewer/toc.py index 05c7373993..5eca6cc0c0 100644 --- a/src/calibre/gui2/viewer/toc.py +++ b/src/calibre/gui2/viewer/toc.py @@ -89,6 +89,7 @@ class TOCView(QTreeView): } ''' + QApplication.instance().palette_manager.tree_view_hover_style()) + self.setProperty('hovered_item_is_highlighted', True) def mouseMoveEvent(self, ev): if self.indexAt(ev.pos()).isValid(): From 5160369cb4ad5f82c026ae922cd9379ef55dfa56 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 3 Feb 2023 22:19:29 +0530 Subject: [PATCH 0283/2055] ... --- src/calibre/gui2/progress_indicator/CalibreStyle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/progress_indicator/CalibreStyle.cpp b/src/calibre/gui2/progress_indicator/CalibreStyle.cpp index b54c6de5b1..fc769d59b8 100644 --- a/src/calibre/gui2/progress_indicator/CalibreStyle.cpp +++ b/src/calibre/gui2/progress_indicator/CalibreStyle.cpp @@ -87,7 +87,7 @@ static void draw_arrow(Qt::ArrowType type, QPainter *painter, const QStyleOption if (rect.isEmpty()) return; - const qreal dpi = option->fontMetrics.fontDpi(); + const qreal dpi = std::max(76.0, option->fontMetrics.fontDpi()); const int arrowWidth = int(dpiScaled(14, dpi)); const int arrowHeight = int(dpiScaled(8, dpi)); From 01bb16a692ce8b1064382682fc9ddb393ee394f3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 4 Feb 2023 13:01:55 +0530 Subject: [PATCH 0284/2055] Update Bloomberg --- recipes/bloomberg.recipe | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/recipes/bloomberg.recipe b/recipes/bloomberg.recipe index 75f0812f09..ac57b37a74 100644 --- a/recipes/bloomberg.recipe +++ b/recipes/bloomberg.recipe @@ -34,12 +34,15 @@ class Bloomberg(BasicNewsRecipe): br.open(url) except Exception as e: url = e.hdrs.get('location') - html = br.open(url).read() + soup = self.index_to_soup(url) + link = soup.find('a', attrs={'href':lambda x: x and x.startswith('https://www.bloomberg.com')}) + html = br.open(link['href']).read() pt = PersistentTemporaryFile('.html') pt.write(html) pt.close() return pt.name + def get_browser(self): br = browser() br.set_handle_redirect(False) From 4ef38e686cfb2f67575eaf7470d9a188bea95fb2 Mon Sep 17 00:00:00 2001 From: xxyzz Date: Sat, 4 Feb 2023 15:57:28 +0800 Subject: [PATCH 0285/2055] Ignore AppleDouble files on ebook device When macOS sends file to e-reader device, it also sends a AppleDouble format file has the same name with "._" prefix. Ignore these files so they won't be added to the device metadata file. --- src/calibre/devices/usbms/driver.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index 0fc1e9ebd7..c398aaad8c 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -239,6 +239,9 @@ class USBMS(CLI, Device): def update_booklist(filename, path, prefix): changed = False + # Ignore AppleDouble files + if filename.startswith("._"): + return False if path_to_ext(filename) in all_formats and self.is_allowed_book_file(filename, path, prefix): try: lpath = os.path.join(path, filename).partition(self.normalize_path(prefix))[2] From 305c45f6727ef41bd20601f67994e6bc6ebf081f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 5 Feb 2023 09:40:01 +0530 Subject: [PATCH 0286/2055] Edit book: Spell Check dialog: Fix second word not getting selected when after first word is fixed --- src/calibre/gui2/tweak_book/spell.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/tweak_book/spell.py b/src/calibre/gui2/tweak_book/spell.py index 695f9a5794..0e719ed80a 100644 --- a/src/calibre/gui2/tweak_book/spell.py +++ b/src/calibre/gui2/tweak_book/spell.py @@ -1284,6 +1284,7 @@ class SpellCheck(Dialog): self.change_requested.emit(w, new_word) def do_change_word(self, w, new_word): + current_row = self.words_view.currentIndex().row() self.undo_cache.clear() changed_files = replace_word(current_container(), new_word, self.words_model.words[w], w[1], undo_cache=self.undo_cache) if changed_files: @@ -1292,7 +1293,7 @@ class SpellCheck(Dialog): row = self.words_model.row_for_word(w) if row == -1: row = self.words_view.currentIndex().row() - if row < self.words_model.rowCount() - 1: + if row < self.words_model.rowCount() - 1 and current_row > 0: row += 1 if row > -1: self.words_view.highlight_row(row) From 0eb533e17b77c0c4ac37774fa9fea5153898fb17 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 5 Feb 2023 13:55:27 +0530 Subject: [PATCH 0287/2055] Content server viewer: When long tapping images open them in an in-window popup instead of a separate window since many browser default to blocking new windows for stupid reasons. --- src/pyj/image_popup.pyj | 166 +++++++++++++++++++++++++++++++++++++++ src/pyj/read_book/ui.pyj | 3 +- 2 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 src/pyj/image_popup.pyj diff --git a/src/pyj/image_popup.pyj b/src/pyj/image_popup.pyj new file mode 100644 index 0000000000..49bcd1bd10 --- /dev/null +++ b/src/pyj/image_popup.pyj @@ -0,0 +1,166 @@ +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2023, Kovid Goyal +from __python__ import bound_methods, hash_literals + +from elementmaker import E + +from ajax import absolute_path +from dom import add_extra_css, svgicon, unique_id +from gettext import gettext as _ +from popups import MODAL_Z_INDEX +from utils import debounce + +add_extra_css(def(): + bg = '#333' + fg = '#ddd' + button_bg = '#000' + css = f'.calibre-image-popup {{ background-color: {bg}; color: {fg}; }}' + css += f'.calibre-image-popup a {{ background-color: {button_bg}; }}' + css += f'.calibre-image-popup a:hover {{ background-color: {fg}; color: {button_bg}; }}' + return css +) + + +def fit_image(width, height, pwidth, pheight): + ''' + Fit image in box of width pwidth and height pheight. + @param width: Width of image + @param height: Height of image + @param pwidth: Width of box + @param pheight: Height of box + @return: scaled, new_width, new_height. scaled is True iff new_width and/or new_height is different from width or height. + ''' + scaled = height > pheight or width > pwidth + if height > pheight: + corrf = pheight / float(height) + width, height = Math.floor(corrf*width), pheight + if width > pwidth: + corrf = pwidth / float(width) + width, height = pwidth, Math.floor(corrf*height) + if height > pheight: + corrf = pheight / float(height) + width, height = Math.floor(corrf*width), pheight + + return scaled, int(width), int(height) + + +class ImagePopup: + + def __init__(self): + self._container = None + self.container_id = unique_id('image-popup') + self.img = None + self.img_loading = False + self.img_ok = False + + @property + def container(self): + if self._container is None: + self._container = E.div( + style=f'display:none; position:absolute; left:0; top: 0; z-index: {MODAL_Z_INDEX}; width: 100vw; height: 100vh; padding: 0; margin: 0; border-width: 0; box-sizing: border-box', + id=self.container_id, tabindex='0', class_='calibre-image-popup', + E.div( + style='position: fixed; top: 0; left: 0; text-align: right; width: 100%; font-size: 200%; padding: 0.25ex; box-sizing: border-box', + E.a( + svgicon('close'), title=_('Close'), + style='padding: 0.25ex; display: inline-block; border-radius: 100%; cursor: pointer', + onclick=self.hide_container + ), + ), + E.canvas( + width=window.innerWidth + '', height=window.innerHeight + '', + style='width: 100%; height: 100%; margin: 0; padding: 0; border-width: 0; box-sizing: border-box; display: block', + aria_label='Popup view of image', + ), + ) + document.body.appendChild(self._container) + window.addEventListener('resize', debounce(self.resize_canvas, 250)) + return self._container + + @property + def canvas(self): + return self.container.getElementsByTagName('canvas')[0] + + def resize_canvas(self): + c = self.canvas + dpr = Math.max(1, window.devicePixelRatio) + c.width = Math.ceil(window.innerWidth * dpr) + c.height = Math.ceil(window.innerHeight * dpr) + c.style.width = (c.width / dpr) + 'px' + c.style.height = (c.height / dpr) + 'px' + ctx = c.getContext('2d') + ctx.setTransform(1, 0, 0, 1, 0, 0) + ctx.scale(dpr, dpr) + ctx.fillStyle = ctx.strokeStyle = '#eee' + ctx.font = '16px sans-serif' + self.update_canvas() + + def show_container(self): + self.container.style.display = 'block' + + def hide_container(self): + self.container.style.display = 'none' + + def show_url(self, url): + self.img = Image() + self.img.addEventListener('load', self.image_loaded) + self.img.addEventListener('error', self.image_failed) + self.img_loading = True + self.img_ok = True + self.img.src = url + self.show_container() + self.resize_canvas() + self.update_canvas() + + def update_canvas(self): + canvas = self.canvas + ctx = canvas.getContext('2d') + ctx.clearRect(0, 0, canvas.width, canvas.height) + dpr = Math.max(1, window.devicePixelRatio) + canvas_width, canvas_height = canvas.width / dpr, canvas.height / dpr + + def draw_centered_text(text): + tm = ctx.measureText(text) + x = Math.max(0, (canvas_width - tm.width) / 2) + y = (canvas_height - 16) / 2 + ctx.fillText(text, x, y) + + if self.img_loading: + draw_centered_text(_('Loading image, please wait…')) + return + if not self.img_ok: + draw_centered_text(_('Loading the image failed')) + return + + def draw_full_image_fit_to_canvas(): + scaled, width, height = fit_image(self.img.width, self.img.height, canvas_width, canvas_height) + scaled + x = (canvas_width - width) / 2 + y = (canvas_height - height) / 2 + ctx.drawImage(self.img, x, y, width, height) + + draw_full_image_fit_to_canvas() + + def image_loaded(self): + self.img_loading = False + self.img_ok = True + self.update_canvas() + + def image_failed(self): + self.img_loading = False + self.img_ok = False + self.update_canvas() + + +popup = None + + +def show_image(url): + nonlocal popup + if popup is None: + popup = ImagePopup() + popup.show_url(url) + + +def develop(container): + show_image(absolute_path('get/cover/1698')) diff --git a/src/pyj/read_book/ui.pyj b/src/pyj/read_book/ui.pyj index 50c13eccee..01a3e721de 100644 --- a/src/pyj/read_book/ui.pyj +++ b/src/pyj/read_book/ui.pyj @@ -21,6 +21,7 @@ from read_book.view import View from session import get_interface_data from utils import debounce, full_screen_element, human_readable, request_full_screen from widgets import create_button +from image_popup import show_image RENDER_VERSION = __RENDER_VERSION__ MATHJAX_VERSION = "__MATHJAX_VERSION__" @@ -219,7 +220,7 @@ class ReadUI: ui_operations.get_file( self.view.book, image_file_name, def(blob, name, mimetype): url = window.URL.createObjectURL(blob) - window.open(url) + show_image(url) ) def load_book(self, library_id, book_id, fmt, metadata, force_reload): From 20bcb4f416bd786de0e4824b77565a357b7723f4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 5 Feb 2023 14:24:54 +0530 Subject: [PATCH 0288/2055] Button to toggle fit to window --- imgsrc/srv/fit-to-screen.svg | 1 + src/pyj/image_popup.pyj | 39 +++++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 imgsrc/srv/fit-to-screen.svg diff --git a/imgsrc/srv/fit-to-screen.svg b/imgsrc/srv/fit-to-screen.svg new file mode 100644 index 0000000000..9236e069df --- /dev/null +++ b/imgsrc/srv/fit-to-screen.svg @@ -0,0 +1 @@ + diff --git a/src/pyj/image_popup.pyj b/src/pyj/image_popup.pyj index 49bcd1bd10..f77a2ac527 100644 --- a/src/pyj/image_popup.pyj +++ b/src/pyj/image_popup.pyj @@ -13,10 +13,10 @@ from utils import debounce add_extra_css(def(): bg = '#333' fg = '#ddd' - button_bg = '#000' + button_bg = '#333' css = f'.calibre-image-popup {{ background-color: {bg}; color: {fg}; }}' - css += f'.calibre-image-popup a {{ background-color: {button_bg}; }}' - css += f'.calibre-image-popup a:hover {{ background-color: {fg}; color: {button_bg}; }}' + css += f'.calibre-image-popup a {{ padding: 0.25ex; display: inline-block; border-radius: 100%; cursor: pointer; background-color: {button_bg}; }}' + css += f'.calibre-image-popup.fit-to-window a.fit-to-window {{ color: {button_bg}; background-color: {fg}; }}' return css ) @@ -60,10 +60,13 @@ class ImagePopup: style=f'display:none; position:absolute; left:0; top: 0; z-index: {MODAL_Z_INDEX}; width: 100vw; height: 100vh; padding: 0; margin: 0; border-width: 0; box-sizing: border-box', id=self.container_id, tabindex='0', class_='calibre-image-popup', E.div( - style='position: fixed; top: 0; left: 0; text-align: right; width: 100%; font-size: 200%; padding: 0.25ex; box-sizing: border-box', + style='position: fixed; top: 0; left: 0; text-align: right; width: 100%; font-size: 200%; padding: 0.25ex; box-sizing: border-box; display: flex; justify-content: space-between', + E.a( + svgicon('fit-to-screen'), title=_('Fit image to screen'), class_='fit-to-window', + onclick=self.toggle_fit_to_window, + ), E.a( svgicon('close'), title=_('Close'), - style='padding: 0.25ex; display: inline-block; border-radius: 100%; cursor: pointer', onclick=self.hide_container ), ), @@ -81,6 +84,20 @@ class ImagePopup: def canvas(self): return self.container.getElementsByTagName('canvas')[0] + @property + def fit_to_window(self): + return self.container.classList.contains('fit-to-window') + + @fit_to_window.setter + def fit_to_window(self, val): + c = self.container + if val: + c.classList.add('fit-to-window') + c.querySelector('a.fit-to-window').title = _('Do not fit image to window') + else: + c.classList.remove('fit-to-window') + c.querySelector('a.fit-to-window').title = _('Fit image to window') + def resize_canvas(self): c = self.canvas dpr = Math.max(1, window.devicePixelRatio) @@ -107,10 +124,10 @@ class ImagePopup: self.img.addEventListener('error', self.image_failed) self.img_loading = True self.img_ok = True + self.fit_to_window = True self.img.src = url self.show_container() self.resize_canvas() - self.update_canvas() def update_canvas(self): canvas = self.canvas @@ -139,7 +156,15 @@ class ImagePopup: y = (canvas_height - height) / 2 ctx.drawImage(self.img, x, y, width, height) - draw_full_image_fit_to_canvas() + if self.fit_to_window: + draw_full_image_fit_to_canvas() + return + + ctx.drawImage(self.img, 0, 0) + + def toggle_fit_to_window(self): + self.fit_to_window ^= True + self.update_canvas() def image_loaded(self): self.img_loading = False From 7b7d2d44b1833325b0fed00e18dc74173083e845 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 5 Feb 2023 14:55:35 +0530 Subject: [PATCH 0289/2055] Keyboard controls for image popup --- src/pyj/image_popup.pyj | 87 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 4 deletions(-) diff --git a/src/pyj/image_popup.pyj b/src/pyj/image_popup.pyj index f77a2ac527..00cb45204a 100644 --- a/src/pyj/image_popup.pyj +++ b/src/pyj/image_popup.pyj @@ -59,6 +59,7 @@ class ImagePopup: self._container = E.div( style=f'display:none; position:absolute; left:0; top: 0; z-index: {MODAL_Z_INDEX}; width: 100vw; height: 100vh; padding: 0; margin: 0; border-width: 0; box-sizing: border-box', id=self.container_id, tabindex='0', class_='calibre-image-popup', + onkeydown=self.onkeydown, E.div( style='position: fixed; top: 0; left: 0; text-align: right; width: 100%; font-size: 200%; padding: 0.25ex; box-sizing: border-box; display: flex; justify-content: space-between', E.a( @@ -80,6 +81,76 @@ class ImagePopup: window.addEventListener('resize', debounce(self.resize_canvas, 250)) return self._container + def onkeydown(self, ev): + ev.preventDefault(), ev.stopPropagation() + console.log(ev.key) + if ev.key is ' ': + return self.toggle_fit_to_window() + if ev.key is 'Escape': + return self.hide_container() + if ev.key is 'ArrowDown': + return self.scroll_down((window.innerHeight - 10) if ev.getModifierState("Control") else 10) + if ev.key is 'PageDown': + return self.scroll_down(window.innerHeight-10) + if ev.key is 'ArrowUp': + return self.scroll_up((window.innerHeight - 10) if ev.getModifierState("Control") else 10) + if ev.key is 'PageUp': + return self.scroll_up(window.innerHeight-10) + if ev.key is 'ArrowLeft': + return self.scroll_left((window.innerWidth - 10) if ev.getModifierState("Control") else 10) + if ev.key is 'ArrowRight': + return self.scroll_right((window.innerWidth - 10) if ev.getModifierState("Control") else 10) + + @property + def vertical_max_bounce_distance(self): + return Math.min(60, window.innerHeight / 2) + + @property + def horizontal_max_bounce_distance(self): + return Math.min(60, window.innerWidth / 2) + + def scroll_down(self, amt): + if self.img_ok and not self.fit_to_window: + canvas_height = self.canvas_height + if self.img.height <= canvas_height: + return + miny = canvas_height - self.img.height - self.vertical_max_bounce_distance + y = Math.max(self.y - amt, miny) + if y is not self.y: + self.y = y + self.update_canvas() + + def scroll_up(self, amt): + if self.img_ok and not self.fit_to_window: + canvas_height = self.canvas_height + if self.img.height <= canvas_height: + return + y = Math.min(self.y + amt, self.vertical_max_bounce_distance) + if y is not self.y: + self.y = y + self.update_canvas() + + def scroll_right(self, amt): + if self.img_ok and not self.fit_to_window: + canvas_width = self.canvas_width + if self.img.width <= canvas_width: + return + minx = canvas_width - self.img.width - self.horizontal_max_bounce_distance + x = Math.max(self.x - amt, minx) + if x is not self.x: + self.x = x + self.update_canvas() + + def scroll_left(self, amt): + if self.img_ok and not self.fit_to_window: + canvas_width = self.canvas_width + if self.img.width <= canvas_width: + return + x = Math.min(self.x + amt, self.horizontal_max_bounce_distance) + if x is not self.x: + self.x = x + self.update_canvas() + @property def canvas(self): return self.container.getElementsByTagName('canvas')[0] @@ -123,18 +194,26 @@ class ImagePopup: self.img.addEventListener('load', self.image_loaded) self.img.addEventListener('error', self.image_failed) self.img_loading = True - self.img_ok = True + self.x = self.y = 0 + self.img_ok = False self.fit_to_window = True self.img.src = url self.show_container() self.resize_canvas() + @property + def canvas_width(self): + return self.canvas.width / Math.max(1, window.devicePixelRatio) + + @property + def canvas_height(self): + return self.canvas.height / Math.max(1, window.devicePixelRatio) + def update_canvas(self): canvas = self.canvas ctx = canvas.getContext('2d') ctx.clearRect(0, 0, canvas.width, canvas.height) - dpr = Math.max(1, window.devicePixelRatio) - canvas_width, canvas_height = canvas.width / dpr, canvas.height / dpr + canvas_width, canvas_height = self.canvas_width, self.canvas_height def draw_centered_text(text): tm = ctx.measureText(text) @@ -160,7 +239,7 @@ class ImagePopup: draw_full_image_fit_to_canvas() return - ctx.drawImage(self.img, 0, 0) + ctx.drawImage(self.img, self.x, self.y) def toggle_fit_to_window(self): self.fit_to_window ^= True From 3eecc86589b59ee2fb03d93829a0462d85ad5f62 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 5 Feb 2023 16:10:15 +0530 Subject: [PATCH 0290/2055] Implement touch gestures for panning image in popup --- src/pyj/image_popup.pyj | 46 +++++++++++++++++++++++++++++++-- src/pyj/read_book/flow_mode.pyj | 15 ++++++++--- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/pyj/image_popup.pyj b/src/pyj/image_popup.pyj index 00cb45204a..9ec425fd47 100644 --- a/src/pyj/image_popup.pyj +++ b/src/pyj/image_popup.pyj @@ -8,6 +8,8 @@ from ajax import absolute_path from dom import add_extra_css, svgicon, unique_id from gettext import gettext as _ from popups import MODAL_Z_INDEX +from read_book.flow_mode import FlickAnimator +from read_book.touch import TouchHandler, install_handlers from utils import debounce add_extra_css(def(): @@ -44,9 +46,14 @@ def fit_image(width, height, pwidth, pheight): return scaled, int(width), int(height) -class ImagePopup: +class ImagePopup(TouchHandler): def __init__(self): + TouchHandler.__init__(self) + self.flick_animator = fa = FlickAnimator() + fa.scroll_x = self.scroll_x + fa.scroll_y = self.scroll_y + fa.cancel_current_drag_scroll = def(): pass self._container = None self.container_id = unique_id('image-popup') self.img = None @@ -78,12 +85,12 @@ class ImagePopup: ), ) document.body.appendChild(self._container) + install_handlers(self.canvas, self) window.addEventListener('resize', debounce(self.resize_canvas, 250)) return self._container def onkeydown(self, ev): ev.preventDefault(), ev.stopPropagation() - console.log(ev.key) if ev.key is ' ': return self.toggle_fit_to_window() if ev.key is 'Escape': @@ -101,6 +108,27 @@ class ImagePopup: if ev.key is 'ArrowRight': return self.scroll_right((window.innerWidth - 10) if ev.getModifierState("Control") else 10) + def handle_gesture(self, gesture): + self.flick_animator.stop() + if gesture.type is 'swipe': + if not self.img_ok or self.fit_to_window: + return + if gesture.points.length > 1 and not gesture.is_held: + delta = gesture.points[-2] - gesture.points[-1] + if Math.abs(delta) >= 1: + if gesture.axis is 'vertical': + self.scroll_y(delta) + else: + self.scroll_x(delta) + if not gesture.active and not gesture.is_held: + self.flick_animator.start(gesture) + if gesture.type is 'pinch' and self.img_ok: + if gesture.direction is 'in': + self.fit_to_window = True + else: + self.fit_to_window = False + self.update_canvas() + @property def vertical_max_bounce_distance(self): return Math.min(60, window.innerHeight / 2) @@ -151,6 +179,18 @@ class ImagePopup: self.x = x self.update_canvas() + def scroll_y(self, amt): + if amt < 0: + self.scroll_up(-amt) + elif amt > 0: + self.scroll_down(amt) + + def scroll_x(self, amt): + if amt < 0: + self.scroll_left(-amt) + elif amt > 0: + self.scroll_right(amt) + @property def canvas(self): return self.container.getElementsByTagName('canvas')[0] @@ -184,9 +224,11 @@ class ImagePopup: self.update_canvas() def show_container(self): + self.flick_animator.stop() self.container.style.display = 'block' def hide_container(self): + self.flick_animator.stop() self.container.style.display = 'none' def show_url(self, url): diff --git a/src/pyj/read_book/flow_mode.pyj b/src/pyj/read_book/flow_mode.pyj index d01c367df7..1354c44b5e 100644 --- a/src/pyj/read_book/flow_mode.pyj +++ b/src/pyj/read_book/flow_mode.pyj @@ -422,8 +422,11 @@ class FlickAnimator: def __init__(self): self.animation_id = None - def start(self, gesture): + def cancel_current_drag_scroll(self): cancel_drag_scroll() + + def start(self, gesture): + self.cancel_current_drag_scroll() self.vertical = gesture.axis is 'vertical' now = window.performance.now() points = times = None @@ -449,11 +452,17 @@ class FlickAnimator: if abs(delta) >= 1: delta = Math.round(delta) if self.vertical: - window.scrollBy(0, delta) + self.scroll_y(delta) else: - window.scrollBy(delta, 0) + self.scroll_x(delta) self.animation_id = window.requestAnimationFrame(self.auto_scroll) + def scroll_y(self, delta): + window.scrollBy(0, delta) + + def scroll_x(self, delta): + window.scrollBy(delta, 0) + def stop(self): if self.animation_id is not None: window.cancelAnimationFrame(self.animation_id) From 9531c8611723a5cc2eb732250c2a198dfc88f4d8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 5 Feb 2023 19:56:26 +0530 Subject: [PATCH 0291/2055] string changes --- Changelog.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Changelog.txt b/Changelog.txt index e1bdb05a31..6a108ac27b 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -119,7 +119,7 @@ - Content server home page: When showing recently read books from across devices hide the entries for which loading the cover fails -- Windows Text-to-speech: Dont fail to configure if one of the voices has no defined language +- Windows Text-to-speech: Do not fail to configure if one of the voices has no defined language - Fix a regression in calibre 5 that broke using a file for the --extra-css option of ebook-convert @@ -173,7 +173,7 @@ - Book list: Workaround for change in Qt 6 behavior where clicking on an already selected row does not deselect other rows -- [1998165] Windows: Fix a regression in calibre 6 causing Open With to not extract icons from EXE files +- [1998165] Windows: Fix a regression in calibre 6 causing Open with to not extract icons from EXE files :: improved recipes - Indian Express From d0cbe42b3057b8790af5bdcb7942dc5b63a8302c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 6 Feb 2023 18:47:29 +0530 Subject: [PATCH 0292/2055] E-book viewer: Fix a regression that caused a spurious error on Windows when reading out selected text --- src/calibre/gui2/tts/windows.py | 46 ++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/calibre/gui2/tts/windows.py b/src/calibre/gui2/tts/windows.py index c540ce3cbf..5f57b3e2cf 100644 --- a/src/calibre/gui2/tts/windows.py +++ b/src/calibre/gui2/tts/windows.py @@ -76,35 +76,39 @@ class Client: self.backend.speak(self.current_chunks[self.current_chunk_idx], is_cued=True) def handle_event(self, x): - if isinstance(x, MarkReached) and self.current_chunks: - self.last_mark = x.id - self.callback_ignoring_errors(Event(EventType.mark, x.id)) - elif isinstance(x, MediaStateChanged) and self.current_chunks: - if x.state is MediaState.ended: - if self.current_chunk_idx >= len(self.current_chunks) - 1: + if isinstance(x, MarkReached): + if self.current_chunks: + self.last_mark = x.id + self.callback_ignoring_errors(Event(EventType.mark, x.id)) + elif isinstance(x, MediaStateChanged): + if self.current_chunks: + if x.state is MediaState.ended: + if self.current_chunk_idx >= len(self.current_chunks) - 1: + self.clear_chunks() + self.callback_ignoring_errors(Event(EventType.end)) + else: + self.current_chunk_idx += 1 + self.speak_current_chunk() + elif x.state is MediaState.failed: self.clear_chunks() - self.callback_ignoring_errors(Event(EventType.end)) - else: - self.current_chunk_idx += 1 - self.speak_current_chunk() - elif x.state is MediaState.failed: - self.clear_chunks() - self.callback_ignoring_errors(Event(EventType.cancel)) - e = x.as_exception() - e.display_to_user = True - raise e - elif x.state is MediaState.opened: - self.callback_ignoring_errors(Event(EventType.resume if self.next_start_is_resume else EventType.begin)) - self.next_start_is_resume = False + self.callback_ignoring_errors(Event(EventType.cancel)) + e = x.as_exception() + e.display_to_user = True + raise e + elif x.state is MediaState.opened: + self.callback_ignoring_errors(Event(EventType.resume if self.next_start_is_resume else EventType.begin)) + self.next_start_is_resume = False elif isinstance(x, Error): raise x.as_exception(check_for_no_audio_devices=True) else: raise KeyError(f'Unknown event type: {x}') def speak_simple_text(self, text): - self.current_callback = None + self.backend.pause() self.clear_chunks() - self.backend.speak(text) + self.current_callback = None + if text: + self.backend.speak(text) def speak_marked_text(self, text, callback): self.backend.pause() From 20538568641cd5c3d0b05fd2fa6d5b62d720286b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 7 Feb 2023 11:56:44 +0530 Subject: [PATCH 0293/2055] E-book viewer: Read aloud: Fix a regression in the previous release that caused an error when using Read aloud on a chapter with no text, such as the cover page. Fixes #2006062 [Read Aloud not working on Windows](https://bugs.launchpad.net/calibre/+bug/2006062) --- src/calibre/gui2/tts/windows.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/tts/windows.py b/src/calibre/gui2/tts/windows.py index 5f57b3e2cf..ce1fe605ba 100644 --- a/src/calibre/gui2/tts/windows.py +++ b/src/calibre/gui2/tts/windows.py @@ -32,6 +32,13 @@ def split_into_chunks(marked_text, chunk_size): yield chunk +def chunk_has_text(chunk): + for x in chunk: + if isinstance(x, str) and x: + return True + return False + + class Client: mark_template = '' @@ -73,7 +80,19 @@ class Client: self.dispatch_on_main_thread(partial(self.handle_event, msg)) def speak_current_chunk(self): - self.backend.speak(self.current_chunks[self.current_chunk_idx], is_cued=True) + chunk = self.current_chunks[self.current_chunk_idx] + if chunk_has_text(chunk): + self.backend.speak(chunk, is_cued=True) + else: + self.handle_end_event() + + def handle_end_event(self): + if self.current_chunk_idx >= len(self.current_chunks) - 1: + self.clear_chunks() + self.callback_ignoring_errors(Event(EventType.end)) + else: + self.current_chunk_idx += 1 + self.speak_current_chunk() def handle_event(self, x): if isinstance(x, MarkReached): @@ -83,12 +102,7 @@ class Client: elif isinstance(x, MediaStateChanged): if self.current_chunks: if x.state is MediaState.ended: - if self.current_chunk_idx >= len(self.current_chunks) - 1: - self.clear_chunks() - self.callback_ignoring_errors(Event(EventType.end)) - else: - self.current_chunk_idx += 1 - self.speak_current_chunk() + self.handle_end_event() elif x.state is MediaState.failed: self.clear_chunks() self.callback_ignoring_errors(Event(EventType.cancel)) From 902941adcc60bf0a2ca7e422a9f7ce1ecb5e09cd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 7 Feb 2023 16:49:34 +0530 Subject: [PATCH 0294/2055] Update CNN recipe --- recipes/cnn.recipe | 49 ++++++++++------------------------------------ 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/recipes/cnn.recipe b/recipes/cnn.recipe index 72af0e0a82..cb6ef666b6 100644 --- a/recipes/cnn.recipe +++ b/recipes/cnn.recipe @@ -4,8 +4,7 @@ __copyright__ = '2008, Kovid Goyal ' Profile to download CNN ''' -import re -from calibre.web.feeds.news import BasicNewsRecipe +from calibre.web.feeds.news import BasicNewsRecipe, classes class CNN(BasicNewsRecipe): @@ -18,38 +17,14 @@ class CNN(BasicNewsRecipe): no_stylesheets = True use_embedded_content = False - oldest_article = 15 + oldest_article = 2 ignore_duplicate_articles = {'url'} - # recursions = 1 - # match_regexps = [r'http://sportsillustrated.cnn.com/.*/[1-9].html'] max_articles_per_feed = 25 - compress_news_images = True - compress_news_images_auto_size = 12 - - extra_css = ''' - h1 {font-size:xx-large; font-family:Arial,Helvetica,sans-serif;} - .cnn_story_author, .cnn_stryathrtmp {font-size:xx-small; color:#4D4D4D; font-family:Arial,Helvetica,sans-serif;} - .cnn_strycaptiontxt, .cnnArticleGalleryPhotoContainer {font-size:xx-small; color:#4D4D4D; font-family:Arial,Helvetica,sans-serif;} - .cnn_strycbftrtxt, .cnnEditorialNote {font-size:xx-small; color:#4D4D4D; font-family:Arial,Helvetica,sans-serif;} - .cnn_strycntntlft {font-size:medium; font-family:Arial,Helvetica,sans-serif;} - ''' - - preprocess_regexps = [ - (re.compile(r'', re.DOTALL), lambda m: ''), - (re.compile(r'', re.DOTALL), lambda m: ''), - (re.compile(r'', re.DOTALL), lambda m: ''), - ] - + remove_attributes = ['style', 'height', 'width'] keep_only_tags = [ - dict(id=['body-text', 'storycontent']), - dict(attrs={'class': ['pg-headline', 'metadata']}), - ] - - remove_tags = [ - dict(attrs={'class': lambda x: x and bool({ - 'video__end-slate', 'owl-filmstrip', 'el-embed-instagram', - }.intersection(set(x.split())))}), + classes('headline__wrapper headline__sub-container article__main'), ] + remove_tags = [classes('video-inline_carousel')] feeds = [ ('Top News', 'http://rss.cnn.com/rss/cnn_topstories.rss'), @@ -68,15 +43,6 @@ class CNN(BasicNewsRecipe): ('Most Popular', 'http://rss.cnn.com/rss/cnn_mostpopular.rss') ] - def preprocess_html(self, soup): - body = soup.find('body') - for h2 in soup.findAll(attrs={'class': 'pg-headline'}): - h2.extract() - body.insert(0, h2) - for img in soup.findAll('img', attrs={'data-src-medium': True}): - img['src'] = img['data-src-medium'] - return soup - def get_article_url(self, article): ans = BasicNewsRecipe.get_article_url(self, article) ans = ans.partition('?')[0] @@ -93,3 +59,8 @@ class CNN(BasicNewsRecipe): self.log("\nCover unavailable") masthead = None return masthead + + def preprocess_html(self, soup): + for img in soup.findAll('img', attrs={'src':lambda x: x and x.endswith('.svg')}): + img.extract() + return soup From 28c3422398e47e802b43dc026c27189ec9ef5050 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 7 Feb 2023 16:51:41 +0530 Subject: [PATCH 0295/2055] The Monthly by unkn0wn --- recipes/the_monthly.recipe | 61 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 recipes/the_monthly.recipe diff --git a/recipes/the_monthly.recipe b/recipes/the_monthly.recipe new file mode 100644 index 0000000000..e6884cabb8 --- /dev/null +++ b/recipes/the_monthly.recipe @@ -0,0 +1,61 @@ +from calibre.web.feeds.news import BasicNewsRecipe, classes + + +class monthly(BasicNewsRecipe): + title = 'The Monthly' + __author__ = 'unkn0wn' + description = ( + 'The Monthly is one of Australia’s boldest voices, providing enlightening commentary and vigorous,' + ' at times controversial, debate on the issues that affect the nation.' + ) + no_stylesheets = True + use_embedded_content = False + encoding = 'utf-8' + language = 'en_AU' + remove_attributes = ['height', 'width'] + ignore_duplicate_articles = {'url'} + + timefmt = ' [%B %Y]' + + extra_css = ''' + .article-page__title {font-weight:bold; font-size:large;} + .article-page__author, .category, .caption {font-size:small; color:#404040;} + .article__summary, .author-details-bottom-wrapper, em {font-style:italic; color:#202020;} + ''' + keep_only_tags = [ + classes('article-page') + ] + remove_tags = [ + classes( + 'node-teaser-hidden article-page__social__icons sidebar-toc article-footer-container' + ' article-page__sidebar toc-list-container tm-promo-banner-article-p4-wrapper edition' + ) + ] + + def parse_index(self): + soup = self.index_to_soup('https://www.themonthly.com.au/latest-edition') + if cov := soup.find(**classes('article__edition-image')): + self.cover_url = cov.find('img')['src'] + self.log(self.cover_url) + + feeds = [] + main = soup.find(**classes('main')) + for art in main.findAll(**classes('article')): + if hr := art.find('a', attrs={'href':lambda x: x and x.startswith('/issue/')}): + url = 'https://www.themonthly.com.au' + hr['href'] + else: + if hr := art.findParent('a', attrs={'href':lambda x: x and x.startswith('/issue/')}): + url = 'https://www.themonthly.com.au' + hr['href'] + title = self.tag_to_string(art.find(**classes('article__title'))) + desc = '' + if text := art.find(**classes('article__text')): + desc = self.tag_to_string(text) + if auth := art.find(**classes('article__author')): + desc = self.tag_to_string(auth) + ' | ' + desc + if cat := art.find(**classes('category')): + desc = self.tag_to_string(cat) + ' | ' + desc + if not title or not url: + continue + self.log(' ', title, '\n\t', url, '\n\t', desc) + feeds.append({'title': title, 'url': url, 'description':desc}) + return [('The Monthly', feeds)] From 1c30c6f644306c33c3afbc86440072b36130e8b2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 14 Feb 2023 09:39:10 +0530 Subject: [PATCH 0296/2055] Fix a regression in calibre 5.0 that broke sorting the device view by title if one of the books has an empty title. Fixes #2007165 [Cannot sort device by title](https://bugs.launchpad.net/calibre/+bug/2007165) More py3 goodness --- src/calibre/gui2/library/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index c76ca43a37..a01999dab3 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -1602,7 +1602,7 @@ class DeviceBooksModel(BooksModel): # {{{ return ax keygen = { - 'title': ('title_sorter', lambda x: sort_key(x) if x else ''), + 'title': ('title_sorter', lambda x: sort_key(x) if x else b''), 'authors' : author_key, 'size' : ('size', int), 'timestamp': ('datetime', functools.partial(dt_factory, assume_utc=True)), From 515fbd3c4ba2dfc0a0f0dbb79afa5da7406a8dd3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 14 Feb 2023 13:01:01 +0530 Subject: [PATCH 0297/2055] read aloud: Fix a regression in the previous release that caused the read aloud controls to not reappear is read aloud is canceled and restarted. Fixes #2007039 ["Read aloud" feature glitches on closing and reopening it](https://bugs.launchpad.net/calibre/+bug/2007039) --- src/pyj/read_book/read_aloud.pyj | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pyj/read_book/read_aloud.pyj b/src/pyj/read_book/read_aloud.pyj index dcfb99ad2a..6c5ede5a41 100644 --- a/src/pyj/read_book/read_aloud.pyj +++ b/src/pyj/read_book/read_aloud.pyj @@ -243,8 +243,6 @@ class ReadAloud: self.play() if data is not None: pass - elif which is 'cancel': - self.state = STOPPED def send_message(self, type, **kw): self.view.iframe_wrapper.send_message('tts', type=type, **kw) From 1893c41f1a3575500733ff0858b81cc7540d7aeb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 14 Feb 2023 16:21:13 +0530 Subject: [PATCH 0298/2055] Deccan Herald by unkn0wn --- recipes/deccan_herald.recipe | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 recipes/deccan_herald.recipe diff --git a/recipes/deccan_herald.recipe b/recipes/deccan_herald.recipe new file mode 100644 index 0000000000..9d3ec3b47b --- /dev/null +++ b/recipes/deccan_herald.recipe @@ -0,0 +1,55 @@ +from calibre.ptempfile import PersistentTemporaryFile +from calibre.web.feeds.news import BasicNewsRecipe, classes + +class herald(BasicNewsRecipe): + title = 'Deccan Herald' + __author__ = 'unkn0wn' + description = 'Deccan Herald is an Indian English language daily newspaper published from the Indian state of Karnataka.' + language = 'en_IN' + no_stylesheets = True + remove_attributes = ['height', 'width', 'style'] + ignore_duplicate_articles = {'url'} + encoding = 'utf-8' + + articles_are_obfuscated = True + + def get_obfuscated_article(self, url): + br = self.get_browser() + try: + br.open(url) + except Exception as e: + url = e.hdrs.get('location') + soup = self.index_to_soup(url) + link = soup.find('a', href=True) + skip_sections =[ # add sections you want to skip + '/sports/', '/video/', '/bengaluru-crime/', '/metrolife/', + '/karnataka-districts/', '/brandspot/', '/entertainment/', + ] + if any(x in link['href'] for x in skip_sections): + self.log('Aborting Article ', link['href']) + self.abort_article('skipping section') + + self.log('Downloading ', link['href']) + html = br.open(link['href']).read() + pt = PersistentTemporaryFile('.html') + pt.write(html) + pt.close() + return pt.name + + keep_only_tags = [ + classes('article-title article-author__name'), + dict(name='div', attrs={'id':'main-content'}) + + ] + + remove_tags = [ + classes( + 'storyShare social-media-icons in_article_video static_text' + ' nl-optin-mobile dk_only article-banner-adver-wrapper wb_holder' + ' field-name-field-tags section-full strip--business' + ) + ] + + feeds = [ + ('DH', 'https://news.google.com/rss/search?q=when:27h+allinurl:deccanherald.com&hl=en-IN&gl=IN&ceid=IN:en') + ] From 3936de93f58c5c1108e2e4e8bd6d6dace9efb1ec Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 14 Feb 2023 16:21:59 +0530 Subject: [PATCH 0299/2055] Horizons by unkn0wn --- recipes/horizons.recipe | 68 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 recipes/horizons.recipe diff --git a/recipes/horizons.recipe b/recipes/horizons.recipe new file mode 100644 index 0000000000..bae979a043 --- /dev/null +++ b/recipes/horizons.recipe @@ -0,0 +1,68 @@ +''' +https://www.cirsd.org/en/horizons +''' + +from calibre.web.feeds.news import BasicNewsRecipe, classes + +class horizons(BasicNewsRecipe): + title = 'Horizons' + __author__ = 'unkn0wn' + description = (' Horizons – Journal of International Relations and Sustainable Development.' + ' Horizons serves as a high-level platform for influential voices from around the world to' + ' provide informed analysis and conduct reasoned exchanges on the full spectrum of issues' + ' that shape international developments.') + no_stylesheets = True + use_embedded_content = False + encoding = 'utf-8' + language = 'en' + remove_attributes = ['style', 'height', 'width'] + masthead_url = 'https://www.cirsd.org/bundles/olpublic/images/horizons-logo.jpg' + ignore_duplicate_articles = {'url'} + extra_css = 'em{color:#404040;}' + + keep_only_tags = [ + dict(name='div', attrs={'class':'article'}) + ] + remove_tags = [ + classes('back-link'), + dict(name='div', attrs={'class':'single-post-footer'}) + ] + + def parse_index(self): + soup = self.index_to_soup('https://www.cirsd.org/en/horizons') + a = soup.findAll('a', href=True, attrs={'class':'horizon-gallery-box'})[0] #use 1 for previous edition + url = a['href'] + if url.startswith('/'): + url = 'https://www.cirsd.org' + url + self.cover_url = a.find('img')['src'] + self.log(self.cover_url) + issue = a.find('div', attrs={'class':'horizon-gallery-title'}) + if issue: + self.timefmt = ' [' + self.tag_to_string(issue).strip() + ']' + self.log('Downloading Issue: ', self.timefmt) + soup = self.index_to_soup(url) + + feeds = [] + for section in soup.findAll('h2', attrs={'class':'mt-3'}): + secname = self.tag_to_string(section).strip() + self.log(secname) + articles = [] + div = section.findNext('div', attrs={'class':'mb-3'}) + for li in div.findAll('li', attrs={'class':'mb-2'}): + a = li.find('a', href=True) + url = a['href'] + if url.startswith('/'): + url = 'https://www.cirsd.org' + url + title = self.tag_to_string(a) + span = li.find('span', attrs={'class':'section-author'}) + desc = '' + if span: + desc = self.tag_to_string(span).strip() + self.log('\t', title, '\n\t', desc, '\n\t\t', url) + articles.append({ + 'title': title, + 'url': url, + 'description': desc}) + if articles: + feeds.append((secname, articles)) + return feeds From 0a3033a213a518f1b479b36ace586c3a4f94fdc4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 14 Feb 2023 16:22:37 +0530 Subject: [PATCH 0300/2055] Science X by unkn0wn --- recipes/science_x.recipe | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 recipes/science_x.recipe diff --git a/recipes/science_x.recipe b/recipes/science_x.recipe new file mode 100644 index 0000000000..044f95ca96 --- /dev/null +++ b/recipes/science_x.recipe @@ -0,0 +1,44 @@ +''' +https://sciencex.com/ +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class scix(BasicNewsRecipe): + title = 'Science X' + description = ( + 'Science X is a network of high-quality websites that provides the most complete and comprehensive ' + 'daily coverage of science, technology, and medical news. Articles from phys.org, medicalxpress.com' + '& techxplore.com' + ) + language = 'en' + __author__ = 'unkn0wn' + oldest_article = 1 # days + max_articles_per_feed = 50 + encoding = 'utf-8' + remove_attributes = ['height', 'width'] + ignore_duplicate_articles = {'url', 'title'} + + extra_css = ''' + #figure {text-align:center; font-size:small;} + em, blockquote {color:#202020;} + .article__info, .article-byline, .article-main__more, .d-print-block {font-size:small; color:#404040;} + ''' + + resolve_internal_links = True + remove_empty_feeds = True + + keep_only_tags = [dict(name='article', attrs={'class':'news-article'})] + + feeds = [ + ('Tech Xplore', 'https://techxplore.com/rss-feed/'), + ('Medical Xpress', 'https://medicalxpress.com/rss-feed/'), + ('Phys.org', 'https://phys.org/rss-feed/') + #https://medicalxpress.com/feeds/ + #https://techxplore.com/feeds/ + ] + + def preprocess_html(self, soup): + for figure in soup.findAll('figure'): + figure['id'] = 'figure' + return soup From 94a5ee60433db44c0cc3f873d7dd717293bda99a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 14 Feb 2023 16:23:20 +0530 Subject: [PATCH 0301/2055] The Economist Espresso by unkn0wn --- recipes/economist_espresso.recipe | 51 +++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 recipes/economist_espresso.recipe diff --git a/recipes/economist_espresso.recipe b/recipes/economist_espresso.recipe new file mode 100644 index 0000000000..a3705c4aa3 --- /dev/null +++ b/recipes/economist_espresso.recipe @@ -0,0 +1,51 @@ +''' +https://www.economist.com/the-world-in-brief +''' + +from calibre.web.feeds.news import BasicNewsRecipe, classes + +class Espresso(BasicNewsRecipe): + title = 'The Economist Espresso' + language = 'en' + __author__ = 'unkn0wn' + description = ( + 'Espresso is a rich, full-flavoured shot of daily global analysis' + ' from the editors of The Economist to get you up to speed, fast.' + 'Maximise your understanding of the most significant business, ' + 'economic, political and cultural developments globally.' + ) + no_stylesheets = True + remove_attributes = ['height', 'width', 'style'] + use_embedded_content = False + + extra_css = ''' + h1 { text-align:center; } + ._main-image, ._description { text-align:center; font-size:small; } + ._quote-container { font-size:x-large; font-style:italic; color:#202020; } + ''' + + keep_only_tags = [ + dict(name='main', attrs={'id':'content'}) + ] + + remove_tags = [ + classes('_podcast-promo _newsletter-promo-container _time-last-updated') + ] + + def parse_index(self): + return [ + ('Espresso', + [ + { + 'title': 'The World in Brief', + 'url': 'https://www.economist.com/the-world-in-brief', + 'description': 'Catch up quickly on the global stories that matter' + }, + ] + ), + ] + + def preprocess_html(self, soup): + for h3 in soup.findAll('h3'): + h3.name = 'h1' + return soup From 04cfa38056e69ebe1b1c8f59bc20c5ba82a93abb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 15 Feb 2023 08:55:00 +0530 Subject: [PATCH 0302/2055] Nicer Read Aloud error message on windows systems without windows media pack --- src/calibre/utils/windows/winspeech.cpp | 5 ++++- src/calibre/utils/windows/winspeech.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/calibre/utils/windows/winspeech.cpp b/src/calibre/utils/windows/winspeech.cpp index b86020eb34..b12cc1bd67 100644 --- a/src/calibre/utils/windows/winspeech.cpp +++ b/src/calibre/utils/windows/winspeech.cpp @@ -838,6 +838,8 @@ handle_stdin_message(winrt::hstring const &&msg) { return exit_code; } +#define INITIALIZE_FAILURE_MESSAGE "Failed to initialize SpeechSynthesizer and MediaPlayer" + static PyObject* run_main_loop(PyObject*, PyObject*) { if (!run_catching_exceptions([]() { @@ -864,7 +866,7 @@ run_main_loop(PyObject*, PyObject*) { media_player = MediaPlayer(); media_player.AudioCategory(MediaPlayerAudioCategory::Speech); media_player.AutoPlay(true); - }, "Failed to initialize SpeechSynthesizer and MediaPlayer", __LINE__)) { + }, INITIALIZE_FAILURE_MESSAGE, __LINE__)) { return PyLong_FromLongLong(1); } @@ -916,6 +918,7 @@ static PyMethodDef methods[] = { static int exec_module(PyObject *m) { + PyModule_AddStringMacro(m, INITIALIZE_FAILURE_MESSAGE); return 0; } diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index 476f56a070..8c91716d28 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -107,11 +107,22 @@ class SpeechError(OSError): class NoAudioDevices(OSError): + display_to_user = True def __init__(self): super().__init__(_('No active audio output devices found.' ' Connect headphones or speakers. If you are using Remote Desktop then enable Remote Audio for it.')) +class NoMediaPack(OSError): + display_to_user = True + + def __init__(self): + super().__init__(_('This computer is missing the Windows MediaPack, which is needed for Read aloud. Instructions' + ' for installing it are available at {}').format( + + 'https://support.medal.tv/support/solutions/articles/48001157311-windows-is-missing-media-pack')) + + class Error(NamedTuple): msg: str error: str = '' @@ -121,8 +132,11 @@ class Error(NamedTuple): related_to: int = 0 def as_exception(self, msg='', check_for_no_audio_devices=False): + from calibre_extensions.winspeech import INITIALIZE_FAILURE_MESSAGE if check_for_no_audio_devices and self.hr == 0xc00d36fa: return NoAudioDevices() + if check_for_no_audio_devices and self.hr == 0x80070002 and self.msg == INITIALIZE_FAILURE_MESSAGE: + return NoMediaPack() return SpeechError(self, msg) From 006697d24fb50fff871bd8af1d6385849a024403 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 16 Feb 2023 07:08:46 +0530 Subject: [PATCH 0303/2055] ... --- src/calibre/utils/windows/winspeech.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/windows/winspeech.py b/src/calibre/utils/windows/winspeech.py index 8c91716d28..7f465b4e06 100644 --- a/src/calibre/utils/windows/winspeech.py +++ b/src/calibre/utils/windows/winspeech.py @@ -117,7 +117,7 @@ class NoMediaPack(OSError): display_to_user = True def __init__(self): - super().__init__(_('This computer is missing the Windows MediaPack, which is needed for Read aloud. Instructions' + super().__init__(_('This computer is missing the Windows MediaPack, or the DLLs are corrupted. This is needed for Read aloud. Instructions' ' for installing it are available at {}').format( 'https://support.medal.tv/support/solutions/articles/48001157311-windows-is-missing-media-pack')) From c3b7029667fa63b3777ed6403075ef14e7dc19c4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 16 Feb 2023 11:01:24 +0530 Subject: [PATCH 0304/2055] Content server: Workaround for Safari regression causing bookmarks to disappear on reload. Fixes #2006726 [calibre web view - missing bookmarks](https://bugs.launchpad.net/calibre/+bug/2006726) --- src/pyj/read_book/db.pyj | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pyj/read_book/db.pyj b/src/pyj/read_book/db.pyj index 0965112f6c..5512f46e26 100644 --- a/src/pyj/read_book/db.pyj +++ b/src/pyj/read_book/db.pyj @@ -172,11 +172,13 @@ class DB: transaction = self.idb.transaction(stores) req = transaction.objectStore(store).get(data) req.onsuccess = def(event): - proceed(req.result) + if proceed: + proceed(req.result) elif op is 'put': transaction = self.idb.transaction(stores, 'readwrite') req = transaction.objectStore(store).put(data) - req.onsuccess = proceed + if proceed: + req.onsuccess = proceed req.onerror = def(event): self.display_error(error_msg, event) @@ -327,7 +329,7 @@ class DB: changed = True book.annotations_map[unkey] = merged if changed: - self.do_op(['books'], book, _('Failed to write to the books database'), op='put') + self.do_op(['books'], book, _('Failed to write to the books database'), def(): None;, op='put') ) def get_file(self, book, name, proceed): From 44f157bc941ecdc584e69947fde025cd28c6ca96 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 16 Feb 2023 11:04:11 +0530 Subject: [PATCH 0305/2055] ... --- src/pyj/read_book/db.pyj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyj/read_book/db.pyj b/src/pyj/read_book/db.pyj index 5512f46e26..079735e629 100644 --- a/src/pyj/read_book/db.pyj +++ b/src/pyj/read_book/db.pyj @@ -301,11 +301,11 @@ class DB: if book.metadata: for key in Object.keys(new_metadata): book.metadata[key] = new_metadata[key] - self.do_op(['books'], book, _('Failed to write to the books database'), op='put') + self.do_op(['books'], book, _('Failed to write to the books database'), def(): None;, op='put') def save_reading_rates(self, book, rates): book.saved_reading_rates = rates - self.do_op(['books'], book, _('Failed to write to the books database'), op='put') + self.do_op(['books'], book, _('Failed to write to the books database'), def(): None;, op='put') def update_annotations_data_from_key(self, library_id, book_id, fmt, new_data): unkey = username_key(get_interface_data().username) From c4f3fe57459780583cdc66d1828f2e3d45132fb9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 16 Feb 2023 12:59:48 +0530 Subject: [PATCH 0306/2055] E-book viewer: Fix some adjacent highlights with nothing in between them not being displayed. --- src/pyj/range_utils.pyj | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/pyj/range_utils.pyj b/src/pyj/range_utils.pyj index 1f6c697fd6..51d0d7782d 100644 --- a/src/pyj/range_utils.pyj +++ b/src/pyj/range_utils.pyj @@ -151,6 +151,13 @@ def create_wrapper_function(wrapper_elem, r, intersecting_wrappers, process_wrap current_range.setEnd(node, end_offset) end_node = current_wrapper end_offset = 1 + if current_range.collapsed: + # Dont wrap empty ranges. This is needed otherwise two adjacent + # selections of text will incorrectly be detected as overlapping. + # For example: highlight abc then def in the word abcdef here the + # second highlight's first range is the collapsed range at the end + # of abc + return crw = node.parentNode?.dataset?.calibreRangeWrapper if crw: intersecting_wrappers[crw] = True @@ -158,7 +165,6 @@ def create_wrapper_function(wrapper_elem, r, intersecting_wrappers, process_wrap if process_wrapper: process_wrapper(current_wrapper) all_wrappers.push(current_wrapper) - return current_wrapper return wrap_node @@ -184,10 +190,13 @@ def wrap_text_in_range(styler, r, class_to_add_to_last, process_wrapper): all_wrappers = v'[]' wrap_node = create_wrapper_function(wrapper_elem, r, intersecting_wrappers, process_wrapper, all_wrappers) text_nodes_in_range(r).map(wrap_node) + ancestor = r.commonAncestorContainer + if ancestor.nodeType is Node.TEXT_NODE: + ancestor = ancestor.parentNode # remove any empty text nodes created by surroundContents() on either # side of the wrapper. This happens for instance on Chrome when # wrapping all text inside some text - r.commonAncestorContainer.normalize() + ancestor.normalize() crw = wrapper_elem.dataset.calibreRangeWrapper v'delete intersecting_wrappers[crw]' if class_to_add_to_last and all_wrappers.length: From c9b0c23aeb635ce1c45120f187711508f2f9a9d7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 16 Feb 2023 13:42:16 +0530 Subject: [PATCH 0307/2055] Respond to wheel event in the image popup --- src/pyj/image_popup.pyj | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/pyj/image_popup.pyj b/src/pyj/image_popup.pyj index 9ec425fd47..502b4190fd 100644 --- a/src/pyj/image_popup.pyj +++ b/src/pyj/image_popup.pyj @@ -67,6 +67,7 @@ class ImagePopup(TouchHandler): style=f'display:none; position:absolute; left:0; top: 0; z-index: {MODAL_Z_INDEX}; width: 100vw; height: 100vh; padding: 0; margin: 0; border-width: 0; box-sizing: border-box', id=self.container_id, tabindex='0', class_='calibre-image-popup', onkeydown=self.onkeydown, + onwheel=self.onwheel, E.div( style='position: fixed; top: 0; left: 0; text-align: right; width: 100%; font-size: 200%; padding: 0.25ex; box-sizing: border-box; display: flex; justify-content: space-between', E.a( @@ -89,6 +90,16 @@ class ImagePopup(TouchHandler): window.addEventListener('resize', debounce(self.resize_canvas, 250)) return self._container + def onwheel(self, ev): + ev.stopPropagation(), ev.preventDefault() + dy = ev.deltaY + dx = ev.deltaX + if ev.deltaMode is window.WheelEvent.DOM_DELTA_LINE: + dy *= 20 + dx *= 20 + self.scroll_y(dy) + self.scroll_x(dx) + def onkeydown(self, ev): ev.preventDefault(), ev.stopPropagation() if ev.key is ' ': @@ -285,6 +296,7 @@ class ImagePopup(TouchHandler): def toggle_fit_to_window(self): self.fit_to_window ^= True + self.x = self.y = 0 self.update_canvas() def image_loaded(self): From a26ba63e1ffd8868ba86a826c4388b85b7abfcc9 Mon Sep 17 00:00:00 2001 From: Steffen Siebert Date: Thu, 16 Feb 2023 21:23:22 +0100 Subject: [PATCH 0308/2055] Extend FileTypePlugin with postconvert() and postdelete() methods --- src/calibre/customize/__init__.py | 32 +++++++++++++++++++++++ src/calibre/customize/ui.py | 39 ++++++++++++++++++++++++++++- src/calibre/db/cache.py | 6 ++++- src/calibre/gui2/actions/convert.py | 3 ++- 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 0863226e05..8a72ac0c35 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -334,6 +334,16 @@ class FileTypePlugin(Plugin): # {{{ #: methods of the plugin are called. on_postimport = False + #: If True, this plugin is run after a book file is added + #: to the database. In this case the postconvert method of + #: the plugin is called. + on_postconvert = False + + #: If True, this plugin is run after a book file is deleted + #: from the database. In this case the postdelete method of + #: the plugin is called. + on_postdelete = False + #: If True, this plugin is run just before a conversion on_preprocess = False @@ -378,6 +388,28 @@ class FileTypePlugin(Plugin): # {{{ ''' pass # Default implementation does nothing + def postconvert(self, book_id, book_format, db): + ''' + Called post conversion, i.e., after the book file has been added to the database. It is + useful for modifying the book record based on the contents of the newly added file. + + :param book_id: Database id of the added book. + :param book_format: The file type of the book that was added. + :param db: Library database. + ''' + pass # Default implementation does nothing + + def postdelete(self, book_id, book_format, db): + ''' + Called post deletion, i.e., after the book file has been deleted from the database. It is + useful for modifying the book record based on the format of the deleted file. + + :param book_id: Database id of the added book. + :param book_format: The file type of the book that was added. + :param db: Library database. + ''' + pass # Default implementation does nothing + def postadd(self, book_id, fmt_map, db): ''' Called post add, i.e. after a book has been added to the db. Note that diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index 3d77efd9d2..c4f6d1995b 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -122,15 +122,19 @@ def is_disabled(plugin): _on_import = {} _on_postimport = {} +_on_postconvert = {} +_on_postdelete = {} _on_preprocess = {} _on_postprocess = {} _on_postadd = [] def reread_filetype_plugins(): - global _on_import, _on_postimport, _on_preprocess, _on_postprocess, _on_postadd + global _on_import, _on_postimport, _on_postconvert, _on_postdelete, _on_preprocess, _on_postprocess, _on_postadd _on_import = defaultdict(list) _on_postimport = defaultdict(list) + _on_postconvert = defaultdict(list) + _on_postdelete = defaultdict(list) _on_preprocess = defaultdict(list) _on_postprocess = defaultdict(list) _on_postadd = [] @@ -146,6 +150,10 @@ def reread_filetype_plugins(): if plugin.on_postimport: _on_postimport[ft].append(plugin) _on_postadd.append(plugin) + if plugin.on_postconvert: + _on_postconvert[ft].append(plugin) + if plugin.on_postdelete: + _on_postdelete[ft].append(plugin) if plugin.on_preprocess: _on_preprocess[ft].append(plugin) if plugin.on_postprocess: @@ -155,6 +163,7 @@ def reread_filetype_plugins(): def plugins_for_ft(ft, occasion): op = { 'import':_on_import, 'preprocess':_on_preprocess, 'postprocess':_on_postprocess, 'postimport':_on_postimport, + 'postconvert':_on_postconvert, 'postdelete':_on_postdelete, }[occasion] for p in chain(op.get(ft, ()), op.get('*', ())): if not is_disabled(p): @@ -207,6 +216,34 @@ def run_plugins_on_postimport(db, book_id, fmt): traceback.print_exc() +def run_plugins_on_postconvert(db, book_id, fmt): + customization = config['plugin_customization'] + fmt = fmt.lower() + for plugin in plugins_for_ft(fmt, 'postconvert'): + plugin.site_customization = customization.get(plugin.name, '') + with plugin: + try: + plugin.postconvert(book_id, fmt, db) + except: + print('Running file type plugin %s failed with traceback:'% + plugin.name) + traceback.print_exc() + + +def run_plugins_on_postdelete(db, book_id, fmt): + customization = config['plugin_customization'] + fmt = fmt.lower() + for plugin in plugins_for_ft(fmt, 'postdelete'): + plugin.site_customization = customization.get(plugin.name, '') + with plugin: + try: + plugin.postdelete(book_id, fmt, db) + except: + print('Running file type plugin %s failed with traceback:'% + plugin.name) + traceback.print_exc() + + def run_plugins_on_postadd(db, book_id, fmt_map): customization = config['plugin_customization'] for plugin in _on_postadd: diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 17a7676a7e..a91a99930c 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -24,7 +24,7 @@ from time import monotonic, sleep, time from calibre import as_unicode, detect_ncpus, isbytestring from calibre.constants import iswindows, preferred_encoding from calibre.customize.ui import ( - run_plugins_on_import, run_plugins_on_postadd, run_plugins_on_postimport, + run_plugins_on_import, run_plugins_on_postadd, run_plugins_on_postimport, run_plugins_on_postdelete, ) from calibre.db import SPOOL_SIZE, _get_next_series_num_for_list from calibre.db.annotations import merge_annotations @@ -1865,6 +1865,10 @@ class Cache: size_map = table.remove_formats(formats_map, self.backend) self.fields['size'].table.update_sizes(size_map) + + for book_id, fmts in iteritems(formats_map): + run_plugins_on_postdelete(self, book_id, fmt) + self._update_last_modified(tuple(formats_map)) self.event_dispatcher(EventType.formats_removed, formats_map) diff --git a/src/calibre/gui2/actions/convert.py b/src/calibre/gui2/actions/convert.py index 0cfd8c39b1..bedd359ea5 100644 --- a/src/calibre/gui2/actions/convert.py +++ b/src/calibre/gui2/actions/convert.py @@ -9,7 +9,7 @@ import os from functools import partial from qt.core import QModelIndex, QTimer -from calibre.customize.ui import plugin_for_input_format +from calibre.customize.ui import plugin_for_input_format, run_plugins_on_postconvert from calibre.gui2 import Dispatcher, error_dialog, gprefs from calibre.gui2.actions import InterfaceAction from calibre.gui2.tools import convert_bulk_ebook, convert_single_ebook @@ -274,6 +274,7 @@ class ConvertAction(InterfaceAction): with open(temp_files[-1].name, 'rb') as data: db.add_format(book_id, fmt, data, index_is_id=True) + run_plugins_on_postconvert(db, book_id, fmt) self.gui.book_converted.emit(book_id, fmt) self.gui.status_bar.show_message(job.description + ' ' + _('completed'), 2000) From 7fed39c1651c3c126642f0e900da5577a13ca8cf Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 17 Feb 2023 08:02:09 +0530 Subject: [PATCH 0309/2055] version 6.13.0 --- Changelog.txt | 36 ++++++++++++++++++++++++++++++++++++ src/calibre/constants.py | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Changelog.txt b/Changelog.txt index 6a108ac27b..baf842b5d5 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -23,6 +23,42 @@ # - title by author # }}} +{{{ 6.13.0 2023-02-17 + +:: new features + +- Content server: E-book viewer: Long tapping on an image now causes it to be displayed in an internal popup rather than a new window as some browsers block the creation of new windows + +:: bug fixes + +- E-book viewer: Fix some adjacent highlights with nothing in between them not being displayed. + +- [2006726] Content server: Workaround for Safari regression causing bookmarks to disappear on reload + +- [2007039] E-book viewer: Read aloud: Fix a regression in the previous release that caused the Read aloud controls to not reappear when Read aloud is canceled and restarted + +- [2006062] E-book viewer: Read aloud: Fix a regression in the previous release that caused an error when using Read aloud on a chapter with no text, such as the cover page + +- E-book viewer: Fix a regression that caused a spurious error on Windows when reading out selected text + +- [2007165] Fix a regression in calibre 5.0 that broke sorting the device view by title if one of the books has an empty title + +- Edit book: Spell Check dialog: Fix second word not getting selected when after first word is fixed + +- [2004621] Improve hover highlight color in tree views + +:: improved recipes +- CNN +- Bloomberg + +:: new recipes +- The Economist Espresso by unkn0wn +- Science X by unkn0wn +- Horizons by unkn0wn +- Deccan Herald by unkn0wn +- The Monthly by unkn0wn +}}} + {{{ 6.12.0 2023-02-03 :: new features diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 450a1078d4..846b5ee5de 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -5,7 +5,7 @@ from functools import lru_cache import sys, locale, codecs, os, collections, collections.abc __appname__ = 'calibre' -numeric_version = (6, 12, 0) +numeric_version = (6, 13, 0) __version__ = '.'.join(map(str, numeric_version)) git_version = None __author__ = "Kovid Goyal " From da8b0c997648be3e5f6375f6e43ec13842b0ce10 Mon Sep 17 00:00:00 2001 From: xcffl Date: Sun, 19 Feb 2023 08:08:10 +0000 Subject: [PATCH 0310/2055] align qtbase version between sources.json and unix-ci.py --- setup/unix-ci.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/unix-ci.py b/setup/unix-ci.py index 5b05c9aebe..4a16de89fb 100644 --- a/setup/unix-ci.py +++ b/setup/unix-ci.py @@ -90,7 +90,7 @@ def download_and_decompress(url, dest, compression=None): def install_qt_source_code(): dest = os.path.expanduser('~/qt-base') os.mkdir(dest) - download_and_decompress('https://download.calibre-ebook.com/qtbase-everywhere-src-6.2.2.tar.xz', dest, 'J') + download_and_decompress('https://download.calibre-ebook.com/qtbase-everywhere-src-6.4.2.tar.xz', dest, 'J') qdir = glob.glob(dest + '/*')[0] os.environ['QT_SRC'] = qdir From 1f2b890f10861b920d14fc0e09999b1a753b7fe1 Mon Sep 17 00:00:00 2001 From: Steffen Siebert Date: Sun, 19 Feb 2023 13:28:31 +0100 Subject: [PATCH 0311/2055] Address code review comments --- src/calibre/customize/__init__.py | 11 +++++++---- src/calibre/srv/convert.py | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 8a72ac0c35..8f8c6ccf9c 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -390,8 +390,9 @@ class FileTypePlugin(Plugin): # {{{ def postconvert(self, book_id, book_format, db): ''' - Called post conversion, i.e., after the book file has been added to the database. It is - useful for modifying the book record based on the contents of the newly added file. + Called post conversion, i.e., after the book file has been added to the database. Note that + it is run after a conversion only, not after a book is added. It is useful for modifying + the book record based on the contents of the newly added file. :param book_id: Database id of the added book. :param book_format: The file type of the book that was added. @@ -401,8 +402,10 @@ class FileTypePlugin(Plugin): # {{{ def postdelete(self, book_id, book_format, db): ''' - Called post deletion, i.e., after the book file has been deleted from the database. It is - useful for modifying the book record based on the format of the deleted file. + Called post deletion, i.e., after the book file has been deleted from the database. Note + that it is not run when a book record is deleted, only when one or more formats from the + book are deleted. It is useful for modifying the book record based on the format of the + deleted file. :param book_id: Database id of the added book. :param book_format: The file type of the book that was added. diff --git a/src/calibre/srv/convert.py b/src/calibre/srv/convert.py index b6407b3079..7741913d9b 100644 --- a/src/calibre/srv/convert.py +++ b/src/calibre/srv/convert.py @@ -7,7 +7,7 @@ import shutil import tempfile from threading import Lock -from calibre.customize.ui import input_profiles, output_profiles +from calibre.customize.ui import input_profiles, output_profiles, run_plugins_on_postconvert from calibre.db.errors import NoSuchBook from calibre.srv.changes import formats_added from calibre.srv.errors import BookNotFound, HTTPNotFound @@ -194,6 +194,7 @@ def conversion_status(ctx, rd, job_id): except NoSuchBook: raise HTTPNotFound( f'book_id {job_status.book_id} not found in library') + run_plugins_on_postconvert(db, job_status.book_id, fmt) formats_added({job_status.book_id: (fmt,)}) ans['size'] = os.path.getsize(job_status.output_path) ans['fmt'] = fmt From 1ef455148a19d0f15bccc98560706b666f057c87 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 19 Feb 2023 19:08:48 +0530 Subject: [PATCH 0312/2055] Cleanup previous PR --- src/calibre/customize/__init__.py | 9 ++++----- src/calibre/customize/ui.py | 18 +++++++----------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index 8f8c6ccf9c..3da9230dff 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -334,9 +334,8 @@ class FileTypePlugin(Plugin): # {{{ #: methods of the plugin are called. on_postimport = False - #: If True, this plugin is run after a book file is added - #: to the database. In this case the postconvert method of - #: the plugin is called. + #: If True, this plugin is run after a book is converted. + #: In this case the postconvert method of the plugin is called. on_postconvert = False #: If True, this plugin is run after a book file is deleted @@ -390,8 +389,8 @@ class FileTypePlugin(Plugin): # {{{ def postconvert(self, book_id, book_format, db): ''' - Called post conversion, i.e., after the book file has been added to the database. Note that - it is run after a conversion only, not after a book is added. It is useful for modifying + Called post conversion, i.e., after the conversion output book file has been added to the database. + Note that it is run after a conversion only, not after a book is added. It is useful for modifying the book record based on the contents of the newly added file. :param book_id: Database id of the added book. diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index c4f6d1995b..57d064f8f4 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -210,9 +210,8 @@ def run_plugins_on_postimport(db, book_id, fmt): with plugin: try: plugin.postimport(book_id, fmt, db) - except: - print('Running file type plugin %s failed with traceback:'% - plugin.name) + except Exception: + print(f'Running file type plugin {plugin.name} failed with traceback:', file=sys.stderr) traceback.print_exc() @@ -224,9 +223,8 @@ def run_plugins_on_postconvert(db, book_id, fmt): with plugin: try: plugin.postconvert(book_id, fmt, db) - except: - print('Running file type plugin %s failed with traceback:'% - plugin.name) + except Exception: + print(f'Running file type plugin {plugin.name} failed with traceback:', file=sys.stderr) traceback.print_exc() @@ -238,9 +236,8 @@ def run_plugins_on_postdelete(db, book_id, fmt): with plugin: try: plugin.postdelete(book_id, fmt, db) - except: - print('Running file type plugin %s failed with traceback:'% - plugin.name) + except Exception: + print(f'Running file type plugin {plugin.name} failed with traceback:', file=sys.stderr) traceback.print_exc() @@ -254,8 +251,7 @@ def run_plugins_on_postadd(db, book_id, fmt_map): try: plugin.postadd(book_id, fmt_map, db) except Exception: - print('Running file type plugin %s failed with traceback:'% - plugin.name) + print(f'Running file type plugin {plugin.name} failed with traceback:', file=sys.stderr) traceback.print_exc() # }}} From 109a845751889146f70d8645a6fd118452c490e4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Feb 2023 17:23:35 +0530 Subject: [PATCH 0313/2055] Update Bloomberg and Deccan Herald --- recipes/bloomberg.recipe | 19 ++++++++++++++----- recipes/deccan_herald.recipe | 30 ++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/recipes/bloomberg.recipe b/recipes/bloomberg.recipe index ac57b37a74..ef8fee5e8c 100644 --- a/recipes/bloomberg.recipe +++ b/recipes/bloomberg.recipe @@ -4,7 +4,6 @@ from calibre.ptempfile import PersistentTemporaryFile import json import re - class Bloomberg(BasicNewsRecipe): title = u'Bloomberg' language = 'en' @@ -12,7 +11,7 @@ class Bloomberg(BasicNewsRecipe): no_stylesheets = True use_embedded_content = False remove_attributes = ['style', 'height', 'width'] - ignore_duplicate_articles = {'url'} + ignore_duplicate_articles = {'url', 'title'} resolve_internal_links = True oldest_article = 2 # days delay = 1.5 @@ -36,6 +35,9 @@ class Bloomberg(BasicNewsRecipe): url = e.hdrs.get('location') soup = self.index_to_soup(url) link = soup.find('a', attrs={'href':lambda x: x and x.startswith('https://www.bloomberg.com')}) + if '/videos/' in link['href']: + self.abort_article('Aborting Video article') + self.log('Found link: ', link['href']) html = br.open(link['href']).read() pt = PersistentTemporaryFile('.html') pt.write(html) @@ -49,7 +51,14 @@ class Bloomberg(BasicNewsRecipe): return br feeds = [ - ('Articles', 'https://news.google.com/rss/search?q=when:24h+allinurl:bloomberg.com&hl=en-US&gl=US&ceid=US:en'), + ('Features', + 'https://news.google.com/rss/search?q=when:27h+allinurl:bloomberg.com%2Fnews%2Ffeatures%2F&hl=en-US&gl=US&ceid=US:en'), + ('News', + 'https://news.google.com/rss/search?q=when:27h+allinurl:bloomberg.com%2Fnews%2Farticles%2F&hl=en-US&gl=US&ceid=US:en'), + ('Opinion', 'https://news.google.com/rss/search?q=when:27h+allinurl:bloomberg.com%2Fopinion%2F&hl=en-US&gl=US&ceid=US:en'), + ('Newsletters', + 'https://news.google.com/rss/search?q=when:27h+allinurl:bloomberg.com%2Fnews%2Fnewsletters%2F&hl=en-US&gl=US&ceid=US:en'), + ('Others', 'https://news.google.com/rss/search?q=when:27h+allinurl:bloomberg.com&hl=en-US&gl=US&ceid=US:en') ] def preprocess_raw_html(self, raw, *a): @@ -84,12 +93,12 @@ class Bloomberg(BasicNewsRecipe): if 'ledeImageUrl' in data: if data['ledeImageUrl'] is not None: - lede = '

'.format(data['ledeImageUrl'].replace('\\', '')) + lede = '

'.format(data['ledeImageUrl']) if data['ledeDescription'] is not None: caption = '' + data['ledeDescription'] + '' - body = data['body'].replace('\\', '') + body = data['body'] html = '' + cat + title + subhead + auth + lede + caption + '

' + body return html diff --git a/recipes/deccan_herald.recipe b/recipes/deccan_herald.recipe index 9d3ec3b47b..bb8d69e3db 100644 --- a/recipes/deccan_herald.recipe +++ b/recipes/deccan_herald.recipe @@ -8,9 +8,9 @@ class herald(BasicNewsRecipe): language = 'en_IN' no_stylesheets = True remove_attributes = ['height', 'width', 'style'] - ignore_duplicate_articles = {'url'} + ignore_duplicate_articles = {'url', 'title'} encoding = 'utf-8' - + articles_are_obfuscated = True def get_obfuscated_article(self, url): @@ -22,26 +22,26 @@ class herald(BasicNewsRecipe): soup = self.index_to_soup(url) link = soup.find('a', href=True) skip_sections =[ # add sections you want to skip - '/sports/', '/video/', '/bengaluru-crime/', '/metrolife/', + '/video/', '/bengaluru-crime/', '/metrolife/', '/karnataka-districts/', '/brandspot/', '/entertainment/', ] if any(x in link['href'] for x in skip_sections): self.log('Aborting Article ', link['href']) self.abort_article('skipping section') - + self.log('Downloading ', link['href']) html = br.open(link['href']).read() pt = PersistentTemporaryFile('.html') pt.write(html) pt.close() return pt.name - + keep_only_tags = [ classes('article-title article-author__name'), dict(name='div', attrs={'id':'main-content'}) - + ] - + remove_tags = [ classes( 'storyShare social-media-icons in_article_video static_text' @@ -49,7 +49,17 @@ class herald(BasicNewsRecipe): ' field-name-field-tags section-full strip--business' ) ] - + feeds = [ - ('DH', 'https://news.google.com/rss/search?q=when:27h+allinurl:deccanherald.com&hl=en-IN&gl=IN&ceid=IN:en') - ] + ('Nation', 'https://news.google.com/rss/search?q=when:27h+allinurl:deccanherald.com%2Fnational%2F&hl=en-IN&gl=IN&ceid=IN:en'), + ('Karnataka', 'https://news.google.com/rss/search?q=when:27h+allinurl:deccanherald.com%2Fstate%2F&hl=en-IN&gl=IN&ceid=IN:en'), + ('Opinion', 'https://news.google.com/rss/search?q=when:27h+allinurl:deccanherald.com%2Fopinion%2F&hl=en-IN&gl=IN&ceid=IN:en'), + ('City', + 'https://news.google.com/rss/search?q=when:27h+allinurl:deccanherald.com%2Fcity%2F&hl=en-IN&gl=IN&ceid=IN:en'), + ('Business', 'https://news.google.com/rss/search?q=when:27h+allinurl:deccanherald.com%2Fbusiness%2F&hl=en-IN&gl=IN&ceid=IN:en'), + ('World', + 'https://news.google.com/rss/search?q=when:27h+allinurl:deccanherald.com%2Finternational%2F&hl=en-IN&gl=IN&ceid=IN:en'), + ('Sports', + 'https://news.google.com/rss/search?q=when:27h+allinurl:deccanherald.com%2Fsports%2F&hl=en-IN&gl=IN&ceid=IN:en'), + ('Others', 'https://news.google.com/rss/search?q=when:27h+allinurl:deccanherald.com&hl=en-IN&gl=IN&ceid=IN:en'), + ] \ No newline at end of file From 815cd507b92a4998a24d0e41495f31910c66aadb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 24 Feb 2023 07:50:56 +0530 Subject: [PATCH 0314/2055] When computing title sorts strip leading and trailing quotes, not just leading quotes --- src/calibre/ebooks/metadata/__init__.py | 33 +++++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index 66961ba9fb..35ddd231a9 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -13,7 +13,7 @@ from contextlib import suppress from calibre import relpath, guess_type, prints, force_unicode from calibre.utils.config_base import tweaks -from polyglot.builtins import codepoint_to_chr, iteritems, as_unicode +from polyglot.builtins import iteritems, as_unicode from polyglot.urllib import quote, unquote, urlparse @@ -176,8 +176,25 @@ def get_title_sort_pat(lang=None): return ans -_ignore_starts = '\'"'+''.join(codepoint_to_chr(x) for x in - list(range(0x2018, 0x201e))+[0x2032, 0x2033]) +quote_pairs = { + # https://en.wikipedia.org/wiki/Quotation_mark + '"': ('"',), + "'": ("'",), + '“': ('”','“'), + '”': ('”','”'), + '„': ('”','“'), + '‚': ('’','‘'), + '’': ('’','‘'), + '‘': ('’','‘'), + '‹': ('›',), + '›': ('‹',), + '《': ('》',), + '〈': ('〉',), + '»': ('«', '»'), + '«': ('«', '»'), + '「': ('」',), + '『': ('』',), +} def title_sort(title, order=None, lang=None): @@ -186,8 +203,11 @@ def title_sort(title, order=None, lang=None): title = title.strip() if order == 'strictly_alphabetic': return title - if title and title[0] in _ignore_starts: + if title and title[0] in quote_pairs: + q = title[0] title = title[1:] + if title and title[-1] in quote_pairs[q]: + title = title[:-1] match = get_title_sort_pat(lang).search(title) if match: try: @@ -197,8 +217,11 @@ def title_sort(title, order=None, lang=None): else: if prep: title = title[len(prep):] + ', ' + prep - if title[0] in _ignore_starts: + if title[0] in quote_pairs: + q = title[0] title = title[1:] + if title and title[-1] in quote_pairs[q]: + title = title[:-1] return title.strip() From f851d78e6a349f3235a32e53c69b880ef6fe0463 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 24 Feb 2023 21:21:16 +0530 Subject: [PATCH 0315/2055] Update New York Magazine --- recipes/nymag.recipe | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/recipes/nymag.recipe b/recipes/nymag.recipe index 2bc8166ddb..46050559af 100644 --- a/recipes/nymag.recipe +++ b/recipes/nymag.recipe @@ -24,12 +24,10 @@ class NewYorkMagazine(BasicNewsRecipe): remove_javascript = True encoding = 'utf-8' keep_only_tags = [ - classes('lede-text headline-primary article-timestamp by-authors'), - dict(id='main'), - dict(itemprop='articleBody'), + dict(name='article', attrs={'class':lambda x: x and 'article' in x.split()}) ] remove_tags = [ - classes('related-stories start-discussion'), + classes('related-stories start-discussion newsletter-flex-text comments-link tags related secondary-area'), dict(id=['minibrowserbox', 'article-related', 'article-tools']) ] remove_attributes = ['srcset'] @@ -70,6 +68,9 @@ class NewYorkMagazine(BasicNewsRecipe): return feeds def preprocess_html(self, soup): + if lede := soup.findAll('div', attrs={'class':lambda x: x and 'lede-image-wrapper' in x.split()}): + if len(lede) > 1: + lede[1].extract() for img in soup.findAll('img', attrs={'data-src': True}): img['src'] = img['data-src'] return soup From fea57acc6b83e96c41cd39abda16841add30e30c Mon Sep 17 00:00:00 2001 From: un-pogaz <46523284+un-pogaz@users.noreply.github.com> Date: Sun, 26 Feb 2023 15:15:03 +0100 Subject: [PATCH 0316/2055] 'The buttons on the search bar' not translated --- src/calibre/gui2/preferences/toolbar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/preferences/toolbar.py b/src/calibre/gui2/preferences/toolbar.py index 44c6945e3d..5d0f1d9f0d 100644 --- a/src/calibre/gui2/preferences/toolbar.py +++ b/src/calibre/gui2/preferences/toolbar.py @@ -246,7 +246,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): ('toolbar', _('The main toolbar')), ('toolbar-device', _('The main toolbar when a device is connected')), ('toolbar-child', _('The optional second toolbar')), - ('searchbar', ('The buttons on the search bar')), + ('searchbar', _('The buttons on the search bar')), ('menubar', _('The menubar')), ('menubar-device', _('The menubar when a device is connected')), ('context-menu', _('The context menu for the books in the ' From 8d5840afc65788e7cc0fe9cbf7de67908464c5fb Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 26 Feb 2023 16:37:56 +0000 Subject: [PATCH 0317/2055] Enhancement #2006979: Hierarchical search menu in Additional Restrictions --- src/calibre/gui2/search_box.py | 37 ++++++++++++-------- src/calibre/gui2/search_restriction_mixin.py | 32 ++++++++--------- 2 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 6070a5fe7a..71a77a00e0 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -439,17 +439,12 @@ class SavedSearchBoxMixin: # {{{ def init_saved_seach_box_mixin(self): pass - def populate_add_saved_search_menu(self, to_menu): - m = to_menu - m.clear() - m.clear() - m.addAction(QIcon.ic('search_add_saved.png'), _('Add Saved search'), self.add_saved_search) - m.addAction(QIcon.ic('search_copy_saved.png'), _('Get Saved search expression'), - self.get_saved_search_text) - m.addAction(QIcon.ic('folder_saved_search.png'), _('Manage Saved searches'), - partial(self.do_saved_search_edit, None)) - m.addSeparator() - db = self.current_db + def add_saved_searches_to_menu(self, menu, db, add_action_func=None): + def add_action(current_menu, whole_name, last_component, func=None): + if add_action_func is None: + return current_menu.addAction(last_component, func) + return add_action_func(current_menu, whole_name, last_component) + folder_icon = QIcon.ic('folder_saved_search.png') search_icon = QIcon.ic('search.png') use_hierarchy = 'search' in db.new_api.pref('categories_using_hierarchy', []) @@ -459,7 +454,7 @@ class SavedSearchBoxMixin: # {{{ components = tuple(n.strip() for n in name.split('.')) hierarchy = components[:-1] last = components[-1] - current_menu = m + current_menu = menu # Walk the hierarchy, creating submenus as needed for i,c in enumerate(hierarchy, start=1): hierarchical_prefix = '.'.join(hierarchy[:i]) @@ -469,10 +464,22 @@ class SavedSearchBoxMixin: # {{{ submenus[hierarchical_prefix] = current_menu else: current_menu = submenus[hierarchical_prefix] - ac = current_menu.addAction(last, partial(self.search.set_search_string, 'search:"='+name+'"')) + ac = add_action(current_menu, name, last, partial(self.search.set_search_string, 'search:"='+name+'"')) else: - ac = m.addAction(name, partial(self.search.set_search_string, 'search:"='+name+'"')) - ac.setIcon(search_icon) + ac = add_action(current_menu, name, name, partial(self.search.set_search_string, 'search:"='+name+'"')) + if ac.icon().isNull(): + ac.setIcon(search_icon) + + def populate_add_saved_search_menu(self, to_menu): + m = to_menu + m.clear() + m.addAction(QIcon.ic('search_add_saved.png'), _('Add Saved search'), self.add_saved_search) + m.addAction(QIcon.ic('search_copy_saved.png'), _('Get Saved search expression'), + self.get_saved_search_text) + m.addAction(QIcon.ic('folder_saved_search.png'), _('Manage Saved searches'), + partial(self.do_saved_search_edit, None)) + m.addSeparator() + self.add_saved_searches_to_menu(m, self.current_db) def saved_searches_changed(self, set_restriction=None, recount=True): self.build_search_restriction_list() diff --git a/src/calibre/gui2/search_restriction_mixin.py b/src/calibre/gui2/search_restriction_mixin.py index 5f35b92cbd..b03524885a 100644 --- a/src/calibre/gui2/search_restriction_mixin.py +++ b/src/calibre/gui2/search_restriction_mixin.py @@ -547,7 +547,6 @@ class SearchRestrictionMixin: def build_search_restriction_list(self): self.search_restriction_list_built = True - from calibre.gui2.ui import get_gui m = self.ar_menu m.clear() @@ -562,26 +561,25 @@ class SearchRestrictionMixin: current_restriction = self.library_view.model().db.data.get_search_restriction_name() m.setIcon(self.checked if current_restriction else self.empty) - def add_action(txt, index): - self.search_restriction.addItem(txt) - txt = self._trim_restriction_name(txt) - if txt == current_restriction: - a = m.addAction(self.checked, txt if txt else self.no_restriction) + dex = 0 + def add_action(current_menu, name, last): + nonlocal dex + self.search_restriction.addItem(name) + txt = self._trim_restriction_name(last) + if name == current_restriction: + a = current_menu.addAction(self.checked, txt if txt else self.no_restriction) else: - a = m.addAction(self.empty, txt if txt else self.no_restriction) + a = current_menu.addAction(txt if txt else self.no_restriction) a.triggered.connect(partial(self.search_restriction_triggered, - action=a, index=index)) + action=a, index=dex)) + dex += 1 + return a - add_action('', 0) - add_action(_('*current search'), 1) - dex = 2 + add_action(m, '', '') + add_action(m, _('*current search'), _('*current search')) if current_restriction_text: - add_action(current_restriction_text, 2) - dex += 1 - - for n in sorted(get_gui().current_db.saved_search_names(), key=sort_key): - add_action(n, dex) - dex += 1 + add_action(m, current_restriction_text) + self.add_saved_searches_to_menu(m, self.library_view.model().db, add_action) def search_restriction_triggered(self, action=None, index=None): self.search_restriction.setCurrentIndex(index) From 78638a28f2e4208e57c446e34846a9b17c6c818e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 27 Feb 2023 08:13:11 +0530 Subject: [PATCH 0318/2055] ... --- src/calibre/gui2/toc/main.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py index fcb293c22c..3be48c992d 100644 --- a/src/calibre/gui2/toc/main.py +++ b/src/calibre/gui2/toc/main.py @@ -1127,7 +1127,7 @@ class TOCEditor(QDialog): # {{{ tb = None try: self.ebook = get_container(self.pathtobook, log=self.log) - except: + except Exception: import traceback tb = traceback.format_exc() if self.working: @@ -1162,6 +1162,15 @@ class TOCEditor(QDialog): # {{{ # }}} +def develop(): + from calibre.gui2 import Application + app = Application([]) + d = TOCEditor(sys.argv[-1]) + d.start() + d.exec() + del app + + def main(shm_name=None): import json import struct From 94b606756c80d133a4268e18ea4c43bbe2f9f300 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 27 Feb 2023 08:21:35 +0530 Subject: [PATCH 0319/2055] ... --- src/calibre/gui2/toc/main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py index 3be48c992d..30d9770dc9 100644 --- a/src/calibre/gui2/toc/main.py +++ b/src/calibre/gui2/toc/main.py @@ -1021,8 +1021,7 @@ class TOCEditor(QDialog): # {{{ l.addWidget(s) self.loading_widget = lw = QWidget(self) s.addWidget(lw) - ll = self.ll = QVBoxLayout() - lw.setLayout(ll) + ll = self.ll = QVBoxLayout(lw) self.pi = pi = ProgressIndicator() pi.setDisplaySize(QSize(200, 200)) pi.startAnimation() From d9bd60bb33aac9aa00125c76f232c869acc4b4c6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 27 Feb 2023 08:22:47 +0530 Subject: [PATCH 0320/2055] Dont show geometry debug output by default --- src/calibre/gui2/geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/geometry.py b/src/calibre/gui2/geometry.py index af765daac1..dc39430083 100644 --- a/src/calibre/gui2/geometry.py +++ b/src/calibre/gui2/geometry.py @@ -13,7 +13,7 @@ from calibre.utils.config_base import tweaks def is_debugging(): - return _is_debugging() and not tweaks.get('suppress_geometry_debug_output') + return _is_debugging() and tweaks.get('show_geometry_debug_output') def debug(*a, **kw): From 312f5bd1e719903ed7ec2c2d7760230f1351df36 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 27 Feb 2023 08:24:06 +0530 Subject: [PATCH 0321/2055] ... --- src/calibre/gui2/toc/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py index 30d9770dc9..b2bf4407eb 100644 --- a/src/calibre/gui2/toc/main.py +++ b/src/calibre/gui2/toc/main.py @@ -1162,6 +1162,8 @@ class TOCEditor(QDialog): # {{{ def develop(): + from calibre.utils.webengine import setup_fake_protocol + setup_fake_protocol() from calibre.gui2 import Application app = Application([]) d = TOCEditor(sys.argv[-1]) From ebda3cdfa6bc0f814096d6e7747d9590d1573be3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 27 Feb 2023 08:45:51 +0530 Subject: [PATCH 0322/2055] fix bug in last PR that broke switching libraries --- src/calibre/gui2/search_box.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 71a77a00e0..b2c148f49c 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -450,11 +450,11 @@ class SavedSearchBoxMixin: # {{{ use_hierarchy = 'search' in db.new_api.pref('categories_using_hierarchy', []) submenus = {} for name in sorted(db.saved_search_names(), key=lambda x: primary_sort_key(x.strip())): + current_menu = menu if use_hierarchy: components = tuple(n.strip() for n in name.split('.')) hierarchy = components[:-1] last = components[-1] - current_menu = menu # Walk the hierarchy, creating submenus as needed for i,c in enumerate(hierarchy, start=1): hierarchical_prefix = '.'.join(hierarchy[:i]) From c1994f4af1e0e81a9277b9f20cc43ac09e868b57 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 27 Feb 2023 11:23:40 +0530 Subject: [PATCH 0323/2055] macOS: ToC Editor: Fix mouse becoming unuseable when trying to create a new entry. Fixes #2004639 [Unable to edit TOC](https://bugs.launchpad.net/calibre/+bug/2004639) --- src/calibre/gui2/toc/main.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py index b2bf4407eb..9f422c9365 100644 --- a/src/calibre/gui2/toc/main.py +++ b/src/calibre/gui2/toc/main.py @@ -10,13 +10,13 @@ from functools import partial from qt.core import ( QAbstractItemView, QCheckBox, QCursor, QDialog, QDialogButtonBox, QEvent, QFrame, QGridLayout, QIcon, QInputDialog, QItemSelectionModel, QKeySequence, QLabel, QMenu, - QPushButton, QScrollArea, QSize, QSizePolicy, QStackedWidget, Qt, QToolButton, - QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, pyqtSignal, + QPushButton, QScrollArea, QSize, QSizePolicy, QStackedWidget, Qt, QTimer, + QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, pyqtSignal, ) from threading import Thread from time import monotonic -from calibre.constants import TOC_DIALOG_APP_UID, islinux, iswindows +from calibre.constants import TOC_DIALOG_APP_UID, islinux, ismacos, iswindows from calibre.ebooks.oeb.polish.container import AZW3Container, get_container from calibre.ebooks.oeb.polish.toc import ( TOC, add_id, commit_toc, from_files, from_links, from_xpaths, get_toc, @@ -1064,6 +1064,15 @@ class TOCEditor(QDialog): # {{{ def add_new_item(self, item, where): self.item_edit(item, where) self.stacks.setCurrentIndex(2) + if ismacos: + QTimer.singleShot(0, self.workaround_macos_mouse_with_webview_bug) + + def workaround_macos_mouse_with_webview_bug(self): + # macOS is weird: https://bugs.launchpad.net/calibre/+bug/2004639 + # needed as of Qt 6.4.2 + d = info_dialog(self, _('Loading...'), _('Loading view, please wait...'), show_copy_button=False) + QTimer.singleShot(0, d.reject) + d.exec() def accept(self): if monotonic() - self.last_accept_at < 1: From 287dc3d94e78e40123cb53219ef3e9ad65bf84e2 Mon Sep 17 00:00:00 2001 From: Guido Falsi Date: Tue, 28 Feb 2023 23:15:05 +0100 Subject: [PATCH 0324/2055] Fix improper use of double quotes. SQLite has historically supported using double quotes for string literals against SQL standards. The default for supporting this has been recently changed upstream [1] (6.f) Ref: https://sqlite.org/quirks.html#dblquote https://www.sqlite.org/releaselog/3_41_0.html [1] --- src/calibre/db/backend.py | 4 ++-- src/calibre/db/schema_upgrades.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 614abdbcdc..4e6bd479de 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1385,8 +1385,8 @@ class DB: @property def custom_tables(self): return {x[0] for x in self.conn.get( - 'SELECT name FROM sqlite_master WHERE type="table" AND ' - '(name GLOB "custom_column_*" OR name GLOB "books_custom_column_*")')} + 'SELECT name FROM sqlite_master WHERE type=\'table\' AND ' + '(name GLOB \'custom_column_*\' OR name GLOB \'books_custom_column_*\')')} @classmethod def exists_at(cls, path): diff --git a/src/calibre/db/schema_upgrades.py b/src/calibre/db/schema_upgrades.py index c502622720..e5b4bf4419 100644 --- a/src/calibre/db/schema_upgrades.py +++ b/src/calibre/db/schema_upgrades.py @@ -300,7 +300,7 @@ class SchemaUpgrade: for field in itervalues(self.field_metadata): if field['is_category'] and not field['is_custom'] and 'link_column' in field: table = self.db.get( - 'SELECT name FROM sqlite_master WHERE type="table" AND name=?', + 'SELECT name FROM sqlite_master WHERE type=\'table\' AND name=?', ('books_%s_link'%field['table'],), all=False) if table is not None: create_tag_browser_view(field['table'], field['link_column'], field['column']) @@ -376,7 +376,7 @@ class SchemaUpgrade: for field in itervalues(self.field_metadata): if field['is_category'] and not field['is_custom'] and 'link_column' in field: table = self.db.get( - 'SELECT name FROM sqlite_master WHERE type="table" AND name=?', + 'SELECT name FROM sqlite_master WHERE type=\'table\' AND name=?', ('books_%s_link'%field['table'],), all=False) if table is not None: create_std_tag_browser_view(field['table'], field['link_column'], From d3949bdecf9b3acdbe0d9bf0d9491533c9bda059 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 1 Mar 2023 19:24:04 +0530 Subject: [PATCH 0325/2055] More SQLITE double quote to single quote changes --- resources/fts_sqlite.sql | 6 +++--- resources/metadata_sqlite.sql | 16 ++++++++-------- src/calibre/db/backend.py | 20 ++++++++++---------- src/calibre/db/cache.py | 2 +- src/calibre/db/fts/connect.py | 6 +++--- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/resources/fts_sqlite.sql b/resources/fts_sqlite.sql index cda94b4f11..24348c15b8 100644 --- a/resources/fts_sqlite.sql +++ b/resources/fts_sqlite.sql @@ -11,10 +11,10 @@ CREATE TABLE fts_db.books_text ( id INTEGER PRIMARY KEY, format TEXT NOT NULL COLLATE NOCASE, format_hash TEXT NOT NULL COLLATE NOCASE, format_size INTEGER NOT NULL DEFAULT 0, - searchable_text TEXT NOT NULL DEFAULT "", + searchable_text TEXT NOT NULL DEFAULT '', text_size INTEGER NOT NULL DEFAULT 0, - text_hash TEXT NOT NULL COLLATE NOCASE DEFAULT "", - err_msg TEXT DEFAULT "", + text_hash TEXT NOT NULL COLLATE NOCASE DEFAULT '', + err_msg TEXT DEFAULT '', UNIQUE(book, format) ); diff --git a/resources/metadata_sqlite.sql b/resources/metadata_sqlite.sql index f7190ba588..aa7e04c2ad 100644 --- a/resources/metadata_sqlite.sql +++ b/resources/metadata_sqlite.sql @@ -1,7 +1,7 @@ CREATE TABLE authors ( id INTEGER PRIMARY KEY, name TEXT NOT NULL COLLATE NOCASE, sort TEXT COLLATE NOCASE, - link TEXT NOT NULL DEFAULT "", + link TEXT NOT NULL DEFAULT '', UNIQUE(name) ); CREATE TABLE books ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -11,13 +11,13 @@ CREATE TABLE books ( id INTEGER PRIMARY KEY AUTOINCREMENT, pubdate TIMESTAMP DEFAULT CURRENT_TIMESTAMP, series_index REAL NOT NULL DEFAULT 1.0, author_sort TEXT COLLATE NOCASE, - isbn TEXT DEFAULT "" COLLATE NOCASE, - lccn TEXT DEFAULT "" COLLATE NOCASE, - path TEXT NOT NULL DEFAULT "", + isbn TEXT DEFAULT '' COLLATE NOCASE, + lccn TEXT DEFAULT '' COLLATE NOCASE, + path TEXT NOT NULL DEFAULT '', flags INTEGER NOT NULL DEFAULT 1, uuid TEXT, has_cover BOOL DEFAULT 0, - last_modified TIMESTAMP NOT NULL DEFAULT "2000-01-01 00:00:00+00:00"); + last_modified TIMESTAMP NOT NULL DEFAULT '2000-01-01 00:00:00+00:00'); CREATE TABLE books_authors_link ( id INTEGER PRIMARY KEY, book INTEGER NOT NULL, author INTEGER NOT NULL, @@ -72,7 +72,7 @@ CREATE TABLE custom_columns ( datatype TEXT NOT NULL, mark_for_delete BOOL DEFAULT 0 NOT NULL, editable BOOL DEFAULT 1 NOT NULL, - display TEXT DEFAULT "{}" NOT NULL, + display TEXT DEFAULT '{}' NOT NULL, is_multiple BOOL DEFAULT 0 NOT NULL, normalized BOOL NOT NULL, UNIQUE(label) @@ -91,7 +91,7 @@ CREATE TABLE feeds ( id INTEGER PRIMARY KEY, ); CREATE TABLE identifiers ( id INTEGER PRIMARY KEY, book INTEGER NOT NULL, - type TEXT NOT NULL DEFAULT "isbn" COLLATE NOCASE, + type TEXT NOT NULL DEFAULT 'isbn' COLLATE NOCASE, val TEXT NOT NULL COLLATE NOCASE, UNIQUE(book, type) ); @@ -151,7 +151,7 @@ CREATE TABLE annotations ( id INTEGER PRIMARY KEY, annot_id TEXT NOT NULL, annot_type TEXT NOT NULL, annot_data TEXT NOT NULL, - searchable_text TEXT NOT NULL DEFAULT "", + searchable_text TEXT NOT NULL DEFAULT '', UNIQUE(book, user_type, user, format, annot_type, annot_id) ); diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 4e6bd479de..ffc13cfb30 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -162,7 +162,7 @@ class DBPrefs(dict): # {{{ data = json.dumps(self, indent=2, default=to_json) if not isinstance(data, bytes): data = data.encode('utf-8') - with open(to_filename, "wb") as f: + with open(to_filename, 'wb') as f: f.write(data) except: import traceback @@ -172,7 +172,7 @@ class DBPrefs(dict): # {{{ def read_serialized(cls, library_path, recreate_prefs=False): from_filename = os.path.join(library_path, 'metadata_db_prefs_backup.json') - with open(from_filename, "rb") as f: + with open(from_filename, 'rb') as f: return json.load(f, object_hook=from_json) # }}} @@ -1531,8 +1531,8 @@ class DB: path = os.path.abspath(os.path.join(self.library_path, path, 'cover.jpg')) if windows_atomic_move is not None: if not isinstance(dest, string_or_bytes): - raise Exception("Error, you must pass the dest as a path when" - " using windows_atomic_move") + raise Exception('Error, you must pass the dest as a path when' + ' using windows_atomic_move') if os.access(path, os.R_OK) and dest and not samefile(dest, path): windows_atomic_move.copy_path_to(path, dest) return True @@ -1638,8 +1638,8 @@ class DB: return False if windows_atomic_move is not None: if not isinstance(dest, string_or_bytes): - raise Exception("Error, you must pass the dest as a path when" - " using windows_atomic_move") + raise Exception('Error, you must pass the dest as a path when' + ' using windows_atomic_move') if dest: if samefile(dest, path): # Ensure that the file has the same case as dest @@ -1919,11 +1919,11 @@ class DB: text = 'annotations.searchable_text' if highlight_start is not None and highlight_end is not None: if snippet_size is not None: - text = 'snippet({fts_table}, 0, "{highlight_start}", "{highlight_end}", "…", {snippet_size})'.format( + text = "snippet({fts_table}, 0, '{highlight_start}', '{highlight_end}', '…', {snippet_size})".format( fts_table=fts_table, highlight_start=highlight_start, highlight_end=highlight_end, snippet_size=max(1, min(snippet_size, 64))) else: - text = f'highlight({fts_table}, 0, "{highlight_start}", "{highlight_end}")' + text = f"highlight({fts_table}, 0, '{highlight_start}', '{highlight_end}')" query = 'SELECT {0}.id, {0}.book, {0}.format, {0}.user_type, {0}.user, {0}.annot_data, {1} FROM {0} ' query = query.format('annotations', text) query += ' JOIN {fts_table} ON annotations.id = {fts_table}.rowid'.format(fts_table=fts_table) @@ -1994,7 +1994,7 @@ class DB: new_annot['title'] = annot_data['title'] replacements.append((json.dumps(new_annot), timestamp, annot_id)) if replacements: - self.executemany('UPDATE annotations SET annot_data=?, timestamp=?, searchable_text="" WHERE id=?', replacements) + self.executemany("UPDATE annotations SET annot_data=?, timestamp=?, searchable_text='' WHERE id=?", replacements) if removals: self.executemany('DELETE FROM annotations WHERE id=?', removals) @@ -2085,7 +2085,7 @@ class DB: def annotation_count_for_book(self, book_id): for (count,) in self.execute(''' SELECT count(id) FROM annotations - WHERE book=? AND json_extract(annot_data, "$.removed") IS NULL + WHERE book=? AND json_extract(annot_data, '$.removed') IS NULL ''', (book_id,)): return count return 0 diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index a91a99930c..2ef2f5d449 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -943,7 +943,7 @@ class Cache: # We can't clear the composite caches because a read lock is set. # As a consequence the value of a composite column that calls # virtual_libraries() might be wrong. Oh well. Log and keep running. - print("Couldn't get write lock after vls_for_books_cache was loaded", file=sys.stderr) + print('Couldn\'t get write lock after vls_for_books_cache was loaded', file=sys.stderr) traceback.print_exc() if get_cover: diff --git a/src/calibre/db/fts/connect.py b/src/calibre/db/fts/connect.py index 9ea3d5c35a..c7b6b9ce2e 100644 --- a/src/calibre/db/fts/connect.py +++ b/src/calibre/db/fts/connect.py @@ -39,7 +39,7 @@ class FTS: if conn.fts_dbpath is None: main_db_path = os.path.abspath(conn.db_filename('main')) dbpath = os.path.join(os.path.dirname(main_db_path), 'full-text-search.db') - conn.execute(f'ATTACH DATABASE "{dbpath}" AS fts_db') + conn.execute(f"ATTACH DATABASE '{dbpath}' AS fts_db") SchemaUpgrade(conn) conn.execute('UPDATE fts_db.dirtied_formats SET in_progress=FALSE WHERE in_progress=TRUE') num_dirty = conn.get('''SELECT COUNT(*) from fts_db.dirtied_formats''')[0][0] @@ -160,9 +160,9 @@ class FTS: text = 'books_text.searchable_text' if highlight_start is not None and highlight_end is not None: if snippet_size is not None: - text = f'snippet("{fts_table}", 0, "{highlight_start}", "{highlight_end}", "…", {max(1, min(snippet_size, 64))})' + text = f'''snippet("{fts_table}", 0, '{highlight_start}', '{highlight_end}', '…', {max(1, min(snippet_size, 64))})''' else: - text = f'highlight("{fts_table}", 0, "{highlight_start}", "{highlight_end}")' + text = f'''highlight("{fts_table}", 0, '{highlight_start}', '{highlight_end}')''' text = ', ' + text else: text = '' From 901ab57aa598f4d66a7b62ab40702c5b9aa8fc6d Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Fri, 3 Mar 2023 12:01:53 +0000 Subject: [PATCH 0326/2055] Enhancement #2009115: User Category Editor: Hide items already in category --- src/calibre/gui2/dialogs/tag_categories.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/tag_categories.py b/src/calibre/gui2/dialogs/tag_categories.py index 9df414d4c8..f498b47005 100644 --- a/src/calibre/gui2/dialogs/tag_categories.py +++ b/src/calibre/gui2/dialogs/tag_categories.py @@ -1,7 +1,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -from collections import namedtuple +from collections import namedtuple, defaultdict from qt.core import QApplication, QDialog, QIcon, QListWidgetItem, Qt from calibre.constants import islinux @@ -213,9 +213,14 @@ class TagCategories(QDialog, Ui_TagCategories): idx = self.category_filter_box.currentIndex() filter_key = self.category_filter_box.itemData(idx) self.available_items_box.clear() + applied = defaultdict(set) + for it in self.applied_items: + applied[it.k].add(it.v) for it in self.sorted_items: if idx != 0 and it.k != filter_key: continue + if it.v in applied[it.k]: + continue self.available_items_box.addItem(self.make_available_list_item(it.k, it.v)) def fill_applied_items(self): @@ -228,6 +233,7 @@ class TagCategories(QDialog, Ui_TagCategories): self.applied_items_box.clear() for tup in self.applied_items: self.applied_items_box.addItem(self.make_applied_list_item(tup)) + self.display_filtered_categories() def apply_button_clicked(self): self.apply_tags(node=None) From e2bd25b5911b985be87de838deeab551676af1da Mon Sep 17 00:00:00 2001 From: jjcoffee Date: Fri, 3 Mar 2023 18:53:47 +0100 Subject: [PATCH 0327/2055] Fix DR Nyheder recipe The old one was completely broken (DR's website is completely different), so I had to rewrite it from scratch. --- recipes/dr_dk.recipe | 168 ++++++++++++++++++++++++++++++------------- 1 file changed, 120 insertions(+), 48 deletions(-) diff --git a/recipes/dr_dk.recipe b/recipes/dr_dk.recipe index 2ba55a2e36..814e22d2d6 100644 --- a/recipes/dr_dk.recipe +++ b/recipes/dr_dk.recipe @@ -1,58 +1,130 @@ #!/usr/bin/env python # vim:fileencoding=utf-8 +# License: GPLv3 Copyright: 2023, Joel Davies + from __future__ import unicode_literals, division, absolute_import, print_function from calibre.web.feeds.news import BasicNewsRecipe -__license__ = 'GPL v3' -__copyright__ = '2010, Darko Miletic ' -''' -DR.dk -''' - - class DRNyheder(BasicNewsRecipe): - title = 'DR Nyheder' - __author__ = 'Darko Miletic' - publisher = 'DR Nyheder' - description = 'Her finder du nyheder fra DR og alle vores TV og Radio kanaler live og on demand - når du har lyst.' - category = 'news, politics, money, culture, sport, science, Denmark' - oldest_article = 2 - max_articles_per_feed = 50 - no_stylesheets = True - encoding = 'utf8' - use_embedded_content = False - language = 'da' - auto_cleanup = False - keep_only_tags = [ - dict(name="h1", attrs={'id': 'access-content'}), - dict(name="p", attrs={'class': 'summary'}), - dict(name="span", attrs={'itemprop': 'datePublished'}), - dict(name="div", attrs={'class': 'wcms-article-content'}), - ] - - remove_tags = [ - dict(name='menu', attrs={'class': 'share'}), - dict(name='menu', attrs={'class': 'dr-site-share-horizontal'}), - ] - - # Feed are found here: http://www.dr.dk/nyheder/dr-nyheder-som-rss-feed + # Feeds are found here: https://www.dr.dk/nyheder/dr-nyheder-som-rss-feed feeds = [ - ('Indland', 'http://www.dr.dk/nyheder/service/feeds/indland'), - ('Udland', 'http://www.dr.dk/nyheder/service/feeds/udland'), - ('Penge', 'http://www.dr.dk/nyheder/service/feeds/penge'), - ('Politik', 'http://www.dr.dk/nyheder/service/feeds/politik'), - ('Kultur', 'http://www.dr.dk/nyheder/service/feeds/kultur'), - ('Sporten', 'http://www.dr.dk/nyheder/service/feeds/sporten'), - ('Viden', 'http://www.dr.dk/nyheder/service/feeds/viden'), - ('Lev Nu', 'http://www.dr.dk/nyheder/service/feeds/levnu'), - ('DR Hovedstadsområdet', 'http://www.dr.dk/Nyheder/Service/feeds/regionale/kbh/'), - ('DR Bornholm', 'http://www.dr.dk/Nyheder/Service/feeds/regionale/bornholm/'), - ('DR Syd og Sønderjylland', 'http://www.dr.dk/Nyheder/Service/feeds/regionale/syd/'), - ('DR Fyn', 'http://www.dr.dk/Nyheder/Service/feeds/regionale/fyn/'), - ('DR Nordjylland', 'http://www.dr.dk/Nyheder/Service/feeds/regionale/nord/'), - ('DR Trekantområdet', 'http://www.dr.dk/Nyheder/Service/feeds/regionale/trekanten/'), - ('DR Sjælland', 'http://www.dr.dk/Nyheder/Service/feeds/regionale/sjaelland/'), - ('DR Østjylland', 'http://www.dr.dk/Nyheder/Service/feeds/regionale/oestjylland/'), + ('Seneste nyt', 'https://www.dr.dk/nyheder/service/feeds/senestenyt'), + ('Indland', 'https://www.dr.dk/nyheder/service/feeds/indland'), + ('Udland', 'https://www.dr.dk/nyheder/service/feeds/udland'), + ('Penge', 'https://www.dr.dk/nyheder/service/feeds/penge'), + ('Politik', 'https://www.dr.dk/nyheder/service/feeds/politik'), + #('Sporten', 'https://www.dr.dk/nyheder/service/feeds/sporten'), + #('Seneste sport', 'https://www.dr.dk/nyheder/service/feeds/senestesport'), + ('Viden', 'https://www.dr.dk/nyheder/service/feeds/viden'), + ('Kultur', 'https://www.dr.dk/nyheder/service/feeds/kultur'), + ('Musik', 'https://www.dr.dk/nyheder/service/feeds/musik'), + ('Mit Liv', 'https://www.dr.dk/nyheder/service/feeds/mitliv'), + ('Mad', 'https://www.dr.dk/nyheder/service/feeds/mad'), + ('Vejret', 'https://www.dr.dk/nyheder/service/feeds/vejret'), + ('Regionale', 'https://www.dr.dk/nyheder/service/feeds/regionale'), + ('DR Hovedstadsområdet', 'https://www.dr.dk/nyheder/service/feeds/regionale/kbh'), + ('DR Bornholm', 'https://www.dr.dk/nyheder/service/feeds/regionale/bornholm'), + ('DR Syd og Sønderjylland', 'https://www.dr.dk/nyheder/service/feeds/regionale/syd'), + ('DR Fyn', 'https://www.dr.dk/nyheder/service/feeds/regionale/fyn'), + ('DR Midt- og Vestjylland', 'https://www.dr.dk/nyheder/service/feeds/regionale/vest'), + ('DR Nordjylland', 'https://www.dr.dk/nyheder/service/feeds/regionale/nord'), + ('DR Trekantområdet', 'https://www.dr.dk/nyheder/service/feeds/regionale/trekanten'), + ('DR Sjælland', 'https://www.dr.dk/nyheder/service/feeds/regionale/sjaelland'), + ('DR Østjylland', 'https://www.dr.dk/nyheder/service/feeds/regionale/oestjylland') ] + title = 'DR Nyheder' + __author__ = 'Joel Davies' + publisher = 'DR Nyheder' + description = 'Her finder du nyheder fra DR.' + category = 'news, politics, money, culture, sport, science, Denmark' + publication_type = 'newspaper' + encoding = 'utf8' + language = 'da' + oldest_article = 4 # 2 might be best + max_articles_per_feed = 50 # 100 better, this is just for testing + no_stylesheets = True + use_embedded_content = False + auto_cleanup = False + remove_empty_feeds = True + ignore_duplicate_articles = {'title', 'url'} + simultaneous_downloads = 20 + compress_news_images = True + masthead_url = 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/DR_logo.svg/1024px-DR_logo.svg.png' + + extra_css = ''' + .dre-byline__contributions { + margin-bottom: 10px; + } + + .dre-byline__contributions div { + display: inline; + } + + .dre-byline__contribution + .dre-byline__contribution:before { + display: inline; + content: ", "; + } + + .dre-standard-article__figure { + margin-bottom: 30px; + text-align: center; + } + + .dre-picture { + margin-bottom: 10px; + } + + .dre-picture__image { + max-width: 100%; + height: auto; + } + + .dre-standard-article__figure-caption { + font-size: .85em; + color: #575757; + } + ''' + + # Skip articles with /stories/ URL as these are Instagram story-style interactive pieces that play videos + # Also DRTV as these are just links to the live TV channel + def preprocess_raw_html(self, raw_html, url): + if '/stories/' in url or '/drtv/' in url: + self.abort_article('Skipping unsupported article type') + return raw_html + + # Generate cover from the first image on the dr.dk homepage + def get_cover_url(self): + cover_url = None + soup = self.index_to_soup('https://www.dr.dk/') + main_content = soup.find('ul', attrs={'class': 'dre-grid-layout'}) + cover_item = main_content.find('img') + if cover_item: + cover_url = cover_item['src'] + return cover_url + + + keep_only_tags = [ + + dict(name="h1", attrs={'class': 'dre-article-title__heading'}), # Title + dict(name="div", attrs={'class': 'dre-article-byline'}), # Author + dict(name="figure", attrs={'class': 'dre-standard-article__figure'}), # Comment out to remove images + dict(name="p", attrs={'class': 'dre-article-body-paragraph'}), # All body text of the article + dict(name="article", attrs={'itemtype': 'http://schema.org/NewsArticle'}), + #dict(name="h1", attrs={'class': 'hydra-latest-news-page-short-news__title'}), + #dict(name="p", attrs={'class': 'hydra-latest-news-page-short-news__paragraph'}), + #dict(name="div", attrs={'class': 'dre-speech'}), + #dict(name="div", attrs={'itemprop': 'author'}) + ] + + remove_tags = [ + dict(name='ol', attrs={'class': 'hydra-latest-news-page__list'}), + dict(name='div', attrs={'class': ['hydra-latest-news-page-short-news__share', 'hydra-latest-news-page-short-news__a11y-container', 'hydra-latest-news-page-short-news__meta', 'hydra-latest-news-page-short-news__image-slider', 'dre-byline__dates']}), + dict(name="source"), + #dict(name='menu', attrs={'class': 'share'}), + #dict(name='menu', attrs={'class': 'dr-site-share-horizontal'}), + ] + + # Fixes images having the wrong aspect ratio + remove_attributes = ['width', 'height'] From 74a59d04adf855f7c4619f316d2e2f76c3f8ef45 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 4 Mar 2023 06:33:31 +0530 Subject: [PATCH 0328/2055] pep8 --- recipes/dr_dk.recipe | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/recipes/dr_dk.recipe b/recipes/dr_dk.recipe index 814e22d2d6..a9cd6e6bb6 100644 --- a/recipes/dr_dk.recipe +++ b/recipes/dr_dk.recipe @@ -2,7 +2,6 @@ # vim:fileencoding=utf-8 # License: GPLv3 Copyright: 2023, Joel Davies -from __future__ import unicode_literals, division, absolute_import, print_function from calibre.web.feeds.news import BasicNewsRecipe class DRNyheder(BasicNewsRecipe): @@ -52,7 +51,7 @@ class DRNyheder(BasicNewsRecipe): simultaneous_downloads = 20 compress_news_images = True masthead_url = 'https://upload.wikimedia.org/wikipedia/commons/thumb/1/18/DR_logo.svg/1024px-DR_logo.svg.png' - + extra_css = ''' .dre-byline__contributions { margin-bottom: 10px; @@ -75,7 +74,7 @@ class DRNyheder(BasicNewsRecipe): .dre-picture { margin-bottom: 10px; } - + .dre-picture__image { max-width: 100%; height: auto; @@ -86,7 +85,7 @@ class DRNyheder(BasicNewsRecipe): color: #575757; } ''' - + # Skip articles with /stories/ URL as these are Instagram story-style interactive pieces that play videos # Also DRTV as these are just links to the live TV channel def preprocess_raw_html(self, raw_html, url): @@ -103,10 +102,10 @@ class DRNyheder(BasicNewsRecipe): if cover_item: cover_url = cover_item['src'] return cover_url - - + + keep_only_tags = [ - + dict(name="h1", attrs={'class': 'dre-article-title__heading'}), # Title dict(name="div", attrs={'class': 'dre-article-byline'}), # Author dict(name="figure", attrs={'class': 'dre-standard-article__figure'}), # Comment out to remove images @@ -117,10 +116,12 @@ class DRNyheder(BasicNewsRecipe): #dict(name="div", attrs={'class': 'dre-speech'}), #dict(name="div", attrs={'itemprop': 'author'}) ] - + remove_tags = [ dict(name='ol', attrs={'class': 'hydra-latest-news-page__list'}), - dict(name='div', attrs={'class': ['hydra-latest-news-page-short-news__share', 'hydra-latest-news-page-short-news__a11y-container', 'hydra-latest-news-page-short-news__meta', 'hydra-latest-news-page-short-news__image-slider', 'dre-byline__dates']}), + dict(name='div', attrs={'class': [ + 'hydra-latest-news-page-short-news__share', 'hydra-latest-news-page-short-news__a11y-container', + 'hydra-latest-news-page-short-news__meta', 'hydra-latest-news-page-short-news__image-slider', 'dre-byline__dates']}), dict(name="source"), #dict(name='menu', attrs={'class': 'share'}), #dict(name='menu', attrs={'class': 'dr-site-share-horizontal'}), From f14d66ce0869738ddd1d110e8dec8f2fc21ce9ab Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 4 Mar 2023 06:47:26 +0530 Subject: [PATCH 0329/2055] ... --- src/calibre/db/search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/db/search.py b/src/calibre/db/search.py index b4de2f014b..bd8853cbe0 100644 --- a/src/calibre/db/search.py +++ b/src/calibre/db/search.py @@ -80,7 +80,7 @@ def _match(query, value, matchkind, use_primary_find_in_search=True, case_sensit if regex.search(query, t, flags) is not None: return True except regex.error as e: - raise ParseException(_('Invalid regular expression: {}').format(str(e))) + raise ParseException(_('Invalid regular expression: {!r} with error: {}').format(query, str(e))) elif matchkind == ACCENT_MATCH: if primary_contains(query, t): return True From 68087432ff629fbe5b0684eb127a12b58ed0baef Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 4 Mar 2023 17:48:57 +0530 Subject: [PATCH 0330/2055] Comments editor: Add a shortcut for paste and match style --- src/calibre/gui2/comments_editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index d69d2b59d7..b7cd80675b 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -294,7 +294,7 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{ r('remove_format', 'edit-clear', _('Remove formatting')) r('copy', 'edit-copy', _('Copy'), shortcut=QKeySequence.StandardKey.Copy) r('paste', 'edit-paste', _('Paste'), shortcut=QKeySequence.StandardKey.Paste) - r('paste_and_match_style', 'edit-paste', _('Paste and match style')) + r('paste_and_match_style', 'edit-paste', _('Paste and match style'), shortcut=QKeySequence('ctrl+shift+v', QKeySequence.SequenceFormat.PortableText)) r('cut', 'edit-cut', _('Cut'), shortcut=QKeySequence.StandardKey.Cut) r('indent', 'format-indent-more', _('Increase indentation')) r('outdent', 'format-indent-less', _('Decrease indentation')) From 8a0e91a2e94328432ac7c79a6ab04dab8f6def7f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 5 Mar 2023 09:19:01 +0530 Subject: [PATCH 0331/2055] Update The Mainichi --- recipes/mainichi_en.recipe | 137 ++++++------------------------------- 1 file changed, 20 insertions(+), 117 deletions(-) diff --git a/recipes/mainichi_en.recipe b/recipes/mainichi_en.recipe index 8a265a5ab1..d49e960bb8 100644 --- a/recipes/mainichi_en.recipe +++ b/recipes/mainichi_en.recipe @@ -1,24 +1,13 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -__license__ = "GPL v3" -__copyright__ = ( - "2010, Hiroshi Miura . " - "2021, Albert Aparicio Isarn " -) - """ www.mainichi.jp/english """ -from datetime import datetime - +from calibre.ptempfile import PersistentTemporaryFile from calibre.web.feeds.news import BasicNewsRecipe - class MainichiEnglishNews(BasicNewsRecipe): title = u"The Mainichi" - __author__ = "Albert Aparicio Isarn (old version by Hiroshi Miura)" + __author__ = 'unkn0wn' description = "Japanese traditional newspaper Mainichi news in English" publisher = "Mainichi News" @@ -29,114 +18,28 @@ class MainichiEnglishNews(BasicNewsRecipe): index = "http://mainichi.jp/english/" masthead_url = index + "images/themainichi.png" - oldest_article = 2 - max_articles_per_feed = 40 no_stylesheets = True remove_javascript = True + auto_cleanup = True - remove_tags_before = {"id": "main-cont"} - remove_tags_after = {"class": "main-text"} - remove_tags = [{"name": "div", "id": "tools"}, {"name": "div", "class": "sub"}] + ignore_duplicate_articles = {'title'} - def get_pickup_section(self, soup): - # Topmost story - top = soup.find("section", attrs={"class": "pickup section"}) - top_link = top.find("p", attrs={"class": "midashi"}).find("a") + articles_are_obfuscated = True + def get_obfuscated_article(self, url): + br = self.get_browser() try: - top_date = ( - soup.find("div", attrs={"id": "main"}) - .find("div", attrs={"class": "date-box"}) - .find("p", attrs={"class": "date"}) - .string - ) + br.open(url) + except Exception as e: + url = e.hdrs.get('location') + soup = self.index_to_soup(url) + link = soup.find('a', href=True) + html = br.open(link['href']).read() + pt = PersistentTemporaryFile('.html') + pt.write(html) + pt.close() + return pt.name - top_date_formatted = datetime.strptime(top_date, "%A, %B %d, %Y").strftime("%Y/%m/%d") - except AttributeError: - # If date not present, assume it is from today - top_date_formatted = datetime.now().strftime("%Y/%m/%d") - - top_description = top.find("p", attrs={"class": "txt"}).text - - return [ - { - "title": top_link.string, - "date": top_date_formatted, - "url": "https:" + top_link["href"], - "description": top_description, - } - ] - - def retrieve_news_from_column(self, column): - column_news = [] - - for item in column.findAll("li"): - if item: - itema = item.find("a") - date_item = itema.find("p", attrs={"class": "date"}) - - column_news.append( - { - "title": itema.find("span").string, - "date": date_item.string.strip("()") if date_item else "", - "url": "https:" + itema["href"], - "description": "", - } - ) - - return column_news - - def get_top_stories(self, soup): - top_stories = self.get_pickup_section(soup) - - news_section = soup.find("section", attrs={"class": "newslist"}) - top_news = news_section.find("div", attrs={"class": "main-box"}).find("ul") - - top_stories.extend(self.retrieve_news_from_column(top_news)) - - return top_stories - - def get_editor_picks(self, soup): - editor_picks = [] - - news_section = soup.find("section", attrs={"class": "newslist"}) - news = news_section.find("div", attrs={"class": "sub-box"}).find("ul") - - editor_picks.extend(self.retrieve_news_from_column(news)) - - return editor_picks - - def get_section(self, section): - soup = self.index_to_soup(self.index + section + "index.html") - - section_news_items = self.get_pickup_section(soup) - - news_columns = ( - soup.find("section", attrs={"class": "newslist section"}) - .find("div", attrs={"class": "col-set"}) - .find("ul") - ) - - section_news_items.extend(self.retrieve_news_from_column(news_columns)) - - return section_news_items - - def parse_index(self): - soup = self.index_to_soup(self.index + "index.html") - - feeds = [ - ("Top Stories", self.get_top_stories(soup)), - ("Editor's Picks", self.get_editor_picks(soup)), - # ("Latest Articles", self.get_section(self.index + "latest"+"index.html")), - ("Japan", self.get_section("japan")), - ("World", self.get_section("world")), - ("Business", self.get_section("business")), - ("Sports", self.get_section("sports")), - ("Science", self.get_section("science")), - ("Entertainment", self.get_section("entertainment")), - ("Opinion", self.get_section("opinion")), - ("Lifestyle", self.get_section("lifestyle")), - ("Obituaries", self.get_section("obituaries")), - ] - - return feeds + feeds = [ + ('Articles', 'https://news.google.com/rss/search?q=when:48h+allinurl:mainichi.jp%2Fenglish%2Farticles%2F&hl=en-US&gl=US&ceid=US:en') + ] From 2a286eb0f079c20c84d1b65bbe92c18fd59d2bee Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 5 Mar 2023 19:57:23 +0530 Subject: [PATCH 0332/2055] Update New Scientist --- recipes/new_scientist.recipe | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/recipes/new_scientist.recipe b/recipes/new_scientist.recipe index 5f55d013c7..89ea94bb69 100644 --- a/recipes/new_scientist.recipe +++ b/recipes/new_scientist.recipe @@ -64,11 +64,11 @@ class NewScientist(BasicNewsRecipe): """ keep_only_tags = [ - classes('article-header article__content') + classes('article-header article__content ArticleHeader ArticleContent') ] remove_tags = [ - classes('social__button-container') + classes('social__button-container ArticleHeader__SocialWrapper') ] def preprocess_html(self, soup): @@ -83,6 +83,11 @@ class NewScientist(BasicNewsRecipe): ans = BasicNewsRecipe.get_article_url(self, article) return ans.partition('?')[0] + def print_version(self, url): + if '/video/' in url: + return None + return url + def get_browser(self): br = BasicNewsRecipe.get_browser(self) if self.username is not None and self.password is not None: @@ -94,7 +99,7 @@ class NewScientist(BasicNewsRecipe): br['email'] = self.username br['password'] = self.password res = br.submit().read() - if b'>Log out<' not in res: + if b'>Your account<' not in res: raise ValueError('Failed to log in to New Scientist, check your username and password') return br From 79e550fefc97f9bfaaa6d0d193ff22a10b6ef7e2 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 6 Mar 2023 13:18:19 +0000 Subject: [PATCH 0333/2055] Enhancement #2009304: Display Calculated Text Column like Comments in Book Details --- src/calibre/ebooks/metadata/book/render.py | 5 +- .../gui2/preferences/create_custom_column.py | 94 +++++++++++++++---- 2 files changed, 79 insertions(+), 20 deletions(-) diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index 8a5285941d..e8fd0e5ee8 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -127,7 +127,8 @@ def mi_to_html( name = field name += ':' disp = metadata['display'] - if metadata['datatype'] == 'comments' or field == 'comments': + if (metadata['datatype'] == 'comments' or field == 'comments' + or disp.get('composite_show_in_comments', '')): val = getattr(mi, field) if val: ctype = disp.get('interpret_as') or 'html' @@ -155,7 +156,7 @@ def mi_to_html( '%s%s'%( name, rating_font, star_string))) - elif metadata['datatype'] == 'composite': + elif metadata['datatype'] == 'composite' and not disp.get('composite_show_in_comments', ''): val = getattr(mi, field) if val: val = force_unicode(val) diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index 11d47e453c..469cf97bc9 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -117,6 +117,7 @@ class CreateCustomColumn(QDialog): for t in self.column_types: self.column_type_box.addItem(self.column_types[t]['text']) self.column_type_box.currentIndexChanged.connect(self.datatype_changed) + self.composite_in_comments_box.stateChanged.connect(self.composite_show_in_comments_clicked) if not self.editing_col: self.datatype_changed() @@ -146,22 +147,31 @@ class CreateCustomColumn(QDialog): self.column_types)) self.column_type_box.setCurrentIndex(column_numbers[ct]) self.column_type_box.setEnabled(False) + + self.datatype_changed() + if ct == 'datetime': if c['display'].get('date_format', None): self.format_box.setText(c['display'].get('date_format', '')) elif ct in ['composite', '*composite']: self.composite_box.setText(c['display'].get('composite_template', '')) - sb = c['display'].get('composite_sort', 'text') - vals = ['text', 'number', 'date', 'bool'] - if sb in vals: - sb = vals.index(sb) + if c['display'].get('composite_show_in_comments', ''): + self.composite_in_comments_box.setChecked(True) + idx = max(0, self.composite_heading_position.findData(c['display'].get('heading_position', 'hide'))) + self.composite_heading_position.setCurrentIndex(idx) else: - sb = 0 - self.composite_sort_by.setCurrentIndex(sb) - self.composite_make_category.setChecked( - c['display'].get('make_category', False)) - self.composite_contains_html.setChecked( - c['display'].get('contains_html', False)) + self.composite_in_comments_box.setChecked(False) + sb = c['display'].get('composite_sort', 'text') + vals = ['text', 'number', 'date', 'bool'] + if sb in vals: + sb = vals.index(sb) + else: + sb = 0 + self.composite_sort_by.setCurrentIndex(sb) + self.composite_make_category.setChecked( + c['display'].get('make_category', False)) + self.composite_contains_html.setChecked( + c['display'].get('contains_html', False)) elif ct == 'enumeration': self.enum_box.setText(','.join(c['display'].get('enum_values', []))) self.enum_colors.setText(','.join(c['display'].get('enum_colors', []))) @@ -202,7 +212,6 @@ class CreateCustomColumn(QDialog): elif ct not in ('composite', '*composite'): self.default_value.setText(dv) - self.datatype_changed() if ct in ['text', 'composite', 'enumeration']: self.use_decorations.setChecked(c['display'].get('use_decorations', False)) elif ct == '*text': @@ -445,6 +454,24 @@ class CreateCustomColumn(QDialog): ':select(beam)}">Beam book</a> ' 'will generate a link to the book on the Beam e-books site.') + '

') l.addWidget(cch) + l.addStretch() + add_row(None, l) + l = QHBoxLayout() + self.composite_in_comments_box = cmc = QCheckBox(_("Show with comments in book details")) + cmc.setToolTip('Tooltip') + l.addWidget(cmc) + self.composite_heading_position = chp = QComboBox(self) + for k, text in ( + ('hide', _('No heading')), + ('above', _('Show heading above the text')), + ('side', _('Show heading to the side of the text')) + ): + chp.addItem(text, k) + chp.setToolTip(_('Choose whether or not the column heading is shown in the Book\n' + 'details panel and, if shown, where')) + self.composite_heading_position_label = la = QLabel(_('Column heading:')) + l.addWidget(la), l.addWidget(chp) + l.addStretch() add_row(None, l) # Default value @@ -463,6 +490,24 @@ class CreateCustomColumn(QDialog): if clicked: self.bool_button_group.setFocusProxy(button) + def composite_show_in_comments_clicked(self, state): + if state == 2: # Qt.CheckState.Checked but passed as an int + self.composite_sort_by.setEnabled(False) + self.composite_sort_by_label.setEnabled(False) + self.composite_make_category.setEnabled(False) + self.composite_contains_html.setEnabled(False) + self.composite_heading_position.setEnabled(True) + self.composite_heading_position_label.setEnabled(True) + self.composite_heading_position.setCurrentIndex(0) + else: + self.composite_sort_by.setEnabled(True) + self.composite_sort_by_label.setEnabled(True) + self.composite_make_category.setEnabled(True) + self.composite_contains_html.setEnabled(True) + self.composite_heading_position.setEnabled(False) + self.composite_heading_position_label.setEnabled(False) + self.composite_heading_position.setCurrentIndex(0) + def datatype_changed(self, *args): try: col_type = self.column_types[self.column_type_box.currentIndex()]['datatype'] @@ -522,9 +567,13 @@ class CreateCustomColumn(QDialog): 'after the decimal point and thousands separated by commas.') + '

' ) self.format_label.setText(l), self.format_default_label.setText(dl) + for x in ('in_comments_box', 'heading_position', 'heading_position_label'): + getattr(self, 'composite_'+x).setVisible(col_type == 'composite') for x in ('box', 'default_label', 'label', 'sort_by', 'sort_by_label', 'make_category', 'contains_html'): - getattr(self, 'composite_'+x).setVisible(col_type in ['composite', '*composite']) + getattr(self, 'composite_'+x).setVisible(col_type in ('composite', '*composite')) + self.composite_heading_position.setEnabled(False) + for x in ('box', 'default_label', 'colors', 'colors_label'): getattr(self, 'enum_'+x).setVisible(col_type == 'enumeration') for x in ('value_label', 'value'): @@ -609,12 +658,21 @@ class CreateCustomColumn(QDialog): if not str(self.composite_box.text()).strip(): return self.simple_error('', _('You must enter a template for ' 'composite columns')) - display_dict = {'composite_template':str(self.composite_box.text()).strip(), - 'composite_sort': ['text', 'number', 'date', 'bool'] - [self.composite_sort_by.currentIndex()], - 'make_category': self.composite_make_category.isChecked(), - 'contains_html': self.composite_contains_html.isChecked(), - } + if self.composite_in_comments_box.isChecked(): + display_dict = {'composite_template':str(self.composite_box.text()).strip(), + 'composite_show_in_comments': self.composite_in_comments_box.isChecked(), + 'heading_position': self.composite_heading_position.currentData(), + 'composite_show_in_comments': True, + } + else: + display_dict = {'composite_template':str(self.composite_box.text()).strip(), + 'composite_sort': ['text', 'number', 'date', 'bool'] + [self.composite_sort_by.currentIndex()], + 'make_category': self.composite_make_category.isChecked(), + 'contains_html': self.composite_contains_html.isChecked(), + 'composite_show_in_comments': False, + } + elif col_type == 'enumeration': if not str(self.enum_box.text()).strip(): return self.simple_error('', _('You must enter at least one ' From 4292dcbdda61df599d7a65261fc9250fbf1da3a3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 6 Mar 2023 19:43:36 +0530 Subject: [PATCH 0334/2055] Also handle composite_show_in_comments in the content server --- src/pyj/book_list/book_details.pyj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyj/book_list/book_details.pyj b/src/pyj/book_list/book_details.pyj index 6afdf57085..fd958d7961 100644 --- a/src/pyj/book_list/book_details.pyj +++ b/src/pyj/book_list/book_details.pyj @@ -334,7 +334,7 @@ def render_metadata(mi, table, book_id, iframe_css): # {{{ name = fm.name or field datatype = fm.datatype val = mi[field] - if field is 'comments' or datatype is 'comments': + if field is 'comments' or datatype is 'comments' or fm.display?.composite_show_in_comments: if not val: return ias = fm.display?.interpret_as or 'html' From 4a3f3a65625f54a053eaaebfa2405ca54e91312f Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 6 Mar 2023 15:43:59 +0000 Subject: [PATCH 0335/2055] Fix using an integer constant for Qt.CheckState.Checked --- src/calibre/gui2/preferences/create_custom_column.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index 469cf97bc9..61ec4bd297 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -491,7 +491,7 @@ class CreateCustomColumn(QDialog): self.bool_button_group.setFocusProxy(button) def composite_show_in_comments_clicked(self, state): - if state == 2: # Qt.CheckState.Checked but passed as an int + if state == Qt.CheckState.Checked.value: # state is passed as an int self.composite_sort_by.setEnabled(False) self.composite_sort_by_label.setEnabled(False) self.composite_make_category.setEnabled(False) From dea427ce8c2eec80f24a8605e4a08b6772e61948 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 6 Mar 2023 20:00:14 +0000 Subject: [PATCH 0336/2055] I forgot the tooltip --- src/calibre/gui2/preferences/create_custom_column.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index 61ec4bd297..8bb0de93cd 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -458,13 +458,17 @@ class CreateCustomColumn(QDialog): add_row(None, l) l = QHBoxLayout() self.composite_in_comments_box = cmc = QCheckBox(_("Show with comments in book details")) - cmc.setToolTip('Tooltip') + cmc.setToolTip('

' + _('If you check this box then the column contents ' + 'will show in the Comments section in book details, ' + 'which is on the right hand side. The output of the ' + 'column template must be plain text or html.') + '

') l.addWidget(cmc) self.composite_heading_position = chp = QComboBox(self) for k, text in ( ('hide', _('No heading')), - ('above', _('Show heading above the text')), - ('side', _('Show heading to the side of the text')) + ('above', _('Show heading above the text')) + # we don't offer 'side' because that is what you get if you don't + # check the box. ): chp.addItem(text, k) chp.setToolTip(_('Choose whether or not the column heading is shown in the Book\n' From 6420d449baab7ed0743f271e03f13deca7674f44 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 8 Mar 2023 20:55:00 +0530 Subject: [PATCH 0337/2055] Update The Saturday Paper --- recipes/the_saturday_paper.recipe | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/recipes/the_saturday_paper.recipe b/recipes/the_saturday_paper.recipe index 4558a4b52e..8fa9b80d3e 100644 --- a/recipes/the_saturday_paper.recipe +++ b/recipes/the_saturday_paper.recipe @@ -31,6 +31,11 @@ class SaturdayPaper(BasicNewsRecipe): ' article-page__sidebar article-page__social__icons share-wrapper article-footer-container') ] + def get_cover_url(self): + soup = self.index_to_soup('https://www.thesaturdaypaper.com.au/editions/') + div = soup.find('div', attrs={'class':'article__image'}) + return div.img['src'] + def parse_index(self): feeds = [ ('News', 'https://www.thesaturdaypaper.com.au/news'), From 6bf2d269059a612fbc6ca0f23cd7481f64f38511 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 8 Mar 2023 20:55:49 +0530 Subject: [PATCH 0338/2055] Update Strange Horizons --- recipes/strange_horizons.recipe | 200 +++++++++----------------------- 1 file changed, 53 insertions(+), 147 deletions(-) diff --git a/recipes/strange_horizons.recipe b/recipes/strange_horizons.recipe index 200b2e61d3..a39183644a 100644 --- a/recipes/strange_horizons.recipe +++ b/recipes/strange_horizons.recipe @@ -1,160 +1,66 @@ -#!/usr/bin/env python - -from collections import OrderedDict - -from calibre.web.feeds.news import BasicNewsRecipe - +from collections import defaultdict +from calibre.web.feeds.news import BasicNewsRecipe, classes +import re class StrangeHorizons(BasicNewsRecipe): - # Recipe metadata - title = "Strange Horizons" - description = "A magazine of speculative fiction and related nonfiction. Best downloaded on weekends" - publication_type = "magazine" - language = "en" - __author__ = "Peter Fidelman, based on work by Jim DeVona" - __version__ = "2.0" + title = 'Strange Horizons' + description = 'A magazine of speculative fiction and related nonfiction. Best downloaded on weekends' + __author__ = 'unkn0wn' + no_stylesheets = True + use_embedded_content = False + encoding = 'utf-8' + language = 'en' + remove_attributes = ['style', 'height', 'width'] + masthead_url = 'http://strangehorizons.com/wordpress/wp-content/themes/strangehorizons/images/sh-logo.jpg' - # Cruft filters to apply to each article found by parse_index - keep_only_tags = [dict(name="div", attrs={"class": "post"})] - remove_tags_after = [dict(name="br", attrs={"class": "clear_both"})] - remove_tags = [ - dict(name="div", attrs={"class": "single-title-header row"}), - dict(name="div", attrs={"class": "podcast-title"}), + extra_css = ''' + .author-biographies, .content-warning-container-ltr, .category {font-size:small; font-style:italic; font-color:#404040;} + .byline {font-size:small; font-color:#202020;} + .title {font-size:large; text-align:center;} + ''' + + ignore_duplicate_articles = {'url'} + + keep_only_tags = [ + classes('post-container') ] - # Styles to apply to each article - no_stylesheets = True - extra_css = """ - div.image-left { margin: 0.5em auto 1em auto; } - div.image-right { margin: 0.5em auto 1em auto; } - div.illustration { margin: 0.5em auto 1em auto; text-align: center; } - p.image-caption { margin-top: 0.25em; margin-bottom: 1em; font-size: 75%; text-align: center; } - h1 { font-size: 160%; } - h2 { font-size: 110%; } - h3 { font-size: 85%; } - h4 { font-size: 80%; } - p { font-size: 90%; margin: 1em 1em 1em 15px; } - p.author-bio { font-size: 75%; font-style: italic; margin: 1em 1em 1em 15px; } - p.author-bio i, p.author-bio cite, p.author-bio .foreign { font-style: normal; } - p.author-copyright { font-size: 75%; text-align: center; margin: 3em 1em 1em 15px; } - p.content-date { font-weight: bold; } - p.dedication { font-style: italic; } - div.stanza { margin-bottom: 1em; } - div.stanza p { margin: 0px 1em 0px 15px; font-size: 90%; } - p.verse-line { margin-bottom: 0px; margin-top: 0px; } - p.verse-line-indent-1 { margin-bottom: 0px; margin-top: 0px; text-indent: 2em; } - p.verse-line-indent-2 { margin-bottom: 0px; margin-top: 0px; text-indent: 4em; } - p.verse-stanza-break { margin-bottom: 0px; margin-top: 0px; } - .foreign { font-style: italic; } - .thought { font-style: italic; } - .thought cite { font-style: normal; } - .thought em { font-style: normal; } - blockquote { font-size: 90%; font-style: italic; } - blockquote cite { font-style: normal; } - blockquote em { font-style: normal; } - blockquote .foreign { font-style: normal; } - blockquote .thought { font-style: normal; } - .speaker { font-weight: bold; } - pre { margin-left: 15px; } - div.screenplay { font-family: monospace; } - blockquote.screenplay-dialogue { font-style: normal; font-size: 100%; } - .screenplay p.dialogue-first { margin-top: 0; } - .screenplay p.speaker { margin-bottom: 0; text-align: center; font-weight: normal; } - blockquote.typed-letter { font-style: normal; font-size: 100%; font-family: monospace; } - .no-italics { font-style: normal; } - """ - - def get_date(self): - frontSoup = self.index_to_soup("http://strangehorizons.com") - dateDiv = frontSoup.find( - "div", attrs={"class": "current-issue-widget issue-medium issue"} - ) - url = dateDiv.a["href"] - date = url.split('/')[-2] - return date + remove_tags = [ + dict(name = 'button'), + classes('font-size sharedaddy comments-form-row') + ] def parse_index(self): - # Change this to control what issue to grab. Must be of the format - # D-month-YYYY; for example, "4-july-2005". Alternately, use - # self.get_date() to retrieve the latest issue. + main = self.index_to_soup('http://strangehorizons.com/issue/') + issue = main.find(attrs={'class':lambda x: x and 'current-issue-widget' in x.split()}) + current = issue.find('a', href=lambda x: x and x.startswith('http://strangehorizons.com/issue/')) + date = issue.find(**classes('date')) + self.timefmt = ' [' + self.tag_to_string(date) + ']' + self.log('Downloading Issue:', self.timefmt, current['href']) + soup = self.index_to_soup(current['href']) - dateStr = self.get_date() + feeds_dict = defaultdict(list) - issueUrl = "http://strangehorizons.com/issue/%s/" % dateStr - soup = self.index_to_soup(issueUrl) + for art in soup.findAll('div', attrs={'class':'article'}): + for ti in art.findAll(**classes('title')): + if a := ti.find('a', href=True): + url = a['href'] + title = self.tag_to_string(ti).strip() - sections = OrderedDict() + sec = 'Articles' + if cat := art.find(**classes('category')): + sec = self.tag_to_string(cat).strip() - # - # Each div with class="article" is an article. - # - articles = soup.findAll(attrs={"class": "article"}) + desc = '' + if exp := ti.find_next_sibling(**classes('excerpt')): + desc = self.tag_to_string(exp) + desc + desc = re.sub(r"\d{5} ", "", desc) + if auth := ti.find_next_sibling(**classes('author')): + desc = self.tag_to_string(auth) + ' | ' + desc - for article in articles: - # - # What kind of article is this? - # - categoryDiv = article.find("div", {"class": "category"}) - categoryStr = self.tag_to_string(categoryDiv.a) + if not title or not url: + continue - # - # Ignore podcasts, as they cannot be converted to text. - # - if categoryStr == "Podcasts": - continue - - # - # Reviews must be special-cased, as several reviews - # may be packed into the same div. - # - if categoryStr == "Reviews": - reviews = article.findAll(attrs={"class": "review"}) - for review in reviews: - titleDiv = review.find("div", {"class": "title"}) - url = titleDiv.a["href"] - titleStr = self.tag_to_string(titleDiv.a).strip() - - authorDiv = review.find("div", {"class": "author"}) - authorStr = self.tag_to_string(authorDiv.a).strip() - - if categoryStr not in sections: - sections[categoryStr] = [] - sections[categoryStr].append({ - "title": titleStr, - "author": authorStr, - "url": url, - "description": "", - "date": dateStr, - }) - - # - # Assume anything else is an ordinary article. Ought - # to work for "Fiction", "Poetry", "Articles", etc. - # - else: - titleDiv = article.find("div", {"class": "title"}) - url = titleDiv.a["href"] - titleStr = self.tag_to_string(titleDiv.a).strip() - - authorDiv = article.find("div", {"class": "author"}) - authorStr = self.tag_to_string(authorDiv.a).strip() - - # The excerpt consistently starts with a - # comment containing one number. This comment - # is not removed by tag_to_string so we must - # remove it ourself. We do this by removing - # the first word of the excerpt. - excerptDiv = article.find("div", {"class": "excerpt"}) - excerptStr = self.tag_to_string(excerptDiv).strip() - excerptStr = " ".join(excerptStr.split(" ")[1:]) - - if categoryStr not in sections: - sections[categoryStr] = [] - sections[categoryStr].append({ - "title": titleStr, - "author": authorStr, - "url": url, - "description": excerptStr, - "date": dateStr, - }) - return sections.items() + self.log(sec, '\n\t', title, '\n\t', desc, '\n\t\t', url) + feeds_dict[sec].append({"title": title, "url": url, "description": desc}) + return [(section, articles) for section, articles in feeds_dict.items()] From 15de218afd7cea3d23fab262a0ae9835a4760433 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 8 Mar 2023 19:11:29 +0000 Subject: [PATCH 0339/2055] Fix the tooltip for the new 'put composites with comments' check box. --- src/calibre/gui2/preferences/create_custom_column.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index 8bb0de93cd..931ac9e6cc 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -459,8 +459,11 @@ class CreateCustomColumn(QDialog): l = QHBoxLayout() self.composite_in_comments_box = cmc = QCheckBox(_("Show with comments in book details")) cmc.setToolTip('

' + _('If you check this box then the column contents ' - 'will show in the Comments section in book details, ' - 'which is on the right hand side. The output of the ' + 'will show in the Comments section in book details. ' + 'You can indicate whether not to have a header or ' + 'to put a header above the column. If you want a ' + "header beside the data, don't check this box. " + 'If this box is checked then the output of the ' 'column template must be plain text or html.') + '

') l.addWidget(cmc) self.composite_heading_position = chp = QComboBox(self) From e5cbf7d34048f5b76dca6056acc5ef5a1253ec1e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 9 Mar 2023 09:19:07 +0530 Subject: [PATCH 0340/2055] Fix #2008427 [another ampersand escape bug](https://bugs.launchpad.net/calibre/+bug/2008427) --- src/calibre/gui2/tag_browser/view.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/tag_browser/view.py b/src/calibre/gui2/tag_browser/view.py index 2e0d22a4a0..7e21e755f3 100644 --- a/src/calibre/gui2/tag_browser/view.py +++ b/src/calibre/gui2/tag_browser/view.py @@ -1008,11 +1008,12 @@ class TagsView(QTreeView): # {{{ if tag is None: cm = self.context_menu cm.addSeparator() - sm = cm.addAction(_('Change {} category icon').format(category), + acategory = category.replace('&', '&&') + sm = cm.addAction(_('Change {} category icon').format(acategory), partial(self.context_menu_handler, action='set_icon', key=key, category=category)) sm.setIcon(QIcon.ic('icon_choose.png')) - sm = cm.addAction(_('Restore {} category default icon').format(category), + sm = cm.addAction(_('Restore {} category default icon').format(acategory), partial(self.context_menu_handler, action='clear_icon', key=key, category=category)) sm.setIcon(QIcon.ic('edit-clear.png')) From 7c8195115315040fc67d48c370de6c4422eb7beb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 9 Mar 2023 09:23:20 +0530 Subject: [PATCH 0341/2055] Fix #2008537 [Content Server: is_tag_browser editable](https://bugs.launchpad.net/calibre/+bug/2008537) --- src/pyj/book_list/edit_metadata.pyj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyj/book_list/edit_metadata.pyj b/src/pyj/book_list/edit_metadata.pyj index f501a31d1a..16d0d745d0 100644 --- a/src/pyj/book_list/edit_metadata.pyj +++ b/src/pyj/book_list/edit_metadata.pyj @@ -37,7 +37,7 @@ from utils import ( from widgets import create_button CLASS_NAME = 'edit-metadata-panel' -IGNORED_FIELDS = {'sort', 'uuid', 'id', 'urls_from_identifiers', 'lang_names', 'last_modified', 'path', 'marked', 'size', 'ondevice', 'cover', 'au_map', 'isbn'} +IGNORED_FIELDS = {'sort', 'uuid', 'id', 'urls_from_identifiers', 'lang_names', 'last_modified', 'path', 'marked', 'size', 'ondevice', 'cover', 'au_map', 'isbn', 'in_tag_browser'} def identity(x): return x value_to_json = identity From 5725b1cd2c689cb718d83c102f73f1aed22e1c92 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 9 Mar 2023 11:40:06 +0530 Subject: [PATCH 0342/2055] Make find_pages useable with things other than directories --- src/calibre/ebooks/comic/input.py | 42 ++++++++++++------- .../ebooks/conversion/plugins/comic_input.py | 10 +++-- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/calibre/ebooks/comic/input.py b/src/calibre/ebooks/comic/input.py index a0424e9f92..878997aa6c 100644 --- a/src/calibre/ebooks/comic/input.py +++ b/src/calibre/ebooks/comic/input.py @@ -39,32 +39,41 @@ def extract_comic(path_to_comic_file): return tdir -def find_pages(dir, sort_on_mtime=False, verbose=False): +def generate_entries_from_dir(path): + from functools import partial + from calibre import walk + ans = {} + for x in walk(path): + x = os.path.abspath(x) + ans[x] = partial(os.path.getmtime, x) + return ans + + +def find_pages(dir_or_items, sort_on_mtime=False, verbose=False): ''' Find valid comic pages in a previously un-archived comic. - :param dir: Directory in which extracted comic lives + :param dir_or_items: Directory in which extracted comic lives or a dict of paths to function getting mtime :param sort_on_mtime: If True sort pages based on their last modified time. Otherwise, sort alphabetically. ''' extensions = {'jpeg', 'jpg', 'gif', 'png', 'webp'} + items = generate_entries_from_dir(dir_or_items) if isinstance(dir_or_items, str) else dir_or_items + sep_counts = set() pages = [] - for datum in os.walk(dir): - for name in datum[-1]: - path = os.path.abspath(os.path.join(datum[0], name)) - if '__MACOSX' in path: - continue - for ext in extensions: - if path.lower().endswith('.'+ext): - pages.append(path) - break - sep_counts = {x.replace(os.sep, '/').count('/') for x in pages} + for path in items: + if '__MACOSX' in path: + continue + ext = path.rpartition('.')[2].lower() + if ext in extensions: + sep_counts.add(path.replace(os.sep, '/').count('/')) + pages.append(path) # Use the full path to sort unless the files are in folders of different # levels, in which case simply use the filenames. basename = os.path.basename if len(sep_counts) > 1 else lambda x: x if sort_on_mtime: def key(x): - return os.stat(x).st_mtime + return items[x]() else: def key(x): return numeric_sort_key(basename(x)) @@ -72,7 +81,12 @@ def find_pages(dir, sort_on_mtime=False, verbose=False): pages.sort(key=key) if verbose: prints('Found comic pages...') - prints('\t'+'\n\t'.join([os.path.relpath(p, dir) for p in pages])) + try: + base = os.path.commonpath(pages) + except ValueError: + pass + else: + prints('\t'+'\n\t'.join([os.path.relpath(p, base) for p in pages])) return pages diff --git a/src/calibre/ebooks/conversion/plugins/comic_input.py b/src/calibre/ebooks/conversion/plugins/comic_input.py index c4c3ceed82..2956dd07d8 100644 --- a/src/calibre/ebooks/conversion/plugins/comic_input.py +++ b/src/calibre/ebooks/conversion/plugins/comic_input.py @@ -6,10 +6,13 @@ __docformat__ = 'restructuredtext en' Based on ideas from comiclrf created by FangornUK. ''' -import shutil, textwrap, codecs, os +import codecs +import os +import shutil +import textwrap -from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation from calibre import CurrentDir +from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation from calibre.ptempfile import PersistentTemporaryDirectory @@ -126,8 +129,7 @@ class ComicInput(InputFormatPlugin): return comics def get_pages(self, comic, tdir2): - from calibre.ebooks.comic.input import (extract_comic, process_pages, - find_pages) + from calibre.ebooks.comic.input import extract_comic, find_pages, process_pages tdir = extract_comic(comic) new_pages = find_pages(tdir, sort_on_mtime=self.opts.no_sort, verbose=self.opts.verbose) From 60e8a67dcf5984b3b0b080f341aeef3ec24dab01 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 9 Mar 2023 11:51:15 +0530 Subject: [PATCH 0343/2055] Function to get headers from RAR files --- src/calibre/utils/unrar.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/calibre/utils/unrar.py b/src/calibre/utils/unrar.py index e57cea62c0..87a96d1fbd 100644 --- a/src/calibre/utils/unrar.py +++ b/src/calibre/utils/unrar.py @@ -61,6 +61,14 @@ def names(path_or_stream): yield from names(path, only_useful=True) +def headers(path_or_stream): + from unrardll import headers, is_useful + with StreamAsPath(path_or_stream) as path: + for h in headers(path): + if is_useful(h): + yield h + + def comment(path_or_stream): from unrardll import comment with StreamAsPath(path_or_stream) as path: From a86b88c634227cc1f60c2ad6e74fc05f5820404d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 9 Mar 2023 14:03:33 +0530 Subject: [PATCH 0344/2055] Bump unrardll version --- bypy/sources.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bypy/sources.json b/bypy/sources.json index 9a9c261ab1..4580ec79f7 100644 --- a/bypy/sources.json +++ b/bypy/sources.json @@ -569,8 +569,8 @@ { "name": "unrardll", "unix": { - "filename": "unrardll-0.1.5.tar.gz", - "hash": "sha256:8bebb480b96cd49d4290d814914f39cff75cf0fa0514c4790bb32b1757227c78", + "filename": "unrardll-0.1.7.tar.gz", + "hash": "sha256:e1067fe27bb4de204ef8f3692f23d93c5d3b4292f78b292c6fc7dc4f75749f76", "urls": ["pypi"] } }, From 7e8dc39ff88724e9e258df554e45b171724717b5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 9 Mar 2023 14:58:37 +0530 Subject: [PATCH 0345/2055] Edit metadata: When setting a cover from comic files allow choosing which page to use as the cover. Fixes #2007765 [Set cover from format: Image selection for comic files](https://bugs.launchpad.net/calibre/+bug/2007765) --- src/calibre/ebooks/comic/input.py | 6 +-- src/calibre/ebooks/metadata/archive.py | 57 +++++++++++++++++++++++ src/calibre/gui2/actions/edit_metadata.py | 10 ++-- src/calibre/gui2/metadata/pdf_covers.py | 54 ++++++++++++--------- src/calibre/gui2/metadata/single.py | 13 +++--- src/calibre/utils/unrar.py | 6 +++ 6 files changed, 108 insertions(+), 38 deletions(-) diff --git a/src/calibre/ebooks/comic/input.py b/src/calibre/ebooks/comic/input.py index 878997aa6c..2c3b70709f 100644 --- a/src/calibre/ebooks/comic/input.py +++ b/src/calibre/ebooks/comic/input.py @@ -57,7 +57,7 @@ def find_pages(dir_or_items, sort_on_mtime=False, verbose=False): :param sort_on_mtime: If True sort pages based on their last modified time. Otherwise, sort alphabetically. ''' - extensions = {'jpeg', 'jpg', 'gif', 'png', 'webp'} + from calibre.libunzip import comic_exts items = generate_entries_from_dir(dir_or_items) if isinstance(dir_or_items, str) else dir_or_items sep_counts = set() pages = [] @@ -65,8 +65,8 @@ def find_pages(dir_or_items, sort_on_mtime=False, verbose=False): if '__MACOSX' in path: continue ext = path.rpartition('.')[2].lower() - if ext in extensions: - sep_counts.add(path.replace(os.sep, '/').count('/')) + if ext in comic_exts: + sep_counts.add(path.replace('\\', '/').count('/')) pages.append(path) # Use the full path to sort unless the files are in folders of different # levels, in which case simply use the filenames. diff --git a/src/calibre/ebooks/metadata/archive.py b/src/calibre/ebooks/metadata/archive.py index 37bc601393..b73754a7ae 100644 --- a/src/calibre/ebooks/metadata/archive.py +++ b/src/calibre/ebooks/metadata/archive.py @@ -199,3 +199,60 @@ def get_comic_metadata(stream, stream_type, series_index='volume'): comment = get_comment(stream) return parse_comic_comment(comment or b'{}', series_index=series_index) + + +def get_comic_images(path, tdir, first=1, last=0): # first and last use 1 based indexing + from functools import partial + with open(path, 'rb') as f: + fmt = archive_type(f) + if fmt not in ('zip', 'rar'): + return 0 + items = {} + if fmt == 'rar': + from calibre.utils.unrar import headers + for h in headers(path): + items[h['filename']] = lambda : partial(h.get, 'file_time', 0) + else: + from zipfile import ZipFile + with ZipFile(path) as zf: + for i in zf.infolist(): + items[i.filename] = partial(getattr, i, 'date_time') + from calibre.ebooks.comic.input import find_pages + pages = find_pages(items) + if last <= 0: + last = len(pages) + pages = pages[first-1:last] + + def make_filename(num, ext): + return f'{num:08d}{ext}' + + if fmt == 'rar': + all_pages = {p:i+first for i, p in enumerate(pages)} + from calibre.utils.unrar import extract_members + current = None + def callback(x): + nonlocal current + if isinstance(x, dict): + if current is not None: + current.close() + fname = x['filename'] + if fname in all_pages: + ext = os.path.splitext(fname)[1] + num = all_pages[fname] + current = open(os.path.join(tdir, make_filename(num, ext)), 'wb') + return True + return False + if isinstance(x, bytes): + current.write(x) + extract_members(path, callback) + if current is not None: + current.close() + else: + import shutil + with ZipFile(path) as zf: + for i, name in enumerate(pages): + num = i + first + ext = os.path.splitext(name)[1] + with open(os.path.join(tdir, make_filename(num, ext)), 'wb') as dest, zf.open(name) as src: + shutil.copyfileobj(src, dest) + return len(pages) diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index a2bdd1fa69..64885a1dc9 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -983,13 +983,13 @@ class EditMetadataAction(InterfaceAction): fmt = fmt.lower() cdata = None db = self.gui.current_db.new_api - if fmt == 'pdf': - pdfpath = db.format_abspath(book_id, fmt) - if pdfpath is None: + if fmt in ('pdf', 'cbz', 'cbr'): + path = db.format_abspath(book_id, fmt) + if path is None: return error_dialog(self.gui, _('Format file missing'), _( - 'Cannot read cover as the %s file is missing from this book') % 'PDF', show=True) + 'Cannot read cover as the %s file is missing from this book') % fmt.upper(), show=True) from calibre.gui2.metadata.pdf_covers import PDFCovers - d = PDFCovers(pdfpath, parent=self.gui) + d = PDFCovers(path, parent=self.gui) ret = d.exec() if ret == QDialog.DialogCode.Accepted: cpath = d.cover_path diff --git a/src/calibre/gui2/metadata/pdf_covers.py b/src/calibre/gui2/metadata/pdf_covers.py index c4a95bbffa..3c6078734a 100644 --- a/src/calibre/gui2/metadata/pdf_covers.py +++ b/src/calibre/gui2/metadata/pdf_covers.py @@ -5,20 +5,23 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import sys, shutil, os -from threading import Thread -from glob import glob - +import os +import shutil +import sys from qt.core import ( - QDialog, QApplication, QLabel, QVBoxLayout, QDialogButtonBox, Qt, QAbstractItemView, QListView, - pyqtSignal, QListWidget, QListWidgetItem, QSize, QPixmap, QStyledItemDelegate, sip + QAbstractItemView, QApplication, QDialog, QDialogButtonBox, QLabel, QListView, + QListWidget, QListWidgetItem, QPixmap, QSize, QStyledItemDelegate, Qt, QTimer, + QVBoxLayout, pyqtSignal, sip, ) +from threading import Thread from calibre import as_unicode +from calibre.ebooks.metadata.archive import get_comic_images from calibre.ebooks.metadata.pdf import page_images from calibre.gui2 import error_dialog, file_icon_provider -from calibre.ptempfile import PersistentTemporaryDirectory from calibre.gui2.progress_indicator import WaitLayout +from calibre.libunzip import comic_exts +from calibre.ptempfile import PersistentTemporaryDirectory class CoverDelegate(QStyledItemDelegate): @@ -42,11 +45,13 @@ class PDFCovers(QDialog): def __init__(self, pdfpath, parent=None): QDialog.__init__(self, parent) self.pdfpath = pdfpath - self.stack = WaitLayout(_('Rendering PDF pages, please wait...'), parent=self) + self.ext = os.path.splitext(pdfpath)[1][1:].lower() + self.is_pdf = self.ext == 'pdf' + self.stack = WaitLayout(_('Rendering {} pages, please wait...').format('PDF' if self.is_pdf else _('comic book')), parent=self) self.container = self.stack.after self.container.l = l = QVBoxLayout(self.container) - self.la = la = QLabel(_('Choose a cover from the list of PDF pages below')) + self.la = la = QLabel(_('Choose a cover from the list of pages below')) l.addWidget(la) self.covers = c = QListWidget(self) l.addWidget(c) @@ -67,16 +72,15 @@ class PDFCovers(QDialog): l.addWidget(bb) self.rendering_done.connect(self.show_pages, type=Qt.ConnectionType.QueuedConnection) self.first = 1 - self.setWindowTitle(_('Choose cover from PDF')) - self.setWindowIcon(file_icon_provider().icon_from_ext('pdf')) + self.setWindowTitle(_('Choose cover from book')) + self.setWindowIcon(file_icon_provider().icon_from_ext(self.ext)) self.resize(QSize(800, 600)) self.tdir = PersistentTemporaryDirectory('_pdf_covers') - self.start_rendering() + QTimer.singleShot(0, self.start_rendering) def start_rendering(self): self.hide_pages() - self.thread = Thread(target=self.render) - self.thread.daemon = True + self.thread = Thread(target=self.render, daemon=True, name='RenderPages') self.thread.start() @property @@ -97,11 +101,14 @@ class PDFCovers(QDialog): self.error = None try: os.mkdir(self.current_tdir) - page_images(self.pdfpath, self.current_tdir, first=self.first, last=self.first + PAGES_PER_RENDER - 1) - except Exception as e: - if self.covers.count(): - pass + if self.is_pdf: + page_images(self.pdfpath, self.current_tdir, first=self.first, last=self.first + PAGES_PER_RENDER - 1) else: + get_comic_images(self.pdfpath, self.current_tdir, first=self.first, last=self.first + PAGES_PER_RENDER - 1) + except Exception as e: + import traceback + traceback.print_exc() + if not self.covers.count(): self.error = as_unicode(e) if not sip.isdeleted(self) and self.isVisible(): self.rendering_done.emit() @@ -113,14 +120,14 @@ class PDFCovers(QDialog): def show_pages(self): if self.error is not None: error_dialog(self, _('Failed to render'), - _('Could not render this PDF file'), show=True, det_msg=self.error) + _('Could not render this file'), show=True, det_msg=self.error) self.reject() return self.stack.stop() - files = glob(os.path.join(self.current_tdir, '*.jpg')) + glob(os.path.join(self.current_tdir, '*.jpeg')) + files = tuple(x for x in os.listdir(self.current_tdir) if os.path.splitext(x)[1][1:].lower() in comic_exts) if not files and not self.covers.count(): error_dialog(self, _('Failed to render'), - _('This PDF has no pages'), show=True) + _('This book has no pages'), show=True) self.reject() return @@ -130,13 +137,14 @@ class PDFCovers(QDialog): dpr = self.devicePixelRatio() for i, f in enumerate(sorted(files)): - p = QPixmap(f).scaled( + path = os.path.join(self.current_tdir, f) + p = QPixmap(path).scaled( self.covers.iconSize()*dpr, aspectRatioMode=Qt.AspectRatioMode.IgnoreAspectRatio, transformMode=Qt.TransformationMode.SmoothTransformation) p.setDevicePixelRatio(dpr) i = QListWidgetItem(_('page %d') % (self.first + i)) i.setData(Qt.ItemDataRole.DecorationRole, p) - i.setData(Qt.ItemDataRole.UserRole, f) + i.setData(Qt.ItemDataRole.UserRole, path) self.covers.addItem(i) self.first += len(files) if len(files) == PAGES_PER_RENDER: diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index d8cb7334c6..041c5c883c 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -415,24 +415,23 @@ class MetadataSingleDialogBase(QDialog): if mi is not None: self.update_from_mi(mi) - def get_pdf_cover(self): - pdfpath = self.formats_manager.get_format_path(self.db, self.book_id, - 'pdf') + def choose_cover_from_pages(self, ext): + path = self.formats_manager.get_format_path(self.db, self.book_id, ext.lower()) from calibre.gui2.metadata.pdf_covers import PDFCovers - d = PDFCovers(pdfpath, parent=self) + d = PDFCovers(path, parent=self) if d.exec() == QDialog.DialogCode.Accepted: cpath = d.cover_path if cpath: with open(cpath, 'rb') as f: - self.update_cover(f.read(), 'PDF') + self.update_cover(f.read(), ext.upper()) d.cleanup() def cover_from_format(self, *args): ext = self.formats_manager.get_selected_format() if ext is None: return - if ext == 'pdf': - return self.get_pdf_cover() + if ext in ('pdf', 'cbz', 'cbr'): + return self.choose_cover_from_pages(ext) try: mi, ext = self.formats_manager.get_selected_format_metadata(self.db, self.book_id) diff --git a/src/calibre/utils/unrar.py b/src/calibre/utils/unrar.py index 87a96d1fbd..ed5bc8e033 100644 --- a/src/calibre/utils/unrar.py +++ b/src/calibre/utils/unrar.py @@ -94,6 +94,12 @@ def extract_member( return name, data +def extract_members(path_or_stream, callback): + from unrardll import extract_members + with StreamAsPath(path_or_stream) as path: + extract_members(path, callback) + + def extract_first_alphabetically(stream): from calibre.libunzip import sort_key names_ = sorted(( From b529a975e9b18c92fa9b5acf9d0487c95667896e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 9 Mar 2023 15:44:54 +0530 Subject: [PATCH 0346/2055] Content server viewer: Fix searching only showing results from the current chapter onwards. Fixes #2009268 [search in book on content server only returns results beyond current location](https://bugs.launchpad.net/calibre/+bug/2009268) --- src/pyj/read_book/search_worker.pyj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyj/read_book/search_worker.pyj b/src/pyj/read_book/search_worker.pyj index 9433ef0cee..2cf195b75c 100644 --- a/src/pyj/read_book/search_worker.pyj +++ b/src/pyj/read_book/search_worker.pyj @@ -184,6 +184,7 @@ def search_in_text_of(name): def queue_next_spine_item(spine_idx, allow_current_name): + spine_idx = spine_idx % wc.current_book.spine.length name = wc.current_book.spine[spine_idx] if wc.current_query.query.only_first_match and wc.result_num > 0: send_search_complete() From 491ee6ad98a6f3bfa6811491f3278716f54f30dd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 9 Mar 2023 16:02:09 +0530 Subject: [PATCH 0347/2055] Fix #2008538 [Content Server: Better message for non-readable formats](https://bugs.launchpad.net/calibre/+bug/2008538) --- src/pyj/read_book/ui.pyj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pyj/read_book/ui.pyj b/src/pyj/read_book/ui.pyj index 01a3e721de..7f9c516548 100644 --- a/src/pyj/read_book/ui.pyj +++ b/src/pyj/read_book/ui.pyj @@ -311,6 +311,7 @@ class ReadUI: def start_load(self, book_id, fmt, metadata, force_reload): self.current_book_id = book_id + self.current_book_fmt = fmt metadata = metadata or library_data.metadata[book_id] self.current_metadata = metadata or {'title':_('Book id #') + book_id} update_window_title('', self.current_metadata.title) @@ -344,6 +345,12 @@ class ReadUI: if end_type is 'abort': return if end_type is not 'load': + if xhr.status is 404: + fmt = (self.current_book_fmt or 'UNKNOWN').toUpperCase() + if 'cannot be viewed' in xhr.error_html: + return self.show_error( + _('Failed to view book'), _('Viewing of the {} format is not supported.').format(fmt), xhr.error_html) + return self.show_error(_('Failed to view book'), _('The {} format is not available.').format(fmt), xhr.error_html) return self.show_error(_('Failed to load book manifest'), _('The book manifest failed to load, click "Show details" for more information.').format(title=self.current_metadata.title), xhr.error_html) From e4e199e2139ccb7f8b71ac59b5fca857134bf9b4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 9 Mar 2023 17:34:34 +0530 Subject: [PATCH 0348/2055] Check book: Nicer handling of line offsets Apparently some errors from the CSS checker library reference line numbers, so rather than using an offset prepend extra newlines. Fixes #2009735 [Private bug](https://bugs.launchpad.net/calibre/+bug/2009735) --- src/calibre/ebooks/oeb/polish/check/css.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/calibre/ebooks/oeb/polish/check/css.py b/src/calibre/ebooks/oeb/polish/check/css.py index f910c47e92..ceeac61324 100644 --- a/src/calibre/ebooks/oeb/polish/check/css.py +++ b/src/calibre/ebooks/oeb/polish/check/css.py @@ -205,6 +205,9 @@ def create_job(name, css, line_offset=0, is_declaration=False, fix_data=None): if is_declaration: css = 'div{\n' + css + '\n}' line_offset -= 1 + if line_offset > 0: + css = ('\n' * line_offset) + css + line_offset = 0 return Job(name, css, line_offset, fix_data) From 26d7e65e66e2e2c21ca5a774381640750f98513c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 9 Mar 2023 17:45:06 +0530 Subject: [PATCH 0349/2055] pep8 --- src/calibre/gui2/preferences/create_custom_column.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index 931ac9e6cc..07a21141d1 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -667,7 +667,6 @@ class CreateCustomColumn(QDialog): 'composite columns')) if self.composite_in_comments_box.isChecked(): display_dict = {'composite_template':str(self.composite_box.text()).strip(), - 'composite_show_in_comments': self.composite_in_comments_box.isChecked(), 'heading_position': self.composite_heading_position.currentData(), 'composite_show_in_comments': True, } From 70cfb56e1fc8b7a75ceed1b83c3e4468d0dfed14 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 10 Mar 2023 05:44:41 +0530 Subject: [PATCH 0350/2055] version 6.14.0 --- Changelog.txt | 31 +++++++++++++++++++++++++++++++ src/calibre/constants.py | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/Changelog.txt b/Changelog.txt index baf842b5d5..66d6a2b836 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -23,6 +23,37 @@ # - title by author # }}} +{{{ 6.14.0 2023-03-10 + +:: new features + +- [2007765] Edit metadata: When setting a cover from comic files allow choosing which page to use as the cover + +- [2009304] Allow display of columns built from other columns as comments in Book details + +- Comments editor: Add a shortcut for "Paste and match style" (Ctrl+Shift+v) + +:: bug fixes + +- [2004639] macOS: ToC Editor: Fix mouse becoming unusable when trying to create a new entry + +- When computing title sorts strip leading and trailing quotes, not just leading quotes + +- [2009268] Content server viewer: Fix searching only showing results from the current chapter onwards + +- [2009735] Check book: Fix some incorrect line numbers reported in a few CSS error messages + +:: improved recipes +- Strange Horizons +- The Saturday Paper +- New Scientist +- The Mainichi +- DR Nyheder +- New York Magazine +- Bloomberg +- Deccan Herald +}}} + {{{ 6.13.0 2023-02-17 :: new features diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 846b5ee5de..0e8064d9b5 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -5,7 +5,7 @@ from functools import lru_cache import sys, locale, codecs, os, collections, collections.abc __appname__ = 'calibre' -numeric_version = (6, 13, 0) +numeric_version = (6, 14, 0) __version__ = '.'.join(map(str, numeric_version)) git_version = None __author__ = "Kovid Goyal " From baa80730e4140dcc8cc0807a7b76ca4728b8485f Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 12 Mar 2023 21:12:18 +0000 Subject: [PATCH 0351/2055] Bug #2011345: QuickView: Two errors --- src/calibre/gui2/dialogs/quickview.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/dialogs/quickview.py b/src/calibre/gui2/dialogs/quickview.py index 98b033d1f5..207fae23a2 100644 --- a/src/calibre/gui2/dialogs/quickview.py +++ b/src/calibre/gui2/dialogs/quickview.py @@ -15,7 +15,7 @@ from qt.core import ( QShortcut, QTimer, QStyle) from calibre.customize.ui import find_plugin -from calibre.gui2 import gprefs +from calibre.gui2 import gprefs, error_dialog from calibre.gui2.dialogs.quickview_ui import Ui_Quickview from calibre.utils.date import timestampfromdt from calibre.utils.icu import sort_key @@ -348,8 +348,10 @@ class Quickview(QDialog, Ui_Quickview): a.setEnabled(book_displayed) a = m.addAction(self.quickview_icon, _('Quickview this cell'), partial(self.quickview_item, row, column)) - a.setEnabled(self.is_category(self.column_order[column]) and - book_displayed and not self.lock_qv.isChecked()) + key = self.column_order[column] + a.setEnabled(self.is_category(key) and book_displayed and + key in self.view.visible_columns and + not self.lock_qv.isChecked()) m.addSeparator() m.addAction(self.view_icon, _('Open book in the E-book viewer'), partial(self.view_plugin._view_calibre_books, [book_id])) @@ -517,9 +519,11 @@ class Quickview(QDialog, Ui_Quickview): self.indicate_no_items() def is_category(self, key): - return key is not None and (self.fm[key]['is_category'] or + return key is not None and ( + self.fm[key]['table'] is not None and + (self.fm[key]['is_category'] or (self.fm[key]['datatype'] == 'composite' and - self.fm[key]['display'].get('make_category', False))) + self.fm[key]['display'].get('make_category', False)))) def _refresh(self, book_id, key): ''' @@ -718,7 +722,6 @@ class Quickview(QDialog, Ui_Quickview): self.select_book_and_qv(row, self.key_to_table_widget_column(self.current_key)) def book_not_in_view_error(self): - from calibre.gui2 import error_dialog error_dialog(self, _('Quickview: Book not in library view'), _('The book you selected is not currently displayed in ' 'the library view, perhaps because of a search or a ' @@ -745,6 +748,7 @@ class Quickview(QDialog, Ui_Quickview): else: self.quickview_item(row, self.key_to_table_widget_column(self.current_key)) except: + traceback.print_exc() self.book_not_in_view_error() def edit_metadata(self, book_id, follow_library_view=True): @@ -784,6 +788,13 @@ class Quickview(QDialog, Ui_Quickview): if QApplication.keyboardModifiers() in (Qt.KeyboardModifier.ControlModifier, Qt.KeyboardModifier.ShiftModifier): self.edit_metadata(book_id) else: + if key not in self.view.visible_columns: + error_dialog(self, _("Quickview: Column cannot be selected"), + _("The column you double-clicked, '{}', is not shown in the " + "library view. The book/column cannot be selected by Quickview.").format(key), + show=True, + show_copy_button=False) + return self.view.select_cell(self.db.data.id_to_index(book_id), self.view.column_map.index(key)) From 6e4c37822838863d4a886ea9ea34af8c4fe6b2ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Beir=C3=B3?= Date: Mon, 13 Mar 2023 11:15:21 +0100 Subject: [PATCH 0352/2055] Update el_pais.recipe Removed unrelated text Added author of the article Downloads all pictures Improves downloaded pictures quality Downloads cover if available Updated some feeds Adds some style to author's name, photographer's name, picture text Added publication_type --- recipes/el_pais.recipe | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/recipes/el_pais.recipe b/recipes/el_pais.recipe index c3195a2a8f..8f72c0720e 100644 --- a/recipes/el_pais.recipe +++ b/recipes/el_pais.recipe @@ -1,8 +1,8 @@ #!/usr/bin/env python __license__ = 'GPL v3' -__author__ = 'Jordi Balcells, based on an earlier version by Lorenzo Vigentini & Kovid Goyal' +__author__ = 'Alvaro Beiro, improving Jordi Balcells work based on an earlier version by Lorenzo Vigentini & Kovid Goyal' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' -description = 'Main daily newspaper from Spain - v1.04 (19, October 2010)' +description = 'Main daily newspaper from Spain - v1.05 (13, March 2023)' __docformat__ = 'restructuredtext en' ''' @@ -13,12 +13,13 @@ from calibre.web.feeds.news import BasicNewsRecipe class ElPais(BasicNewsRecipe): - __author__ = 'Kovid Goyal & Lorenzo Vigentini & Jordi Balcells' + __author__ = 'Kovid Goyal & Lorenzo Vigentini & Jordi Balcells & Alvaro Beiro' description = 'Main daily newspaper from Spain' - title = u'El Pais' + title = u'El Pa\xeds' publisher = u'Ediciones El Pa\xeds SL' category = 'News, politics, culture, economy, general interest' + publication_type = 'newspaper' language = 'es' timefmt = '[%a, %d %b, %Y]' @@ -31,6 +32,8 @@ class ElPais(BasicNewsRecipe): remove_javascript = True no_stylesheets = True + + extra_css = 'span._db {max-width: 100%; height: auto;} .a_m_p {font-size: .75rem;} .a_m_m {text-transform: uppercase; padding-top: 0.5rem;} div.a_md_a {text-align: center; text-transform: uppercase; font-size: .8rem;}' keep_only_tags = [ dict(attrs={'class': [ @@ -41,6 +44,8 @@ class ElPais(BasicNewsRecipe): 'articulo-titulares', 'articulo-apertura', 'articulo__contenedor' + 'a_e_m', + 'a_md_a', ]}), dict(name='div', attrs={'class': 'a_c',}), @@ -57,20 +62,26 @@ class ElPais(BasicNewsRecipe): 'more_info', 'articulo-apoyos', 'top10', + 'a_ei', + 'w-cta', + 'ph-v_b', ] }, ), dict(id='cta_id'), dict(name='svg'), ] - + + remove_attributes = ['width', 'height'] + feeds = [ (u'Espa\xf1a', u'https://feeds.elpais.com/mrss-s/pages/ep/site/elpais.com/section/espana/portada'), (u'Internacional', u'https://feeds.elpais.com/mrss-s/pages/ep/site/elpais.com/section/internacional/portada'), - (u'Opini\xf3n', u'https://elpais.com/rss/elpais/opinion.xml'), + (u'Economía', u'https://feeds.elpais.com/mrss-s/pages/ep/site/elpais.com/section/economia/portada'), + (u'Opinión', u'http://ep00.epimg.net/rss/elpais/opinion.xml'), (u'Ciencia', u'https://feeds.elpais.com/mrss-s/pages/ep/site/elpais.com/section/ciencia/portada'), - (u'Tecnolog\xeda', + (u'Tecnología', u'https://feeds.elpais.com/mrss-s/pages/ep/site/elpais.com/section/tecnologia/portada'), (u'Cultura', u'https://feeds.elpais.com/mrss-s/pages/ep/site/elpais.com/section/cultura/portada'), (u'Estilo', u'https://feeds.elpais.com/mrss-s/pages/ep/site/elpais.com/section/estilo/portada'), @@ -79,3 +90,19 @@ class ElPais(BasicNewsRecipe): (u'Sociedad', u'https://feeds.elpais.com/mrss-s/pages/ep/site/elpais.com/section/sociedad/portada'), (u'Blogs', u'http://ep01.epimg.net/rss/elpais/blogs.xml'), ] + + def get_cover_url(self): + from datetime import date + cover = 'https://srv00.epimg.net/pdf/elpais/snapshot/' + str(date.today().year) + '/' + date.today().strftime('%m') + '/elpais/' + str(date.today().year) + date.today().strftime('%m') + date.today().strftime('%d') + 'Big.jpg' + br = BasicNewsRecipe.get_browser(self) + try: + br.open(cover) + except: + self.log("\nCover unavailable") + cover = None + return cover + + def image_url_processor(cls, baseurl, url): + splitUrl = url.split("cloudfront-") + parsedUrl = 'https://cloudfront-' + splitUrl[1] + return parsedUrl From 6a0654c9e4bec53824cdf8cc9d27d9e93df615fe Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 13 Mar 2023 21:25:06 +0000 Subject: [PATCH 0353/2055] Bug reported in https://www.mobileread.com/forums/showthread.php?t=352726 --- src/calibre/gui2/search_restriction_mixin.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/search_restriction_mixin.py b/src/calibre/gui2/search_restriction_mixin.py index b03524885a..7de3dad76c 100644 --- a/src/calibre/gui2/search_restriction_mixin.py +++ b/src/calibre/gui2/search_restriction_mixin.py @@ -543,7 +543,10 @@ class SearchRestrictionMixin: self.rebuild_vl_tabs() def _trim_restriction_name(self, name): - return name[0:MAX_VIRTUAL_LIBRARY_NAME_LENGTH].strip() + name = name.strip() + if len(name) < MAX_VIRTUAL_LIBRARY_NAME_LENGTH or name.endswith('…'): + return name + return name[0:MAX_VIRTUAL_LIBRARY_NAME_LENGTH].strip() + '…' def build_search_restriction_list(self): self.search_restriction_list_built = True @@ -566,7 +569,7 @@ class SearchRestrictionMixin: nonlocal dex self.search_restriction.addItem(name) txt = self._trim_restriction_name(last) - if name == current_restriction: + if self._trim_restriction_name(name) == self._trim_restriction_name(current_restriction): a = current_menu.addAction(self.checked, txt if txt else self.no_restriction) else: a = current_menu.addAction(txt if txt else self.no_restriction) @@ -578,7 +581,7 @@ class SearchRestrictionMixin: add_action(m, '', '') add_action(m, _('*current search'), _('*current search')) if current_restriction_text: - add_action(m, current_restriction_text) + add_action(m, current_restriction_text, current_restriction_text) self.add_saved_searches_to_menu(m, self.library_view.model().db, add_action) def search_restriction_triggered(self, action=None, index=None): From 2d2cea29e00ed0972a07d902d12698cd74497ebc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 14 Mar 2023 09:08:38 +0530 Subject: [PATCH 0354/2055] ... --- recipes/el_pais.recipe | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/recipes/el_pais.recipe b/recipes/el_pais.recipe index 8f72c0720e..0bf6fb5ce3 100644 --- a/recipes/el_pais.recipe +++ b/recipes/el_pais.recipe @@ -32,8 +32,13 @@ class ElPais(BasicNewsRecipe): remove_javascript = True no_stylesheets = True - - extra_css = 'span._db {max-width: 100%; height: auto;} .a_m_p {font-size: .75rem;} .a_m_m {text-transform: uppercase; padding-top: 0.5rem;} div.a_md_a {text-align: center; text-transform: uppercase; font-size: .8rem;}' + + extra_css = ''' +span._db {max-width: 100%; height: auto;} +.a_m_p {font-size: .75rem;} +.a_m_m {text-transform: uppercase; padding-top: 0.5rem;} +div.a_md_a {text-align: center; text-transform: uppercase; font-size: .8rem;} +''' keep_only_tags = [ dict(attrs={'class': [ @@ -45,7 +50,7 @@ class ElPais(BasicNewsRecipe): 'articulo-apertura', 'articulo__contenedor' 'a_e_m', - 'a_md_a', + 'a_md_a', ]}), dict(name='div', attrs={'class': 'a_c',}), @@ -71,9 +76,9 @@ class ElPais(BasicNewsRecipe): dict(id='cta_id'), dict(name='svg'), ] - + remove_attributes = ['width', 'height'] - + feeds = [ (u'Espa\xf1a', u'https://feeds.elpais.com/mrss-s/pages/ep/site/elpais.com/section/espana/portada'), (u'Internacional', @@ -90,10 +95,12 @@ class ElPais(BasicNewsRecipe): (u'Sociedad', u'https://feeds.elpais.com/mrss-s/pages/ep/site/elpais.com/section/sociedad/portada'), (u'Blogs', u'http://ep01.epimg.net/rss/elpais/blogs.xml'), ] - + def get_cover_url(self): from datetime import date - cover = 'https://srv00.epimg.net/pdf/elpais/snapshot/' + str(date.today().year) + '/' + date.today().strftime('%m') + '/elpais/' + str(date.today().year) + date.today().strftime('%m') + date.today().strftime('%d') + 'Big.jpg' + cover = ('https://srv00.epimg.net/pdf/elpais/snapshot/' + + str(date.today().year) + '/' + date.today().strftime('%m') + '/elpais/' + + str(date.today().year) + date.today().strftime('%m') + date.today().strftime('%d') + 'Big.jpg') br = BasicNewsRecipe.get_browser(self) try: br.open(cover) @@ -101,7 +108,7 @@ class ElPais(BasicNewsRecipe): self.log("\nCover unavailable") cover = None return cover - + def image_url_processor(cls, baseurl, url): splitUrl = url.split("cloudfront-") parsedUrl = 'https://cloudfront-' + splitUrl[1] From 6be2cc69a8e8361ac24e143b4cc2d1350b7de239 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 14 Mar 2023 09:42:56 +0530 Subject: [PATCH 0355/2055] Forgot that the ToC editor in the edit book tool uses a different code path. Copy over the macOS workaround. --- src/calibre/gui2/tweak_book/toc.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/tweak_book/toc.py b/src/calibre/gui2/tweak_book/toc.py index 6758f1f4fe..5f085f15dd 100644 --- a/src/calibre/gui2/tweak_book/toc.py +++ b/src/calibre/gui2/tweak_book/toc.py @@ -5,14 +5,15 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' from qt.core import ( - QAction, QApplication, QDialog, QDialogButtonBox, QGridLayout, QIcon, QMenu, - QSize, QStackedWidget, QStyledItemDelegate, Qt, QTimer, QTreeWidget, - QTreeWidgetItem, QVBoxLayout, QWidget, pyqtSignal + QAction, QApplication, QDialog, QDialogButtonBox, QGridLayout, QIcon, QMenu, QSize, + QStackedWidget, QStyledItemDelegate, Qt, QTimer, QTreeWidget, QTreeWidgetItem, + QVBoxLayout, QWidget, pyqtSignal, ) from time import monotonic +from calibre.constants import ismacos from calibre.ebooks.oeb.polish.toc import commit_toc, get_toc -from calibre.gui2 import error_dialog, make_view_use_window_background +from calibre.gui2 import error_dialog, info_dialog, make_view_use_window_background from calibre.gui2.toc.main import ItemEdit, TOCView from calibre.gui2.tweak_book import TOP, actions, current_container, tprefs from calibre_extensions.progress_indicator import set_no_activate_on_click @@ -61,6 +62,15 @@ class TOCEditor(QDialog): def add_new_item(self, item, where): self.item_edit(item, where) self.stacks.setCurrentIndex(1) + if ismacos: + QTimer.singleShot(0, self.workaround_macos_mouse_with_webview_bug) + + def workaround_macos_mouse_with_webview_bug(self): + # macOS is weird: https://bugs.launchpad.net/calibre/+bug/2004639 + # needed as of Qt 6.4.2 + d = info_dialog(self, _('Loading...'), _('Loading view, please wait...'), show_copy_button=False) + QTimer.singleShot(0, d.reject) + d.exec() def accept(self): if monotonic() - self.last_accept_at < 1: From 2d244e8996cf62787ecd20a0cfa059fcea0368e7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 14 Mar 2023 09:53:33 +0530 Subject: [PATCH 0356/2055] Mention the sort by tool in the manual --- manual/gui.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/manual/gui.rst b/manual/gui.rst index 96983a58b3..e707ea5c94 100644 --- a/manual/gui.rst +++ b/manual/gui.rst @@ -325,6 +325,9 @@ The Search & Sort section allows you to perform several powerful actions on your * You can configure which fields you want displayed by using the :ref:`configuration` dialog. + * To perform multiple column based sub-sorting add the :guilabel:`Sort by` + tool to a toolbar via :guilabel:`Preferences->Toolbars & menus`. + .. _search_interface: The search interface From 8df18d2d219ebd04b7220d3ed29a4e079df6366f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 14 Mar 2023 09:55:26 +0530 Subject: [PATCH 0357/2055] ... --- manual/gui.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/manual/gui.rst b/manual/gui.rst index e707ea5c94..fdf9057bd0 100644 --- a/manual/gui.rst +++ b/manual/gui.rst @@ -311,7 +311,10 @@ Search & sort The Search & Sort section allows you to perform several powerful actions on your book collections. - * You can sort them by title, author, date, rating, etc. by clicking on the column titles. You can also sub-sort, i.e. sort on multiple columns. For example, if you click on the title column and then the author column, the book will be sorted by author and then all the entries for the same author will be sorted by title. + * You can sort them by title, author, date, rating, etc. by clicking on the column titles. + You can also sub-sort, i.e. sort on multiple columns. + For example, if you click on the title column and then the author column, the book will be sorted by + author and then all the entries for the same author will be sorted by title. * You can search for a particular book or set of books using the Search bar. More on that below. @@ -325,7 +328,7 @@ The Search & Sort section allows you to perform several powerful actions on your * You can configure which fields you want displayed by using the :ref:`configuration` dialog. - * To perform multiple column based sub-sorting add the :guilabel:`Sort by` + * To perform complex multiple column based sub-sorting add the :guilabel:`Sort by` tool to a toolbar via :guilabel:`Preferences->Toolbars & menus`. .. _search_interface: From 102427ddb8e7d209bdd14b4234f8fd3e1cae5aae Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 14 Mar 2023 19:47:10 +0530 Subject: [PATCH 0358/2055] Pass dbpath as a param to SQLite --- src/calibre/db/fts/connect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/db/fts/connect.py b/src/calibre/db/fts/connect.py index c7b6b9ce2e..39ca819a31 100644 --- a/src/calibre/db/fts/connect.py +++ b/src/calibre/db/fts/connect.py @@ -39,7 +39,7 @@ class FTS: if conn.fts_dbpath is None: main_db_path = os.path.abspath(conn.db_filename('main')) dbpath = os.path.join(os.path.dirname(main_db_path), 'full-text-search.db') - conn.execute(f"ATTACH DATABASE '{dbpath}' AS fts_db") + conn.execute("ATTACH DATABASE ? AS fts_db", (dbpath,)) SchemaUpgrade(conn) conn.execute('UPDATE fts_db.dirtied_formats SET in_progress=FALSE WHERE in_progress=TRUE') num_dirty = conn.get('''SELECT COUNT(*) from fts_db.dirtied_formats''')[0][0] From f21f1b227b64ebab10d61bdbd35457a03505ee1d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 15 Mar 2023 09:37:06 +0530 Subject: [PATCH 0359/2055] Fix a regression in the previous release that caused some generated resources to be not included in the released source tarball --- setup/install.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/install.py b/setup/install.py index b21f11b34e..ea0c71d807 100644 --- a/setup/install.py +++ b/setup/install.py @@ -303,7 +303,7 @@ class Sdist(Command): os.mkdir(tdir) subprocess.check_call('git archive HEAD | tar -x -C ' + tdir, shell=True) for x in open('.gitignore').readlines(): - if not x.startswith('resources/'): + if not x.startswith('/resources/'): continue p = x.strip().replace('/', os.sep) for p in glob.glob(p): From 42a4e729caa5038f464dacd4f1a973da78490530 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 15 Mar 2023 11:51:59 +0530 Subject: [PATCH 0360/2055] ... --- src/calibre/srv/TODO.rst | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/calibre/srv/TODO.rst b/src/calibre/srv/TODO.rst index 27aa3936ac..1f49a51bba 100644 --- a/src/calibre/srv/TODO.rst +++ b/src/calibre/srv/TODO.rst @@ -5,9 +5,6 @@ particular order. New features for the in-browser viewer ---------------------------------------- -- Bookmarks and more generally, annotations such as highlighting text and - adding comments - - When reaching the end of the book, show a popup that allows the user to rate the book and optionally delete it from the local storage. @@ -18,8 +15,6 @@ New features for the in-browser viewer New features for the server generally --------------------------------------- -- Create a UI for sending by email - - Add a way to search the set of locally available books stored in offline storage. From 3a43e6bb0aa4996878476f89e96c80986a61fc7a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 16 Mar 2023 06:23:38 +0530 Subject: [PATCH 0361/2055] version 6.14.1 --- Changelog.txt | 8 +++++++- src/calibre/constants.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Changelog.txt b/Changelog.txt index 66d6a2b836..59c2fbda43 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -23,7 +23,7 @@ # - title by author # }}} -{{{ 6.14.0 2023-03-10 +{{{ 6.14.1 2023-03-16 :: new features @@ -43,6 +43,12 @@ - [2009735] Check book: Fix some incorrect line numbers reported in a few CSS error messages +- Fix regression in 6.14.0 that caused some generated resources to be excluded from the calibre source bundle + +- [2011586] Fix regression in 6.14.0 that broke using paths with single quotes in them for the calibre library + +- Fix ToC Editor on macOS in 6.14.0 not working inside the Edit book tool only + :: improved recipes - Strange Horizons - The Saturday Paper diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 0e8064d9b5..4e9b0de30a 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -5,7 +5,7 @@ from functools import lru_cache import sys, locale, codecs, os, collections, collections.abc __appname__ = 'calibre' -numeric_version = (6, 14, 0) +numeric_version = (6, 14, 1) __version__ = '.'.join(map(str, numeric_version)) git_version = None __author__ = "Kovid Goyal " From cbf569bb06c0d0297355c6f46618cca638870ee1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 16 Mar 2023 07:49:09 +0530 Subject: [PATCH 0362/2055] ... --- setup/install.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup/install.py b/setup/install.py index ea0c71d807..fbdc014134 100644 --- a/setup/install.py +++ b/setup/install.py @@ -305,6 +305,7 @@ class Sdist(Command): for x in open('.gitignore').readlines(): if not x.startswith('/resources/'): continue + x = x[1:] p = x.strip().replace('/', os.sep) for p in glob.glob(p): d = self.j(tdir, os.path.dirname(p)) From 48ad093c4a76c74372558de979854668e21a78c4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 17 Mar 2023 08:44:54 +0530 Subject: [PATCH 0363/2055] The Wire by unkn0wn --- recipes/the_wire.recipe | 61 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 recipes/the_wire.recipe diff --git a/recipes/the_wire.recipe b/recipes/the_wire.recipe new file mode 100644 index 0000000000..f77235257f --- /dev/null +++ b/recipes/the_wire.recipe @@ -0,0 +1,61 @@ +from calibre.web.feeds.news import BasicNewsRecipe, classes +from calibre.ptempfile import PersistentTemporaryFile + +class TheWire(BasicNewsRecipe): + title = 'The Wire' + __author__ = 'unkn0wn' + description = 'The Wire is an Indian nonprofit news and opinion website' + language = 'en_IN' + masthead_url = 'https://cdn.thewire.in/wp-content/uploads/thewire-app-images/wire-logo.svg' + + no_stylesheets = True + remove_javascript = True + + keep_only_tags = [ + classes( + 'title shortDesc author__name featured-image postComplete__description' + ' post-content-container thb-article-featured-image post-title ' + 'sharing-counts-off post-bottom-meta' + ) + ] + + ignore_duplicate_articles = {'title'} + resolve_internal_links = True + remove_empty_feeds = True + + articles_are_obfuscated = True + + def get_obfuscated_article(self, url): + br = self.get_browser() + try: + br.open(url) + except Exception as e: + url = e.hdrs.get('location') + soup = self.index_to_soup(url) + link = soup.find('a', href=True) + skip_sections =[ # add sections you want to skip + '/video/', '/videos/', '/media/', 'podcast-' + ] + if any(x in link['href'] for x in skip_sections): + self.log('Aborting Article ', link['href']) + self.abort_article('skipping video links') + + self.log('Downloading ', link['href']) + html = br.open(link['href']).read() + pt = PersistentTemporaryFile('.html') + pt.write(html) + pt.close() + return pt.name + + feeds = [] + + sections = [ + 'government', 'politics', 'law', 'business', 'economy', 'education', 'the-sciences', + 'security', 'tech', 'culture', 'environment', 'health', 'travel', 'rights', + 'labour', 'world', 'diplomacy', 'books', 'south-asia', 'caste', 'communalism', + ] + + for sec in sections: + a = 'https://news.google.com/rss/search?q=when:27h+allinurl:thewire.in{}&hl=en-IN&gl=IN&ceid=IN:en' + feeds.append((sec.capitalize(), a.format('%2F' + sec + '%2F'))) + feeds.append(('Others', a.format(''))) From fbec3adb2c955dcb43efa1435f92bd97a1c04627 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 17 Mar 2023 08:45:03 +0530 Subject: [PATCH 0364/2055] Update The Hindu --- recipes/hindu.recipe | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/recipes/hindu.recipe b/recipes/hindu.recipe index 7101a88bf1..442d6dba2b 100644 --- a/recipes/hindu.recipe +++ b/recipes/hindu.recipe @@ -1,7 +1,7 @@ import json import re from collections import defaultdict -from datetime import date +from datetime import datetime from calibre.web.feeds.news import BasicNewsRecipe, classes @@ -10,10 +10,11 @@ def absurl(url): url = 'https://www.thehindu.com' + url return url - -local_edition = None # Chennai is default edition, for other editions use 'th_hyderabad', 'th_bangalore', 'th_delhi', 'th_kolkata' etc +local_edition = None +# For past editions, set date to, for example, '2023-01-28' +past_edition = None class TheHindu(BasicNewsRecipe): title = 'The Hindu' @@ -22,15 +23,18 @@ class TheHindu(BasicNewsRecipe): no_stylesheets = True masthead_url = 'https://www.thehindu.com/theme/images/th-online/thehindu-logo.svg' remove_attributes = ['style', 'height', 'width'] - extra_css = '.caption{font-size:small; text-align:center;}'\ - '.author{font-size:small; font-weight:bold;}'\ - '.subhead, .subhead_lead {font-weight:bold;}'\ - 'img {display:block; margin:0 auto;}' + + extra_css = ''' + .caption {font-size:small; text-align:center;} + .author {font-size:small; font-weight:bold;} + .subhead, .subhead_lead {font-weight:bold;} + img {display:block; margin:0 auto;} + ''' ignore_duplicate_articles = {'url'} keep_only_tags = [ - classes('article-section ') + classes('article-section') ] remove_tags = [ @@ -44,12 +48,22 @@ class TheHindu(BasicNewsRecipe): img['src'] = img['data-original'] return soup + def __init__(self, *args, **kwargs): + BasicNewsRecipe.__init__(self, *args, **kwargs) + if self.output_profile.short_name.startswith('kindle'): + if not past_edition: + self.title = 'The Hindu ' + datetime.today().strftime('%b %d, %Y') + def parse_index(self): - if local_edition: - yr = str(date.today().year) - mn = date.today().strftime('%m') - dy = date.today().strftime('%d') - url = 'https://www.thehindu.com/todays-paper/' + yr + '-' + mn + '-' + dy + '/' + local_edition + '/' + global local_edition + if local_edition or past_edition: + if local_edition is None: + local_edition = 'th_chennai' + today = datetime.today().strftime('%Y-%m-%d') + if past_edition: + today = past_edition + self.log('Downloading past edition of', local_edition + ' from ' + today) + url = absurl('/todays-paper/' + today + '/' + local_edition + '/') else: url = 'https://www.thehindu.com/todays-paper/' raw = self.index_to_soup(url, raw=True) From 32618d741f180b181c563617a10234c23cbe0223 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 18 Mar 2023 14:21:13 +0530 Subject: [PATCH 0365/2055] Update Live Mint --- recipes/livemint.recipe | 44 +++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/recipes/livemint.recipe b/recipes/livemint.recipe index 72b7a7b307..e63471a28f 100644 --- a/recipes/livemint.recipe +++ b/recipes/livemint.recipe @@ -1,14 +1,10 @@ -#!/usr/bin/env python - import json import re from datetime import date - from calibre.web.feeds.news import BasicNewsRecipe, classes is_saturday = date.today().weekday() == 5 - class LiveMint(BasicNewsRecipe): title = u'Live Mint' description = 'Financial News from India.' @@ -34,28 +30,35 @@ class LiveMint(BasicNewsRecipe): if is_saturday: + oldest_article = 6 # days + + extra_css = ''' + #story-summary-0 {font-style:italic; color:#202020;} + .innerBanner, .storyImgSec {text-align:center; font-size:small;} + .author {font-size:small;} + ''' + keep_only_tags = [ - dict(name='h1'), - dict(name='h2', attrs={'id':'story-summary-0'}), - dict(name='picture'), - dict(name='div', attrs={'class':'innerBanCaption'}), - dict(name='div', attrs={'id':'date-display-before-content'}), - dict(name='div', attrs={'class':'storyContent'}), + classes('storyPageHeading storyContent innerBanner author') ] remove_tags = [ - classes( - 'sidebarAdv similarStoriesClass moreFromSecClass' - ) + classes('hidden-article-url sidebarAdv similarStoriesClass moreFromSecClass linkStories publishDetail'), + dict(attrs={'id':['hidden-article-id-0', 'hidden-article-type-0']}) ] + feeds = [ - ('News', 'https://lifestyle.livemint.com/rss/news'), - ('Food','https://lifestyle.livemint.com/rss/food'), - ('Fashion','https://lifestyle.livemint.com/rss/fashion'), - ('How to Lounge','https://lifestyle.livemint.com/rss/how-to-lounge'), - ('Smart Living','https://lifestyle.livemint.com/rss/smart-living'), + ('Lounge News', 'https://lifestyle.livemint.com/rss/news'), + ('Food', 'https://lifestyle.livemint.com/rss/food'), + ('Fashion', 'https://lifestyle.livemint.com/rss/fashion'), + ('How to Lounge', 'https://lifestyle.livemint.com/rss/how-to-lounge'), + ('Smart Living', 'https://lifestyle.livemint.com/rss/smart-living'), + ('Health', 'https://lifestyle.livemint.com/rss/health'), + ('Relationships', 'https://lifestyle.livemint.com//rss/relationships') ] def preprocess_html(self, soup): + if h2 := soup.find('h2'): + h2.name = 'p' for img in soup.findAll('img', attrs={'data-img': True}): img['src'] = img['data-img'] return soup @@ -72,7 +75,7 @@ class LiveMint(BasicNewsRecipe): ''' keep_only_tags = [ - dict(name='article'), + dict(name='article', attrs={'id':lambda x: x and x.startswith('article_')}), classes('contentSec') ] remove_tags = [ @@ -128,3 +131,6 @@ class LiveMint(BasicNewsRecipe): for img in soup.findAll('img', attrs={'data-src': True}): img['src'] = img['data-src'] return soup + + def populate_article_metadata(self, article, soup, first): + article.title = article.title.replace('','₹') From 762c1c0a195ce97a32bcfc63a9a3323074128fd1 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 19 Mar 2023 14:22:44 +0000 Subject: [PATCH 0366/2055] calibre show-book URL: clear the current search if the book to be shown is not in the search results. --- manual/url_scheme.rst | 6 ++++-- src/calibre/gui2/ui.py | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/manual/url_scheme.rst b/manual/url_scheme.rst index 108e6d773b..ea5478500a 100644 --- a/manual/url_scheme.rst +++ b/manual/url_scheme.rst @@ -56,6 +56,8 @@ brackets at the end of the path to the book folder. You can copy a link to the current book displayed in calibre by right clicking the :guilabel:`Book details` panel and choosing :guilabel:`Copy link to book`. +If a search is active and the book is not matched by the search then the search is cleared. + If a Virtual library is selected, calibre will use it when showing the book. If the book isn't found in that virtual library then the virtual library is cleared. @@ -65,8 +67,8 @@ If you want to switch to a particular Virtual library when showing the book, use or calibre://show-book/Library_Name/book_id?encoded_virtual_library=hex_encoded_virtual_library_name -replacing spaces in the Virtual library name by ``%20``. If the book isn't in that -virtual library then it is ignored. +replacing spaces in the Virtual library name by ``%20``. If the book isn't found in that +virtual library then the virtual library is ignored. Open a specific book in the E-book viewer at a specific position diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index ef19689963..71f4385dc9 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -711,6 +711,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ if vl is not None and vl != '_': self.apply_virtual_library(vl) rows = self.library_view.select_rows((book_id,)) + if not rows: + self.search.set_search_string('') + rows = self.library_view.select_rows((book_id,)) db = self.current_db if not rows and (db.data.get_base_restriction_name() or db.data.get_search_restriction_name()): self.apply_virtual_library() From e5a144a30e3fbd1519c78c91a7f2138db421ac13 Mon Sep 17 00:00:00 2001 From: Aareet Mahadevan Date: Tue, 21 Mar 2023 00:44:18 -0700 Subject: [PATCH 0367/2055] Create tehelka.recipe Adds a recipe via RSS for tehelka.com --- recipes/tehelka.recipe | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 recipes/tehelka.recipe diff --git a/recipes/tehelka.recipe b/recipes/tehelka.recipe new file mode 100644 index 0000000000..41cfe416a1 --- /dev/null +++ b/recipes/tehelka.recipe @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# -*- mode: python -*- +# -*- coding: utf-8 -*- + +__license__ = 'GPL v3' +__copyright__ = '2023, Aareet Mahadevan' +''' +https://www.ambito.com/contenidos/edicion-impresa.html +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class Tehelka(BasicNewsRecipe): + title = 'Tehelka' + oldest_article = 7 + max_articles_per_feed = 10 + auto_cleanup = True + __author__ = 'Aareet Mahadevan' + description = u'Free. Fair. Fearless.' + category = 'news, india' + encoding = 'utf-8' + no_stylesheets = True + remove_empty_feeds = True + remove_javascript = True + use_embedded_content = False + ignore_duplicate_articles = {'title', 'url'} + + + feeds = [ + ('Tehelka', 'http://tehelka.com/rss'), + ] From a19067e17943b27cd9186efa59a9d9d413f2d679 Mon Sep 17 00:00:00 2001 From: Aareet Mahadevan Date: Tue, 21 Mar 2023 00:45:20 -0700 Subject: [PATCH 0368/2055] Update tehelka.recipe --- recipes/tehelka.recipe | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/tehelka.recipe b/recipes/tehelka.recipe index 41cfe416a1..b55557bc97 100644 --- a/recipes/tehelka.recipe +++ b/recipes/tehelka.recipe @@ -5,7 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2023, Aareet Mahadevan' ''' -https://www.ambito.com/contenidos/edicion-impresa.html +http://tehelka.com ''' from calibre.web.feeds.news import BasicNewsRecipe From b55dd98bce1cefab0b0191e29f80de1a7ed3ba1c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Mar 2023 15:46:27 +0530 Subject: [PATCH 0369/2055] Tag mapper: Ensure tag rules are unicode normalized before matching --- src/calibre/ebooks/metadata/tag_mapper.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/calibre/ebooks/metadata/tag_mapper.py b/src/calibre/ebooks/metadata/tag_mapper.py index 4d41f1e616..5143c7fbcd 100644 --- a/src/calibre/ebooks/metadata/tag_mapper.py +++ b/src/calibre/ebooks/metadata/tag_mapper.py @@ -5,6 +5,7 @@ from collections import deque from calibre.utils.icu import lower as icu_lower, upper as icu_upper +from polyglot.builtins import as_unicode def compile_pat(pat): @@ -14,25 +15,29 @@ def compile_pat(pat): def matcher(rule): + import unicodedata + def n(x): + return unicodedata.normalize('NFC', as_unicode(x or '', errors='replace')) + mt = rule['match_type'] if mt == 'one_of': - tags = {icu_lower(x.strip()) for x in rule['query'].split(',')} + tags = {icu_lower(n(x.strip())) for x in rule['query'].split(',')} return lambda x: x in tags if mt == 'not_one_of': - tags = {icu_lower(x.strip()) for x in rule['query'].split(',')} + tags = {icu_lower(n(x.strip())) for x in rule['query'].split(',')} return lambda x: x not in tags if mt == 'matches': - pat = compile_pat(rule['query']) + pat = compile_pat(n(rule['query'])) return lambda x: pat.match(x) is not None if mt == 'not_matches': - pat = compile_pat(rule['query']) + pat = compile_pat(n(rule['query'])) return lambda x: pat.match(x) is None if mt == 'has': - s = icu_lower(rule['query']) + s = icu_lower(n(rule['query'])) return lambda x: s in x return lambda x: False From d64423a95fe144bf19c5fd87b0abbe6c2cbd3e9c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 24 Mar 2023 12:56:14 +0530 Subject: [PATCH 0370/2055] Update Mediapart --- recipes/mediapart.recipe | 255 ++++++++------------------------------- 1 file changed, 48 insertions(+), 207 deletions(-) diff --git a/recipes/mediapart.recipe b/recipes/mediapart.recipe index d5a1518b1f..287251e9b8 100644 --- a/recipes/mediapart.recipe +++ b/recipes/mediapart.recipe @@ -9,6 +9,8 @@ # ( cover image format is changed to .jpeg) # 14 Jan 2021 - Add Mediapart Logo url as masthead_url and change cover # by overlaying the date on top of the Mediapart cover +# 22 Mar 2023 - Switch to Google feeds + from __future__ import unicode_literals __license__ = 'GPL v3' @@ -17,235 +19,74 @@ __copyright__ = '2021, Loïc Houpert . Adapted from: Mediapart ''' -import re from datetime import date, datetime, timezone, timedelta -from calibre.web.feeds import feeds_from_index -from calibre.web.feeds.news import BasicNewsRecipe - - -def classes(classes): - q = frozenset(classes.split(' ')) - return dict( - attrs={'class': lambda x: x and frozenset(x.split()).intersection(q)} - ) - +from calibre.ptempfile import PersistentTemporaryFile +from calibre.web.feeds.news import BasicNewsRecipe, classes class Mediapart(BasicNewsRecipe): title = 'Mediapart' - __author__ = 'Loïc Houpert' + __author__ = 'Loïc Houpert, unkn0wn' description = 'Global news in French from news site Mediapart' publication_type = 'newspaper' language = 'fr' needs_subscription = True - oldest_article = 2 - + use_embedded_content = False no_stylesheets = True keep_only_tags = [ - dict(name='h1'), - dict(name='div', **classes('author')), - classes('news__heading__top__intro news__body__center__article') + classes( + 'news__heading__top news__heading__center news__body__center__article' + ) ] + remove_tags = [ - classes('login-subscribe print-source_url'), + classes('action-links media--rich read-also login-subscribe print-source_url'), dict(name='svg'), ] + conversion_options = {'smarten_punctuation': True} masthead_url = "https://raw.githubusercontent.com/lhoupert/calibre_contrib/main/mediapart_masthead.png" - # cover_url = 'https://raw.githubusercontent.com/lhoupert/calibre_contrib/main/mediapart.jpeg' - # -- + ignore_duplicate_articles = {'title'} + resolve_internal_links = True + remove_empty_feeds = True + + articles_are_obfuscated = True - # Get date in french time zone format - today = datetime.now(timezone.utc) + timedelta(hours=1) - oldest_article_date = today - timedelta(days=oldest_article) + def get_obfuscated_article(self, url): + br = self.get_browser() + try: + br.open(url) + except Exception as e: + url = e.hdrs.get('location') + soup = self.index_to_soup(url) + link = soup.find('a', href=True) + skip_sections =[ # add sections you want to skip + '/video/', '/videos/', '/media/' + ] + if any(x in link['href'] for x in skip_sections): + self.log('Aborting Article ', link['href']) + self.abort_article('skipping video links') - feeds = [ - ('La Une', 'http://www.mediapart.fr/articles/feed'), + self.log('Downloading ', link['href']) + html = br.open(link['href']).read() + pt = PersistentTemporaryFile('.html') + pt.write(html) + pt.close() + return pt.name + + feeds = [] + + sections = [ + 'france', 'international', 'economie', 'culture-idees', 'politique', 'ecologie', 'fil-dactualites' ] - # The feed at 'http://www.mediapart.fr/articles/feed' only displayed the 10 - # last elements so the articles are indexed on specific pages - # in the function my_parse_index. In this function the article are parsed - # using the function get_articles and the dict values dict_article_sources - - def parse_feeds(self): - feeds = super(Mediapart, self).parse_feeds() - feeds += feeds_from_index(self.my_parse_index(feeds)) - return feeds - - def my_parse_index(self, la_une): - - dict_article_sources = [ - { - 'type': 'Brèves', - 'webpage': 'https://www.mediapart.fr/journal/fil-dactualites', - 'separador': { - 'page': 'ul', - 'thread': 'li' - } - }, - { - 'type': 'International', - 'webpage': 'https://www.mediapart.fr/journal/international', - 'separador': { - 'page': 'div', - 'thread': 'div' - } - }, - { - 'type': 'France', - 'webpage': 'https://www.mediapart.fr/journal/france', - 'separador': { - 'page': 'div', - 'thread': 'div' - } - }, - { - 'type': 'Économie', - 'webpage': 'https://www.mediapart.fr/journal/economie', - 'separador': { - 'page': 'div', - 'thread': 'div' - } - }, - { - 'type': 'Culture', - 'webpage': 'https://www.mediapart.fr/journal/culture-idees', - 'separador': { - 'page': 'div', - 'thread': 'div' - } - }, - ] - - def get_articles( - type_of_article, webpage, separador_page='ul', separador_thread='li' - ): - - specific_articles = [] - - webpage_article = [] - soup = self.index_to_soup(webpage) - page = soup.find('main', {'class': 'global-wrapper'}) - if page is None: - page = soup.find('section', {'class': 'news__body-wrapper mb-800'}) - fils = page.find(separador_page, {'class': 'post-list universe-journal'}) - if fils is None: - fils = page.find(separador_page, {'class': 'news__list__content _hasNewsletter'}) - - all_articles = fils.findAll(separador_thread) - for article in all_articles: - try: - # title = article.find('h3', recursive=False) - title = article.find('h3', recursive=True) - if title is None or ''.join(title['class']) == 'title-specific': - # print(f"[BAD title entry] Print value of title:\n {title}") - continue - # print(f"\n[OK title entry] Print value of title:\n {title}\n") - - try: - article_mot_cle = article.find( - 'a', { - 'href': re.compile(r'.*\/mot-cle\/.*') - } - ).renderContents().decode('utf-8') - except Exception: - article_mot_cle = '' - - try: - article_type = article.find( - 'a', { - 'href': re.compile(r'.*\/type-darticles\/.*') - } - ).renderContents().decode('utf-8') - except Exception: - article_type = '' - - for s in title('span'): - s.replaceWith(s.renderContents().decode('utf-8') + "\n") - url = title.find('a', href=True)['href'] - - date = article.find('time', datetime=True)['datetime'] - article_date = datetime.strptime(date, '%Y-%m-%d') - # Add French timezone to date of the article for date check - article_date = article_date.replace(tzinfo=timezone.utc) + timedelta(hours=1) - if article_date < self.oldest_article_date: - print("article_date < self.oldest_article_date\n") - continue - - # print("-------- Recent article added to the list ------- \n") - all_authors = article.findAll( - # 'a', {'class': re.compile(r'\bjournalist\b')} - 'div', {'class': 'teaser__signature'} - ) - if not all_authors: - all_authors = article.findAll( - 'a', {'class': re.compile(r'\bjournalist\b')} - ) - authors = [self.tag_to_string(a) for a in all_authors] - # print(f"Authors in tag : {authors}") - - # If not link to the author profile is available the - # html separador is a span tag - if not all_authors: - try: - all_authors = article.findAll( - 'span', {'class': re.compile(r'\bjournalist\b')} - ) - authors = [self.tag_to_string(a) for a in all_authors] - # print(f"Authors in tag : {authors}") - except: - authors = 'unknown' - - description = article.find('p').renderContents().decode('utf-8') - # print(f"

in article : {self.tag_to_string(description).strip()} ") - - summary = { - 'title': self.tag_to_string(title).strip(), - 'description': description, - 'date': article_date.strftime("%a, %d %b, %Y %H:%M"), - 'author': ', '.join(authors), - 'article_type': article_type, - 'mot_cle': article_mot_cle.capitalize(), - 'url': 'https://www.mediapart.fr' + url, - } - if webpage_article: - if summary['url'] != webpage_article[-1]['url']: - webpage_article.append(summary) - else: - webpage_article.append(summary) - except Exception: - pass - - specific_articles += [(type_of_article, - webpage_article)] if webpage_article else [] - return specific_articles - - articles = [] - - for category in dict_article_sources: - articles += get_articles( - category['type'], category['webpage'], category['separador']['page'], - category['separador']['thread'] - ) - - return articles - - # non-locale specific date parse (strptime("%d %b %Y",s) would work with - # french locale) - def parse_french_date(self, date_str): - date_arr = date_str.lower().split() - return date( - day=int(date_arr[0]), - year=int(date_arr[2]), - month=[ - None, 'janvier', 'février', 'mars', 'avril', 'mai', 'juin', - 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre' - ].index(date_arr[1]) - ) + for sec in sections: + a = 'https://news.google.com/rss/search?q=when:27h+allinurl:mediapart.fr%2Fjournal{}&hl=fr-FR&gl=FR&ceid=FR:fr' + feeds.append((sec.capitalize(), a.format('%2F' + sec + '%2F'))) + feeds.append(('Autres', a.format(''))) def get_browser(self): # -- Handle login @@ -298,7 +139,7 @@ class Mediapart(BasicNewsRecipe): p.setPen(pen) font = QFont() font.setFamily('Times') - font.setPointSize(78) + font.setPointSize(72) p.setFont(font) r = QRect(0, 600, 744,100) p.drawText(r, Qt.AlignmentFlag.AlignJustify | Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignCenter, date) @@ -329,4 +170,4 @@ class Mediapart(BasicNewsRecipe): except Exception: self.log.exception('Failed to generate default cover') return False - return True + return True \ No newline at end of file From b2aa59b484cba89590ac167d6ba872293bf2aa64 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 24 Mar 2023 13:00:46 +0530 Subject: [PATCH 0371/2055] Update LA Times --- recipes/latimes.recipe | 1 + 1 file changed, 1 insertion(+) diff --git a/recipes/latimes.recipe b/recipes/latimes.recipe index 2e0ef1edbc..ff9c94c09b 100644 --- a/recipes/latimes.recipe +++ b/recipes/latimes.recipe @@ -63,6 +63,7 @@ class LATimes(BasicNewsRecipe): keep_only_tags = [ classes('headline page-lead-media authors published-date page-article-container'), + dict(attrs={'data-element':'story-body'}), ] remove_tags= [ From ff4ed26896babcb6de409e6e7b4772f4bbac4f60 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 25 Mar 2023 16:46:14 +0000 Subject: [PATCH 0372/2055] This is a large commit. DB changes: 1) Add link columns to "normalized" tables. All such in-memory tables have an attribute self.link_map. 2) Add API to set and get links for fields 3) get_metadata now includes an attribute giving the link maps for the book. Book link maps are cached. 4) ProxyMetadata can return link maps 5) Added a test for the API. URL Scheme: 1) Added a "book-details" URL that asks calibre to open a book info window on a book in some library Book Details: 1) You can now have multiple book info windows. 2) If an item as an associated link then that link is made available using "(item link)" link text. 3) Book info windows on books in other libraries have no links UI: 1) the Manage Category editor presents a fourth column for links. OPF: 1) The OPF used for backing up (metadata.opf) contains the link map for the book. Currently this isn't used, but it should be used in recover_database. I didn't do anything with OPF3. --- manual/url_scheme.rst | 10 + src/calibre/db/backend.py | 1 + src/calibre/db/cache.py | 91 +++++++ src/calibre/db/fields.py | 2 +- src/calibre/db/lazy.py | 3 + src/calibre/db/schema_upgrades.py | 25 ++ src/calibre/db/tables.py | 27 +- src/calibre/db/tests/add_remove.py | 2 +- src/calibre/db/tests/writing.py | 24 ++ src/calibre/db/write.py | 6 +- src/calibre/ebooks/metadata/book/render.py | 232 +++++++++++------- src/calibre/ebooks/metadata/opf2.py | 6 +- src/calibre/gui2/actions/show_book_details.py | 44 +++- src/calibre/gui2/book_details.py | 35 ++- src/calibre/gui2/dialogs/book_info.py | 134 ++++++---- src/calibre/gui2/dialogs/tag_list_editor.py | 27 +- src/calibre/gui2/tag_browser/ui.py | 7 +- src/calibre/gui2/ui.py | 16 ++ 18 files changed, 519 insertions(+), 173 deletions(-) diff --git a/manual/url_scheme.rst b/manual/url_scheme.rst index ea5478500a..269941e198 100644 --- a/manual/url_scheme.rst +++ b/manual/url_scheme.rst @@ -116,6 +116,16 @@ If you perform a search in calibre and want to generate a link for it you can do so by right clicking the search bar and choosing :guilabel:`Copy search as URL`. +Open a book details window on a book in some library +------------------------------------------------------ + +The URL syntax is:: + + calibre://book-details/Library_Name/book_id + +This opens a book details window on the specified book from the specified library without changing the +current library or the selected book. + .. _hex_encoding: diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index ffc13cfb30..f9b3c06208 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1121,6 +1121,7 @@ class DB: CREATE TABLE %s( id INTEGER PRIMARY KEY AUTOINCREMENT, value %s NOT NULL %s, + link TEXT NOT NULL DEFAULT "", UNIQUE(value)); '''%(table, dt, collate), diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 2ef2f5d449..ed29b05c31 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -153,6 +153,7 @@ class Cache: self.format_metadata_cache = defaultdict(dict) self.formatter_template_cache = {} self.dirtied_cache = {} + self.link_maps_cache = {} self.vls_for_books_cache = None self.vls_for_books_lib_in_process = None self.vls_cache_lock = Lock() @@ -290,6 +291,7 @@ class Cache: self.format_metadata_cache.clear() if search_cache: self._clear_search_caches(book_ids) + self.link_maps_cache = {} @write_api def reload_from_db(self, clear_caches=True): @@ -382,6 +384,8 @@ class Cache: for key in composites: mi.set(key, val=self._composite_for(key, book_id, mi)) + mi.link_maps = self.get_all_link_maps_for_book(book_id) + user_cat_vals = {} if get_user_categories: user_cats = self._pref('user_categories', {}) @@ -2322,6 +2326,93 @@ class Cache: self._mark_as_dirty(changed_books) return changed_books + @read_api + def has_link_map(self, field): + if field not in self.fields: + raise ValueError(f'Lookup name {field} is not a valid name') + table = self.fields[field].table + return hasattr(table, 'link_map') + + @read_api + def get_link_map(self, for_field): + ''' + Return a dict of links for the supplied field. + + field: the lookup name of the field for which the link map is desired + + returns {field_value:link_value, ...} for non-empty links + ''' + if for_field not in self.fields: + raise ValueError(f'Lookup name {for_field} is not a valid name') + table = self.fields[for_field].table + if not hasattr(table, 'link_map'): + raise ValueError(f"Lookup name {for_field} doesn't have a link map") + lm = table.link_map + vm = table.id_map + return dict({vm.get(fid, None):v for fid,v in lm.items() if v}) + + @read_api + def get_all_link_maps_for_book(self, book_id): + ''' + Returns all links for all fields referenced by book identified by book_id + + book_id: the book id in question. + + returns: + {field: {field_value, link_value}, ... + for all fields that have a non-empty link value for that book + + Example: Assume author A has link X, author B has link Y, tag S has link + F, and tag T has link G. IF book 1 has author A and + tag T, this method returns {'authors':{'A':'X'}, 'tags':{'T', 'G'}} + If book 2's author is neither A nor B and has no tags, this + method returns {} + ''' + if book_id in self.link_maps_cache: + return self.link_maps_cache[book_id] + links = {} + def add_links_for_field(f): + field_ids = frozenset(self.field_ids_for(f, book_id)) + table = self.fields[f].table + lm = table.link_map + vm = table.id_map + d = dict({vm.get(fid, None):v for fid,v in lm.items() if v and fid in field_ids}) + if d: + links[f] = d + for field in ('authors', 'publisher', 'series', 'tags'): + add_links_for_field(field) + for field in self.field_metadata.custom_field_keys(include_composites=False): + if self.has_link_map(field): + add_links_for_field(field) + self.link_maps_cache[book_id] = links + return links + + @write_api + def set_link_map(self, field, value_to_link_map): + ''' + Sets links for item values in field + + field: the lookup name + value_to_link_map: dict(field_value:link, ...). Note that these are + values, not field ids. + + returns books changed by setting the link + ''' + if field not in self.fields: + raise ValueError(f'Lookup name {field} is not a valid name') + table = self.fields[field].table + if not hasattr(table, 'link_map'): + raise ValueError(f"Lookup name {field} doesn't have a link map") + fids = {k: self.get_item_id(field, k) for k in value_to_link_map.keys()} + id_to_link_map = {fid:value_to_link_map[k] for k, fid in fids.items() if fid is not None} + result_map = table.set_links(id_to_link_map, self.backend) + changed_books = set() + for id_ in result_map: + changed_books |= self._books_for_field(field, id_) + if changed_books: + self._mark_as_dirty(changed_books) + return changed_books + @read_api def lookup_by_uuid(self, uuid): return self.fields['uuid'].table.lookup_by_uuid(uuid) diff --git a/src/calibre/db/fields.py b/src/calibre/db/fields.py index 49da62475d..a6a443efd9 100644 --- a/src/calibre/db/fields.py +++ b/src/calibre/db/fields.py @@ -642,7 +642,7 @@ class AuthorsField(ManyToManyField): return { 'name': self.table.id_map[author_id], 'sort': self.table.asort_map[author_id], - 'link': self.table.alink_map[author_id], + 'link': self.table.link_map[author_id], } def category_sort_value(self, item_id, book_ids, lang_map): diff --git a/src/calibre/db/lazy.py b/src/calibre/db/lazy.py index 617c590ecf..2ee0ded5c1 100644 --- a/src/calibre/db/lazy.py +++ b/src/calibre/db/lazy.py @@ -330,6 +330,9 @@ class ProxyMetadata(Metadata): sa(self, '_user_metadata', db.field_metadata) def __getattribute__(self, field): + if field == 'link_maps': + db = ga(self, '_db')() + return db.get_all_link_maps_for_book(ga(self, '_book_id')) getter = getters.get(field, None) if getter is not None: return getter(ga(self, '_db'), ga(self, '_book_id'), ga(self, '_cache')) diff --git a/src/calibre/db/schema_upgrades.py b/src/calibre/db/schema_upgrades.py index e5b4bf4419..f7e888f02d 100644 --- a/src/calibre/db/schema_upgrades.py +++ b/src/calibre/db/schema_upgrades.py @@ -793,3 +793,28 @@ CREATE TRIGGER fkc_annot_update def upgrade_version_24(self): self.db.reindex_annotations() + + def upgrade_version_25(self): + for record in self.db.execute( + 'SELECT label,name,datatype,editable,display,normalized,id,is_multiple FROM custom_columns'): + data = { + 'label':record[0], + 'name':record[1], + 'datatype':record[2], + 'editable':bool(record[3]), + 'display':record[4], + 'normalized':bool(record[5]), + 'num':record[6], + 'is_multiple':bool(record[7]), + } + if data['normalized']: + tn = 'custom_column_{}'.format(data['num']) + self.db.execute(f'ALTER TABLE {tn} ADD COLUMN link TEXT NOT NULL DEFAULT "";') + self.db.execute('ALTER TABLE publishers ADD COLUMN link TEXT NOT NULL DEFAULT "";') + self.db.execute('ALTER TABLE series ADD COLUMN link TEXT NOT NULL DEFAULT "";') + self.db.execute('ALTER TABLE tags ADD COLUMN link TEXT NOT NULL DEFAULT "";') + # These aren't necessary in that there is no UI to set links, but having them + # makes the code uniform + self.db.execute('ALTER TABLE languages ADD COLUMN link TEXT NOT NULL DEFAULT "";') + self.db.execute('ALTER TABLE ratings ADD COLUMN link TEXT NOT NULL DEFAULT "";') + diff --git a/src/calibre/db/tables.py b/src/calibre/db/tables.py index 4a2a4da706..18658a3401 100644 --- a/src/calibre/db/tables.py +++ b/src/calibre/db/tables.py @@ -199,19 +199,22 @@ class ManyToOneTable(Table): def read(self, db): self.id_map = {} + self.link_map = {} self.col_book_map = defaultdict(set) self.book_col_map = {} self.read_id_maps(db) self.read_maps(db) def read_id_maps(self, db): - query = db.execute('SELECT id, {} FROM {}'.format( + query = db.execute('SELECT id, {}, link FROM {}'.format( self.metadata['column'], self.metadata['table'])) if self.unserialize is None: - self.id_map = dict(query) + us = lambda x: x else: us = self.unserialize - self.id_map = {book_id:us(val) for book_id, val in query} + for id_, val, link in query: + self.id_map[id_] = us(val) + self.link_map[id_] = link def read_maps(self, db): cbm = self.col_book_map @@ -343,6 +346,14 @@ class ManyToOneTable(Table): self.link_table, lcol, table), (existing_item, item_id, item_id)) return affected_books, new_id + def set_links(self, link_map, db): + link_map = {id_:(l or '').strip() for id_, l in iteritems(link_map)} + link_map = {id_:l for id_, l in iteritems(link_map) if l != self.link_map.get(id_)} + self.link_map.update(link_map) + db.executemany(f'UPDATE {self.metadata["table"]} SET link=? WHERE id=?', + [(v, k) for k, v in iteritems(link_map)]) + return link_map + class RatingTable(ManyToOneTable): @@ -526,7 +537,7 @@ class ManyToManyTable(ManyToOneTable): class AuthorsTable(ManyToManyTable): def read_id_maps(self, db): - self.alink_map = lm = {} + self.link_map = lm = {} self.asort_map = sm = {} self.id_map = im = {} us = self.unserialize @@ -547,8 +558,8 @@ class AuthorsTable(ManyToManyTable): def set_links(self, link_map, db): link_map = {aid:(l or '').strip() for aid, l in iteritems(link_map)} - link_map = {aid:l for aid, l in iteritems(link_map) if l != self.alink_map.get(aid, None)} - self.alink_map.update(link_map) + link_map = {aid:l for aid, l in iteritems(link_map) if l != self.link_map.get(aid, None)} + self.link_map.update(link_map) db.executemany('UPDATE authors SET link=? WHERE id=?', [(v, k) for k, v in iteritems(link_map)]) return link_map @@ -556,14 +567,14 @@ class AuthorsTable(ManyToManyTable): def remove_books(self, book_ids, db): clean = ManyToManyTable.remove_books(self, book_ids, db) for item_id in clean: - self.alink_map.pop(item_id, None) + self.link_map.pop(item_id, None) self.asort_map.pop(item_id, None) return clean def rename_item(self, item_id, new_name, db): ret = ManyToManyTable.rename_item(self, item_id, new_name, db) if item_id not in self.id_map: - self.alink_map.pop(item_id, None) + self.link_map.pop(item_id, None) self.asort_map.pop(item_id, None) else: # Was a simple rename, update the author sort value diff --git a/src/calibre/db/tests/add_remove.py b/src/calibre/db/tests/add_remove.py index 34e627d5c3..2485836d35 100644 --- a/src/calibre/db/tests/add_remove.py +++ b/src/calibre/db/tests/add_remove.py @@ -225,7 +225,7 @@ class AddRemoveTest(BaseTest): self.assertNotIn(3, c.all_book_ids()) self.assertNotIn('Unknown', set(itervalues(table.id_map))) self.assertNotIn(item_id, table.asort_map) - self.assertNotIn(item_id, table.alink_map) + self.assertNotIn(item_id, table.link_map) ae(len(table.id_map), olen-1) # Check that files are removed diff --git a/src/calibre/db/tests/writing.py b/src/calibre/db/tests/writing.py index 5dbfe5c2ef..fc01e295ac 100644 --- a/src/calibre/db/tests/writing.py +++ b/src/calibre/db/tests/writing.py @@ -919,4 +919,28 @@ class WritingTest(BaseTest): ae(cache.field_for('series_index', 1), 2.0) ae(cache.field_for('series_index', 2), 3.5) + def test_link_maps(self): + cache = self.init_cache() + + cache.set_field('tags', {1:'foo'}) + self.assertEqual(('foo',), cache.field_for('tags', 1), 'Setting tag foo failed') + cache.set_field('tags', {1:'foo, bar'}) + self.assertEqual(('foo', 'bar'), cache.field_for('tags', 1), 'Adding second tag failed') + + links = cache.get_link_map('tags') + self.assertDictEqual(links, {}, 'Initial tags link dict is not empty') + links['foo'] = 'url' + cache.set_link_map('tags', links) + links2 = cache.get_link_map('tags') + self.assertDictEqual(links2, links, 'tags link dict mismatch') + + cache.set_field('publisher', {1:'random'}) + cache.set_link_map('publisher', {'random': 'url2'}) + + links = cache.get_all_link_maps_for_book(1) + self.assert_('foo' in links['tags'], 'foo not there') + self.assert_('bar' not in links['tags'], 'bar is there') + self.assert_('random' in links['publisher'], 'random is not there') + self.assertSetEqual({'tags', 'publisher'}, set(links.keys()), 'Link map has extra stuff') + # }}} diff --git a/src/calibre/db/write.py b/src/calibre/db/write.py index 8b240316ff..48ebd1fd03 100644 --- a/src/calibre/db/write.py +++ b/src/calibre/db/write.py @@ -295,7 +295,8 @@ def get_db_id(val, db, m, table, kmap, rid_map, allow_case_change, table.col_book_map[item_id] = set() if is_authors: table.asort_map[item_id] = aus - table.alink_map[item_id] = '' + if hasattr(table, 'link_map'): + table.link_map[item_id] = '' elif allow_case_change and val != table.id_map[item_id]: case_changes[item_id] = val val_map[val] = item_id @@ -491,7 +492,8 @@ def many_many(book_id_val_map, db, field, allow_case_change, *args): table.col_book_map.pop(item_id, None) if is_authors: table.asort_map.pop(item_id, None) - table.alink_map.pop(item_id, None) + if hasattr(table, 'link_map'): + table.link_map.pop(item_id, None) return dirtied diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index e8fd0e5ee8..432be82ad4 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -91,6 +91,24 @@ def mi_to_html( rating_font='Liberation Serif', rtl=False, comments_heading_pos='hide', for_qt=False, vertical_fields=() ): + + show_links = not hasattr(mi, '_bd_dbwref') + + def get_link_map(column): + if not show_links: + return {} + try: + return mi.link_maps[column] + except (KeyError, ValueError): + return {column:{}} + + def add_other_link(field, field_value): + link = get_link_map(field).get(field_value, None) + if link: + return (' %s'%(_('Click to open {}').format(link), link, _('(item link)'))) + else: + return '' + if field_list is None: field_list = get_field_list(mi) ans = [] @@ -170,9 +188,12 @@ def mi_to_html( else: all_vals = [v.strip() for v in val.split(metadata['is_multiple']['cache_to_list']) if v.strip()] - links = ['{}'.format( - search_action(field, x), _('Click to see books with {0}: {1}').format( + if show_links: + links = ['{}'.format( + search_action(field, x), _('Click to see books with {0}: {1}').format( metadata['name'], a(x)), p(x)) for x in all_vals] + else: + links = all_vals val = value_list(metadata['is_multiple']['list_to_ui'], links) ans.append((field, row % (name, val))) elif field == 'path': @@ -188,10 +209,15 @@ def mi_to_html( durl = ':::'.join((durl.split(':::'))[2:]) extra = '
%s'%( prepare_string_for_xml(durl)) - link = '{}{}'.format(action(scheme, loc=loc), + if show_links: + link = '{}{}'.format(action(scheme, loc=loc), prepare_string_for_xml(path, True), pathstr, extra) + else: + link = prepare_string_for_xml(path, True) ans.append((field, row % (name, link))) elif field == 'formats': + # Don't need show_links here because formats are removed from mi on + # cross library displays. if isdevice: continue path = mi.path or '' @@ -209,12 +235,15 @@ def mi_to_html( ans.append((field, row % (name, value_list(', ', fmts)))) elif field == 'identifiers': urls = urls_from_identifiers(mi.identifiers, sort_results=True) - links = [ - '{}'.format( - action('identifier', url=url, name=namel, id_type=id_typ, value=id_val, field='identifiers', book_id=book_id), - a(id_typ), a(id_val), p(namel)) - for namel, id_typ, id_val, url in urls] - links = value_list(', ', links) + if show_links: + links = [ + '{}'.format( + action('identifier', url=url, name=namel, id_type=id_typ, value=id_val, field='identifiers', book_id=book_id), + a(id_typ), a(id_val), p(namel)) + for namel, id_typ, id_val, url in urls] + links = value_list(', ', links) + else: + links = ', '.join(mi.identifiers) if links: ans.append((field, row % (_('Ids')+':', links))) elif field == 'authors': @@ -222,39 +251,46 @@ def mi_to_html( formatter = EvalFormatter() for aut in mi.authors: link = '' - if mi.author_link_map.get(aut): - link = lt = mi.author_link_map[aut] - elif default_author_link: - if default_author_link.startswith('search-'): - which_src = default_author_link.partition('-')[2] - link, lt = author_search_href(which_src, title=mi.title, author=aut) + if show_links: + if default_author_link: + if default_author_link.startswith('search-'): + which_src = default_author_link.partition('-')[2] + link, lt = author_search_href(which_src, title=mi.title, author=aut) + else: + vals = {'author': qquote(aut), 'title': qquote(mi.title)} + try: + vals['author_sort'] = qquote(mi.author_sort_map[aut]) + except KeyError: + vals['author_sort'] = qquote(aut) + link = lt = formatter.safe_format(default_author_link, vals, '', vals) else: - vals = {'author': qquote(aut), 'title': qquote(mi.title)} - try: - vals['author_sort'] = qquote(mi.author_sort_map[aut]) - except KeyError: - vals['author_sort'] = qquote(aut) - link = lt = formatter.safe_format(default_author_link, vals, '', vals) - aut = p(aut) + aut = p(aut) if link: - authors.append('%s'%(a(lt), action('author', url=link, name=aut, title=lt), aut)) + val = '%s'%(a(lt), action('author', url=link, name=aut, title=lt), aut) else: - authors.append(aut) + val = aut + val += add_other_link('authors', aut) + authors.append(val) ans.append((field, row % (name, value_list(' & ', authors)))) elif field == 'languages': if not mi.languages: continue names = filter(None, map(calibre_langcode_to_name, mi.languages)) - names = ['{}'.format(search_action_with_data('languages', n, book_id), _( - 'Search calibre for books with the language: {}').format(n), n) for n in names] + if show_links: + names = ['{}'.format(search_action_with_data('languages', n, book_id), _( + 'Search calibre for books with the language: {}').format(n), n) for n in names] ans.append((field, row % (name, value_list(', ', names)))) elif field == 'publisher': if not mi.publisher: continue - val = '{}'.format( - search_action_with_data('publisher', mi.publisher, book_id), - _('Click to see books with {0}: {1}').format(metadata['name'], a(mi.publisher)), - p(mi.publisher)) + if show_links: + val = '{}'.format( + search_action_with_data('publisher', mi.publisher, book_id), + _('Click to see books with {0}: {1}').format(metadata['name'], a(mi.publisher)), + p(mi.publisher)) + val += add_other_link('publisher', mi.publisher) + else: + val = p(mi.publisher) ans.append((field, row % (name, val))) elif field == 'title': # otherwise title gets metadata['datatype'] == 'text' @@ -268,79 +304,85 @@ def mi_to_html( if val is None: continue val = p(val) - if metadata['datatype'] == 'series': - sidx = mi.get(field+'_index') - if sidx is None: - sidx = 1.0 - try: - st = metadata['search_terms'][0] - except Exception: - st = field - series = getattr(mi, field) - val = _( - '%(sidx)s of ' - '%(series)s') % dict( - sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), cls="series_name", - series=p(series), href=search_action_with_data(st, series, book_id, field), - tt=p(_('Click to see books in this series'))) - elif metadata['datatype'] == 'datetime': - aval = getattr(mi, field) - if is_date_undefined(aval): - continue - aval = format_date(aval, 'yyyy-MM-dd') - key = field if field != 'timestamp' else 'date' - if val == aval: - val = '{}'.format( - search_action_with_data(key, str(aval), book_id, None, original_value=val), a( - _('Click to see books with {0}: {1}').format(metadata['name'] or field, val)), val) - else: - val = '{}'.format( - search_action_with_data(key, str(aval), book_id, None, original_value=val), a( - _('Click to see books with {0}: {1} (derived from {2})').format( - metadata['name'] or field, aval, val)), val) - elif metadata['datatype'] == 'text' and metadata['is_multiple']: - try: - st = metadata['search_terms'][0] - except Exception: - st = field - all_vals = mi.get(field) - if not metadata.get('display', {}).get('is_names', False): - all_vals = sorted(all_vals, key=sort_key) - links = ['{}'.format( - search_action_with_data(st, x, book_id, field), _('Click to see books with {0}: {1}').format( - metadata['name'] or field, a(x)), p(x)) - for x in all_vals] - val = value_list(metadata['is_multiple']['list_to_ui'], links) - elif metadata['datatype'] == 'text' or metadata['datatype'] == 'enumeration': - # text/is_multiple handled above so no need to add the test to the if - try: - st = metadata['search_terms'][0] - except Exception: - st = field - val = '{}'.format( - search_action_with_data(st, unescaped_val, book_id, field), a( - _('Click to see books with {0}: {1}').format(metadata['name'] or field, val)), val) - elif metadata['datatype'] == 'bool': - val = '{}'.format( - search_action_with_data(field, val, book_id, None), a( - _('Click to see books with {0}: {1}').format(metadata['name'] or field, val)), val) - else: - try: - aval = str(getattr(mi, field)) - if not aval: + if show_links: + if metadata['datatype'] == 'series': + sidx = mi.get(field+'_index') + if sidx is None: + sidx = 1.0 + try: + st = metadata['search_terms'][0] + except Exception: + st = field + series = getattr(mi, field) + val = _( + '%(sidx)s of ' + '%(series)s') % dict( + sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), cls="series_name", + series=p(series), href=search_action_with_data(st, series, book_id, field), + tt=p(_('Click to see books in this series'))) + val += add_other_link('series', mi.series) + elif metadata['datatype'] == 'datetime': + aval = getattr(mi, field) + if is_date_undefined(aval): continue + aval = format_date(aval, 'yyyy-MM-dd') + key = field if field != 'timestamp' else 'date' if val == aval: val = '{}'.format( - search_action_with_data(field, str(aval), book_id, None, original_value=val), a( + search_action_with_data(key, str(aval), book_id, None, original_value=val), a( _('Click to see books with {0}: {1}').format(metadata['name'] or field, val)), val) else: val = '{}'.format( - search_action_with_data(field, str(aval), book_id, None, original_value=val), a( + search_action_with_data(key, str(aval), book_id, None, original_value=val), a( _('Click to see books with {0}: {1} (derived from {2})').format( metadata['name'] or field, aval, val)), val) - except Exception: - import traceback - traceback.print_exc() + elif metadata['datatype'] == 'text' and metadata['is_multiple']: + try: + st = metadata['search_terms'][0] + except Exception: + st = field + all_vals = mi.get(field) + if not metadata.get('display', {}).get('is_names', False): + all_vals = sorted(all_vals, key=sort_key) + links = [] + for x in all_vals: + v = '{}'.format( + search_action_with_data(st, x, book_id, field), _('Click to see books with {0}: {1}').format( + metadata['name'] or field, a(x)), p(x)) + v += add_other_link(field, x) + links.append(v) + val = value_list(metadata['is_multiple']['list_to_ui'], links) + elif metadata['datatype'] == 'text' or metadata['datatype'] == 'enumeration': + # text/is_multiple handled above so no need to add the test to the if + try: + st = metadata['search_terms'][0] + except Exception: + st = field + v = '{}'.format( + search_action_with_data(st, unescaped_val, book_id, field), a( + _('Click to see books with {0}: {1}').format(metadata['name'] or field, val)), val) + val = v + add_other_link(field, val) + elif metadata['datatype'] == 'bool': + val = '{}'.format( + search_action_with_data(field, val, book_id, None), a( + _('Click to see books with {0}: {1}').format(metadata['name'] or field, val)), val) + else: + try: + aval = str(getattr(mi, field)) + if not aval: + continue + if val == aval: + val = '{}'.format( + search_action_with_data(field, str(aval), book_id, None, original_value=val), a( + _('Click to see books with {0}: {1}').format(metadata['name'] or field, val)), val) + else: + val = '{}'.format( + search_action_with_data(field, str(aval), book_id, None, original_value=val), a( + _('Click to see books with {0}: {1} (derived from {2})').format( + metadata['name'] or field, aval, val)), val) + except Exception: + import traceback + traceback.print_exc() ans.append((field, row % (name, val))) diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index 50dc3384a9..b56eb178f4 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -595,6 +595,8 @@ class OPF: # {{{ renderer=dump_dict) author_link_map = MetadataField('author_link_map', is_dc=False, formatter=json.loads, renderer=dump_dict) + link_map = MetadataField('link_maps', is_dc=False, + formatter=json.loads, renderer=dump_dict) def __init__(self, stream, basedir=os.getcwd(), unquote_urls=True, populate_spine=True, try_to_guess_cover=False, preparsed_opf=None, read_toc=True): @@ -1310,7 +1312,7 @@ class OPF: # {{{ for attr in ('title', 'authors', 'author_sort', 'title_sort', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'tags', 'category', 'comments', 'book_producer', - 'pubdate', 'user_categories', 'author_link_map'): + 'pubdate', 'user_categories', 'author_link_map', 'link_map'): val = getattr(mi, attr, None) if attr == 'rating' and val: val = float(val) @@ -1673,6 +1675,8 @@ def metadata_to_opf(mi, as_string=True, default_lang=None): return factory('meta', name='calibre:' + n, content=c) if getattr(mi, 'author_link_map', None) is not None: meta('author_link_map', dump_dict(mi.author_link_map)) + if getattr(mi, 'link_maps', None) is not None: + meta('link_maps', dump_dict(mi.link_maps)) if mi.series: meta('series', mi.series) if mi.series_index is not None: diff --git a/src/calibre/gui2/actions/show_book_details.py b/src/calibre/gui2/actions/show_book_details.py index e74847885e..6a79e847a1 100644 --- a/src/calibre/gui2/actions/show_book_details.py +++ b/src/calibre/gui2/actions/show_book_details.py @@ -23,25 +23,61 @@ class ShowBookDetailsAction(InterfaceAction): def genesis(self): self.qaction.triggered.connect(self.show_book_info) self.memory = [] + self.dialogs = [None, ] - def show_book_info(self, *args): + def show_book_info(self, *args, **kwargs): if self.gui.current_view() is not self.gui.library_view: error_dialog(self.gui, _('No detailed info available'), _('No detailed information is available for books ' 'on the device.')).exec() return + library_path = kwargs.get('library_path', None) + book_id = kwargs.get('book_id', None) + library_id = kwargs.get('library_id', None) + query = kwargs.get('query', None) index = self.gui.library_view.currentIndex() - if index.isValid(): - d = BookInfo(self.gui, self.gui.library_view, index, - self.gui.book_details.handle_click) + if library_path or index.isValid(): + # Window #0 is slaved to changes in the book list. As such + # it must not be used for details from other libraries. + for dn,v in enumerate(self.dialogs): + if dn == 0 and library_path: + continue + if v is None: + break + else: + self.dialogs.append(None) + dn += 1 + + try: + d = BookInfo(self.gui, self.gui.library_view, index, + self.gui.book_details.handle_click, dialog_number=dn, + library_id=library_id, library_path=library_path, book_id=book_id, query=query) + except ValueError as e: + error_dialog(self.gui, _('Book not found'), str(e)).exec() + return + d.open_cover_with.connect(self.gui.bd_open_cover_with, type=Qt.ConnectionType.QueuedConnection) + self.dialogs[dn] = d self.memory.append(d) d.closed.connect(self.closed, type=Qt.ConnectionType.QueuedConnection) d.show() + def shutting_down(self): + for d in self.dialogs: + if d: + d.done(0) + + def library_about_to_change(self, *args): + for i,d in enumerate(self.dialogs): + if i == 0: + continue + if d: + d.done(0) + def closed(self, d): try: d.closed.disconnect(self.closed) + self.dialogs[d.dialog_number] = None self.memory.remove(d) except ValueError: pass diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index fc38d86d41..f39fbf5b12 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -42,12 +42,15 @@ InternetSearch = namedtuple('InternetSearch', 'author where') def set_html(mi, html, text_browser): - from calibre.gui2.ui import get_gui - gui = get_gui() + if hasattr(mi, '_bd_dbwref') and mi._bd_dbwref is not None: + db = mi._bd_dbwref + else: + from calibre.gui2.ui import get_gui + db = get_gui().current_db book_id = getattr(mi, 'id', None) search_paths = [] - if gui and book_id is not None: - path = gui.current_db.abspath(book_id, index_is_id=True) + if db and book_id is not None: + path = db.abspath(book_id, index_is_id=True) if path: search_paths = [path] text_browser.setSearchPaths(search_paths) @@ -203,10 +206,16 @@ def comments_pat(): return re.compile(r'', re.DOTALL) -def render_html(mi, vertical, widget, all_fields=False, render_data_func=None, pref_name='book_display_fields'): # {{{ - from calibre.gui2.ui import get_gui +def render_html(mi, vertical, widget, all_fields=False, render_data_func=None, + pref_name='book_display_fields', + pref_value=None): # {{{ + if hasattr(mi, '_bd_dbwref') and mi._bd_dbwref is not None: + db = mi._bd_dbwref + else: + from calibre.gui2.ui import get_gui + db = get_gui().current_db func = render_data_func or partial(render_data, - vertical_fields=get_gui().current_db.prefs.get('book_details_vertical_categories') or ()) + vertical_fields=db.prefs.get('book_details_vertical_categories') or ()) try: table, comment_fields = func(mi, all_fields=all_fields, use_roman_numbers=config['use_roman_numerals_for_series_number'], pref_name=pref_name) @@ -246,9 +255,12 @@ def render_html(mi, vertical, widget, all_fields=False, render_data_func=None, p return ans -def get_field_list(fm, use_defaults=False, pref_name='book_display_fields'): - from calibre.gui2.ui import get_gui - db = get_gui().current_db +def get_field_list(fm, use_defaults=False, pref_name='book_display_fields', mi=None): + if mi is not None and hasattr(mi, '_bd_dbwref') and mi._bd_dbwref is not None: + db = mi._bd_dbwref + else: + from calibre.gui2.ui import get_gui + db = get_gui().current_db if use_defaults: src = db.prefs.defaults else: @@ -267,7 +279,8 @@ def get_field_list(fm, use_defaults=False, pref_name='book_display_fields'): def render_data(mi, use_roman_numbers=True, all_fields=False, pref_name='book_display_fields', vertical_fields=()): - field_list = get_field_list(getattr(mi, 'field_metadata', field_metadata), pref_name=pref_name) + field_list = get_field_list(getattr(mi, 'field_metadata', field_metadata), + pref_name=pref_name, mi=mi) field_list = [(x, all_fields or display) for x, display in field_list] return mi_to_html( mi, field_list=field_list, use_roman_numbers=use_roman_numbers, rtl=is_rtl(), diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index 19ed838c92..e205a21119 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -3,6 +3,8 @@ import textwrap +import weakref + from qt.core import ( QAction, QApplication, QBrush, QCheckBox, QDialog, QDialogButtonBox, QGridLayout, QHBoxLayout, QIcon, QKeySequence, QLabel, QListView, QModelIndex, QPalette, QPixmap, @@ -114,17 +116,19 @@ class Configure(Dialog): class Details(HTMLDisplay): - def __init__(self, book_info, parent=None): + def __init__(self, book_info, parent=None, allow_context_menu=True): HTMLDisplay.__init__(self, parent) self.book_info = book_info self.edit_metadata = getattr(parent, 'edit_metadata', None) self.setDefaultStyleSheet(css()) + self.allow_context_menu = allow_context_menu def sizeHint(self): return QSize(350, 350) def contextMenuEvent(self, ev): - details_context_menu_event(self, ev, self.book_info, edit_metadata=self.edit_metadata) + if self.allow_context_menu: + details_context_menu_event(self, ev, self.book_info, edit_metadata=self.edit_metadata) class BookInfo(QDialog): @@ -132,8 +136,11 @@ class BookInfo(QDialog): closed = pyqtSignal(object) open_cover_with = pyqtSignal(object, object) - def __init__(self, parent, view, row, link_delegate): - QDialog.__init__(self, parent) + def __init__(self, parent, view, row, link_delegate, dialog_number=None, + library_id=None, library_path=None, book_id=None, query=None): + QDialog.__init__(self, None, flags=Qt.WindowType.Window) + self.dialog_number = dialog_number + self.library_id = library_id self.marked = None self.gui = parent self.splitter = QSplitter(self) @@ -150,7 +157,8 @@ class BookInfo(QDialog): self.cover.sizeHint = self.details_size_hint self.splitter.addWidget(self.cover) - self.details = Details(parent.book_details.book_info, self) + self.details = Details(parent.book_details.book_info, self, + allow_context_menu=library_path is None) self.details.anchor_clicked.connect(self.on_link_clicked) self.link_delegate = link_delegate self.details.setAttribute(Qt.WidgetAttribute.WA_OpaquePaintEvent, False) @@ -172,47 +180,76 @@ class BookInfo(QDialog): hl.setContentsMargins(0, 0, 0, 0) l2.addLayout(hl, l2.rowCount(), 0, 1, -1) hl.addWidget(self.fit_cover), hl.addStretch() - self.clabel = QLabel('

{}'.format( - _('Configure this view'), _('Configure'))) - self.clabel.linkActivated.connect(self.configure) - hl.addWidget(self.clabel) - self.previous_button = QPushButton(QIcon.ic('previous.png'), _('&Previous'), self) - self.previous_button.clicked.connect(self.previous) - l2.addWidget(self.previous_button, l2.rowCount(), 0) - self.next_button = QPushButton(QIcon.ic('next.png'), _('&Next'), self) - self.next_button.clicked.connect(self.next) - l2.addWidget(self.next_button, l2.rowCount() - 1, 1) + if self.dialog_number == 0: + self.previous_button = QPushButton(QIcon.ic('previous.png'), _('&Previous'), self) + self.previous_button.clicked.connect(self.previous) + l2.addWidget(self.previous_button, l2.rowCount(), 0) + self.next_button = QPushButton(QIcon.ic('next.png'), _('&Next'), self) + self.next_button.clicked.connect(self.next) + l2.addWidget(self.next_button, l2.rowCount() - 1, 1) + self.ns = QShortcut(QKeySequence('Alt+Right'), self) + self.ns.activated.connect(self.next) + self.ps = QShortcut(QKeySequence('Alt+Left'), self) + self.ps.activated.connect(self.previous) + self.next_button.setToolTip(_('Next [%s]')% + str(self.ns.key().toString(QKeySequence.SequenceFormat.NativeText))) + self.previous_button.setToolTip(_('Previous [%s]')% + str(self.ps.key().toString(QKeySequence.SequenceFormat.NativeText))) - self.view = view self.path_to_book = None self.current_row = None - self.refresh(row) - self.view.model().new_bookdisplay_data.connect(self.slave) - self.fit_cover.stateChanged.connect(self.toggle_cover_fit) - self.ns = QShortcut(QKeySequence('Alt+Right'), self) - self.ns.activated.connect(self.next) - self.ps = QShortcut(QKeySequence('Alt+Left'), self) - self.ps.activated.connect(self.previous) - self.next_button.setToolTip(_('Next [%s]')% - str(self.ns.key().toString(QKeySequence.SequenceFormat.NativeText))) - self.previous_button.setToolTip(_('Previous [%s]')% - str(self.ps.key().toString(QKeySequence.SequenceFormat.NativeText))) + self.slave_connected = False + if library_path is not None: + self.view = None + from calibre.db.legacy import LibraryDatabase + db = LibraryDatabase(library_path, read_only=True, is_second_db=True) + if book_id is None: + ids = db.new_api.search(query) + if len(ids) == 0: + raise ValueError(_('Query "{}" found no books').format(query)) + book_id = sorted([i for i in ids])[0] + if not db.new_api.has_id(book_id): + raise ValueError(_("Book {} doesn't exist").format(book_id)) + mi = db.new_api.get_metadata(book_id, get_cover=False) + mi.cover_data = [None, db.new_api.cover(book_id, as_image=True)] + mi.path = None + mi.format_files = dict() + mi.formats = list() + mi.marked = '' + mi._bd_dbwref = weakref.proxy(db) + self.refresh(row, mi) + else: + self.view = view + if dialog_number == 0: + self.slave_connected = True + self.view.model().new_bookdisplay_data.connect(self.slave) + self.refresh(row) - self.restore_geometry(gprefs, 'book_info_dialog_geometry') + ema = get_gui().iactions['Edit Metadata'].menuless_qaction + a = self.ema = QAction('edit metadata', self) + a.setShortcut(ema.shortcut()) + self.addAction(a) + a.triggered.connect(self.edit_metadata) + vb = get_gui().iactions['View'].menuless_qaction + a = self.vba = QAction('view book', self) + a.setShortcut(vb.shortcut()) + a.triggered.connect(self.view_book) + self.addAction(a) + self.clabel = QLabel('
{}'.format( + _('Configure this view'), _('Configure'))) + self.clabel.linkActivated.connect(self.configure) + hl.addWidget(self.clabel) + self.fit_cover.stateChanged.connect(self.toggle_cover_fit) + self.restore_geometry(gprefs, self.geometry_string('book_info_dialog_geometry')) try: - self.splitter.restoreState(gprefs.get('book_info_dialog_splitter_state')) + self.splitter.restoreState(gprefs.get(self.geometry_string('book_info_dialog_splitter_state'))) except Exception: pass - ema = get_gui().iactions['Edit Metadata'].menuless_qaction - a = self.ema = QAction('edit metadata', self) - a.setShortcut(ema.shortcut()) - self.addAction(a) - a.triggered.connect(self.edit_metadata) - vb = get_gui().iactions['View'].menuless_qaction - a = self.vba = QAction('view book', self) - a.setShortcut(vb.shortcut()) - a.triggered.connect(self.view_book) - self.addAction(a) + + def geometry_string(self, txt): + if self.dialog_number is None or self.dialog_number == 0: + return txt + return txt + '_' + str(self.dialog_number) def sizeHint(self): try: @@ -248,10 +285,11 @@ class BookInfo(QDialog): self.link_delegate(link) def done(self, r): - self.save_geometry(gprefs, 'book_info_dialog_geometry') - gprefs['book_info_dialog_splitter_state'] = bytearray(self.splitter.saveState()) + self.save_geometry(gprefs, self.geometry_string('book_info_dialog_geometry')) + gprefs[self.geometry_string('book_info_dialog_splitter_state')] = bytearray(self.splitter.saveState()) ret = QDialog.done(self, r) - self.view.model().new_bookdisplay_data.disconnect(self.slave) + if self.slave_connected: + self.view.model().new_bookdisplay_data.disconnect(self.slave) self.view = self.link_delegate = self.gui = None self.closed.emit(self) return ret @@ -342,11 +380,15 @@ class BookInfo(QDialog): # Indicates books was deleted from library, or row numbers have # changed return - - self.previous_button.setEnabled(False if row == 0 else True) - self.next_button.setEnabled(False if row == self.view.model().rowCount(QModelIndex())-1 else True) + if self.dialog_number == 0: + self.previous_button.setEnabled(False if row == 0 else True) + self.next_button.setEnabled(False if row == self.view.model().rowCount(QModelIndex())-1 else True) + self.setWindowTitle(mi.title + ' ' + _('(the current book)')) + elif self.library_id is not None: + self.setWindowTitle(mi.title + ' ' + _('(from {})').format(self.library_id)) + else: + self.setWindowTitle(mi.title + ' ' + _('(will not change)')) self.current_row = row - self.setWindowTitle(mi.title) self.cover_pixmap = QPixmap.fromImage(mi.cover_data[1]) self.path_to_book = getattr(mi, 'path', None) try: diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index a452b57391..355a7cf344 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -132,7 +132,9 @@ class EditColumnDelegate(QItemDelegate): else: editor = EnLineEdit(parent) return editor - return None + editor = EnLineEdit(parent) + editor.setClearButtonEnabled(True) + return editor def destroyEditor(self, editor, index): self.editing_finished.emit(index.row()) @@ -142,12 +144,13 @@ class EditColumnDelegate(QItemDelegate): class TagListEditor(QDialog, Ui_TagListEditor): def __init__(self, window, cat_name, tag_to_match, get_book_ids, sorter, - ttm_is_first_letter=False, category=None, fm=None): + ttm_is_first_letter=False, category=None, fm=None, link_map=None): QDialog.__init__(self, window) Ui_TagListEditor.__init__(self) self.setupUi(self) self.verticalLayout_2.setAlignment(Qt.AlignmentFlag.AlignCenter) self.search_box.setMinimumContentsLength(25) + self.link_map = link_map # Put the category name into the title bar t = self.windowTitle() @@ -171,6 +174,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.to_delete = set() self.all_tags = {} self.original_names = {} + self.links = {} self.ordered_tags = [] self.sorter = sorter @@ -413,13 +417,15 @@ class TagListEditor(QDialog, Ui_TagListEditor): select_item = None self.table.blockSignals(True) self.table.clear() - self.table.setColumnCount(3) + self.table.setColumnCount(4) self.name_col = QTableWidgetItem(self.category_name) self.table.setHorizontalHeaderItem(0, self.name_col) self.count_col = QTableWidgetItem(_('Count')) self.table.setHorizontalHeaderItem(1, self.count_col) self.was_col = QTableWidgetItem(_('Was')) self.table.setHorizontalHeaderItem(2, self.was_col) + self.link_col = QTableWidgetItem(_('Link')) + self.table.setHorizontalHeaderItem(3, self.link_col) self.table.setRowCount(len(tags)) for row,tag in enumerate(tags): @@ -457,6 +463,16 @@ class TagListEditor(QDialog, Ui_TagListEditor): item.setData(Qt.ItemDataRole.DisplayRole, tag) self.table.setItem(row, 2, item) + item = QTableWidgetItem() + if self.link_map is None: + item.setFlags(item.flags() & ~(Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable)) + item.setText(_('no links available')) + else: + item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsSelectable) + item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable) + item.setText(self.link_map.get(tag, '')) + self.table.setItem(row, 3, item) + if self.last_sorted_by == 'name': self.table.sortByColumn(0, Qt.SortOrder(self.name_order)) elif self.last_sorted_by == 'count': @@ -662,4 +678,9 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.table.sortByColumn(2, Qt.SortOrder(self.was_order)) def accepted(self): + self.links = {} + for r in range(0, self.table.rowCount()): + l = self.table.item(r, 3).text() + if l: + self.links[self.table.item(r, 0).text()] = l self.save_geometry() diff --git a/src/calibre/gui2/tag_browser/ui.py b/src/calibre/gui2/tag_browser/ui.py index 34afb2951d..51d49f3bd6 100644 --- a/src/calibre/gui2/tag_browser/ui.py +++ b/src/calibre/gui2/tag_browser/ui.py @@ -58,6 +58,7 @@ class TagBrowserMixin: # {{{ partial(func, *args)) fm = db.new_api.field_metadata categories = [x[0] for x in find_categories(fm) if fm.is_custom_field(x[0])] + categories = [c for c in categories if fm[c]['datatype'] != 'composite'] if categories: if len(categories) > 5: m = m.addMenu(_('Custom columns')) @@ -281,7 +282,8 @@ class TagBrowserMixin: # {{{ tag_to_match=tag, get_book_ids=partial(self.get_book_ids, db=db, category=category), sorter=key, ttm_is_first_letter=is_first_letter, - fm=db.field_metadata[category]) + fm=db.field_metadata[category], + link_map=db.new_api.get_link_map(category)) d.exec() if d.result() == QDialog.DialogCode.Accepted: to_rename = d.to_rename # dict of old id to new name @@ -300,6 +302,9 @@ class TagBrowserMixin: # {{{ db.new_api.remove_items(category, to_delete) db.new_api.rename_items(category, to_rename, change_index=False) + # Must do this at the end so renames are accounted for + db.new_api.set_link_map(category, d.links) + # Clean up the library view self.do_tag_item_renamed() self.tags_view.recount() diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 71f4385dc9..2d6dafea63 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -690,6 +690,22 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ library_path = self.library_broker.path_for_library_id(library_id) if not db_matches(self.current_db, library_id, library_path): self.library_moved(library_path) + elif action == 'book-details': + parts = tuple(filter(None, path.split('/'))) + if len(parts) != 2: + return + library_id, book_id = parts + library_id = decode_library_id(library_id) + library_path = self.library_broker.path_for_library_id(library_id) + if library_path is None: + return + try: + book_id = int(book_id) + except Exception: + prints('Ignoring invalid book id', book_id, file=sys.stderr) + return + details = self.iactions['Show Book Details'] + details.show_book_info(library_id=library_id, library_path=library_path, book_id=book_id) elif action == 'show-book': parts = tuple(filter(None, path.split('/'))) if len(parts) != 2: From d95d7f40486f6cbf4f7039fe89ceeeaa497e2cc6 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 25 Mar 2023 17:03:51 +0000 Subject: [PATCH 0373/2055] Fixes: the UI didn't properly delete links --- src/calibre/db/cache.py | 2 ++ src/calibre/gui2/dialogs/tag_list_editor.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index ed29b05c31..04793117b9 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2397,6 +2397,8 @@ class Cache: values, not field ids. returns books changed by setting the link + + NB: this method doesn't change values not in the value_to_link_map ''' if field not in self.fields: raise ValueError(f'Lookup name {field} is not a valid name') diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index 355a7cf344..d0436dc238 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -546,6 +546,8 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.table.blockSignals(False) def finish_editing(self, edited_item): + if edited_item.column != 0: + return if not edited_item.text(): error_dialog(self, _('Item is blank'), _( 'An item cannot be set to nothing. Delete it instead.'), show=True) @@ -680,7 +682,5 @@ class TagListEditor(QDialog, Ui_TagListEditor): def accepted(self): self.links = {} for r in range(0, self.table.rowCount()): - l = self.table.item(r, 3).text() - if l: - self.links[self.table.item(r, 0).text()] = l + self.links[self.table.item(r, 0).text()] = self.table.item(r, 3).text() self.save_geometry() From 6f6da271609616d618aff773c5014df2ddfb44d7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 26 Mar 2023 10:03:41 +0530 Subject: [PATCH 0374/2055] Similar books: Fix changing the search to be over a custom column not working. Fixes #2012803 ["Similar Books" does not search using column lookups names specified in settings](https://bugs.launchpad.net/calibre/+bug/2012803) --- src/calibre/gui2/actions/similar_books.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/actions/similar_books.py b/src/calibre/gui2/actions/similar_books.py index 3d74bb6068..ea1ae5e4d7 100644 --- a/src/calibre/gui2/actions/similar_books.py +++ b/src/calibre/gui2/actions/similar_books.py @@ -77,7 +77,7 @@ class SimilarBooksAction(InterfaceAction): # Get the value of the requested field. Can be a list or a simple # val. It is possible that col no longer exists, in which case fall # back to the default - if col not in mi: + if col not in mi.all_field_keys(): col = db.prefs.defaults[key] val = mi.get(col, None) if not val: From 26d88d1d65cbb048ddd6be4a327a36bb5e3ee26f Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 26 Mar 2023 11:25:01 +0100 Subject: [PATCH 0375/2055] Improved tests for link maps. Includes a fix for a problem the test found. --- src/calibre/db/cache.py | 3 +++ src/calibre/db/tests/writing.py | 25 ++++++++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 04793117b9..f2c7069da5 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2405,6 +2405,9 @@ class Cache: table = self.fields[field].table if not hasattr(table, 'link_map'): raise ValueError(f"Lookup name {field} doesn't have a link map") + # Clear the links for book cache as we don't know what will be affected + self.link_maps_cache = {} + fids = {k: self.get_item_id(field, k) for k in value_to_link_map.keys()} id_to_link_map = {fid:value_to_link_map[k] for k, fid in fids.items() if fid is not None} result_map = table.set_links(id_to_link_map, self.backend) diff --git a/src/calibre/db/tests/writing.py b/src/calibre/db/tests/writing.py index fc01e295ac..7b2d78b266 100644 --- a/src/calibre/db/tests/writing.py +++ b/src/calibre/db/tests/writing.py @@ -922,11 +922,13 @@ class WritingTest(BaseTest): def test_link_maps(self): cache = self.init_cache() + # Add two tags cache.set_field('tags', {1:'foo'}) self.assertEqual(('foo',), cache.field_for('tags', 1), 'Setting tag foo failed') cache.set_field('tags', {1:'foo, bar'}) self.assertEqual(('foo', 'bar'), cache.field_for('tags', 1), 'Adding second tag failed') + # Check adding a link links = cache.get_link_map('tags') self.assertDictEqual(links, {}, 'Initial tags link dict is not empty') links['foo'] = 'url' @@ -934,13 +936,26 @@ class WritingTest(BaseTest): links2 = cache.get_link_map('tags') self.assertDictEqual(links2, links, 'tags link dict mismatch') + # Check getting links for a book and that links are correct cache.set_field('publisher', {1:'random'}) cache.set_link_map('publisher', {'random': 'url2'}) - links = cache.get_all_link_maps_for_book(1) - self.assert_('foo' in links['tags'], 'foo not there') - self.assert_('bar' not in links['tags'], 'bar is there') - self.assert_('random' in links['publisher'], 'random is not there') - self.assertSetEqual({'tags', 'publisher'}, set(links.keys()), 'Link map has extra stuff') + self.assertSetEqual({v for v in links.keys()}, {'tags', 'publisher'}, 'Wrong link keys') + self.assertSetEqual({v for v in links['tags'].keys()}, {'foo', }, 'Should be "foo"') + self.assertSetEqual({v for v in links['publisher'].keys()}, {'random', }, 'Should be "random"') + self.assertEqual('url', links['tags']['foo'], 'link for tag foo is wrong') + self.assertEqual('url2', links['publisher']['random'], 'link for publisher random is wrong') + + # Now test deleting the links. + links = cache.get_link_map('tags') + to_del = {l:'' for l in links.keys()} + cache.set_link_map('tags', to_del) + self.assertEqual({}, cache.get_link_map('tags'), 'links on tags were not deleted') + links = cache.get_link_map('publisher') + to_del = {l:'' for l in links.keys()} + cache.set_link_map('publisher', to_del) + self.assertEqual({}, cache.get_link_map('publisher'), 'links on publisher were not deleted') + self.assertEqual({}, cache.get_all_link_maps_for_book(1), 'Not all links for book were deleted') + # }}} From a4e49d85c1de31908359d800fc9ced18017055ae Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 27 Mar 2023 12:53:26 +0100 Subject: [PATCH 0376/2055] Changes as discussed. Tests improved to check that the cache is cleared and that ProxyMetadata returns the link map --- src/calibre/db/cache.py | 16 +++++++++++++++- src/calibre/db/lazy.py | 7 ++++--- src/calibre/db/schema_upgrades.py | 15 +++++++++------ src/calibre/db/tests/writing.py | 19 +++++++++++++++++++ 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index f2c7069da5..e5f600ed16 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -291,7 +291,14 @@ class Cache: self.format_metadata_cache.clear() if search_cache: self._clear_search_caches(book_ids) - self.link_maps_cache = {} + self.clear_link_map_cache(book_ids) + + def clear_link_map_cache(self, book_ids=None): + if book_ids is None: + self.link_maps_cache = {} + else: + for book in book_ids: + self.link_maps_cache.pop(book, None) @write_api def reload_from_db(self, clear_caches=True): @@ -1487,6 +1494,7 @@ class Cache: if update_path and do_path_update: self._update_path(dirtied, mark_as_dirtied=False) self._mark_as_dirty(dirtied) + self.clear_link_map_cache(dirtied) self.event_dispatcher(EventType.metadata_changed, name, dirtied) return dirtied @@ -1502,6 +1510,7 @@ class Cache: self.format_metadata_cache.pop(book_id, None) if mark_as_dirtied: self._mark_as_dirty(book_ids) + self.clear_link_map_cache(book_ids) @read_api def get_a_dirtied_book(self): @@ -2161,6 +2170,7 @@ class Cache: for book_id in moved_books: self._set_field(f.index_field.name, {book_id:self._get_next_series_num_for(self._fast_field_for(f, book_id), field=field)}) self._mark_as_dirty(affected_books) + self.clear_link_map_cache(affected_books) self.event_dispatcher(EventType.items_renamed, field, affected_books, id_map) return affected_books, id_map @@ -2180,6 +2190,7 @@ class Cache: self._set_field(field.index_field.name, {bid:1.0 for bid in affected_books}) else: self._mark_as_dirty(affected_books) + self.clear_link_map_cache(affected_books) self.event_dispatcher(EventType.items_removed, field, affected_books, item_ids) return affected_books @@ -2314,6 +2325,7 @@ class Cache: self._set_field('author_sort', val_map) if changed_books: self._mark_as_dirty(changed_books) + self.clear_link_map_cache(changed_books) return changed_books @write_api @@ -2324,6 +2336,7 @@ class Cache: changed_books |= self._books_for_field('authors', author_id) if changed_books: self._mark_as_dirty(changed_books) + self.clear_link_map_cache(changed_books) return changed_books @read_api @@ -2416,6 +2429,7 @@ class Cache: changed_books |= self._books_for_field(field, id_) if changed_books: self._mark_as_dirty(changed_books) + self.clear_link_map_cache(changed_books) return changed_books @read_api diff --git a/src/calibre/db/lazy.py b/src/calibre/db/lazy.py index 2ee0ded5c1..1882dcd3a8 100644 --- a/src/calibre/db/lazy.py +++ b/src/calibre/db/lazy.py @@ -330,9 +330,6 @@ class ProxyMetadata(Metadata): sa(self, '_user_metadata', db.field_metadata) def __getattribute__(self, field): - if field == 'link_maps': - db = ga(self, '_db')() - return db.get_all_link_maps_for_book(ga(self, '_book_id')) getter = getters.get(field, None) if getter is not None: return getter(ga(self, '_db'), ga(self, '_book_id'), ga(self, '_cache')) @@ -354,6 +351,10 @@ class ProxyMetadata(Metadata): return custom_getter(field, ga(self, '_db'), ga(self, '_book_id'), ga(self, '_cache')) return composite_getter(self, field, ga(self, '_db'), ga(self, '_book_id'), ga(self, '_cache'), ga(self, 'formatter'), ga(self, 'template_cache')) + if field == 'link_maps': + db = ga(self, '_db')() + return db.get_all_link_maps_for_book(ga(self, '_book_id')) + try: return ga(self, '_cache')[field] except KeyError: diff --git a/src/calibre/db/schema_upgrades.py b/src/calibre/db/schema_upgrades.py index f7e888f02d..8f7f5c5b6c 100644 --- a/src/calibre/db/schema_upgrades.py +++ b/src/calibre/db/schema_upgrades.py @@ -795,6 +795,7 @@ CREATE TRIGGER fkc_annot_update self.db.reindex_annotations() def upgrade_version_25(self): + alters = [] for record in self.db.execute( 'SELECT label,name,datatype,editable,display,normalized,id,is_multiple FROM custom_columns'): data = { @@ -809,12 +810,14 @@ CREATE TRIGGER fkc_annot_update } if data['normalized']: tn = 'custom_column_{}'.format(data['num']) - self.db.execute(f'ALTER TABLE {tn} ADD COLUMN link TEXT NOT NULL DEFAULT "";') - self.db.execute('ALTER TABLE publishers ADD COLUMN link TEXT NOT NULL DEFAULT "";') - self.db.execute('ALTER TABLE series ADD COLUMN link TEXT NOT NULL DEFAULT "";') - self.db.execute('ALTER TABLE tags ADD COLUMN link TEXT NOT NULL DEFAULT "";') + alters.append(f'ALTER TABLE {tn} ADD COLUMN link TEXT NOT NULL DEFAULT "";') + + alters.append('ALTER TABLE publishers ADD COLUMN link TEXT NOT NULL DEFAULT "";') + alters.append('ALTER TABLE series ADD COLUMN link TEXT NOT NULL DEFAULT "";') + alters.append('ALTER TABLE tags ADD COLUMN link TEXT NOT NULL DEFAULT "";') # These aren't necessary in that there is no UI to set links, but having them # makes the code uniform - self.db.execute('ALTER TABLE languages ADD COLUMN link TEXT NOT NULL DEFAULT "";') - self.db.execute('ALTER TABLE ratings ADD COLUMN link TEXT NOT NULL DEFAULT "";') + alters.append('ALTER TABLE languages ADD COLUMN link TEXT NOT NULL DEFAULT "";') + alters.append('ALTER TABLE ratings ADD COLUMN link TEXT NOT NULL DEFAULT "";') + self.db.execute('\n'.join(alters)) diff --git a/src/calibre/db/tests/writing.py b/src/calibre/db/tests/writing.py index 7b2d78b266..891d7d739c 100644 --- a/src/calibre/db/tests/writing.py +++ b/src/calibre/db/tests/writing.py @@ -946,6 +946,25 @@ class WritingTest(BaseTest): self.assertEqual('url', links['tags']['foo'], 'link for tag foo is wrong') self.assertEqual('url2', links['publisher']['random'], 'link for publisher random is wrong') + # Check that renaming a tag keeps the link and clears the link map cache for the book + self.assertTrue(1 in cache.link_maps_cache, "book not in link_map_cache") + tag_id = cache.get_item_id('tags', 'foo') + cache.rename_items('tags', {tag_id: 'foobar'}) + self.assertTrue(1 not in cache.link_maps_cache, "book still in link_map_cache") + links = cache.get_link_map('tags') + self.assertTrue('foobar' in links, "rename foo lost the link") + self.assertEqual(links['foobar'], 'url', "The link changed contents") + links = cache.get_all_link_maps_for_book(1) + self.assertTrue(1 in cache.link_maps_cache, "book not put back into link_map_cache") + self.assertDictEqual({'publisher': {'random': 'url2'}, 'tags': {'foobar': 'url'}}, + links, "book links incorrect after tag rename") + + # Check ProxyMetadata + mi = cache.get_proxy_metadata(1) + self.assertDictEqual({'publisher': {'random': 'url2'}, 'tags': {'foobar': 'url'}}, + mi.link_maps, "ProxyMetadata didn't return the right link map") + + # Now test deleting the links. links = cache.get_link_map('tags') to_del = {l:'' for l in links.keys()} From aabd29c571f8603b083863d6217001bc4647382a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 30 Mar 2023 08:37:17 +0530 Subject: [PATCH 0377/2055] Clean up DB code from previous PR Note that string literals in SQLITE should be single quoted. https://sqlite.org/quirks.html#dblquote --- resources/metadata_sqlite.sql | 7 ++++++- src/calibre/db/cache.py | 23 +++++++++++------------ src/calibre/db/schema_upgrades.py | 13 ++++++------- src/calibre/db/tables.py | 19 ++++++++++--------- src/calibre/db/tests/metadata.db | Bin 264192 -> 264192 bytes 5 files changed, 33 insertions(+), 29 deletions(-) diff --git a/resources/metadata_sqlite.sql b/resources/metadata_sqlite.sql index aa7e04c2ad..356695b1a3 100644 --- a/resources/metadata_sqlite.sql +++ b/resources/metadata_sqlite.sql @@ -97,6 +97,7 @@ CREATE TABLE identifiers ( id INTEGER PRIMARY KEY, ); CREATE TABLE languages ( id INTEGER PRIMARY KEY, lang_code TEXT NOT NULL COLLATE NOCASE, + link TEXT NOT NULL DEFAULT '', UNIQUE(lang_code) ); CREATE TABLE library_id ( id INTEGER PRIMARY KEY, @@ -116,19 +117,23 @@ CREATE TABLE preferences(id INTEGER PRIMARY KEY, CREATE TABLE publishers ( id INTEGER PRIMARY KEY, name TEXT NOT NULL COLLATE NOCASE, sort TEXT COLLATE NOCASE, + link TEXT NOT NULL DEFAULT '', UNIQUE(name) ); CREATE TABLE ratings ( id INTEGER PRIMARY KEY, rating INTEGER CHECK(rating > -1 AND rating < 11), + link TEXT NOT NULL DEFAULT '', UNIQUE (rating) ); CREATE TABLE series ( id INTEGER PRIMARY KEY, name TEXT NOT NULL COLLATE NOCASE, sort TEXT COLLATE NOCASE, + link TEXT NOT NULL DEFAULT '', UNIQUE (name) ); CREATE TABLE tags ( id INTEGER PRIMARY KEY, name TEXT NOT NULL COLLATE NOCASE, + link TEXT NOT NULL DEFAULT '', UNIQUE (name) ); CREATE TABLE last_read_positions ( id INTEGER PRIMARY KEY, @@ -633,4 +638,4 @@ CREATE TRIGGER series_update_trg BEGIN UPDATE series SET sort=title_sort(NEW.name) WHERE id=NEW.id; END; -pragma user_version=25; +pragma user_version=26; diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index e5f600ed16..949ae5c4e0 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2341,10 +2341,7 @@ class Cache: @read_api def has_link_map(self, field): - if field not in self.fields: - raise ValueError(f'Lookup name {field} is not a valid name') - table = self.fields[field].table - return hasattr(table, 'link_map') + return hasattr(getattr(self.fields.get(field), 'table', None), 'link_map') @read_api def get_link_map(self, for_field): @@ -2358,11 +2355,12 @@ class Cache: if for_field not in self.fields: raise ValueError(f'Lookup name {for_field} is not a valid name') table = self.fields[for_field].table - if not hasattr(table, 'link_map'): + lm = getattr(table, 'link_map', None) + if lm is None: raise ValueError(f"Lookup name {for_field} doesn't have a link map") lm = table.link_map vm = table.id_map - return dict({vm.get(fid, None):v for fid,v in lm.items() if v}) + return {vm.get(fid):v for fid,v in lm.items() if v} @read_api def get_all_link_maps_for_book(self, book_id): @@ -2381,21 +2379,22 @@ class Cache: If book 2's author is neither A nor B and has no tags, this method returns {} ''' - if book_id in self.link_maps_cache: - return self.link_maps_cache[book_id] + cached = self.link_maps_cache.get(book_id) + if cached is not None: + return cached links = {} def add_links_for_field(f): field_ids = frozenset(self.field_ids_for(f, book_id)) table = self.fields[f].table lm = table.link_map vm = table.id_map - d = dict({vm.get(fid, None):v for fid,v in lm.items() if v and fid in field_ids}) + d = {vm.get(fid):v for fid,v in lm.items() if v and fid in field_ids} if d: links[f] = d for field in ('authors', 'publisher', 'series', 'tags'): add_links_for_field(field) for field in self.field_metadata.custom_field_keys(include_composites=False): - if self.has_link_map(field): + if self._has_link_map(field): add_links_for_field(field) self.link_maps_cache[book_id] = links return links @@ -2415,8 +2414,8 @@ class Cache: ''' if field not in self.fields: raise ValueError(f'Lookup name {field} is not a valid name') - table = self.fields[field].table - if not hasattr(table, 'link_map'): + table = getattr(self.fields[field], 'table', None) + if table is None: raise ValueError(f"Lookup name {field} doesn't have a link map") # Clear the links for book cache as we don't know what will be affected self.link_maps_cache = {} diff --git a/src/calibre/db/schema_upgrades.py b/src/calibre/db/schema_upgrades.py index 8f7f5c5b6c..4c395a8d3f 100644 --- a/src/calibre/db/schema_upgrades.py +++ b/src/calibre/db/schema_upgrades.py @@ -810,14 +810,13 @@ CREATE TRIGGER fkc_annot_update } if data['normalized']: tn = 'custom_column_{}'.format(data['num']) - alters.append(f'ALTER TABLE {tn} ADD COLUMN link TEXT NOT NULL DEFAULT "";') + alters.append(f"ALTER TABLE {tn} ADD COLUMN link TEXT NOT NULL DEFAULT '';") - alters.append('ALTER TABLE publishers ADD COLUMN link TEXT NOT NULL DEFAULT "";') - alters.append('ALTER TABLE series ADD COLUMN link TEXT NOT NULL DEFAULT "";') - alters.append('ALTER TABLE tags ADD COLUMN link TEXT NOT NULL DEFAULT "";') + alters.append("ALTER TABLE publishers ADD COLUMN link TEXT NOT NULL DEFAULT '';") + alters.append("ALTER TABLE series ADD COLUMN link TEXT NOT NULL DEFAULT '';") + alters.append("ALTER TABLE tags ADD COLUMN link TEXT NOT NULL DEFAULT '';") # These aren't necessary in that there is no UI to set links, but having them # makes the code uniform - alters.append('ALTER TABLE languages ADD COLUMN link TEXT NOT NULL DEFAULT "";') - alters.append('ALTER TABLE ratings ADD COLUMN link TEXT NOT NULL DEFAULT "";') + alters.append("ALTER TABLE languages ADD COLUMN link TEXT NOT NULL DEFAULT '';") + alters.append("ALTER TABLE ratings ADD COLUMN link TEXT NOT NULL DEFAULT '';") self.db.execute('\n'.join(alters)) - diff --git a/src/calibre/db/tables.py b/src/calibre/db/tables.py index 18658a3401..47e7c66f20 100644 --- a/src/calibre/db/tables.py +++ b/src/calibre/db/tables.py @@ -16,6 +16,9 @@ from calibre_extensions.speedup import parse_date as _c_speedup from polyglot.builtins import iteritems, itervalues +def identity(x): + return x + def c_parse(val): try: year, month, day, hour, minutes, seconds, tzsecs = _c_speedup(val) @@ -208,10 +211,7 @@ class ManyToOneTable(Table): def read_id_maps(self, db): query = db.execute('SELECT id, {}, link FROM {}'.format( self.metadata['column'], self.metadata['table'])) - if self.unserialize is None: - us = lambda x: x - else: - us = self.unserialize + us = identity if self.unserialize is None else self.unserialize for id_, val, link in query: self.id_map[id_] = us(val) self.link_map[id_] = link @@ -347,11 +347,12 @@ class ManyToOneTable(Table): return affected_books, new_id def set_links(self, link_map, db): - link_map = {id_:(l or '').strip() for id_, l in iteritems(link_map)} - link_map = {id_:l for id_, l in iteritems(link_map) if l != self.link_map.get(id_)} - self.link_map.update(link_map) - db.executemany(f'UPDATE {self.metadata["table"]} SET link=? WHERE id=?', - [(v, k) for k, v in iteritems(link_map)]) + link_map = {id_:(l or '').strip() for id_, l in link_map.items()} + link_map = {id_:l for id_, l in link_map.items() if l != self.link_map.get(id_)} + if link_map: + self.link_map.update(link_map) + db.executemany(f'UPDATE {self.metadata["table"]} SET link=? WHERE id=?', + tuple((v, k) for k, v in link_map.items())) return link_map diff --git a/src/calibre/db/tests/metadata.db b/src/calibre/db/tests/metadata.db index 57ae6ab43fadf3724e12761935d90f6de9d12789..9e77a45f482f00bb4ace8f55dff8d0500885ba02 100644 GIT binary patch delta 769 zcmZvZ-%FEW6vxjw&-T9gzRk9gy|W?POX}veUDk@RSlzv=P7Y5t2 z*>!{Ao|_=>W(8ezQ_n>dnO7k(|A3^1(Z%vcFugFS_sxl}_FSC@&iQ=5=X^7HcP8)7 z`#W3!NU;C5tkSXn1+?np1<&Dt(*@m=RX+Y|92dTrZGZvt(nWd)Z3nt}dR8@}QOxy- z&>FsW24|*jO^c0r@)d$!L%0*DdhTDFxi`_(#qT`WZ8gNiS1dJZmEj8h#NrcFO3F8M zYbn4%d}O&z3HK(R)_6L-r#7|KSgon1mV7AU6^zKoo4A6kpM&5gMda;m6sMFiz)2Xh znZwQ2GFjj*n@iRfHzFh(SyO9V$;0&71~%7#Q7VRY*a6tV-!wpPt<(L`E#k2~V#lS% zM3LCWrV@@&f0Qa#1SVjFo`K1h58QXR%sP2UH39+3jf(XmJ*_ur7BU`1ycFb@k$IA6 zr&+aZe3#3D5@F2;bcGgJ9(~gNo(`d&J;URwVVX2|LA>}t<^OSxzsVKOdE3yzhhgDX#v_9!so%g7Q-%q(|QSD^nAE*dcP!**R3(H^a zYz<{O#Cnf9VgNCr^|2Fr_9X9Bjn-D0i;K}d_NIP5B#sQSPI)=Wo-2+az#*xnSQ2%9 alP6W9rNxf!?pZclKNYaoP^2}wvG@nJUDBuk delta 679 zcmYk3Ur19?9LLY^ch;`lUG+-pW$M`GTw7bELa|`Z5-Nx!vXBasO_UMQAE}2y26;B$ z8{F*osQFU8g^}kWg4ABbhuL!vruNW-fq~MCVE4F&o~Q5O$LIU~@%@b#&GDjHYz`ZY z0kVU)=}6bXmw}_bcAeorVz5&;GGgT82+XV+1B?woJ3NH%+~-U$>!L5GS@AgJZ`1KL zctdxB*h1%aph_NHhu@%!%vgDzqsR}KqmE>GuFd@fx2X>^Hq2(ghiNjm;j|qv{95Xn z+v0(O;krw_r@*V8yV9hk=rX9$~?Dy$w9x)`URUqbb^dO4zV0S05 z=6_%uUqwC8#GncG@H~G0P-p0%(8dbt?mvvmy?(^*TGckh7{u@`uJL7uY62OXMCq_u z5z#Ea9}8YO@e-$KWdg!vCPAadNvxO4gvqF$V32^Pco$zw^9xpiYEGsX@E&;bZK6}N zfjK0g*F6MBRmz+Cp%FQs}aI)`No4$s5RF)zhume-4gCv>)pe^P1wGE;krACrSA{?6^n jGRR8(0#EHP>Je8&O0yaoRJGSH@-YSZ_95v&f9C%IxqiPZ From 6ddef1181931de8b5ab2e6da97f4665675991667 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 30 Mar 2023 08:56:19 +0530 Subject: [PATCH 0378/2055] Use a getter for link_maps in ProxyMetadata Also allow is_null() to work with it. --- src/calibre/db/cache.py | 2 +- src/calibre/db/lazy.py | 14 ++++++++++---- src/calibre/ebooks/metadata/book/base.py | 1 + 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 949ae5c4e0..0a3f6b4d69 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -391,7 +391,7 @@ class Cache: for key in composites: mi.set(key, val=self._composite_for(key, book_id, mi)) - mi.link_maps = self.get_all_link_maps_for_book(book_id) + mi.link_maps = self._get_all_link_maps_for_book(book_id) user_cat_vals = {} if get_user_categories: diff --git a/src/calibre/db/lazy.py b/src/calibre/db/lazy.py index 1882dcd3a8..01053ccb8c 100644 --- a/src/calibre/db/lazy.py +++ b/src/calibre/db/lazy.py @@ -147,6 +147,15 @@ def adata_getter(field): return func +def link_maps_getter(dbref, book_id, cache): + try: + ans = cache['link_maps'] + except KeyError: + db = dbref() + ans = cache['link_maps'] = db.get_all_link_maps_for_book(book_id) + return ans + + def dt_getter(field): def func(dbref, book_id, cache): try: @@ -300,6 +309,7 @@ getters = { 'application_id':lambda x, book_id, y: book_id, 'id':lambda x, book_id, y: book_id, 'virtual_libraries':virtual_libraries_getter, + 'link_maps': link_maps_getter, } for field in ('comments', 'publisher', 'identifiers', 'series', 'rating'): @@ -351,10 +361,6 @@ class ProxyMetadata(Metadata): return custom_getter(field, ga(self, '_db'), ga(self, '_book_id'), ga(self, '_cache')) return composite_getter(self, field, ga(self, '_db'), ga(self, '_book_id'), ga(self, '_cache'), ga(self, 'formatter'), ga(self, 'template_cache')) - if field == 'link_maps': - db = ga(self, '_db')() - return db.get_all_link_maps_for_book(ga(self, '_book_id')) - try: return ga(self, '_cache')[field] except KeyError: diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index a7afd67f46..df7fcad934 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -46,6 +46,7 @@ NULL_VALUES = { 'title' : _('Unknown'), 'user_categories' : {}, 'author_link_map' : {}, + 'link_maps' : {}, 'language' : 'und' } From 44001e2c607884d58d111d07996c2ef12fc8f8bf Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 30 Mar 2023 13:25:54 +0530 Subject: [PATCH 0379/2055] Some link map micro optimizations I missed in my previous review --- src/calibre/db/cache.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 0a3f6b4d69..aac848f7d5 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2384,11 +2384,11 @@ class Cache: return cached links = {} def add_links_for_field(f): - field_ids = frozenset(self.field_ids_for(f, book_id)) table = self.fields[f].table lm = table.link_map vm = table.id_map - d = {vm.get(fid):v for fid,v in lm.items() if v and fid in field_ids} + d = {vm.get(fid):v for fid, v in lm.items() if v} + d.pop(None, None) if d: links[f] = d for field in ('authors', 'publisher', 'series', 'tags'): @@ -2410,7 +2410,7 @@ class Cache: returns books changed by setting the link - NB: this method doesn't change values not in the value_to_link_map + Note: this method doesn't change values not in the value_to_link_map ''' if field not in self.fields: raise ValueError(f'Lookup name {field} is not a valid name') @@ -2420,7 +2420,7 @@ class Cache: # Clear the links for book cache as we don't know what will be affected self.link_maps_cache = {} - fids = {k: self.get_item_id(field, k) for k in value_to_link_map.keys()} + fids = self._get_item_ids(field, value_to_link_map) id_to_link_map = {fid:value_to_link_map[k] for k, fid in fids.items() if fid is not None} result_map = table.set_links(id_to_link_map, self.backend) changed_books = set() From f2445ad87aab1c1c1864a431d64ef194d19d46e5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 30 Mar 2023 13:41:20 +0530 Subject: [PATCH 0380/2055] Get rid of author_link_map Also implement support for link_maps metadata in EPUB3 and PDF. Reading proceeds by returning the link_maps if present, otherwise look for a legacy author_link_map and return a link_maps constructed from it. Writing simply sets link_maps, which as per the sceme above takes precedence anyway. --- src/calibre/db/cache.py | 3 -- src/calibre/db/lazy.py | 2 +- src/calibre/db/restore.py | 39 +++++++++-------- src/calibre/db/tests/base.py | 2 +- src/calibre/db/tests/legacy.py | 1 - src/calibre/ebooks/metadata/book/__init__.py | 4 +- src/calibre/ebooks/metadata/book/base.py | 1 - src/calibre/ebooks/metadata/opf2.py | 44 ++++++++++++++++---- src/calibre/ebooks/metadata/opf3.py | 27 +++++++++--- src/calibre/ebooks/metadata/opf3_test.py | 30 ++++++------- src/calibre/ebooks/metadata/xmp.py | 13 ++++-- src/calibre/utils/formatter_functions.py | 5 ++- 12 files changed, 112 insertions(+), 59 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index aac848f7d5..dbf91f73de 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -325,19 +325,16 @@ class Cache: aut_list = [adata[i] for i in author_ids] aum = [] aus = {} - aul = {} for rec in aut_list: aut = rec['name'] aum.append(aut) aus[aut] = rec['sort'] - aul[aut] = rec['link'] mi.title = self._field_for('title', book_id, default_value=_('Unknown')) mi.authors = aum mi.author_sort = self._field_for('author_sort', book_id, default_value=_('Unknown')) mi.author_sort_map = aus - mi.author_link_map = aul mi.comments = self._field_for('comments', book_id) mi.publisher = self._field_for('publisher', book_id) n = utcnow() diff --git a/src/calibre/db/lazy.py b/src/calibre/db/lazy.py index 01053ccb8c..07a73a81aa 100644 --- a/src/calibre/db/lazy.py +++ b/src/calibre/db/lazy.py @@ -315,7 +315,7 @@ getters = { for field in ('comments', 'publisher', 'identifiers', 'series', 'rating'): getters[field] = simple_getter(field) -for field in ('author_sort_map', 'author_link_map'): +for field in ('author_sort_map',): getters[field] = adata_getter(field) for field in ('timestamp', 'pubdate', 'last_modified'): diff --git a/src/calibre/db/restore.py b/src/calibre/db/restore.py index 726804f2a1..2e03435713 100644 --- a/src/calibre/db/restore.py +++ b/src/calibre/db/restore.py @@ -5,18 +5,22 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import re, os, traceback, shutil, time -from threading import Thread +import os +import re +import shutil +import time +import traceback +from contextlib import suppress from operator import itemgetter +from threading import Thread -from calibre.ptempfile import TemporaryDirectory -from calibre.ebooks.metadata.opf2 import OPF +from calibre import force_unicode, isbytestring +from calibre.constants import filesystem_encoding from calibre.db.backend import DB, DBPrefs from calibre.db.cache import Cache -from calibre.constants import filesystem_encoding +from calibre.ebooks.metadata.opf2 import OPF +from calibre.ptempfile import TemporaryDirectory from calibre.utils.date import utcfromtimestamp -from calibre import isbytestring, force_unicode -from polyglot.builtins import iteritems NON_EBOOK_EXTENSIONS = frozenset(( 'jpg', 'jpeg', 'gif', 'png', 'bmp', @@ -59,7 +63,7 @@ class Restore(Thread): self.mismatched_dirs = [] self.successes = 0 self.tb = None - self.authors_links = {} + self.link_maps = {} @property def errors_occurred(self): @@ -209,11 +213,13 @@ class Restore(Thread): else: self.mismatched_dirs.append(dirpath) - alm = mi.get('author_link_map', {}) - for author, link in iteritems(alm): - existing_link, timestamp = self.authors_links.get(author, (None, None)) - if existing_link is None or existing_link != link and timestamp < mi.timestamp: - self.authors_links[author] = (link, mi.timestamp) + alm = mi.get('link_maps', {}) + for field, lmap in alm.items(): + dest = self.link_maps.setdefault(field, {}) + for item, link in lmap.items(): + existing_link, timestamp = dest.get(item, (None, None)) + if existing_link is None or existing_link != link and timestamp < mi.timestamp: + dest[item] = link, mi.timestamp def create_cc_metadata(self): self.books.sort(key=itemgetter('timestamp')) @@ -262,10 +268,9 @@ class Restore(Thread): self.failed_restores.append((book, traceback.format_exc())) self.progress_callback(book['mi'].title, i+1) - id_map = db.get_item_ids('authors', [author for author in self.authors_links]) - link_map = {aid:self.authors_links[name][0] for name, aid in iteritems(id_map) if aid is not None} - if link_map: - db.set_link_for_authors(link_map) + for field, lmap in self.link_maps.items(): + with suppress(Exception): + db.set_link_map(field, {k:v[0] for k, v in lmap.items()}) db.close() def replace_db(self): diff --git a/src/calibre/db/tests/base.py b/src/calibre/db/tests/base.py index 3ccdf5b643..010f1d744e 100644 --- a/src/calibre/db/tests/base.py +++ b/src/calibre/db/tests/base.py @@ -102,7 +102,7 @@ class BaseTest(unittest.TestCase): self.assertEqual(allfk1, allfk2) all_keys = {'format_metadata', 'id', 'application_id', - 'author_sort_map', 'author_link_map', 'book_size', + 'author_sort_map', 'link_maps', 'book_size', 'ondevice_col', 'last_modified', 'has_cover', 'cover_data'}.union(allfk1) for attr in all_keys: diff --git a/src/calibre/db/tests/legacy.py b/src/calibre/db/tests/legacy.py index 2ba66b3ae8..1a7b3b9e81 100644 --- a/src/calibre/db/tests/legacy.py +++ b/src/calibre/db/tests/legacy.py @@ -575,7 +575,6 @@ class LegacyTest(BaseTest): omi = [db.get_metadata(x) for x in (0, 1, 2)] nmi = [ndb.get_metadata(x) for x in (0, 1, 2)] self.assertEqual([x.author_sort_map for x in omi], [x.author_sort_map for x in nmi]) - self.assertEqual([x.author_link_map for x in omi], [x.author_link_map for x in nmi]) db.close() ndb = self.init_legacy(self.cloned_library) diff --git a/src/calibre/ebooks/metadata/book/__init__.py b/src/calibre/ebooks/metadata/book/__init__.py index 6835319590..93cc9207eb 100644 --- a/src/calibre/ebooks/metadata/book/__init__.py +++ b/src/calibre/ebooks/metadata/book/__init__.py @@ -85,8 +85,8 @@ CALIBRE_METADATA_FIELDS = frozenset(( # a dict of user category names, where the value is a list of item names # from the book that are in that category 'user_categories', - # a dict of author to an associated hyperlink - 'author_link_map', + # a dict of items to associated hyperlink + 'link_maps', )) ALL_METADATA_FIELDS = SOCIAL_METADATA_FIELDS.union( diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index df7fcad934..b28ed647ef 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -45,7 +45,6 @@ NULL_VALUES = { 'author_sort' : _('Unknown'), 'title' : _('Unknown'), 'user_categories' : {}, - 'author_link_map' : {}, 'link_maps' : {}, 'language' : 'und' } diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index b56eb178f4..58b9928bf4 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -17,6 +17,7 @@ import os import re import sys import uuid +from contextlib import suppress from lxml import etree from calibre import guess_type, prints @@ -458,6 +459,37 @@ class MetadataField: obj.set_text(elem, self.renderer(val)) +class LinkMapsField: + + def __get__(self, obj, type=None): + ans = obj.get_metadata_element('link_maps') + if ans is not None: + ans = obj.get_text(ans) + if ans: + with suppress(Exception): + return json.loads(ans) + ans = obj.get_metadata_element('author_link_map') + if ans is not None: + ans = obj.get_text(ans) + if ans: + with suppress(Exception): + return {'authors': json.loads(ans)} + return {} + + def __set__(self, obj, val): + elem = obj.get_metadata_element('author_link_map') + if elem is not None: + elem.getparent().remove(elem) + elem = obj.get_metadata_element('link_maps') + if not val: + if elem is not None: + elem.getparent().remove(elem) + return + if elem is None: + elem = obj.create_metadata_element('link_maps', is_dc=False) + obj.set_text(elem, dump_dict(val)) + + class TitleSortField(MetadataField): def __get__(self, obj, type=None): @@ -593,10 +625,7 @@ class OPF: # {{{ user_categories = MetadataField('user_categories', is_dc=False, formatter=json.loads, renderer=dump_dict) - author_link_map = MetadataField('author_link_map', is_dc=False, - formatter=json.loads, renderer=dump_dict) - link_map = MetadataField('link_maps', is_dc=False, - formatter=json.loads, renderer=dump_dict) + link_maps = LinkMapsField() def __init__(self, stream, basedir=os.getcwd(), unquote_urls=True, populate_spine=True, try_to_guess_cover=False, preparsed_opf=None, read_toc=True): @@ -667,6 +696,7 @@ class OPF: # {{{ ans.set_user_metadata(n, v) ans.set_identifiers(self.get_identifiers()) + ans.link_maps = self.link_maps return ans @@ -1312,7 +1342,7 @@ class OPF: # {{{ for attr in ('title', 'authors', 'author_sort', 'title_sort', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'tags', 'category', 'comments', 'book_producer', - 'pubdate', 'user_categories', 'author_link_map', 'link_map'): + 'pubdate', 'user_categories', 'link_maps'): val = getattr(mi, attr, None) if attr == 'rating' and val: val = float(val) @@ -1673,9 +1703,7 @@ def metadata_to_opf(mi, as_string=True, default_lang=None): def meta(n, c): return factory('meta', name='calibre:' + n, content=c) - if getattr(mi, 'author_link_map', None) is not None: - meta('author_link_map', dump_dict(mi.author_link_map)) - if getattr(mi, 'link_maps', None) is not None: + if not mi.is_null('link_maps'): meta('link_maps', dump_dict(mi.link_maps)) if mi.series: meta('series', mi.series) diff --git a/src/calibre/ebooks/metadata/opf3.py b/src/calibre/ebooks/metadata/opf3.py index 4261b7a423..98f3f4a31b 100644 --- a/src/calibre/ebooks/metadata/opf3.py +++ b/src/calibre/ebooks/metadata/opf3.py @@ -865,16 +865,31 @@ def dict_reader(name, load=json.loads, try2=True): read_user_categories = dict_reader('user_categories') -read_author_link_map = dict_reader('author_link_map') +_read_link_maps = dict_reader('link_maps') +_read_author_link_map = dict_reader('author_link_map') -def dict_writer(name, serialize=dump_dict, remove2=True): +def read_link_maps(root, prefixes, refines): + ans = _read_link_maps(root, prefixes, refines) + if ans is not None: + return ans + ans = _read_author_link_map(root, prefixes, refines) + if ans: + ans = {k: v for k, v in ans.items() if v} + if ans: + return {'authors': ans} + + +def dict_writer(name, serialize=dump_dict, remove2=True, extra_remove=''): pq = f'{CALIBRE_PREFIX}:{name}' def writer(root, prefixes, refines, val): if remove2: for meta in XPath('./opf:metadata/opf:meta[@name="calibre:%s"]' % name)(root): remove_element(meta, refines) + if extra_remove: + for meta in XPath('./opf:metadata/opf:meta[@name="calibre:%s"]' % extra_remove)(root): + remove_element(meta, refines) for meta in XPath('./opf:metadata/opf:meta[@property]')(root): prop = expand_prefix(meta.get('property'), prefixes) if prop.lower() == pq: @@ -889,7 +904,7 @@ def dict_writer(name, serialize=dump_dict, remove2=True): set_user_categories = dict_writer('user_categories') -set_author_link_map = dict_writer('author_link_map') +set_link_maps = dict_writer('link_maps', extra_remove='author_link_map') def deserialize_user_metadata(val): @@ -1054,7 +1069,7 @@ def read_metadata(root, ver=None, return_extra_data=False): s, si = read_series(root, prefixes, refines) if s: ans.series, ans.series_index = s, si - ans.author_link_map = read_author_link_map(root, prefixes, refines) or ans.author_link_map + ans.link_maps = read_link_maps(root, prefixes, refines) or ans.link_maps ans.user_categories = read_user_categories(root, prefixes, refines) or ans.user_categories for name, fm in iteritems(read_user_metadata(root, prefixes, refines) or {}): ans.set_user_metadata(name, fm) @@ -1105,8 +1120,8 @@ def apply_metadata(root, mi, cover_prefix='', cover_data=None, apply_null=False, set_rating(root, prefixes, refines, mi.rating) if ok('series'): set_series(root, prefixes, refines, mi.series, mi.series_index or 1) - if ok('author_link_map'): - set_author_link_map(root, prefixes, refines, getattr(mi, 'author_link_map', None)) + if ok('link_maps'): + set_link_maps(root, prefixes, refines, getattr(mi, 'link_maps', None)) if ok('user_categories'): set_user_categories(root, prefixes, refines, getattr(mi, 'user_categories', None)) # We ignore apply_null for the next two to match the behavior with opf2.py diff --git a/src/calibre/ebooks/metadata/opf3_test.py b/src/calibre/ebooks/metadata/opf3_test.py index 3dd8f0657e..db06ad5f78 100644 --- a/src/calibre/ebooks/metadata/opf3_test.py +++ b/src/calibre/ebooks/metadata/opf3_test.py @@ -2,29 +2,29 @@ # License: GPLv3 Copyright: 2016, Kovid Goyal +import unittest from collections import defaultdict from io import BytesIO -import unittest from calibre.ebooks.metadata.book import ALL_METADATA_FIELDS -from calibre.utils.xml_parse import safe_xml_fromstring from calibre.ebooks.metadata.opf2 import OPF from calibre.ebooks.metadata.opf3 import ( - parse_prefixes, reserved_prefixes, expand_prefix, read_identifiers, - read_metadata, set_identifiers, XPath, set_application_id, read_title, - read_refines, set_title, read_title_sort, read_languages, set_languages, - read_authors, Author, set_authors, ensure_prefix, read_prefixes, - read_book_producers, set_book_producers, read_timestamp, set_timestamp, - read_pubdate, set_pubdate, CALIBRE_PREFIX, read_last_modified, read_comments, - set_comments, read_publisher, set_publisher, read_tags, set_tags, read_rating, - set_rating, read_series, set_series, read_user_metadata, set_user_metadata, - read_author_link_map, read_user_categories, set_author_link_map, set_user_categories, - apply_metadata, read_raster_cover, ensure_is_only_raster_cover + CALIBRE_PREFIX, Author, XPath, apply_metadata, ensure_is_only_raster_cover, + ensure_prefix, expand_prefix, parse_prefixes, read_authors, read_book_producers, + read_comments, read_identifiers, read_languages, read_last_modified, read_link_maps, + read_metadata, read_prefixes, read_pubdate, read_publisher, read_raster_cover, + read_rating, read_refines, read_series, read_tags, read_timestamp, read_title, + read_title_sort, read_user_categories, read_user_metadata, reserved_prefixes, + set_application_id, set_authors, set_book_producers, set_comments, set_identifiers, + set_languages, set_link_maps, set_pubdate, set_publisher, set_rating, set_series, + set_tags, set_timestamp, set_title, set_user_categories, set_user_metadata, ) + # This import is needed to prevent a test from running slowly from calibre.ebooks.oeb.polish.pretty import pretty_opf, pretty_xml_tree # noqa +from calibre.utils.xml_parse import safe_xml_fromstring -read_author_link_map, read_user_categories, set_author_link_map, set_user_categories +read_user_categories, set_user_categories, read_link_maps, set_link_maps TEMPLATE = '''{metadata}{manifest}''' % CALIBRE_PREFIX # noqa default_refines = defaultdict(list) @@ -288,7 +288,7 @@ class TestOPF3(unittest.TestCase): f = globals()['set_' + name] f(root, read_prefixes(root), read_refines(root), val) return rt(root, name) - for name in 'author_link_map user_categories'.split(): + for name in 'link_maps user_categories'.split(): root = self.get_opf('''''' % name) self.ae({'1':1}, rt(root, name)) root = self.get_opf(f'''{{"2":2}}''') @@ -328,7 +328,7 @@ class TestOPF3(unittest.TestCase): conversion docs ebook - + diff --git a/src/calibre/ebooks/metadata/xmp.py b/src/calibre/ebooks/metadata/xmp.py index 17f8715665..b7c72992d3 100644 --- a/src/calibre/ebooks/metadata/xmp.py +++ b/src/calibre/ebooks/metadata/xmp.py @@ -289,13 +289,20 @@ def metadata_from_xmp_packet(raw_bytes): if val: setattr(mi, x, val) break - for x in ('author_link_map', 'user_categories'): + for x in ('link_maps', 'user_categories'): val = first_simple('//calibre:'+x, root) if val: try: setattr(mi, x, json.loads(val)) - except: + except Exception: pass + elif x == 'link_maps': + val = first_simple('//calibre:author_link_map', root) + if val: + try: + setattr(mi, x, {'authors': json.loads(val)}) + except Exception: + pass languages = multiple_sequences('//dc:language', root) if languages: @@ -526,7 +533,7 @@ def metadata_to_xmp_packet(mi): create_series(calibre, mi.series, mi.series_index) if not mi.is_null('timestamp'): create_simple_property(calibre, 'calibre:timestamp', isoformat(mi.timestamp, as_utc=False)) - for x in ('author_link_map', 'user_categories'): + for x in ('link_maps', 'user_categories'): val = getattr(mi, x, None) if val: create_simple_property(calibre, 'calibre:'+x, dump_dict(val)) diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 30540ca2d4..89e0150693 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -2014,7 +2014,10 @@ class BuiltinAuthorLinks(BuiltinFormatterFunction): def evaluate(self, formatter, kwargs, mi, locals, val_sep, pair_sep): if hasattr(mi, '_proxy_metadata'): - link_data = mi._proxy_metadata.author_link_map + link_data = mi._proxy_metadata.link_maps + if not link_data: + return '' + link_data = link_data.get('authors') if not link_data: return '' names = sorted(link_data.keys(), key=sort_key) From 8447e8791fe1fe5de794136e9b92a0188ebae6b8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 30 Mar 2023 14:37:58 +0530 Subject: [PATCH 0381/2055] Add a test for restoring library --- src/calibre/db/tests/writing.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/calibre/db/tests/writing.py b/src/calibre/db/tests/writing.py index 891d7d739c..ab27e50fa4 100644 --- a/src/calibre/db/tests/writing.py +++ b/src/calibre/db/tests/writing.py @@ -354,6 +354,9 @@ class WritingTest(BaseTest): try: ae(sf('title', {1:'title1', 2:'title2', 3:'title3'}), {1,2,3}) ae(sf('authors', {1:'author1 & author2', 2:'author1 & author2', 3:'author1 & author2'}), {1,2,3}) + ae(sf('tags', {1:'tag1', 2:'tag1,tag2', 3:'XXX'}), {1,2,3}) + ae(cache.set_link_map('authors', {'author1': 'link1'}), {1,2,3}) + ae(cache.set_link_map('tags', {'XXX': 'YYY', 'tag2': 'link2'}), {2,3}) count = 6 while cache.dirty_queue_length() and count > 0: mb.join(2) @@ -364,11 +367,24 @@ class WritingTest(BaseTest): mb.join(2) af(mb.is_alive()) from calibre.ebooks.metadata.opf2 import OPF - for book_id in (1, 2, 3): + book_ids = (1,2,3) + for book_id in book_ids: raw = cache.read_backup(book_id) opf = OPF(BytesIO(raw)) ae(opf.title, 'title%d'%book_id) ae(opf.authors, ['author1', 'author2']) + tested_fields = 'title authors tags'.split() + before = {f:cache.all_field_for(f, book_ids) for f in tested_fields} + lbefore = tuple(cache.get_all_link_maps_for_book(i) for i in book_ids) + cache.close() + from calibre.db.restore import Restore + restorer = Restore(cl) + restorer.start() + restorer.join(8) + af(restorer.is_alive()) + cache = self.init_cache(cl) + ae(before, {f:cache.all_field_for(f, book_ids) for f in tested_fields}) + ae(lbefore, tuple(cache.get_all_link_maps_for_book(i) for i in book_ids)) # }}} def test_set_cover(self): # {{{ From 866c15f2334511107d7d4a9ae635b1606dbeb38f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 30 Mar 2023 17:20:07 +0530 Subject: [PATCH 0382/2055] When adding a book import its link map for any items that dont have an existing link --- src/calibre/db/cache.py | 13 +++++++++++-- src/calibre/db/tests/add_remove.py | 4 ++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index dbf91f73de..00747ae09e 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -1989,6 +1989,11 @@ class Cache: if cover is not None: mi.cover, mi.cover_data = None, (None, cover) self._set_metadata(book_id, mi, ignore_errors=True) + lm = getattr(mi, 'link_maps', None) + if lm: + for field, link_map in lm.items(): + if self._has_link_map(field): + self._set_link_map(field, link_map, only_set_if_no_existing_link=True) if preserve_uuid and mi.uuid: self._set_field('uuid', {book_id:mi.uuid}) # Update the caches for fields from the books table @@ -2397,7 +2402,7 @@ class Cache: return links @write_api - def set_link_map(self, field, value_to_link_map): + def set_link_map(self, field, value_to_link_map, only_set_if_no_existing_link=False): ''' Sets links for item values in field @@ -2418,7 +2423,11 @@ class Cache: self.link_maps_cache = {} fids = self._get_item_ids(field, value_to_link_map) - id_to_link_map = {fid:value_to_link_map[k] for k, fid in fids.items() if fid is not None} + if only_set_if_no_existing_link: + lm = table.link_map + id_to_link_map = {fid:value_to_link_map[k] for k, fid in fids.items() if fid is not None and not lm.get(fid)} + else: + id_to_link_map = {fid:value_to_link_map[k] for k, fid in fids.items() if fid is not None} result_map = table.set_links(id_to_link_map, self.backend) changed_books = set() for id_ in result_map: diff --git a/src/calibre/db/tests/add_remove.py b/src/calibre/db/tests/add_remove.py index 2485836d35..7b6f678064 100644 --- a/src/calibre/db/tests/add_remove.py +++ b/src/calibre/db/tests/add_remove.py @@ -148,7 +148,10 @@ class AddRemoveTest(BaseTest): 'Test the creation of new book entries' from calibre.ebooks.metadata.book.base import Metadata cache = self.init_cache() + cache.set_field('authors', {1: 'Creator Two'}) + cache.set_link_map('authors', {'Creator Two': 'original'}) mi = Metadata('Created One', authors=('Creator One', 'Creator Two')) + mi.link_maps = {'authors': {'Creator One': 'link1', 'Creator Two': 'changed'}} book_id = cache.create_book_entry(mi) self.assertIsNot(book_id, None) @@ -163,6 +166,7 @@ class AddRemoveTest(BaseTest): self.assertEqual(('Created One', ('Creator One', 'Creator Two')), (cache.field_for('title', book_id), cache.field_for('authors', book_id))) self.assertEqual(cache.field_for('series_index', book_id), 1.0) self.assertEqual(cache.field_for('pubdate', book_id), UNDEFINED_DATE) + self.assertEqual(cache.get_all_link_maps_for_book(book_id), {'authors': {'Creator One': 'link1', 'Creator Two': 'original'}}) do_test(cache, book_id) # Test that the db contains correct data From b89de1ccf1bd06ab948b4e0f8dbe93d3e014bb31 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 31 Mar 2023 10:39:57 +0530 Subject: [PATCH 0383/2055] Use the library broker for showing book info popups for non-current libraries Also use an icon for the external links --- imgsrc/external-link-for-dark-theme.svg | 1 + imgsrc/external-link.svg | 1 + .../images/external-link-for-dark-theme.png | Bin 0 -> 1290 bytes resources/images/external-link.png | Bin 0 -> 1290 bytes src/calibre/ebooks/metadata/book/render.py | 24 +++++++-------- src/calibre/gui2/book_details.py | 29 +++++++++--------- src/calibre/gui2/dialogs/book_info.py | 8 ++--- 7 files changed, 32 insertions(+), 31 deletions(-) create mode 100644 imgsrc/external-link-for-dark-theme.svg create mode 100644 imgsrc/external-link.svg create mode 100644 resources/images/external-link-for-dark-theme.png create mode 100644 resources/images/external-link.png diff --git a/imgsrc/external-link-for-dark-theme.svg b/imgsrc/external-link-for-dark-theme.svg new file mode 100644 index 0000000000..433c2264dc --- /dev/null +++ b/imgsrc/external-link-for-dark-theme.svg @@ -0,0 +1 @@ + diff --git a/imgsrc/external-link.svg b/imgsrc/external-link.svg new file mode 100644 index 0000000000..6ffb2e7475 --- /dev/null +++ b/imgsrc/external-link.svg @@ -0,0 +1 @@ + diff --git a/resources/images/external-link-for-dark-theme.png b/resources/images/external-link-for-dark-theme.png new file mode 100644 index 0000000000000000000000000000000000000000..afb19352e312ada0f74950236ec39126ddbcff2b GIT binary patch literal 1290 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD~={fzdI*C&U%V4PEuWVgLWMGyfY_ z{mbW~GRQg{2s+(Fm zI=i}iCQq3*ea6gL^EPkUx_!saU3>QK+kf!Tu@fgxoj!B+`psK+A3S{gKb5{~4z~FA3SP+xqa#nXfJ1e!sr=_4CJv&dHve`P#yhUtnQQ$Pemp7EvBaYBkbBrJg(>eob=dze;$;bz{r>oPnWKsQ zq-@mkx!OLxnHd}>(9!mc`R^RJfJ7D zXZae<1gVrwcBf320~u+-vd_IGSq|BM{c)_(?d-&J(~sGud-dKgp7~lgi}Oir)Y%=^ z5;nhfY)_SKWW63Qoo<)$!M;*vv&8+$&)@f1%6XoUFX*tI*I7IL>HFU@AAfw-3{m|l ze_ZB%k51>_-npzt4Cn9vy5NHF%KD!(nthEfKRIS@Jef~*;meY5yw?-A2bs;~vV^4H a&(c#b$RA18Z(j|}j|`r!elF{r5}E)uE1~lM literal 0 HcmV?d00001 diff --git a/resources/images/external-link.png b/resources/images/external-link.png new file mode 100644 index 0000000000000000000000000000000000000000..4bd53578af619ede933fec7c98371b79e41a75a2 GIT binary patch literal 1290 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD~={fzdI*C&U%VWnlO}ipUNDU;TH& zz#z;j3GxeOU}j-uW9Q`J;pGz$5|))$P}I=U*3s3|H!w6ZHZe7`wh4@ij!DbR$<50z zEGj9hs%~oO=-HTxckS7`Z~wtV$4;C)b^6TN>o;%Reem${ zlc&#~zj*oT^_zFUe*gV<@ZX~o3=GVCo-U3d6}R5b{v9q9C~#bS(uV2Z7(_PeRJVWo zBI?EAIPpfa$HpMxM}nXK|7V=~yd-4DZtKG{XTG+4`~CXf*UujxIwyN>=4%VbCKk>j zFX4GJ&ri+1ZOwCY-PGb@^Ft~%SzB4pWUlpF`0=Dr#}bRiL+)X_6sElU)M5X>h?gZ; z_WR@GWsWBHlkWZLG~-x4_e0IU-wN|=`~4Mzm|H`X*D+}?n51f6w`j5Qg}Ehy8jV3* zdSAo4`E5V%-my%LLGtmx>z9Nx+IX0Z7tF|PKCow*!!3mgmqj9sIi{SfUplq6=DfN8 z=lwj}_nC4`I9-q^a{d0D9G-i>Z*IuzKJmXw>~`W^sT#KEw$#!^2b^oVe^xzsX4QNk zbk6}}!A9+hMt3Eq@?7pK`VnFcURO3T?2+H9(_khbd_d|1rvfhvYXRFNCJp95#-7GC z42K*d4%~N$Vi5cPSv}#mb;57m4YBMw;ZC=!8{gi&P-dU7TiNMzE6ax(Nk*o4mxe>r z1#T4l_Z5ikImKP^=l{j>=J1=Wdww=w-^u#t2ebAJ);~Z0W>>Hp{Mi?}hRNdJ$zSTz1DzWV%D43mkS!_z+xmns=>2K;2O{yupn`~3z_hTngW zr${jR{WD-%^GA`T;%5nCnq6eWY=6;&=fbQ%ewr{c+Ic!O`3nj>6lO{JQPJr8>EB`P z3p+FpyK~!!KU&P1*7_r(@wQXVmIJ#4k7f=YwQnCc~r+N zW|wJfm3iRs^4#Ocd1w4M()Q#;L$`nK$$yd?KA!Y_c`(s-#}>{>O!J&|=6w*iyE9-yGX!bR_{N$Lq@nk;Lg)d9K@m^2d i9%MF`%My})KTA)&Ab%uTzkM~RO!IX0b6Mw<&;$UHd@un3 literal 0 HcmV?d00001 diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index 432be82ad4..ec9d71a28d 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -89,25 +89,25 @@ def mi_to_html( mi, field_list=None, default_author_link=None, use_roman_numbers=True, rating_font='Liberation Serif', rtl=False, comments_heading_pos='hide', - for_qt=False, vertical_fields=() + for_qt=False, vertical_fields=(), show_links=True, ): - show_links = not hasattr(mi, '_bd_dbwref') - + link_markup = '↗️' + if for_qt: + link_markup = '' def get_link_map(column): - if not show_links: - return {} try: return mi.link_maps[column] - except (KeyError, ValueError): - return {column:{}} + except Exception: + return {} def add_other_link(field, field_value): - link = get_link_map(field).get(field_value, None) - if link: - return (' %s'%(_('Click to open {}').format(link), link, _('(item link)'))) - else: - return '' + if show_links: + link = get_link_map(field).get(field_value) + if link: + link = prepare_string_for_xml(link, True) + return ' {2}'.format(_('Click to open'), link, link_markup) + return '' if field_list is None: field_list = get_field_list(mi) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index f39fbf5b12..b567c86d50 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -41,14 +41,18 @@ from polyglot.binary import from_hex_bytes InternetSearch = namedtuple('InternetSearch', 'author where') +def db_for_mi(mi): + from calibre.gui2.ui import get_gui + lp = getattr(mi, 'external_library_path', None) + if lp: + return get_gui().library_broker.get_library(lp), True + return get_gui().current_db, False + + def set_html(mi, html, text_browser): - if hasattr(mi, '_bd_dbwref') and mi._bd_dbwref is not None: - db = mi._bd_dbwref - else: - from calibre.gui2.ui import get_gui - db = get_gui().current_db book_id = getattr(mi, 'id', None) search_paths = [] + db, _ = db_for_mi(mi) if db and book_id is not None: path = db.abspath(book_id, index_is_id=True) if path: @@ -209,18 +213,15 @@ def comments_pat(): def render_html(mi, vertical, widget, all_fields=False, render_data_func=None, pref_name='book_display_fields', pref_value=None): # {{{ - if hasattr(mi, '_bd_dbwref') and mi._bd_dbwref is not None: - db = mi._bd_dbwref - else: - from calibre.gui2.ui import get_gui - db = get_gui().current_db + db, is_external = db_for_mi(mi) + show_links = not is_external func = render_data_func or partial(render_data, vertical_fields=db.prefs.get('book_details_vertical_categories') or ()) try: - table, comment_fields = func(mi, all_fields=all_fields, + table, comment_fields = func(mi, all_fields=all_fields, show_links=show_links, use_roman_numbers=config['use_roman_numerals_for_series_number'], pref_name=pref_name) except TypeError: - table, comment_fields = func(mi, all_fields=all_fields, + table, comment_fields = func(mi, all_fields=all_fields, show_links=show_links, use_roman_numbers=config['use_roman_numerals_for_series_number']) def color_to_string(col): @@ -278,7 +279,7 @@ def get_field_list(fm, use_defaults=False, pref_name='book_display_fields', mi=N def render_data(mi, use_roman_numbers=True, all_fields=False, pref_name='book_display_fields', - vertical_fields=()): + vertical_fields=(), show_links=True): field_list = get_field_list(getattr(mi, 'field_metadata', field_metadata), pref_name=pref_name, mi=mi) field_list = [(x, all_fields or display) for x, display in field_list] @@ -286,7 +287,7 @@ def render_data(mi, use_roman_numbers=True, all_fields=False, pref_name='book_di mi, field_list=field_list, use_roman_numbers=use_roman_numbers, rtl=is_rtl(), rating_font=rating_font(), default_author_link=default_author_link(), comments_heading_pos=gprefs['book_details_comments_heading_pos'], for_qt=True, - vertical_fields=vertical_fields + vertical_fields=vertical_fields, show_links=show_links ) # }}} diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index e205a21119..c56ec17900 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -3,7 +3,6 @@ import textwrap -import weakref from qt.core import ( QAction, QApplication, QBrush, QCheckBox, QDialog, QDialogButtonBox, QGridLayout, @@ -201,13 +200,12 @@ class BookInfo(QDialog): self.slave_connected = False if library_path is not None: self.view = None - from calibre.db.legacy import LibraryDatabase - db = LibraryDatabase(library_path, read_only=True, is_second_db=True) + db = get_gui().library_broker.get_library(library_path) if book_id is None: ids = db.new_api.search(query) if len(ids) == 0: raise ValueError(_('Query "{}" found no books').format(query)) - book_id = sorted([i for i in ids])[0] + book_id = sorted(ids)[0] if not db.new_api.has_id(book_id): raise ValueError(_("Book {} doesn't exist").format(book_id)) mi = db.new_api.get_metadata(book_id, get_cover=False) @@ -216,7 +214,7 @@ class BookInfo(QDialog): mi.format_files = dict() mi.formats = list() mi.marked = '' - mi._bd_dbwref = weakref.proxy(db) + mi.external_library_path = library_path self.refresh(row, mi) else: self.view = view From 5d6664b00250f9ce174a88cf0982e1a9dc774bf0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 31 Mar 2023 13:29:49 +0530 Subject: [PATCH 0384/2055] Simplify code for managing multiple book info dialogs --- src/calibre/gui2/actions/show_book_details.py | 40 ++++++------------- src/calibre/gui2/dialogs/book_info.py | 16 ++++---- 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/src/calibre/gui2/actions/show_book_details.py b/src/calibre/gui2/actions/show_book_details.py index 6a79e847a1..1d707cbfa6 100644 --- a/src/calibre/gui2/actions/show_book_details.py +++ b/src/calibre/gui2/actions/show_book_details.py @@ -23,61 +23,45 @@ class ShowBookDetailsAction(InterfaceAction): def genesis(self): self.qaction.triggered.connect(self.show_book_info) self.memory = [] - self.dialogs = [None, ] def show_book_info(self, *args, **kwargs): - if self.gui.current_view() is not self.gui.library_view: - error_dialog(self.gui, _('No detailed info available'), - _('No detailed information is available for books ' - 'on the device.')).exec() - return library_path = kwargs.get('library_path', None) book_id = kwargs.get('book_id', None) library_id = kwargs.get('library_id', None) query = kwargs.get('query', None) index = self.gui.library_view.currentIndex() + if self.gui.current_view() is not self.gui.library_view and not library_path: + error_dialog(self.gui, _('No detailed info available'), + _('No detailed information is available for books ' + 'on the device.')).exec() + return if library_path or index.isValid(): - # Window #0 is slaved to changes in the book list. As such - # it must not be used for details from other libraries. - for dn,v in enumerate(self.dialogs): - if dn == 0 and library_path: - continue - if v is None: - break - else: - self.dialogs.append(None) - dn += 1 - try: d = BookInfo(self.gui, self.gui.library_view, index, - self.gui.book_details.handle_click, dialog_number=dn, + self.gui.book_details.handle_click, library_id=library_id, library_path=library_path, book_id=book_id, query=query) except ValueError as e: error_dialog(self.gui, _('Book not found'), str(e)).exec() return d.open_cover_with.connect(self.gui.bd_open_cover_with, type=Qt.ConnectionType.QueuedConnection) - self.dialogs[dn] = d self.memory.append(d) d.closed.connect(self.closed, type=Qt.ConnectionType.QueuedConnection) d.show() def shutting_down(self): - for d in self.dialogs: - if d: - d.done(0) + for d in self.memory: + d.close() + self.memory = [] def library_about_to_change(self, *args): - for i,d in enumerate(self.dialogs): - if i == 0: - continue - if d: - d.done(0) + for d in self.memory: + if d.for_external_library: + d.close() def closed(self, d): try: d.closed.disconnect(self.closed) - self.dialogs[d.dialog_number] = None self.memory.remove(d) except ValueError: pass diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index c56ec17900..1ef95c593f 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -135,10 +135,10 @@ class BookInfo(QDialog): closed = pyqtSignal(object) open_cover_with = pyqtSignal(object, object) - def __init__(self, parent, view, row, link_delegate, dialog_number=None, + def __init__(self, parent, view, row, link_delegate, library_id=None, library_path=None, book_id=None, query=None): QDialog.__init__(self, None, flags=Qt.WindowType.Window) - self.dialog_number = dialog_number + self.for_external_library = bool(library_path) self.library_id = library_id self.marked = None self.gui = parent @@ -179,7 +179,7 @@ class BookInfo(QDialog): hl.setContentsMargins(0, 0, 0, 0) l2.addLayout(hl, l2.rowCount(), 0, 1, -1) hl.addWidget(self.fit_cover), hl.addStretch() - if self.dialog_number == 0: + if not self.for_external_library: self.previous_button = QPushButton(QIcon.ic('previous.png'), _('&Previous'), self) self.previous_button.clicked.connect(self.previous) l2.addWidget(self.previous_button, l2.rowCount(), 0) @@ -218,7 +218,7 @@ class BookInfo(QDialog): self.refresh(row, mi) else: self.view = view - if dialog_number == 0: + if not self.for_external_library: self.slave_connected = True self.view.model().new_bookdisplay_data.connect(self.slave) self.refresh(row) @@ -245,9 +245,9 @@ class BookInfo(QDialog): pass def geometry_string(self, txt): - if self.dialog_number is None or self.dialog_number == 0: - return txt - return txt + '_' + str(self.dialog_number) + if self.for_external_library: + txt += '_' + 'for_external_library' + return txt def sizeHint(self): try: @@ -378,7 +378,7 @@ class BookInfo(QDialog): # Indicates books was deleted from library, or row numbers have # changed return - if self.dialog_number == 0: + if not self.for_external_library: self.previous_button.setEnabled(False if row == 0 else True) self.next_button.setEnabled(False if row == self.view.model().rowCount(QModelIndex())-1 else True) self.setWindowTitle(mi.title + ' ' + _('(the current book)')) From 00832ca25baf43d78366a2920a976f11707907a6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 31 Mar 2023 13:38:32 +0530 Subject: [PATCH 0385/2055] Final minor cleanup for previous PR --- src/calibre/gui2/dialogs/tag_list_editor.py | 4 +--- src/calibre/gui2/ui.py | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index d0436dc238..df4e8e05d6 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -680,7 +680,5 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.table.sortByColumn(2, Qt.SortOrder(self.was_order)) def accepted(self): - self.links = {} - for r in range(0, self.table.rowCount()): - self.links[self.table.item(r, 0).text()] = self.table.item(r, 3).text() + self.links = {self.table.item(r, 0).text():self.table.item(r, 3).text() for r in range(self.table.rowCount())} self.save_geometry() diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 2d6dafea63..93013cf814 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -698,6 +698,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ library_id = decode_library_id(library_id) library_path = self.library_broker.path_for_library_id(library_id) if library_path is None: + prints('Ignoring unknown library id', library_id, file=sys.stderr) return try: book_id = int(book_id) From 899ffb4e6120b5358eab2a4fc027430699924dfb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 31 Mar 2023 14:01:17 +0530 Subject: [PATCH 0386/2055] Fix typo causing book specific link map to contain entries for all items --- src/calibre/db/cache.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 00747ae09e..76e2925973 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2387,12 +2387,15 @@ class Cache: links = {} def add_links_for_field(f): table = self.fields[f].table - lm = table.link_map - vm = table.id_map - d = {vm.get(fid):v for fid, v in lm.items() if v} - d.pop(None, None) - if d: - links[f] = d + field_ids = self._field_ids_for(f, book_id) + if field_ids: + lm = table.link_map + id_link_map = {fid:lm.get(fid) for fid in field_ids} + vm = table.id_map + d = {vm.get(fid):v for fid, v in id_link_map.items() if v} + d.pop(None, None) + if d: + links[f] = d for field in ('authors', 'publisher', 'series', 'tags'): add_links_for_field(field) for field in self.field_metadata.custom_field_keys(include_composites=False): From 5f515131a5660700b217a35c7933b9e5206f4690 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 31 Mar 2023 14:38:38 +0530 Subject: [PATCH 0387/2055] Content server: Add support for link_maps Only URLs of type http(s) are allowed --- imgsrc/srv/external-link.svg | 1 + src/calibre/db/cache.py | 2 +- src/calibre/srv/metadata.py | 3 +++ src/pyj/book_list/book_details.pyj | 14 +++++++++++--- 4 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 imgsrc/srv/external-link.svg diff --git a/imgsrc/srv/external-link.svg b/imgsrc/srv/external-link.svg new file mode 100644 index 0000000000..7072d735b9 --- /dev/null +++ b/imgsrc/srv/external-link.svg @@ -0,0 +1 @@ + diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 76e2925973..cca798bd4a 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2386,9 +2386,9 @@ class Cache: return cached links = {} def add_links_for_field(f): - table = self.fields[f].table field_ids = self._field_ids_for(f, book_id) if field_ids: + table = self.fields[f].table lm = table.link_map id_link_map = {fid:lm.get(fid) for fid in field_ids} vm = table.id_map diff --git a/src/calibre/srv/metadata.py b/src/calibre/srv/metadata.py index 89e11ce94d..eaef505f1e 100644 --- a/src/calibre/srv/metadata.py +++ b/src/calibre/srv/metadata.py @@ -87,6 +87,9 @@ def book_as_json(db, book_id): langs = ans.get('languages') if langs: ans['lang_names'] = {l:calibre_langcode_to_name(l) for l in langs} + link_maps = db.get_all_link_maps_for_book(book_id) + if link_maps: + ans['link_maps'] = link_maps return ans diff --git a/src/pyj/book_list/book_details.pyj b/src/pyj/book_list/book_details.pyj index fd958d7961..b0187d665f 100644 --- a/src/pyj/book_list/book_details.pyj +++ b/src/pyj/book_list/book_details.pyj @@ -190,6 +190,7 @@ def render_metadata(mi, table, book_id, iframe_css): # {{{ else: fields = filter(allowed_fields, fields) comments = v'[]' + link_maps = mi.link_maps or v'{}' def add_row(field, name, val, is_searchable=False, is_html=False, join=None, search_text=None, use_quotes=True): if val is undefined or val is None: @@ -199,18 +200,25 @@ def render_metadata(mi, table, book_id, iframe_css): # {{{ def add_val(v): if not v.appendChild: v += '' + parent = table.lastChild.lastChild if is_searchable: text_rep = search_text or v - table.lastChild.lastChild.appendChild(E.a( + parent.appendChild(E.a( v, title=_('Click to see books with {0}: {1}').format(name, text_rep), class_='blue-link', href=href_for_search(is_searchable, text_rep, use_quotes=use_quotes) )) + if link_maps[field] and link_maps[field][text_rep]: + url = link_maps[field][text_rep] + if url.startswith('https://') or url.startswith('http://'): + parent.appendChild(document.createTextNode(' ')) + parent.appendChild(E.a( + svgicon('external-link'), title=_('Click to open') + ': ' + url, href=url, target='_new', class_='blue-link')) else: if v.appendChild: - table.lastChild.lastChild.appendChild(v) + parent.appendChild(v) else: - table.lastChild.lastChild.appendChild(document.createTextNode(v)) + parent.appendChild(document.createTextNode(v)) table.appendChild(E.tr(E.td(name + ':'), E.td())) if is_html and /[<>]/.test(val + ''): From ecb22f5e618d56dc3193e3ab9cc63106914b9f2a Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Fri, 31 Mar 2023 15:31:12 +0100 Subject: [PATCH 0388/2055] Revert "Simplify code for managing multiple book info dialogs" This reverts commit 5d6664b00250f9ce174a88cf0982e1a9dc774bf0. --- src/calibre/gui2/actions/show_book_details.py | 40 +++++++++++++------ src/calibre/gui2/dialogs/book_info.py | 16 ++++---- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/calibre/gui2/actions/show_book_details.py b/src/calibre/gui2/actions/show_book_details.py index 1d707cbfa6..6a79e847a1 100644 --- a/src/calibre/gui2/actions/show_book_details.py +++ b/src/calibre/gui2/actions/show_book_details.py @@ -23,45 +23,61 @@ class ShowBookDetailsAction(InterfaceAction): def genesis(self): self.qaction.triggered.connect(self.show_book_info) self.memory = [] + self.dialogs = [None, ] def show_book_info(self, *args, **kwargs): + if self.gui.current_view() is not self.gui.library_view: + error_dialog(self.gui, _('No detailed info available'), + _('No detailed information is available for books ' + 'on the device.')).exec() + return library_path = kwargs.get('library_path', None) book_id = kwargs.get('book_id', None) library_id = kwargs.get('library_id', None) query = kwargs.get('query', None) index = self.gui.library_view.currentIndex() - if self.gui.current_view() is not self.gui.library_view and not library_path: - error_dialog(self.gui, _('No detailed info available'), - _('No detailed information is available for books ' - 'on the device.')).exec() - return if library_path or index.isValid(): + # Window #0 is slaved to changes in the book list. As such + # it must not be used for details from other libraries. + for dn,v in enumerate(self.dialogs): + if dn == 0 and library_path: + continue + if v is None: + break + else: + self.dialogs.append(None) + dn += 1 + try: d = BookInfo(self.gui, self.gui.library_view, index, - self.gui.book_details.handle_click, + self.gui.book_details.handle_click, dialog_number=dn, library_id=library_id, library_path=library_path, book_id=book_id, query=query) except ValueError as e: error_dialog(self.gui, _('Book not found'), str(e)).exec() return d.open_cover_with.connect(self.gui.bd_open_cover_with, type=Qt.ConnectionType.QueuedConnection) + self.dialogs[dn] = d self.memory.append(d) d.closed.connect(self.closed, type=Qt.ConnectionType.QueuedConnection) d.show() def shutting_down(self): - for d in self.memory: - d.close() - self.memory = [] + for d in self.dialogs: + if d: + d.done(0) def library_about_to_change(self, *args): - for d in self.memory: - if d.for_external_library: - d.close() + for i,d in enumerate(self.dialogs): + if i == 0: + continue + if d: + d.done(0) def closed(self, d): try: d.closed.disconnect(self.closed) + self.dialogs[d.dialog_number] = None self.memory.remove(d) except ValueError: pass diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index 1ef95c593f..c56ec17900 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -135,10 +135,10 @@ class BookInfo(QDialog): closed = pyqtSignal(object) open_cover_with = pyqtSignal(object, object) - def __init__(self, parent, view, row, link_delegate, + def __init__(self, parent, view, row, link_delegate, dialog_number=None, library_id=None, library_path=None, book_id=None, query=None): QDialog.__init__(self, None, flags=Qt.WindowType.Window) - self.for_external_library = bool(library_path) + self.dialog_number = dialog_number self.library_id = library_id self.marked = None self.gui = parent @@ -179,7 +179,7 @@ class BookInfo(QDialog): hl.setContentsMargins(0, 0, 0, 0) l2.addLayout(hl, l2.rowCount(), 0, 1, -1) hl.addWidget(self.fit_cover), hl.addStretch() - if not self.for_external_library: + if self.dialog_number == 0: self.previous_button = QPushButton(QIcon.ic('previous.png'), _('&Previous'), self) self.previous_button.clicked.connect(self.previous) l2.addWidget(self.previous_button, l2.rowCount(), 0) @@ -218,7 +218,7 @@ class BookInfo(QDialog): self.refresh(row, mi) else: self.view = view - if not self.for_external_library: + if dialog_number == 0: self.slave_connected = True self.view.model().new_bookdisplay_data.connect(self.slave) self.refresh(row) @@ -245,9 +245,9 @@ class BookInfo(QDialog): pass def geometry_string(self, txt): - if self.for_external_library: - txt += '_' + 'for_external_library' - return txt + if self.dialog_number is None or self.dialog_number == 0: + return txt + return txt + '_' + str(self.dialog_number) def sizeHint(self): try: @@ -378,7 +378,7 @@ class BookInfo(QDialog): # Indicates books was deleted from library, or row numbers have # changed return - if not self.for_external_library: + if self.dialog_number == 0: self.previous_button.setEnabled(False if row == 0 else True) self.next_button.setEnabled(False if row == self.view.model().rowCount(QModelIndex())-1 else True) self.setWindowTitle(mi.title + ' ' + _('(the current book)')) From 8e22dd8822c3a5523fc10e3720e9a84f2b747f29 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Fri, 31 Mar 2023 17:05:17 +0100 Subject: [PATCH 0389/2055] Remake a change that shouldn't have been reverted. --- src/calibre/gui2/actions/show_book_details.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/calibre/gui2/actions/show_book_details.py b/src/calibre/gui2/actions/show_book_details.py index 6a79e847a1..164feb7958 100644 --- a/src/calibre/gui2/actions/show_book_details.py +++ b/src/calibre/gui2/actions/show_book_details.py @@ -22,20 +22,19 @@ class ShowBookDetailsAction(InterfaceAction): def genesis(self): self.qaction.triggered.connect(self.show_book_info) - self.memory = [] self.dialogs = [None, ] def show_book_info(self, *args, **kwargs): - if self.gui.current_view() is not self.gui.library_view: - error_dialog(self.gui, _('No detailed info available'), - _('No detailed information is available for books ' - 'on the device.')).exec() - return library_path = kwargs.get('library_path', None) book_id = kwargs.get('book_id', None) library_id = kwargs.get('library_id', None) query = kwargs.get('query', None) index = self.gui.library_view.currentIndex() + if self.gui.current_view() is not self.gui.library_view and not library_path: + error_dialog(self.gui, _('No detailed info available'), + _('No detailed information is available for books ' + 'on the device.')).exec() + return if library_path or index.isValid(): # Window #0 is slaved to changes in the book list. As such # it must not be used for details from other libraries. @@ -58,7 +57,6 @@ class ShowBookDetailsAction(InterfaceAction): d.open_cover_with.connect(self.gui.bd_open_cover_with, type=Qt.ConnectionType.QueuedConnection) self.dialogs[dn] = d - self.memory.append(d) d.closed.connect(self.closed, type=Qt.ConnectionType.QueuedConnection) d.show() @@ -78,7 +76,6 @@ class ShowBookDetailsAction(InterfaceAction): try: d.closed.disconnect(self.closed) self.dialogs[d.dialog_number] = None - self.memory.remove(d) except ValueError: pass else: From eacbbc1f8858efc614ff069afb2981fec4cd3fd0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 31 Mar 2023 22:49:34 +0530 Subject: [PATCH 0390/2055] Update Saechsische Zeitung --- recipes/saechsische.recipe | 119 ++++++++++++++++++++++++++----------- 1 file changed, 84 insertions(+), 35 deletions(-) diff --git a/recipes/saechsische.recipe b/recipes/saechsische.recipe index 33a64d3fb6..dee96ba5e7 100644 --- a/recipes/saechsische.recipe +++ b/recipes/saechsische.recipe @@ -1,22 +1,21 @@ #!/usr/bin/env python ## -# Written: March 2020 -# Version: 1.0 -# Last update: 2020-03-27 +## Written: March 2020 +## Version: 1.1 +## Last update: 2023-03-31 ## from __future__ import unicode_literals, division, absolute_import, print_function + ''' Fetch RSS-Feeds from saechsische.de ''' from calibre.web.feeds.news import BasicNewsRecipe - def classes(classes): q = frozenset(classes.split(' ')) - return dict( - attrs={'class': lambda x: x and frozenset(x.split()).intersection(q)} - ) + return dict(attrs={'class': lambda x: x and frozenset(x.split()).intersection(q)}) + class Saechsische(BasicNewsRecipe): @@ -32,45 +31,95 @@ class Saechsische(BasicNewsRecipe): no_stylesheets = True remove_javascript = True remove_empty_feeds = True + compress_news_images = True + compress_news_images_auto_size = 8 scale_news_images_to_device = True + delay = 1 ignore_duplicate_articles = {'title', 'url'} - cover_url = 'https://www.saechsische.de/img/logo.svg' + + cover_url = 'https://www.saechsische.de/img/logo.svg' feeds = [ ('Dresden', 'feed://www.saechsische.de/rss/dresden'), ('Sachsen', 'feed://saechsische.de/rss/sachsen'), - ('Dynamo', 'feed://www.saechsische.de/rss/dynamo'), + ('Deutschland und Welt', 'feed://www.saechsische.de/rss/deutschland-welt'), ('Politik', 'feed://www.saechsische.de/rss/politik'), ('Wirtschaft', 'feed://www.saechsische.de/rss/wirtschaft'), ('Feuilleton', 'feed://www.saechsische.de/rss/feuilleton'), ('Sport', 'feed://www.saechsische.de/rss/sport'), - ('Deutschland und Welt', 'feed://www.saechsische.de/rss/deutschland-welt'), - # ('Bautzen', 'feed://www.saechsische.de/rss/bautzen'), - # ('Bischofswerda', 'feed://www.saechsische.de/rss/bischofswerda'), - # ('Dippoldiswalde', 'feed://www.saechsische.de/rss/dippoldiswalde'), - # ('Döbeln', 'feed://www.saechsische.de/rss/doebeln'), - # ('Freital', 'feed://www.saechsische.de/rss/freital'), - # ('Großenhain', 'feed://www.saechsische.de/rss/grossenhain'), - # ('Görlitz', 'feed://www.saechsische.de/rss/goerlitz'), - # ('Kamenz', 'feed://www.saechsische.de/rss/kamenz'), - # ('Löbau', 'feed://www.saechsische.de/rss/loebau'), - # ('Meißen', 'feed://www.saechsische.de/rss/meissen'), - # ('Niesky', 'feed://www.saechsische.de/rss/niesky'), - # ('Pirna', 'feed://www.saechsische.de/rss/pirna'), - # ('Radeberg', 'feed://www.saechsische.de/rss/radeberg'), - # ('Radebeul', 'feed://www.saechsische.de/rss/radebeul'), - # ('Riesa', 'feed://www.saechsische.de/rss/riesa'), - # ('Sebnitz', 'feed://www.saechsische.de/rss/sebnitz'), - # ('Zittau', 'feed://www.saechsische.de/rss/zittau'), + #('Dynamo', 'feed://www.saechsische.de/rss/dynamo'), + #('Bautzen', 'feed://www.saechsische.de/rss/bautzen'), + #('Bischofswerda', 'feed://www.saechsische.de/rss/bischofswerda'), + #('Dippoldiswalde', 'feed://www.saechsische.de/rss/dippoldiswalde'), + #('Döbeln', 'feed://www.saechsische.de/rss/doebeln'), + #('Freital', 'feed://www.saechsische.de/rss/freital'), + #('Großenhain', 'feed://www.saechsische.de/rss/grossenhain'), + #('Görlitz', 'feed://www.saechsische.de/rss/goerlitz'), + #('Kamenz', 'feed://www.saechsische.de/rss/kamenz'), + #('Löbau', 'feed://www.saechsische.de/rss/loebau'), + #('Meißen', 'feed://www.saechsische.de/rss/meissen'), + #('Niesky', 'feed://www.saechsische.de/rss/niesky'), + #('Pirna', 'feed://www.saechsische.de/rss/pirna'), + #('Radeberg', 'feed://www.saechsische.de/rss/radeberg'), + #('Radebeul', 'feed://www.saechsische.de/rss/radebeul'), + #('Riesa', 'feed://www.saechsische.de/rss/riesa'), + #('Sebnitz', 'feed://www.saechsische.de/rss/sebnitz'), + #('Zittau', 'feed://www.saechsische.de/rss/zittau'), ] + template_css = ''' +.article_date { color: gray; font-family: monospace;} +.article_description { text-indent: 0pt; } +a.article { font-weight: bold; text-align:left; } +a.feed { font-weight: bold; } +.calibre_navbar { font-size: 200% !important; } +''' + + extra_css = ''' + h2 {margin-top: 0em;} + ''' keep_only_tags = [ - dict(name='article', attrs={'class': 'article-detail'}), - ] + dict(name='article', attrs={'class':'article-detail'}), + ] - remove_tags = [ - classes('article-fill'), - dict(name='div', attrs={'class': 'article-related-container'}), - dict(name='div', attrs={'id': 'article-header'}), - dict(name='span', attrs={'class': 'article-plus'}), - ] + remove_tags = [ classes('article-fill'), + dict(name='div', attrs={'class':'related-articles'}), + dict(name='a', attrs={'class':'article-remember-link'}), + dict(name='a', attrs={'href':'https://www.saechsische.de/dresden'}), + dict(name='a', attrs={'href':'https://www.saechsische.de/content/newsletter-lp?utm_content=dresden_kompakt'}), + dict(name='div', attrs={'class':'article-detail-socials'}), + dict(name='div', attrs={'class':'d-desktop-none'}), + dict(name='div', attrs={'class':'floating-share-icon'}), + ] + + def parse_feeds(self): + # Call parent's method. + feeds = BasicNewsRecipe.parse_feeds(self) + # Loop through all feeds. + for feed in feeds: + # Loop through all articles in feed. + for article in feed.articles[:]: + # Remove articles with '...' in the url. + if '/anzeige/' in article.url: + print('Removing:',article.title) + feed.articles.remove(article) + elif 'newsletter-dresden' in article.url: + print('Removing:',article.title) + feed.articles.remove(article) + # Remove articles with '...' in the title. + elif 'Newsblog' in article.title: + print('Removing:',article.title) + feed.articles.remove(article) + elif 'Podcast' in article.title: + print('Removing:',article.title) + feed.articles.remove(article) + return feeds + + def preprocess_raw_html(self, raw, url): + # remove Newsblogs, articles requiring login and advertisements + unwanted_article_keywords = ['unser Newsblog', 'Zum Login', '00:00 Uhr',] + for keyword in unwanted_article_keywords: + if keyword in raw: + print('Skipping unwanted article with keyword(s):',keyword) + self.abort_article('Skipping unwanted article') + return raw From ee09a85cf43f9f187640496f7410ad8b692924af Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Fri, 31 Mar 2023 19:11:10 +0100 Subject: [PATCH 0391/2055] Add context menu item to get a book-details link --- src/calibre/gui2/book_details.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index b567c86d50..974b5656bf 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -462,6 +462,7 @@ def create_copy_links(menu, data=None): menu.addSeparator() link(_('Link to show book in calibre'), f'calibre://show-book/{library_id}/{book_id}') + link(_('Link to show book details in calibre'), f'calibre://book-details/{library_id}/{book_id}') if data: field = data.get('field') if data['type'] == 'author': From 86126438ad2995c33e3d8c96ef89850437e80ce6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 1 Apr 2023 07:56:14 +0530 Subject: [PATCH 0392/2055] Book details popup: Fix fields for external libraries not using the correct preference --- src/calibre/gui2/book_details.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index ad78b38a84..3a2bd2fcff 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -257,11 +257,7 @@ def render_html(mi, vertical, widget, all_fields=False, render_data_func=None, def get_field_list(fm, use_defaults=False, pref_name='book_display_fields', mi=None): - if mi is not None and hasattr(mi, '_bd_dbwref') and mi._bd_dbwref is not None: - db = mi._bd_dbwref - else: - from calibre.gui2.ui import get_gui - db = get_gui().current_db + db = db_for_mi(mi) if use_defaults: src = db.prefs.defaults else: From 9aa239b3a856f8c2b32fd6dd3808bef634e30b75 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 1 Apr 2023 09:33:36 +0530 Subject: [PATCH 0393/2055] ... --- src/calibre/gui2/book_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 3a2bd2fcff..b1a699fc2a 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -257,7 +257,7 @@ def render_html(mi, vertical, widget, all_fields=False, render_data_func=None, def get_field_list(fm, use_defaults=False, pref_name='book_display_fields', mi=None): - db = db_for_mi(mi) + db, _ = db_for_mi(mi) if use_defaults: src = db.prefs.defaults else: From 5df12a6434fe85e680c2bf9903a3b71bd98f980d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 1 Apr 2023 10:29:26 +0530 Subject: [PATCH 0394/2055] A new tweak in Preferences->Tweaks to control what program is run when clicking on URLs in calibre --- resources/default_tweaks.py | 11 +++++++++++ src/calibre/gui2/__init__.py | 27 ++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 3f8c016722..fa5ef4ea78 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -561,3 +561,14 @@ value_for_undefined_numbers_when_sorting = 0 # using these functions in composites can be very slow. # Default: False allow_template_database_functions_in_composites = False + + +#: Change the programs that are run when opening files/URLS +# By default, calibre passes URLs to the operating system to open using +# whatever default programs are configured there. Here you can override +# that by specifying the program to use, per URL type. For local files, +# the type is "file" for web links it is "http*". For example: +# openers_by_scheme = { "http*": "firefox %u" } will make calibre run firefox +# for https://whatever URLs. %u is replaced by the URL to be opened. The scheme +# takes a glob pattern allowing a single entry to match multiple URL types. +openers_by_scheme = {} diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 024e403ca7..768312bd1b 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -1410,13 +1410,30 @@ SanitizeLibraryPath = sanitize_env_vars # For old plugins def open_url(qurl): - # Qt 5 requires QApplication to be constructed before trying to use - # QDesktopServices::openUrl() - ensure_app() if isinstance(qurl, string_or_bytes): qurl = QUrl(qurl) + scheme = qurl.scheme().lower() or 'file' + import fnmatch + opener = [] + with suppress(Exception): + for scheme_pat, spec in tweaks['openers_by_scheme'].items(): + if fnmatch.fnmatch(scheme, scheme_pat): + with suppress(Exception): + import shlex + opener = shlex.split(spec) + break with sanitize_env_vars(): - QDesktopServices.openUrl(qurl) + if opener: + import subprocess + cmd = [x.replace('%u', qurl.toString()) for x in opener] + if DEBUG: + print('Running opener:', cmd) + subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + else: + # Qt 5 requires QApplication to be constructed before trying to use + # QDesktopServices::openUrl() + ensure_app() + QDesktopServices.openUrl(qurl) def safe_open_url(qurl): @@ -1426,7 +1443,7 @@ def safe_open_url(qurl): path = qurl.toLocalFile() ext = os.path.splitext(path)[-1].lower()[1:] if ext in ('exe', 'com', 'cmd', 'bat', 'sh', 'psh', 'ps1', 'vbs', 'js', 'wsf', 'vba', 'py', 'rb', 'pl', 'app'): - prints('Refusing to open file:', path) + prints('Refusing to open file:', path, file=sys.stderr) return open_url(qurl) From e16179c9da26360c043713b828f7453217b4ac87 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 1 Apr 2023 11:00:26 +0530 Subject: [PATCH 0395/2055] Fix #2012066 [Add "Restart Now" button when installing plugin from file](https://bugs.launchpad.net/calibre/+bug/2012066) --- src/calibre/gui2/dialogs/plugin_updater.py | 37 ++++++++++++---------- src/calibre/gui2/preferences/plugins.py | 9 +++--- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/calibre/gui2/dialogs/plugin_updater.py b/src/calibre/gui2/dialogs/plugin_updater.py index 0fd4658189..a05f49ed61 100644 --- a/src/calibre/gui2/dialogs/plugin_updater.py +++ b/src/calibre/gui2/dialogs/plugin_updater.py @@ -413,6 +413,25 @@ class DisplayPluginModel(QAbstractTableModel): _('Right-click to see more options')) +def notify_on_successful_install(parent, plugin): + d = info_dialog(parent, _('Success'), + _('Plugin {0} successfully installed under ' + '{1}. You may have to restart calibre ' + 'for the plugin to take effect.').format(plugin.name, plugin.type), + show_copy_button=False) + b = d.bb.addButton(_('&Restart calibre now'), QDialogButtonBox.ButtonRole.AcceptRole) + b.setIcon(QIcon.ic('lt.png')) + d.do_restart = False + + def rf(): + d.do_restart = True + b.clicked.connect(rf) + d.set_details('') + d.exec() + b.clicked.disconnect() + return d.do_restart + + class PluginUpdaterDialog(SizePersistedDialog): initial_extra_size = QSize(350, 100) @@ -716,23 +735,7 @@ class PluginUpdaterDialog(SizePersistedDialog): widget.gui = self.gui widget.check_for_add_to_toolbars(plugin, previously_installed=plugin.name in installed_plugins) self.gui.status_bar.showMessage(_('Plugin installed: %s') % display_plugin.name) - d = info_dialog(self.gui, _('Success'), - _('Plugin {0} successfully installed under ' - '{1}. You may have to restart calibre ' - 'for the plugin to take effect.').format(plugin.name, plugin.type), - show_copy_button=False) - b = d.bb.addButton(_('&Restart calibre now'), QDialogButtonBox.ButtonRole.AcceptRole) - b.setIcon(QIcon.ic('lt.png')) - d.do_restart = False - - def rf(): - d.do_restart = True - b.clicked.connect(rf) - d.set_details('') - d.exec() - b.clicked.disconnect() - do_restart = d.do_restart - + do_restart = notify_on_successful_install(self.gui, plugin) display_plugin.plugin = plugin # We cannot read the 'actual' version information as the plugin will not be loaded yet display_plugin.installed_version = display_plugin.available_version diff --git a/src/calibre/gui2/preferences/plugins.py b/src/calibre/gui2/preferences/plugins.py index 0fd267b929..7316c897ad 100644 --- a/src/calibre/gui2/preferences/plugins.py +++ b/src/calibre/gui2/preferences/plugins.py @@ -331,14 +331,13 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self._plugin_model.endResetModel() self.changed_signal.emit() self.check_for_add_to_toolbars(plugin, previously_installed=plugin.name in installed_plugins) - info_dialog(self, _('Success'), - _('Plugin {0} successfully installed under ' - '{1}. You may have to restart calibre ' - 'for the plugin to take effect.').format(plugin.name, plugin.type), - show=True, show_copy_button=False) + from calibre.gui2.dialogs.plugin_updater import notify_on_successful_install + do_restart = notify_on_successful_install(self, plugin) idx = self._plugin_model.plugin_to_index_by_properties(plugin) if idx.isValid(): self.highlight_index(idx) + if do_restart: + self.restart_now.emit() else: error_dialog(self, _('No valid plugin path'), _('%s is not a valid plugin path')%path).exec() From e11b8c8b46989ca68a18339aa8bbd6335e4c7ac6 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 1 Apr 2023 08:59:45 +0100 Subject: [PATCH 0396/2055] If a show-details link references the current library, use the current db instead of opening it again. --- src/calibre/gui2/actions/show_book_details.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/gui2/actions/show_book_details.py b/src/calibre/gui2/actions/show_book_details.py index 164feb7958..00e993f0b0 100644 --- a/src/calibre/gui2/actions/show_book_details.py +++ b/src/calibre/gui2/actions/show_book_details.py @@ -28,6 +28,8 @@ class ShowBookDetailsAction(InterfaceAction): library_path = kwargs.get('library_path', None) book_id = kwargs.get('book_id', None) library_id = kwargs.get('library_id', None) + if library_path is not None and self.gui.library_broker.is_gui_library(library_path): + library_path = library_id = None query = kwargs.get('query', None) index = self.gui.library_view.currentIndex() if self.gui.current_view() is not self.gui.library_view and not library_path: From fe60fd8125390a841a227e68e07a66bda5b907de Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 1 Apr 2023 11:50:29 +0100 Subject: [PATCH 0397/2055] Big oops. I broke the category editor. --- src/calibre/gui2/dialogs/tag_list_editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index df4e8e05d6..f7a827bc12 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -546,7 +546,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.table.blockSignals(False) def finish_editing(self, edited_item): - if edited_item.column != 0: + if edited_item.column() != 0: return if not edited_item.text(): error_dialog(self, _('Item is blank'), _( From 72ea69f8d966590a73f4a44e0cd7d086ecbbcd59 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 1 Apr 2023 21:55:27 +0100 Subject: [PATCH 0398/2055] Fix links for series and custom series columns being confounded --- src/calibre/ebooks/metadata/book/render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index ec9d71a28d..48a8f43cef 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -320,7 +320,7 @@ def mi_to_html( sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), cls="series_name", series=p(series), href=search_action_with_data(st, series, book_id, field), tt=p(_('Click to see books in this series'))) - val += add_other_link('series', mi.series) + val += add_other_link(field, series) elif metadata['datatype'] == 'datetime': aval = getattr(mi, field) if is_date_undefined(aval): From 265f2d51efabe51d0460f7d2dca88f3d399f29bd Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 1 Apr 2023 22:21:38 +0100 Subject: [PATCH 0399/2055] Enhancement: add a "Copy all links" option to book details --- src/calibre/gui2/book_details.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index b1a699fc2a..ac0f75fbe6 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -451,9 +451,12 @@ def create_copy_links(menu, data=None): library_id = '_hex_-' + library_id.encode('utf-8').hex() book_id = get_gui().library_view.current_id + all_links = [] def link(text, url): def doit(): QApplication.instance().clipboard().setText(url) + nonlocal all_links + all_links.append(url) menu.addAction(QIcon.ic('edit-copy.png'), text, doit) menu.addSeparator() @@ -473,6 +476,13 @@ def create_copy_links(menu, data=None): fmt = fmt.upper() link(_('Link to view {} format of book').format(fmt.upper()), f'calibre://view-book/{library_id}/{book_id}/{fmt}') + if all_links: + menu.addSeparator() + mi = db.new_api.get_proxy_metadata(book_id) + all_links.insert(0, '') + all_links.insert(0, mi.get('title') + ' - ' + ' & '.join(mi.get('authors'))) + link(_('Copy all the above links'), '\n'.join(all_links)) + def details_context_menu_event(view, ev, book_info, add_popup_action=False, edit_metadata=None): url = view.anchorAt(ev.pos()) From 02f50bf6ce2fe3cf2c67838810b3c653bdc07c08 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 2 Apr 2023 11:24:27 +0530 Subject: [PATCH 0400/2055] Use both a colored border and an icon to indicate errors in line edits. Fixes #2007764 [Enhancement Request: Valid/invalid metadata display options](https://bugs.launchpad.net/calibre/+bug/2007764) --- src/calibre/gui2/search_box.py | 12 ++++++++---- src/calibre/gui2/widgets.py | 10 ++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index b2c148f49c..4f1fd86d0a 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -15,6 +15,7 @@ from qt.core import ( QIcon, QApplication, QKeyEvent) from calibre.gui2 import config, question_dialog, gprefs, QT_HIDDEN_CLEAR_ACTION +from calibre.gui2.widgets import stylesheet_for_lineedit from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor from calibre.gui2.dialogs.search import SearchDialog from calibre.utils.icu import primary_sort_key @@ -174,7 +175,7 @@ class SearchBox2(QComboBox): # {{{ def normalize_state(self): self.setToolTip(self.tool_tip_text) - self.line_edit.setStyleSheet('') + self.setStyleSheet('') self.show_parse_error_action(False) def text(self): @@ -194,11 +195,9 @@ class SearchBox2(QComboBox): # {{{ self.setFocus(Qt.FocusReason.OtherFocusReason) def show_parse_error_action(self, to_show, tooltip=''): - try: + if self.parse_error_action is not None: self.parse_error_action.setVisible(to_show) self.parse_error_action.setToolTip(tooltip) - except Exception: - pass def search_done(self, ok): if isinstance(ok, string_or_bytes): @@ -206,9 +205,14 @@ class SearchBox2(QComboBox): # {{{ self.show_parse_error_action(True, tooltip=ok) ok = False if not str(self.currentText()).strip(): + self.setStyleSheet('') self.clear(emit_search=False) return self._in_a_search = ok + if self.parse_error_action is not None and not ok: + self.setStyleSheet(stylesheet_for_lineedit(bool(ok), 'QComboBox')) + else: + self.setStyleSheet('') # Comes from the lineEdit control def key_pressed(self, event): diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index aff4faf2df..e3242feec5 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -555,6 +555,14 @@ def setup_status_actions(self: QLineEdit): self.status_actions[0].setVisible(False) self.status_actions[1].setVisible(False) + +def stylesheet_for_lineedit(ok, selector='QLineEdit') -> str: + if ok is None: + return '' + col = '#50c878' if ok else '#FF2400' + return f'{selector} {{ border: 2px solid {col}; border-radius: 3px }}' + + def update_status_actions(self: QLineEdit, ok, tooltip: str = ''): self.status_actions[0].setVisible(bool(ok)) self.status_actions[1].setVisible(not ok) @@ -564,6 +572,8 @@ def update_status_actions(self: QLineEdit, ok, tooltip: str = ''): self.status_actions[1].setVisible(False) else: self.status_actions[1].setToolTip(tooltip) + self.setStyleSheet(stylesheet_for_lineedit(ok)) + class LineEditIndicators: From 60985b35c164b013880b685107602d3f021deb16 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 2 Apr 2023 11:49:12 +0530 Subject: [PATCH 0401/2055] Remove no longer working recipe --- recipes/telegraph_uk.recipe | 121 ------------------------------------ 1 file changed, 121 deletions(-) delete mode 100644 recipes/telegraph_uk.recipe diff --git a/recipes/telegraph_uk.recipe b/recipes/telegraph_uk.recipe deleted file mode 100644 index ebb6dc2fae..0000000000 --- a/recipes/telegraph_uk.recipe +++ /dev/null @@ -1,121 +0,0 @@ -__license__ = 'GPL v3' -__copyright__ = '2008-2010, Darko Miletic ' -''' -telegraph.co.uk -''' -from calibre.web.feeds.news import BasicNewsRecipe -import json - - -def classes(classes): - q = frozenset(classes.split(' ')) - return dict( - attrs={'class': lambda x: x and frozenset(x.split()).intersection(q)} - ) - - -def absolutize(url): - if url.startswith('/'): - url = 'http://www.telegraph.co.uk' + url - return url - - -class TelegraphUK(BasicNewsRecipe): - title = 'The Telegraph (UK)' - __author__ = 'A10KiloHam, based on work by Darko Miletic and Sujata Raman' - description = 'News from United Kingdom' - oldest_article = 2 - category = 'news, politics, UK' - publisher = 'Telegraph Media Group ltd.' - max_articles_per_feed = 100 - no_stylesheets = True - language = 'en_GB' - encoding = 'utf-8' - needs_subscription = True - ignore_duplicate_articles = {'title', 'url'} - remove_empty_feeds = True - use_embedded_content = False - INDEX = 'https://www.telegraph.co.uk/' - LOGIN = 'https://secure.telegraph.co.uk/customer/secure/login/?redirectTo=https%3A%2F%2Fwww.telegraph.co.uk%2F' - PREFIX = u'https://www.telegraph.co.uk' - - feeds = [(u'News', u'http://www.telegraph.co.uk/news/rss.xml'), - (u'Politics', u'https://www.telegraph.co.uk/politics/rss.xml'), - (u'Business', u'http://www.telegraph.co.uk/business/rss.xml'), - (u'Money', u'http://www.telegraph.co.uk/money/rss.xml'), - (u'Technology', u'http://www.telegraph.co.uk/technology/rss.xml'), - (u'Science', u'http://www.telegraph.co.uk/science/rss.xml'), - (u'Opinion', u'http://www.telegraph.co.uk/opinion/rss.xml'), - (u'Travel', u'http://www.telegraph.co.uk/travel/rss.xml'), - (u'Culture', u'http://www.telegraph.co.uk/culture/rss.xml'), - (u'Lifestyle', u'http://www.telegraph.co.uk/lifestyle/rss.xml'), - (u'Money', u'http://www.telegraph.co.uk/opinion/rss.xml'), - (u'Opinion', u'http://www.telegraph.co.uk/money/rss.xml'), - (u'Fashion', u'http://www.telegraph.co.uk/fashion/rss.xml')] - - keep_only_tags = [ - classes( - 'lead-asset-image-container headline__heading footer-author article-author__meta' - ), - dict(itemprop='articleBody'), - ] - - remove_tags = [ - dict(name=['link', 'meta', 'style']), - classes('videoPlayer'), - ] - remove_attributes = 'width height'.split() - - def get_cover_url(self): - from datetime import date - cover = 'http://img.kiosko.net/' + str( - date.today().year - ) + '/' + date.today().strftime('%m') + '/' + date.today( - ).strftime('%d') + '/uk/daily_telegraph.750.jpg' - br = BasicNewsRecipe.get_browser(self) - try: - br.open(cover) - except: - index = 'http://en.kiosko.net/uk/np/daily_telegraph.html' - soup = self.index_to_soup(index) - for image in soup.findAll('img', src=True): - if image['src'].endswith('750.jpg'): - return image['src'] - self.log("\nCover unavailable") - cover = None - return cover - - def get_browser(self, *a, **kw): - USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0' - br = BasicNewsRecipe.get_browser(self, user_agent=USER_AGENT) - self.log('Forming login request...') - if self.username is not None and self.password is not None: - self.log('Starting login process...') - br.open(self.LOGIN) - br.select_form(nr=0) - br['email'] = self.username - br['password'] = self.password - self.log('Sending login request...') - br.submit() - return br - - def get_article_url(self, article): - url = article.get('link', None) - if 'picture-galleries' in url or 'pictures' in url or 'picturegalleries' in url: - url = None - return url - - def preprocess_html(self, soup): - for img in soup.findAll(attrs={'data-frz-src-array': True}): - img['style'] = '' - img.name = 'img' - d = json.loads(img['data-frz-src-array'].replace("'", '"')) - for item in d: - if int(item.get('width', 0)) > 700: - img['src'] = absolutize(item['src']) - break - for img in soup.findAll('div', attrs={'data-js': 'LazyImage'}): - img['style'] = '' - img.name = 'img' - img['src'] = img['data-srcset'].split()[0] - return soup From 5c3c70bbbd4df195ab04612b7d2f19e671e2a679 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 2 Apr 2023 12:10:52 +0530 Subject: [PATCH 0402/2055] Comments editor: Fix a regression in Qt 6 that caused standard keyboard shortcuts to trigger the underlying Qt implementations instead of our custom implementations --- src/calibre/gui2/comments_editor.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index b7cd80675b..094a65895e 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -262,6 +262,7 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{ self.base_url = None self._parent = weakref.ref(parent) self.comments_pat = re.compile(r'', re.DOTALL) + self.shortcut_map = {} def r(name, icon, text, checkable=False, shortcut=None): ac = QAction(QIcon.ic(icon + '.png'), text, self) @@ -271,6 +272,7 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{ setattr(self, 'action_'+name, ac) ac.triggered.connect(getattr(self, 'do_' + name)) if shortcut is not None: + self.shortcut_map[shortcut] = ac sc = shortcut if isinstance(shortcut, QKeySequence) else QKeySequence(shortcut) ac.setShortcut(sc) ac.setToolTip(text + f' [{sc.toString(QKeySequence.SequenceFormat.NativeText)}]') @@ -493,6 +495,13 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{ c.setCharFormat(QTextCharFormat()) self.update_cursor_position_actions() + def keyPressEvent(self, ev): + for sc, ac in self.shortcut_map.items(): + if isinstance(sc, QKeySequence.StandardKey) and ev.matches(sc): + ac.trigger() + return + return super().keyPressEvent(ev) + def do_copy(self): self.copy() self.focus_self() @@ -814,14 +823,13 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{ return c.hasSelection() def contextMenuEvent(self, ev): - menu = self.createStandardContextMenu() - for action in menu.actions(): - parts = action.text().split('\t') - if len(parts) == 2 and QKeySequence(QKeySequence.StandardKey.Paste).toString(QKeySequence.SequenceFormat.NativeText) in parts[-1]: - menu.insertAction(action, self.action_paste_and_match_style) - break - else: - menu.addAction(self.action_paste_and_match_style) + menu = QMenu(self) + for ac in 'undo redo -- cut copy paste paste_and_match_style -- select_all'.split(): + if ac == '--': + menu.addSeparator() + else: + ac = getattr(self, 'action_' + ac) + menu.addAction(ac) st = self.text() m = QMenu(_('Fonts')) m.addAction(self.action_bold), m.addAction(self.action_italic), m.addAction(self.action_underline) @@ -1239,6 +1247,6 @@ if __name__ == '__main__': w.html = '''

Test Heading

Test blockquote

He hadn't set out to have an affair, much less a long-term, devoted one.

hello''' - w.html = '

Testing a link.

\xa0

ss

' + w.html = '

Testing a link.

\xa0

ss

' app.exec() # print w.html From d84721866ca04f4d1cdc8c6ef83b85faf8057089 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 2 Apr 2023 12:24:57 +0530 Subject: [PATCH 0403/2055] Comments editor: When copying to clipboard, copy clean HTML rather than the junk Qt produces. Fixes #2012760 [Enhancement Request: Copy text alignment between html longtext columns](https://bugs.launchpad.net/calibre/+bug/2012760) --- src/calibre/gui2/comments_editor.py | 78 ++++++++++++++++------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index 094a65895e..4593957d03 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -225,6 +225,42 @@ def cleanup_qt_markup(root): # }}} +def fix_html(original_html, original_txt): + raw = original_html + raw = xml_to_unicode(raw, strip_encoding_pats=True, resolve_entities=True)[0] + comments_pat = re.compile(r'', re.DOTALL) + raw = comments_pat.sub('', raw) + if not original_txt and ' 1: + ans = '
%s
'%(''.join(elems)) + else: + ans = ''.join(elems) + if not ans.startswith('<'): + ans = '

%s

'%ans + return xml_replace_entities(ans) + class EditorWidget(QTextEdit, LineEditECM): # {{{ data_changed = pyqtSignal() @@ -261,7 +297,6 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{ self.em_size = f.horizontalAdvance('m') self.base_url = None self._parent = weakref.ref(parent) - self.comments_pat = re.compile(r'', re.DOTALL) self.shortcut_map = {} def r(name, icon, text, checkable=False, shortcut=None): @@ -736,40 +771,7 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{ @property def html(self): - raw = original_html = self.toHtml() - check = self.toPlainText().strip() - raw = xml_to_unicode(raw, strip_encoding_pats=True, resolve_entities=True)[0] - raw = self.comments_pat.sub('', raw) - if not check and ' 1: - ans = '
%s
'%(''.join(elems)) - else: - ans = ''.join(elems) - if not ans.startswith('<'): - ans = '

%s

'%ans - return xml_replace_entities(ans) + return fix_html(self.toHtml(), self.toPlainText().strip()) @html.setter def html(self, val): @@ -822,6 +824,12 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{ c = self.textCursor() return c.hasSelection() + def createMimeDataFromSelection(self): + ans = super().createMimeDataFromSelection() + html, txt = ans.html(), ans.text() + ans.setHtml(fix_html(html, txt)) + return ans + def contextMenuEvent(self, ev): menu = QMenu(self) for ac in 'undo redo -- cut copy paste paste_and_match_style -- select_all'.split(): From 8e18a509cb7b0c5cbe1e0a4b657afe21289e42a2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 2 Apr 2023 12:59:05 +0530 Subject: [PATCH 0404/2055] A better fix for block properties not being copied in comments_editor Removing the fragment copies causes pasting of the HTML into another comment editor to insert a spurious block. Instead move the comment to before

when needed. --- src/calibre/gui2/comments_editor.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index 4593957d03..71599805cf 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -225,11 +225,12 @@ def cleanup_qt_markup(root): # }}} -def fix_html(original_html, original_txt): +def fix_html(original_html, original_txt, remove_comments=True): raw = original_html raw = xml_to_unicode(raw, strip_encoding_pats=True, resolve_entities=True)[0] - comments_pat = re.compile(r'', re.DOTALL) - raw = comments_pat.sub('', raw) + if remove_comments: + comments_pat = re.compile(r'', re.DOTALL) + raw = comments_pat.sub('', raw) if not original_txt and ')()', r'\2\1', html, count=1) + ans.setHtml(html) return ans def contextMenuEvent(self, ev): @@ -1255,6 +1266,6 @@ if __name__ == '__main__': w.html = '''

Test Heading

Test blockquote

He hadn't set out to have an affair, much less a long-term, devoted one.

hello''' - w.html = '

Testing a link.

\xa0

ss

' + w.html = '

Testing a link.

\xa0

ss

' app.exec() # print w.html From 8e1982c70001070c6ce47779d82c691c3e3e95b3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 2 Apr 2023 14:29:50 +0530 Subject: [PATCH 0405/2055] Content server: Fix re-opening book from home page after making progress not opening to correct last read position when a user is logged in. Fixes 2011755 --- src/pyj/book_list/home.pyj | 6 ++++++ src/pyj/read_book/view.pyj | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/pyj/book_list/home.pyj b/src/pyj/book_list/home.pyj index 41750e0ccd..e91b2447cf 100644 --- a/src/pyj/book_list/home.pyj +++ b/src/pyj/book_list/home.pyj @@ -42,6 +42,12 @@ def update_recently_read_by_user(items): recently_read_by_user.updated = True +def update_book_in_recently_read_by_user_on_home_page(library_id, book_id, book_format, cfi): + for item in recently_read_by_user.items: + if item.library_id is library_id and item.book_id is book_id and item.format is book_format: + item.cfi = cfi + + def show_cover(blob, name, mt, book): img = document.getElementById(this) if not img: diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index d17bc911fc..1aabe9ea6e 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -7,6 +7,7 @@ from elementmaker import E import read_book.iframe # noqa from ajax import ajax_send from book_list.globals import get_session_data +from book_list.home import update_book_in_recently_read_by_user_on_home_page from book_list.theme import cached_color_to_rgba, get_color, set_ui_colors from book_list.ui import query_as_href from dom import add_extra_css, build_rule, clear, set_css, svgicon, unique_id @@ -1245,6 +1246,7 @@ class View: if end_type is not 'load': print('Failed to update last read position, AJAX call did not succeed') ) + update_book_in_recently_read_by_user_on_home_page(key[0], key[1], key[2], data.cfi) @property def current_position_data(self): From d2d69fedfe8997b48a37a26b57806e5468525104 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 2 Apr 2023 12:31:33 +0100 Subject: [PATCH 0406/2055] Add Category edit buttons to columns that have category editors. Authors, Series, Tags, Publisher, custom columns --- src/calibre/gui2/custom_column_widgets.py | 107 ++++++++++++++------- src/calibre/gui2/metadata/basic_widgets.py | 70 +++++++++++++- src/calibre/gui2/metadata/single.py | 52 +++++++--- 3 files changed, 179 insertions(+), 50 deletions(-) diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 243f30331e..013dbebfc7 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -137,6 +137,27 @@ class Base: def connect_data_changed(self, slot): pass + def values_changed(self): + return self.getter() != self.initial_val and (self.getter() or self.initial_val) + + def edit(self): + if self.values_changed(): + d = _save_dialog(self.parent, _('Values changed'), + _('You have changed the values. In order to use this ' + 'editor, you must either discard or apply these ' + 'changes. Apply changes?')) + if d == QMessageBox.StandardButton.Cancel: + return + if d == QMessageBox.StandardButton.Yes: + self.commit(self.book_id) + self.db.commit() + self.initial_val = self.current_val + else: + self.setter(self.initial_val) + from calibre.gui2.ui import get_gui + get_gui().do_tags_list_edit(None, self.key) + self.initialize(self.book_id) + class SimpleText(Base): @@ -448,16 +469,22 @@ class Comments(Base): class MultipleWidget(QWidget): - def __init__(self, parent): + def __init__(self, parent, only_manage_items=False, widget=EditWithComplete, name=None): QWidget.__init__(self, parent) layout = QHBoxLayout() layout.setSpacing(5) layout.setContentsMargins(0, 0, 0, 0) - self.tags_box = EditWithComplete(parent) - layout.addWidget(self.tags_box, stretch=1000) + self.edit_widget = widget(parent) + layout.addWidget(self.edit_widget, stretch=1000) self.editor_button = QToolButton(self) - self.editor_button.setToolTip(_('Open Item editor. If CTRL or SHIFT is pressed, open Manage items')) + if name is None: + name = _('items') + if only_manage_items: + self.editor_button.setToolTip(_('Open the {} Category editor').format(name)) + else: + self.editor_button.setToolTip(_('Open the {} editor. If CTRL or SHIFT ' + 'is pressed, open the {} Category editor').format(name, name)) self.editor_button.setIcon(QIcon.ic('chapters.png')) layout.addWidget(self.editor_button) self.setLayout(layout) @@ -466,34 +493,34 @@ class MultipleWidget(QWidget): return self.editor_button def update_items_cache(self, values): - self.tags_box.update_items_cache(values) + self.edit_widget.update_items_cache(values) def clear(self): - self.tags_box.clear() + self.edit_widget.clear() def setEditText(self): - self.tags_box.setEditText() + self.edit_widget.setEditText() def addItem(self, itm): - self.tags_box.addItem(itm) + self.edit_widget.addItem(itm) def set_separator(self, sep): - self.tags_box.set_separator(sep) + self.edit_widget.set_separator(sep) def set_add_separator(self, sep): - self.tags_box.set_add_separator(sep) + self.edit_widget.set_add_separator(sep) def set_space_before_sep(self, v): - self.tags_box.set_space_before_sep(v) + self.edit_widget.set_space_before_sep(v) def setSizePolicy(self, v1, v2): - self.tags_box.setSizePolicy(v1, v2) + self.edit_widget.setSizePolicy(v1, v2) def setText(self, v): - self.tags_box.setText(v) + self.edit_widget.setText(v) def text(self): - return self.tags_box.text() + return self.edit_widget.text() def _save_dialog(parent, title, msg, det_msg=''): @@ -513,20 +540,18 @@ class Text(Base): self.parent = parent if self.col_metadata['is_multiple']: - w = MultipleWidget(parent) + w = MultipleWidget(parent, name=self.col_metadata['name']) w.set_separator(self.sep['ui_to_list']) if self.sep['ui_to_list'] == '&': w.set_space_before_sep(True) w.set_add_separator(tweaks['authors_completer_append_separator']) w.get_editor_button().clicked.connect(self.edit) - w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) - self.set_to_undefined = w.clear else: - w = EditWithComplete(parent) + w = MultipleWidget(parent, only_manage_items=True, name=self.col_metadata['name']) w.set_separator(None) - w.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon) - w.setMinimumContentsLength(25) - self.set_to_undefined = w.clearEditText + w.get_editor_button().clicked.connect(super().edit) + w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) + self.set_to_undefined = w.clear self.widgets = [QLabel(label_string(self.col_metadata['name']), parent)] self.finish_ui_setup(parent, lambda parent: w) @@ -555,13 +580,12 @@ class Text(Base): self.editor.setText(self.sep['list_to_ui'].join(val)) def getter(self): + val = str(self.editor.text()).strip() if self.col_metadata['is_multiple']: - val = str(self.editor.text()).strip() ans = [x.strip() for x in val.split(self.sep['ui_to_list']) if x.strip()] if not ans: ans = None return ans - val = str(self.editor.currentText()).strip() if not val: val = None return val @@ -592,10 +616,7 @@ class Text(Base): self.setter(d.tags) def connect_data_changed(self, slot): - if self.col_metadata['is_multiple']: - s = self.editor.tags_box.currentTextChanged - else: - s = self.editor.currentTextChanged + s = self.editor.edit_widget.currentTextChanged s.connect(slot) self.signals_to_disconnect.append(s) @@ -603,14 +624,18 @@ class Text(Base): class Series(Base): def setup_ui(self, parent): - w = EditWithComplete(parent, sort_func=title_sort) + self.parent = parent + self.key = self.db.field_metadata.label_to_key(self.col_metadata['label'], + prefer_custom=True) + w = MultipleWidget(parent, only_manage_items=True, name=self.col_metadata['name']) + w.get_editor_button().clicked.connect(self.edit) + w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) + self.set_to_undefined = w.clear w.set_separator(None) - w.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon) - w.setMinimumContentsLength(25) - self.name_widget = w + self.name_widget = w.edit_widget self.widgets = [QLabel(label_string(self.col_metadata['name']), parent)] self.finish_ui_setup(parent, lambda parent: w) - w.editTextChanged.connect(self.series_changed) + self.name_widget.editTextChanged.connect(self.series_changed) w = QLabel(label_string(self.col_metadata['name'])+_(' index'), parent) w.setToolTip(get_tooltip(self.col_metadata, add_index=True)) @@ -628,6 +653,7 @@ class Series(Base): self.idx_widget.setValue(1.0) def initialize(self, book_id): + self.book_id = book_id values = list(self.db.all_custom(num=self.col_id)) values.sort(key=sort_key) val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) @@ -660,6 +686,10 @@ class Series(Base): num=self.col_id) self.idx_widget.setValue(s_index) + def values_changed(self): + val, s_index = self.current_val + return val != self.initial_val or s_index != self.initial_index + @property def current_val(self): val, s_index = self.gui_val @@ -690,14 +720,23 @@ class Enumeration(Base): def setup_ui(self, parent): self.parent = parent + self.key = self.db.field_metadata.label_to_key(self.col_metadata['label'], + prefer_custom=True) + w = MultipleWidget(parent, only_manage_items=True, widget=QComboBox, name=self.col_metadata['name']) + w.get_editor_button().clicked.connect(self.edit) + w.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) + self.set_to_undefined = w.clear + self.name_widget = w.edit_widget self.widgets = [QLabel(label_string(self.col_metadata['name']), parent)] - self.finish_ui_setup(parent, QComboBox) + self.finish_ui_setup(parent, lambda parent: w) + self.editor = self.name_widget vals = self.col_metadata['display']['enum_values'] self.editor.addItem('') for v in vals: self.editor.addItem(v) def initialize(self, book_id): + self.book_id = book_id val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) val = self.normalize_db_val(val) idx = self.editor.findText(val) @@ -710,7 +749,7 @@ class Enumeration(Base): idx = 0 self.editor.setCurrentIndex(idx) - self.initial_val = self.current_val + self.initial_val = val def setter(self, val): self.editor.setCurrentIndex(self.editor.findText(val)) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index d1130e5aa7..4e07503655 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -614,6 +614,7 @@ class SeriesEdit(EditWithComplete, ToMetadataMixin): LABEL = _('&Series:') FIELD_NAME = 'series' data_changed = pyqtSignal() + editor_requested = pyqtSignal() def __init__(self, parent): EditWithComplete.__init__(self, parent, sort_func=title_sort) @@ -651,9 +652,40 @@ class SeriesEdit(EditWithComplete, ToMetadataMixin): if series != self.original_val: self.books_to_refresh |= db.set_series(id_, series, notify=False, commit=True, allow_case_change=True) + @property + def changed(self): + return self.current_val != self.original_val + def break_cycles(self): self.dialog = None + def edit(self, db, id_): + if self.changed: + d = save_dialog(self, _('Series changed'), + _('You have changed the series. In order to use the category' + ' editor, you must either discard or apply these ' + 'changes. Apply changes?')) + if d == QMessageBox.StandardButton.Cancel: + return + if d == QMessageBox.StandardButton.Yes: + self.commit(db, id_) + db.commit() + self.original_val = self.current_val + else: + self.current_val = self.original_val + from calibre.gui2.ui import get_gui + get_gui().do_tags_list_edit(self.current_val, 'series') + db = get_gui().current_db + self.update_items_cache(db.new_api.all_field_names('series')) + self.initialize(db, id_) + + def keyPressEvent(self, ev): + if ev.key() == Qt.Key.Key_F2: + self.editor_requested.emit() + ev.accept() + return + return EditWithComplete.keyPressEvent(self, ev) + class SeriesIndexEdit(make_undoable(QDoubleSpinBox), ToMetadataMixin): @@ -679,7 +711,6 @@ class SeriesIndexEdit(make_undoable(QDoubleSpinBox), ToMetadataMixin): @property def current_val(self): - return self.value() @current_val.setter @@ -1817,6 +1848,7 @@ class PublisherEdit(EditWithComplete, ToMetadataMixin): # {{{ LABEL = _('&Publisher:') FIELD_NAME = 'publisher' data_changed = pyqtSignal() + editor_requested = pyqtSignal() def __init__(self, parent): EditWithComplete.__init__(self, parent) @@ -1833,7 +1865,6 @@ class PublisherEdit(EditWithComplete, ToMetadataMixin): # {{{ @property def current_val(self): - return clean_text(str(self.currentText())) @current_val.setter @@ -1846,13 +1877,46 @@ class PublisherEdit(EditWithComplete, ToMetadataMixin): # {{{ def initialize(self, db, id_): self.books_to_refresh = set() self.update_items_cache(db.new_api.all_field_names('publisher')) - self.original_val = self.current_val = db.new_api.field_for('publisher', id_) + self.current_val = db.new_api.field_for('publisher', id_) + # having this as a separate assignment ensures that original_val is not None + self.original_val = self.current_val def commit(self, db, id_): self.books_to_refresh |= db.set_publisher(id_, self.current_val, notify=False, commit=False, allow_case_change=True) return True + @property + def changed(self): + return self.original_val != self.current_val + + def edit(self, db, id_): + if self.changed: + d = save_dialog(self, _('Publisher changed'), + _('You have changed the publisher. In order to use the category' + ' editor, you must either discard or apply these ' + 'changes. Apply changes?')) + if d == QMessageBox.StandardButton.Cancel: + return + if d == QMessageBox.StandardButton.Yes: + self.commit(db, id_) + db.commit() + self.original_val = self.current_val + else: + self.current_val = self.original_val + from calibre.gui2.ui import get_gui + get_gui().do_tags_list_edit(self.current_val, 'publisher') + db = get_gui().current_db + self.update_items_cache(db.new_api.all_field_names('publisher')) + self.initialize(db, id_) + + def keyPressEvent(self, ev): + if ev.key() == Qt.Key.Key_F2: + self.editor_requested.emit() + ev.accept() + return + return EditWithComplete.keyPressEvent(self, ev) + # }}} # DateEdit {{{ diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index 041c5c883c..dd93d1ef30 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -191,14 +191,18 @@ class MetadataSingleDialogBase(QDialog): self.manage_authors_button = QToolButton(self) self.manage_authors_button.setIcon(QIcon.ic('user_profile.png')) self.manage_authors_button.setToolTip('

' + _( - 'Manage authors. Use to rename authors and correct ' + 'Open the Authors Category editor. Use to rename authors and correct ' 'individual author\'s sort values') + '

') self.manage_authors_button.clicked.connect(self.authors.manage_authors) + self.series_editor_button = QToolButton(self) + self.series_editor_button.setToolTip(_('Open the Series Category editor')) + self.series_editor_button.setIcon(QIcon.ic('chapters.png')) + self.series_editor_button.clicked.connect(self.series_editor) self.series = SeriesEdit(self) + self.series.editor_requested.connect(self.series_editor) self.clear_series_button = QToolButton(self) - self.clear_series_button.setToolTip( - _('Clear series')) + self.clear_series_button.setToolTip(_('Clear series')) self.clear_series_button.clicked.connect(self.series.clear) self.series_index = SeriesIndexEdit(self, self.series) self.basic_metadata_widgets.extend([self.series, self.series_index]) @@ -229,7 +233,7 @@ class MetadataSingleDialogBase(QDialog): self.tags = TagsEdit(self) self.tags_editor_button = QToolButton(self) - self.tags_editor_button.setToolTip(_('Open Tag editor')) + self.tags_editor_button.setToolTip(_('Open the Tag editor. If CTRL or SHIFT is pressed, open the Tags Category editor')) self.tags_editor_button.setIcon(QIcon.ic('chapters.png')) self.tags_editor_button.clicked.connect(self.tags_editor) self.tags.tag_editor_requested.connect(self.tags_editor) @@ -256,7 +260,12 @@ class MetadataSingleDialogBase(QDialog): b.setMenu(QMenu(b)) self.update_paste_identifiers_menu() + self.publisher_editor_button = QToolButton(self) + self.publisher_editor_button.setToolTip(_('Open the Publishers Category editor')) + self.publisher_editor_button.setIcon(QIcon.ic('chapters.png')) + self.publisher_editor_button.clicked.connect(self.publisher_editor) self.publisher = PublisherEdit(self) + self.publisher.editor_requested.connect(self.publisher_editor) self.basic_metadata_widgets.append(self.publisher) self.timestamp = DateEdit(self) @@ -409,6 +418,12 @@ class MetadataSingleDialogBase(QDialog): def tags_editor(self, *args): self.tags.edit(self.db, self.book_id) + def publisher_editor(self, *args): + self.publisher.edit(self.db, self.book_id) + + def series_editor(self, *args): + self.series.edit(self.db, self.book_id) + def metadata_from_format(self, *args): mi, ext = self.formats_manager.get_selected_format_metadata(self.db, self.book_id) @@ -791,7 +806,9 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ sto(self.title_sort, self.manage_authors_button) sto(self.manage_authors_button, self.authors) create_row(1, self.authors, self.deduce_author_sort_button, self.author_sort) - sto(self.author_sort, self.series) + tl.addWidget(self.series_editor_button, 2, 0, 1, 1) + sto(self.author_sort, self.series_editor_button) + sto(self.series_editor_button, self.series) create_row(2, self.series, self.clear_series_button, self.series_index, icon='trash.png') @@ -851,8 +868,9 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{ create_row2(4, self.timestamp, self.timestamp.clear_button) sto(self.timestamp.clear_button, self.pubdate) create_row2(5, self.pubdate, self.pubdate.clear_button) - sto(self.pubdate.clear_button, self.publisher) - create_row2(6, self.publisher, self.publisher.clear_button) + sto(self.pubdate.clear_button, self.publisher_editor_button) + sto(self.publisher_editor_button, self.publisher) + create_row2(6, self.publisher, self.publisher.clear_button, front_button=self.publisher_editor_button) sto(self.publisher.clear_button, self.languages) create_row2(7, self.languages) self.tabs[0].spc_two = QSpacerItem(10, 10, QSizePolicy.Policy.Expanding, @@ -957,8 +975,10 @@ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{ tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1) tl.addWidget(self.manage_authors_button, 2, 0, 1, 1) - tl.addWidget(self.paste_isbn_button, 12, 0, 1, 1) + tl.addWidget(self.series_editor_button, 6, 0, 1, 1) tl.addWidget(self.tags_editor_button, 6, 0, 1, 1) + tl.addWidget(self.publisher_editor_button, 9, 0, 1, 1) + tl.addWidget(self.paste_isbn_button, 12, 0, 1, 1) create_row(0, self.title, self.title_sort, button=self.deduce_title_sort_button, span=2, @@ -983,8 +1003,10 @@ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{ button=self.clear_identifiers_button, icon='trash.png') sto(self.clear_identifiers_button, self.swap_title_author_button) sto(self.swap_title_author_button, self.manage_authors_button) - sto(self.manage_authors_button, self.tags_editor_button) - sto(self.tags_editor_button, self.paste_isbn_button) + sto(self.manage_authors_button, self.series_editor_button) + sto(self.series_editor_button, self.tags_editor_button) + sto(self.tags_editor_button, self.publisher_editor_button) + sto(self.publisher_editor_button, self.paste_isbn_button) tl.addItem(QSpacerItem(1, 1, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding), 13, 1, 1 ,1) @@ -1111,8 +1133,10 @@ class MetadataSingleDialogAlt2(MetadataSingleDialogBase): # {{{ tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1) tl.addWidget(self.manage_authors_button, 2, 0, 2, 1) - tl.addWidget(self.paste_isbn_button, 12, 0, 1, 1) + tl.addWidget(self.series_editor_button, 4, 0, 1, 1) tl.addWidget(self.tags_editor_button, 6, 0, 1, 1) + tl.addWidget(self.publisher_editor_button, 9, 0, 1, 1) + tl.addWidget(self.paste_isbn_button, 12, 0, 1, 1) create_row(0, self.title, self.title_sort, button=self.deduce_title_sort_button, span=2, @@ -1138,8 +1162,10 @@ class MetadataSingleDialogAlt2(MetadataSingleDialogBase): # {{{ button=self.clear_identifiers_button, icon='trash.png') sto(self.clear_identifiers_button, self.swap_title_author_button) sto(self.swap_title_author_button, self.manage_authors_button) - sto(self.manage_authors_button, self.tags_editor_button) - sto(self.tags_editor_button, self.paste_isbn_button) + sto(self.manage_authors_button, self.series_editor_button) + sto(self.series_editor_button, self.tags_editor_button) + sto(self.tags_editor_button, self.publisher_editor_button) + sto(self.publisher_editor_button, self.paste_isbn_button) tl.addItem(QSpacerItem(1, 1, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding), 13, 1, 1 ,1) From f25c42f0273b0aff7c8eafb527505935a3d0a990 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 2 Apr 2023 19:08:51 +0530 Subject: [PATCH 0407/2055] ... --- recipes/boston.com.recipe | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/recipes/boston.com.recipe b/recipes/boston.com.recipe index 47f730a967..5291a54d3e 100644 --- a/recipes/boston.com.recipe +++ b/recipes/boston.com.recipe @@ -101,7 +101,10 @@ def parse_section(raw_html): continue title = text(elem['headlines']) description = text(elem.get('description')) - url = absolutize_url(elem['canonical_url']) + try: + url = absolutize_url(elem['canonical_url']) + except KeyError: + continue yield {'title': title, 'url': url, 'description': description, 'date': ' ' + str(date.date())} From 8959ada93fb313c67d62ee24c59bb0ccfd1cf555 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 2 Apr 2023 19:28:58 +0530 Subject: [PATCH 0408/2055] pep8 --- recipes/mediapart.recipe | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/recipes/mediapart.recipe b/recipes/mediapart.recipe index 287251e9b8..2606d284b0 100644 --- a/recipes/mediapart.recipe +++ b/recipes/mediapart.recipe @@ -19,7 +19,7 @@ __copyright__ = '2021, Loïc Houpert . Adapted from: Mediapart ''' -from datetime import date, datetime, timezone, timedelta +from datetime import datetime, timezone, timedelta from calibre.ptempfile import PersistentTemporaryFile from calibre.web.feeds.news import BasicNewsRecipe, classes @@ -30,7 +30,7 @@ class Mediapart(BasicNewsRecipe): publication_type = 'newspaper' language = 'fr' needs_subscription = True - + use_embedded_content = False no_stylesheets = True @@ -39,12 +39,12 @@ class Mediapart(BasicNewsRecipe): 'news__heading__top news__heading__center news__body__center__article' ) ] - + remove_tags = [ classes('action-links media--rich read-also login-subscribe print-source_url'), dict(name='svg'), ] - + conversion_options = {'smarten_punctuation': True} masthead_url = "https://raw.githubusercontent.com/lhoupert/calibre_contrib/main/mediapart_masthead.png" @@ -52,7 +52,7 @@ class Mediapart(BasicNewsRecipe): ignore_duplicate_articles = {'title'} resolve_internal_links = True remove_empty_feeds = True - + articles_are_obfuscated = True def get_obfuscated_article(self, url): @@ -170,4 +170,4 @@ class Mediapart(BasicNewsRecipe): except Exception: self.log.exception('Failed to generate default cover') return False - return True \ No newline at end of file + return True From 3d2ef6486b83b6b67187bf37d911d39ba1f142fb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 2 Apr 2023 19:49:30 +0530 Subject: [PATCH 0409/2055] Fix #2012797 [Enhancement request: Different shade for active VL tab](https://bugs.launchpad.net/calibre/+bug/2012797) --- src/calibre/gui2/palette.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/calibre/gui2/palette.py b/src/calibre/gui2/palette.py index 9a1d0cb0de..8f646e532f 100644 --- a/src/calibre/gui2/palette.py +++ b/src/calibre/gui2/palette.py @@ -376,6 +376,17 @@ class PaletteManager(QObject): ss = 'QTabBar::tab:selected { font-style: italic }\n\n' if self.is_dark_theme: ss += 'QMenu { border: 1px solid palette(shadow); }' + ss += ''' +QTabBar::tab:selected { + background-color: palette(base); + border: 2px solid gray; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-width: 0; + padding: 2px; + padding-left: 0.5em; +} +''' app.setStyleSheet(ss) app.palette_changed.emit() From f7304bd69132224389c1ef49dfde01b7b44e2abb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 2 Apr 2023 19:56:38 +0530 Subject: [PATCH 0410/2055] ... --- src/calibre/gui2/palette.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/palette.py b/src/calibre/gui2/palette.py index 8f646e532f..b3d40bac79 100644 --- a/src/calibre/gui2/palette.py +++ b/src/calibre/gui2/palette.py @@ -379,7 +379,7 @@ class PaletteManager(QObject): ss += ''' QTabBar::tab:selected { background-color: palette(base); - border: 2px solid gray; + border: 1px solid gray; border-top-left-radius: 4px; border-top-right-radius: 4px; border-bottom-width: 0; From 9b8dce3034985c2fc75cede2b40f544e77c60fc5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 3 Apr 2023 10:10:18 +0530 Subject: [PATCH 0411/2055] string changes --- Changelog.txt | 2 +- resources/default_tweaks.py | 4 ++-- src/calibre/gui2/custom_column_widgets.py | 2 +- src/calibre/gui2/metadata/basic_widgets.py | 2 +- src/calibre/gui2/metadata/single.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Changelog.txt b/Changelog.txt index 59c2fbda43..5a90e8bde1 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -2406,7 +2406,7 @@ Only works with output formats such as EPUB that support CSS transforms - Edit metadata dialog: Use only a single line for custom column date fields -- [1899341] Add an item to search for categories to the category editor context menu. +- [1899341] Add an item to search for categories to the Category editor context menu. - [1899316] Category editor: Add a right click menu to change case of the selected entries. diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index fa5ef4ea78..efacd73f0d 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -563,11 +563,11 @@ value_for_undefined_numbers_when_sorting = 0 allow_template_database_functions_in_composites = False -#: Change the programs that are run when opening files/URLS +#: Change the programs that are run when opening files/URLs # By default, calibre passes URLs to the operating system to open using # whatever default programs are configured there. Here you can override # that by specifying the program to use, per URL type. For local files, -# the type is "file" for web links it is "http*". For example: +# the type is "file" and for web links it is "http*". For example: # openers_by_scheme = { "http*": "firefox %u" } will make calibre run firefox # for https://whatever URLs. %u is replaced by the URL to be opened. The scheme # takes a glob pattern allowing a single entry to match multiple URL types. diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 013dbebfc7..b328d7b6ee 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -483,7 +483,7 @@ class MultipleWidget(QWidget): if only_manage_items: self.editor_button.setToolTip(_('Open the {} Category editor').format(name)) else: - self.editor_button.setToolTip(_('Open the {} editor. If CTRL or SHIFT ' + self.editor_button.setToolTip(_('Open the {} editor. If Ctrl or Shift ' 'is pressed, open the {} Category editor').format(name, name)) self.editor_button.setIcon(QIcon.ic('chapters.png')) layout.addWidget(self.editor_button) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 4e07503655..77bfd4dc6f 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -1893,7 +1893,7 @@ class PublisherEdit(EditWithComplete, ToMetadataMixin): # {{{ def edit(self, db, id_): if self.changed: d = save_dialog(self, _('Publisher changed'), - _('You have changed the publisher. In order to use the category' + _('You have changed the publisher. In order to use the Category' ' editor, you must either discard or apply these ' 'changes. Apply changes?')) if d == QMessageBox.StandardButton.Cancel: diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index dd93d1ef30..85f41d0c0d 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -233,7 +233,7 @@ class MetadataSingleDialogBase(QDialog): self.tags = TagsEdit(self) self.tags_editor_button = QToolButton(self) - self.tags_editor_button.setToolTip(_('Open the Tag editor. If CTRL or SHIFT is pressed, open the Tags Category editor')) + self.tags_editor_button.setToolTip(_('Open the Tag editor. If Ctrl or Shift is pressed, open the Tags Category editor')) self.tags_editor_button.setIcon(QIcon.ic('chapters.png')) self.tags_editor_button.clicked.connect(self.tags_editor) self.tags.tag_editor_requested.connect(self.tags_editor) From b4fa38612b5de8be8983fd105a066ce7553c42ce Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 3 Apr 2023 10:21:20 +0530 Subject: [PATCH 0412/2055] Make tab appearance nicer in locked mode --- src/calibre/gui2/palette.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/palette.py b/src/calibre/gui2/palette.py index b3d40bac79..d919154eec 100644 --- a/src/calibre/gui2/palette.py +++ b/src/calibre/gui2/palette.py @@ -383,8 +383,21 @@ QTabBar::tab:selected { border-top-left-radius: 4px; border-top-right-radius: 4px; border-bottom-width: 0; - padding: 2px; - padding-left: 0.5em; + padding: 2px 8px; + margin-left: -4px; + margin-right: -4px; +} + +QTabBar::tab:first:selected { + margin-left: 0; /* the first selected tab has nothing to overlap with on the left */ +} + +QTabBar::tab:last:selected { + margin-right: 0; /* the last selected tab has nothing to overlap with on the right */ +} + +QTabBar::tab:only-one { + margin: 0; /* if there is only one tab, we don't want overlapping margins */ } ''' app.setStyleSheet(ss) From 2647bbd6218115252b510f3a727f2c8338d0e27e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 3 Apr 2023 11:04:49 +0530 Subject: [PATCH 0413/2055] ... --- resources/default_tweaks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index efacd73f0d..137aa54dc4 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -568,7 +568,7 @@ allow_template_database_functions_in_composites = False # whatever default programs are configured there. Here you can override # that by specifying the program to use, per URL type. For local files, # the type is "file" and for web links it is "http*". For example: -# openers_by_scheme = { "http*": "firefox %u" } will make calibre run firefox +# openers_by_scheme = { "http*": "firefox %u" } will make calibre run Firefox # for https://whatever URLs. %u is replaced by the URL to be opened. The scheme # takes a glob pattern allowing a single entry to match multiple URL types. openers_by_scheme = {} From 8a2b4ac5daf0887f53a37a218b1518a63c414f23 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 3 Apr 2023 13:30:36 +0530 Subject: [PATCH 0414/2055] Use a single button to save vertical space --- .../gui2/preferences/metadata_sources.py | 6 ++- .../gui2/preferences/metadata_sources.ui | 39 +++++++++++-------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/calibre/gui2/preferences/metadata_sources.py b/src/calibre/gui2/preferences/metadata_sources.py index 63f0354843..0ded5efd2d 100644 --- a/src/calibre/gui2/preferences/metadata_sources.py +++ b/src/calibre/gui2/preferences/metadata_sources.py @@ -333,8 +333,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.select_default_button.clicked.connect(self.changed_signal) self.set_as_default_button.clicked.connect(self.fields_model.commit_user_defaults) self.tag_map_rules = self.author_map_rules = None - self.tag_map_rules_button.clicked.connect(self.change_tag_map_rules) - self.author_map_rules_button.clicked.connect(self.change_author_map_rules) + m = QMenu(self) + m.addAction(_('Tags')).triggered.connect(self.change_tag_map_rules) + m.addAction(_('Authors')).triggered.connect(self.change_author_map_rules) + self.map_rules_button.setMenu(m) l = self.page.layout() l.setStretch(0, 1) l.setStretch(1, 1) diff --git a/src/calibre/gui2/preferences/metadata_sources.ui b/src/calibre/gui2/preferences/metadata_sources.ui index 7879706251..dfc98a8844 100644 --- a/src/calibre/gui2/preferences/metadata_sources.ui +++ b/src/calibre/gui2/preferences/metadata_sources.ui @@ -163,8 +163,8 @@ 0 0 - 356 - 462 + 350 + 441 @@ -226,20 +226,25 @@ - + + + + 0 + 0 + + - Create &rules to filter/transform tags + Create &rules to transform tags/authors + + + QToolButton::InstantPopup + + + Qt::ToolButtonTextOnly - - - - Create rules to &transform author names - - - - + Max. &number of tags to download: @@ -249,10 +254,10 @@ - + - + Max. &time to wait after first match is found: @@ -262,14 +267,14 @@ - + secs - + Max. time to wait after first &cover is found: @@ -279,7 +284,7 @@ - + secs From b9063801844f117c2269333fd6834b54acb4f42b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 3 Apr 2023 15:08:56 +0530 Subject: [PATCH 0415/2055] Metadata download: Allow specifying rules to transform publisher names in addition to author and tag names. Fixes #2012304 [[Enhancement] in "Metadata Download," add "rules to filter/transform publisher"](https://bugs.launchpad.net/calibre/+bug/2012304) --- .../ebooks/metadata/sources/identify.py | 6 +- src/calibre/ebooks/metadata/sources/prefs.py | 5 +- src/calibre/gui2/author_mapper.py | 2 +- .../gui2/preferences/metadata_sources.py | 20 ++- .../gui2/preferences/metadata_sources.ui | 2 +- src/calibre/gui2/publisher_mapper.py | 137 ++++++++++++++++++ 6 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 src/calibre/gui2/publisher_mapper.py diff --git a/src/calibre/ebooks/metadata/sources/identify.py b/src/calibre/ebooks/metadata/sources/identify.py index 1e13ce63a4..d400b25dea 100644 --- a/src/calibre/ebooks/metadata/sources/identify.py +++ b/src/calibre/ebooks/metadata/sources/identify.py @@ -501,7 +501,8 @@ def identify(log, abort, # {{{ log('We have %d merged results, merging took: %.2f seconds' % (len(results), time.time() - start_time)) tm_rules = msprefs['tag_map_rules'] - if tm_rules: + pm_rules = msprefs['publisher_map_rules'] + if tm_rules or pm_rules: from calibre.ebooks.metadata.tag_mapper import map_tags am_rules = msprefs['author_map_rules'] if am_rules: @@ -531,6 +532,9 @@ def identify(log, abort, # {{{ r.tags = r.tags[:max_tags] if getattr(r.pubdate, 'year', 2000) <= UNDEFINED_DATE.year: r.pubdate = None + if pm_rules and r.publisher: + pubs = map_tags([r.publisher], pm_rules) + r.publisher = pubs[0] if pubs else '' if msprefs['swap_author_names']: for r in results: diff --git a/src/calibre/ebooks/metadata/sources/prefs.py b/src/calibre/ebooks/metadata/sources/prefs.py index 5725a87d8c..956494ef34 100644 --- a/src/calibre/ebooks/metadata/sources/prefs.py +++ b/src/calibre/ebooks/metadata/sources/prefs.py @@ -18,8 +18,9 @@ msprefs.defaults['swap_author_names'] = False msprefs.defaults['fewer_tags'] = True msprefs.defaults['find_first_edition_date'] = False msprefs.defaults['append_comments'] = False -msprefs.defaults['tag_map_rules'] = [] -msprefs.defaults['author_map_rules'] = [] +msprefs.defaults['tag_map_rules'] = () +msprefs.defaults['author_map_rules'] = () +msprefs.defaults['publisher_map_rules'] = () msprefs.defaults['id_link_rules'] = {} msprefs.defaults['keep_dups'] = False diff --git a/src/calibre/gui2/author_mapper.py b/src/calibre/gui2/author_mapper.py index 5d26d08a9c..3fe9395361 100644 --- a/src/calibre/gui2/author_mapper.py +++ b/src/calibre/gui2/author_mapper.py @@ -34,7 +34,7 @@ class RuleEdit(RuleEditBase): ('not_matches', _('does not match regex pattern')), )) - MSG = _('Create the rule below, the rule can be used to add or ignore files') + MSG = _('Create the rule below, the rule can be used to add or ignore authors') SUBJECT = _('the author, if the author name') VALUE_ERROR = _('You must provide a value for the author name to match') REPLACE_TEXT = _('with the name:') diff --git a/src/calibre/gui2/preferences/metadata_sources.py b/src/calibre/gui2/preferences/metadata_sources.py index 0ded5efd2d..cb9bfe4e97 100644 --- a/src/calibre/gui2/preferences/metadata_sources.py +++ b/src/calibre/gui2/preferences/metadata_sources.py @@ -332,10 +332,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.select_default_button.clicked.connect(self.fields_model.select_user_defaults) self.select_default_button.clicked.connect(self.changed_signal) self.set_as_default_button.clicked.connect(self.fields_model.commit_user_defaults) - self.tag_map_rules = self.author_map_rules = None + self.tag_map_rules = self.author_map_rules = self.publisher_map_rules = None m = QMenu(self) m.addAction(_('Tags')).triggered.connect(self.change_tag_map_rules) m.addAction(_('Authors')).triggered.connect(self.change_author_map_rules) + m.addAction(_('Publisher')).triggered.connect(self.change_publisher_map_rules) self.map_rules_button.setMenu(m) l = self.page.layout() l.setStretch(0, 1) @@ -376,16 +377,25 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): from calibre.gui2.tag_mapper import RulesDialog d = RulesDialog(self) if msprefs.get('tag_map_rules'): - d.rules = msprefs['tag_map_rules'] + d.rules = list(msprefs['tag_map_rules']) if d.exec() == QDialog.DialogCode.Accepted: self.tag_map_rules = d.rules self.changed_signal.emit() + def change_publisher_map_rules(self): + from calibre.gui2.publisher_mapper import RulesDialog + d = RulesDialog(self) + if msprefs.get('publisher_map_rules'): + d.rules = list(msprefs['publisher_map_rules']) + if d.exec() == QDialog.DialogCode.Accepted: + self.publisher_map_rules = d.rules + self.changed_signal.emit() + def change_author_map_rules(self): from calibre.gui2.author_mapper import RulesDialog d = RulesDialog(self) if msprefs.get('author_map_rules'): - d.rules = msprefs['author_map_rules'] + d.rules = list(msprefs['author_map_rules']) if d.exec() == QDialog.DialogCode.Accepted: self.author_map_rules = d.rules self.changed_signal.emit() @@ -395,7 +405,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.sources_model.initialize() self.sources_view.resizeColumnsToContents() self.fields_model.initialize() - self.tag_map_rules = self.author_map_rules = None + self.tag_map_rules = self.author_map_rules = self.publisher_map_rules = None def restore_defaults(self): ConfigWidgetBase.restore_defaults(self) @@ -410,6 +420,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): msprefs['tag_map_rules'] = self.tag_map_rules or [] if self.author_map_rules is not None: msprefs['author_map_rules'] = self.author_map_rules or [] + if self.publisher_map_rules is not None: + msprefs['publisher_map_rules'] = self.publisher_map_rules or [] return ConfigWidgetBase.commit(self) diff --git a/src/calibre/gui2/preferences/metadata_sources.ui b/src/calibre/gui2/preferences/metadata_sources.ui index dfc98a8844..b978aacdb2 100644 --- a/src/calibre/gui2/preferences/metadata_sources.ui +++ b/src/calibre/gui2/preferences/metadata_sources.ui @@ -234,7 +234,7 @@ - Create &rules to transform tags/authors + Create &rules to transform tags/authors/publishers QToolButton::InstantPopup diff --git a/src/calibre/gui2/publisher_mapper.py b/src/calibre/gui2/publisher_mapper.py new file mode 100644 index 0000000000..d678dded0f --- /dev/null +++ b/src/calibre/gui2/publisher_mapper.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python +# License: GPL v3 Copyright: 2018, Kovid Goyal + + +from collections import OrderedDict + +from calibre.ebooks.metadata.tag_mapper import map_tags +from calibre.gui2 import Application, elided_text +from calibre.gui2.tag_mapper import ( + RuleEdit as RuleEditBase, RuleEditDialog as RuleEditDialogBase, + RuleItem as RuleItemBase, Rules as RulesBase, RulesDialog as RulesDialogBase, + Tester as TesterBase, +) +from calibre.utils.config import JSONConfig + +publisher_maps = JSONConfig('publisher-mapping-rules') + + +class RuleEdit(RuleEditBase): + + ACTION_MAP = OrderedDict(( + ('replace', _('Change')), + ('capitalize', _('Capitalize')), + ('titlecase', _('Title-case')), + ('lower', _('Lower-case')), + ('upper', _('Upper-case')), + )) + + MATCH_TYPE_MAP = OrderedDict(( + ('one_of', _('is one of')), + ('not_one_of', _('is not one of')), + ('has', _('contains')), + ('matches', _('matches regex pattern')), + ('not_matches', _('does not match regex pattern')), + )) + + MSG = _('Create the rule below, the rule can be used to modify publishers') + SUBJECT = _('the publisher, if the publisher name') + VALUE_ERROR = _('You must provide a value for the publisher name to match') + REPLACE_TEXT = _('with the name:') + SINGLE_EDIT_FIELD_NAME = 'publisher' + + @property + def can_use_tag_editor(self): + return False + + def update_state(self): + a = self.action.currentData() + replace = a == 'replace' + self.la3.setVisible(replace), self.replace.setVisible(replace) + m = self.match_type.currentData() + is_match = 'matches' in m + self.regex_help.setVisible(is_match) + + @property + def rule(self): + return { + 'action': self.action.currentData(), + 'match_type': self.match_type.currentData(), + 'query': self.query.text().strip(), + 'replace': self.replace.text().strip(), + } + + @rule.setter + def rule(self, rule): + def sc(name): + c = getattr(self, name) + idx = c.findData(str(rule.get(name, ''))) + if idx < 0: + idx = 0 + c.setCurrentIndex(idx) + sc('match_type'), sc('action') + self.query.setText(str(rule.get('query', '')).strip()) + self.replace.setText(str(rule.get('replace', '')).strip()) + + +class RuleEditDialog(RuleEditDialogBase): + + PREFS_NAME = 'edit-publisher-mapping-rule' + RuleEditClass = RuleEdit + + +class RuleItem(RuleItemBase): + + @staticmethod + def text_from_rule(rule, parent): + query = elided_text(rule['query'], font=parent.font(), width=200, pos='right') + text = _( + '{action} the publisher name, if it {match_type}: {query}').format( + action=RuleEdit.ACTION_MAP[rule['action']], match_type=RuleEdit.MATCH_TYPE_MAP[rule['match_type']], query=query) + if rule['action'] == 'replace': + text += '
' + _('to the name') + ' %s' % rule['replace'] + return '
' + text + '
' + + +class Rules(RulesBase): + + RuleItemClass = RuleItem + RuleEditDialogClass = RuleEditDialog + MSG = _('You can specify rules to manipulate publisher names here.' + ' Click the "Add Rule" button' + ' below to get started. The rules will be processed in order for every publisher.') + + +class Tester(TesterBase): + + DIALOG_TITLE = _('Test publisher mapping rules') + PREFS_NAME = 'test-publisher-mapping-rules' + LABEL = _('Enter an publisher name to test:') + PLACEHOLDER = _('Enter publisher and click the "Test" button') + EMPTY_RESULT = '

 

' + + def do_test(self): + publisher = self.value.strip() + ans = map_tags([publisher], self.rules) + self.result.setText((ans or ('',))[0]) + + +class RulesDialog(RulesDialogBase): + + DIALOG_TITLE = _('Edit publisher mapping rules') + PREFS_NAME = 'edit-publisher-mapping-rules' + RulesClass = Rules + TesterClass = Tester + PREFS_OBJECT = publisher_maps + + +if __name__ == '__main__': + app = Application([]) + d = RulesDialog() + d.rules = [ + {'action':'replace', 'query':'alice Bob', 'match_type':'one_of', 'replace':'Alice Bob'}, + ] + d.exec() + from pprint import pprint + pprint(d.rules) + del d, app From bc14bc245adbe8e10e25b5c278af96f4aecfd075 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 4 Apr 2023 08:07:08 +0530 Subject: [PATCH 0416/2055] string changes --- src/calibre/gui2/publisher_mapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/publisher_mapper.py b/src/calibre/gui2/publisher_mapper.py index d678dded0f..f57c1f58fb 100644 --- a/src/calibre/gui2/publisher_mapper.py +++ b/src/calibre/gui2/publisher_mapper.py @@ -106,7 +106,7 @@ class Tester(TesterBase): DIALOG_TITLE = _('Test publisher mapping rules') PREFS_NAME = 'test-publisher-mapping-rules' - LABEL = _('Enter an publisher name to test:') + LABEL = _('Enter a publisher name to test:') PLACEHOLDER = _('Enter publisher and click the "Test" button') EMPTY_RESULT = '

 

' From f4f265e6ec6b4f8e6100f3f8d372b2dde52455da Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 4 Apr 2023 10:18:27 +0530 Subject: [PATCH 0417/2055] DOCX Input: Dont ignore images that are present as fallbacks for a word drawing object. Fixes #2013972 [Pictures disappearing](https://bugs.launchpad.net/calibre/+bug/2013972) --- src/calibre/ebooks/docx/to_html.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/calibre/ebooks/docx/to_html.py b/src/calibre/ebooks/docx/to_html.py index e2a4d7da8a..81a181996d 100644 --- a/src/calibre/ebooks/docx/to_html.py +++ b/src/calibre/ebooks/docx/to_html.py @@ -279,6 +279,13 @@ class Convert: if fallbacks: for choice in choices: ac.remove(choice) + if len(fallbacks) == 1 and ac.getparent().tag.endswith('}r') and len(fallbacks[0]) == 1: + q = fallbacks[0][0] + if q.tag and (q.tag.endswith('}drawing') or q.tag.endswith('}pict')): + p = ac.getparent() + idx = p.index(ac) + p.insert(idx, q) + p.remove(ac) def read_styles(self, relationships_by_type): From 87ab6fa4485121f2faa593eb1739268ee85dff14 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 4 Apr 2023 10:35:30 +0530 Subject: [PATCH 0418/2055] E-book viewer: Fix images embedded inside svg tags not available for viewing in a popup --- src/calibre/srv/render_book.py | 5 +++++ src/pyj/read_book/extract.pyj | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/calibre/srv/render_book.py b/src/calibre/srv/render_book.py index 4512373f52..0b7e11fb47 100644 --- a/src/calibre/srv/render_book.py +++ b/src/calibre/srv/render_book.py @@ -347,6 +347,7 @@ def transform_html(container, name, virtualize_resources, link_uid, link_to_map, link_xpath = XPath('//h:a[@href]') svg_link_xpath = XPath('//svg:a') img_xpath = XPath('//h:img[@src]') + svg_img_xpath = XPath('//svg:image[@xl:href]') res_link_xpath = XPath('//h:link[@href]') root = container.parsed(name) changed_names = set() @@ -357,6 +358,10 @@ def transform_html(container, name, virtualize_resources, link_uid, link_to_map, img_name = container.href_to_name(img.get('src'), name) if img_name: img.set('data-calibre-src', img_name) + for img in svg_img_xpath(root): + img_name = container.href_to_name(img.get(XLINK('href')), name) + if img_name: + img.set('data-calibre-src', img_name) # Disable non-stylesheet link tags. This link will not be loaded by the # browser anyway and will causes the resource load check to hang diff --git a/src/pyj/read_book/extract.pyj b/src/pyj/read_book/extract.pyj index 5d3d0359b1..e5d853fa6c 100644 --- a/src/pyj/read_book/extract.pyj +++ b/src/pyj/read_book/extract.pyj @@ -10,9 +10,10 @@ def get_elements(x, y): nonlocal img_id_counter ans = {'link': None, 'img': None, 'highlight': None, 'crw': None} for elem in document.elementsFromPoint(x, y): - if elem.tagName.toLowerCase() is 'a' and elem.getAttribute('href') and not ans.link: + tl = elem.tagName.toLowerCase() + if tl is 'a' and elem.getAttribute('href') and not ans.link: ans.link = elem.getAttribute('href') - elif elem.tagName.toLowerCase() is 'img' and elem.getAttribute('data-calibre-src') and not ans.img: + elif (tl is 'img' or tl is 'image') and elem.getAttribute('data-calibre-src') and not ans.img: ans.img = elem.getAttribute('data-calibre-src') elif elem.dataset?.calibreRangeWrapper: ans.crw = elem.dataset.calibreRangeWrapper From c7636ad90d2ce2004a6c995ec5aafc739892bed2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 4 Apr 2023 11:57:22 +0530 Subject: [PATCH 0419/2055] E-book viewer: Fix a regression that caused incorrect highlight collision detection in some books. Fixes #2009586 [overlaping highlight problem](https://bugs.launchpad.net/calibre/+bug/2009586) --- src/pyj/range_utils.pyj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyj/range_utils.pyj b/src/pyj/range_utils.pyj index 51d0d7782d..fb2c712037 100644 --- a/src/pyj/range_utils.pyj +++ b/src/pyj/range_utils.pyj @@ -54,7 +54,7 @@ def all_annots_in_range(r, annot_id_uuid_map, ans): doc = parent.ownerDocument or document iterator = doc.createNodeIterator(parent) is_full_tree = parent is doc.documentElement - in_range = not is_full_tree + in_range = is_full_tree while True: node = iterator.nextNode() if not node: From c33df89ce2dc8037146f634ef8d528af3333cb68 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 4 Apr 2023 17:38:25 +0530 Subject: [PATCH 0420/2055] EPUB Output: Do not shrink images to fit the screen size by default. This matches behavior of converting to other formats. A new option has been added to the EPUB Output section to control the maximum size of images. --- src/calibre/ebooks/conversion/config.py | 2 +- .../ebooks/conversion/plugins/epub_output.py | 15 ++- src/calibre/ebooks/conversion/plumber.py | 2 +- src/calibre/ebooks/oeb/transforms/rescale.py | 23 +++- src/calibre/gui2/convert/epub_output.ui | 127 ++++++++++-------- src/pyj/book_list/conversion_widgets.pyj | 1 + 6 files changed, 106 insertions(+), 64 deletions(-) diff --git a/src/calibre/ebooks/conversion/config.py b/src/calibre/ebooks/conversion/config.py index db30f2859b..168a68af6c 100644 --- a/src/calibre/ebooks/conversion/config.py +++ b/src/calibre/ebooks/conversion/config.py @@ -278,7 +278,7 @@ OPTIONS = { 'epub': ( 'dont_split_on_page_breaks', 'flow_size', 'no_default_epub_cover', 'no_svg_cover', 'epub_inline_toc', 'epub_toc_at_end', 'toc_title', - 'preserve_cover_aspect_ratio', 'epub_flatten', 'epub_version'), + 'preserve_cover_aspect_ratio', 'epub_flatten', 'epub_version', 'epub_max_image_size',), 'fb2': ('sectionize', 'fb2_genre'), diff --git a/src/calibre/ebooks/conversion/plugins/epub_output.py b/src/calibre/ebooks/conversion/plugins/epub_output.py index 012eb512b0..a6ed8e0b51 100644 --- a/src/calibre/ebooks/conversion/plugins/epub_output.py +++ b/src/calibre/ebooks/conversion/plugins/epub_output.py @@ -124,6 +124,17 @@ class EPUBOutput(OutputFormatPlugin): ' actually need it.') ), + OptionRecommendation(name='epub_max_image_size', recommended_value='none', + help=_('The maximum image size (width x height). A value of {0} means use the screen size from the output' + ' profile. A value of {1} means no maximum size is specified. For example, a value of {2}' + ' will cause all images to be resized so that their width is no more than {3} pixels and' + ' their height is no more than {4} pixels. Note that this only affects the size of the actual' + ' image files themselves. Any given image may be rendered at a different size depending on the styling' + ' applied to it in the document.' + ).format('none', 'profile', '100x200', 100, 200) + ), + + } recommendations = {('pretty_print', True, OptionRecommendation.HIGH)} @@ -196,8 +207,9 @@ class EPUBOutput(OutputFormatPlugin): self.workaround_ade_quirks() self.workaround_webkit_quirks() self.upshift_markup() + from calibre.ebooks.oeb.transforms.rescale import RescaleImages - RescaleImages(check_colorspaces=True)(oeb, opts) + RescaleImages(check_colorspaces=True)(oeb, opts, max_size=self.opts.epub_max_image_size) from calibre.ebooks.oeb.transforms.split import Split split = Split(not self.opts.dont_split_on_page_breaks, @@ -385,7 +397,6 @@ class EPUBOutput(OutputFormatPlugin): from calibre.ebooks.oeb.base import XPath, XHTML, barename, urlunquote stylesheet = self.oeb.manifest.main_stylesheet - # ADE cries big wet tears when it encounters an invalid fragment # identifier in the NCX toc. frag_pat = re.compile(r'[-A-Za-z0-9_:.]+$') diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py index 667efa5c8b..b3a6945bad 100644 --- a/src/calibre/ebooks/conversion/plumber.py +++ b/src/calibre/ebooks/conversion/plumber.py @@ -140,7 +140,7 @@ OptionRecommendation(name='output_profile', choices=[x.short_name for x in output_profiles()], help=_('Specify the output profile. The output profile ' 'tells the conversion system how to optimize the ' - 'created document for the specified device (such as by resizing images for the device screen size). In some cases, ' + 'created document for the specified device. In some cases, ' 'an output profile can be used to optimize the output for a particular device, but this is rarely necessary. ' 'Choices are:') + ', '.join([ x.short_name for x in output_profiles()]) diff --git a/src/calibre/ebooks/oeb/transforms/rescale.py b/src/calibre/ebooks/oeb/transforms/rescale.py index 958a013316..dc7bc12475 100644 --- a/src/calibre/ebooks/oeb/transforms/rescale.py +++ b/src/calibre/ebooks/oeb/transforms/rescale.py @@ -15,11 +15,11 @@ class RescaleImages: def __init__(self, check_colorspaces=False): self.check_colorspaces = check_colorspaces - def __call__(self, oeb, opts): + def __call__(self, oeb, opts, max_size: str = 'profile'): self.oeb, self.opts, self.log = oeb, opts, oeb.log - self.rescale() + self.rescale(max_size) - def rescale(self): + def rescale(self, max_size: str = 'profile'): from PIL import Image from io import BytesIO @@ -32,6 +32,23 @@ class RescaleImages: page_width -= (self.opts.margin_left + self.opts.margin_right) * self.opts.dest.dpi/72 page_height -= (self.opts.margin_top + self.opts.margin_bottom) * self.opts.dest.dpi/72 + no_scale_size = 99999999999 + if max_size == 'none': + page_width = page_height = no_scale_size + elif max_size != 'profile': + w, __, h = max_size.strip().lower().partition('x') + try: + page_width = int(w.strip()) + except Exception: + page_width = no_scale_size + if page_width <= 0: + page_width = no_scale_size + try: + page_height = int(h.strip()) + except Exception: + page_height = no_scale_size + if page_height <= 0: + page_height = no_scale_size for item in self.oeb.manifest: if item.media_type.startswith('image'): ext = item.media_type.split('/')[-1].upper() diff --git a/src/calibre/gui2/convert/epub_output.ui b/src/calibre/gui2/convert/epub_output.ui index 448c6385b8..3370315b8b 100644 --- a/src/calibre/gui2/convert/epub_output.ui +++ b/src/calibre/gui2/convert/epub_output.ui @@ -14,10 +14,27 @@ Form
- - + + - Preserve cover &aspect ratio + EP&UB version: + + + opt_epub_version + + + + + + + No default &cover + + + + + + + Insert inline &Table of Contents @@ -31,6 +48,46 @@
+ + + + Qt::Vertical + + + + 20 + 262 + + + + + + + + No &SVG cover + + + + + + + + + + &Flatten EPUB file structure + + + + + + + &Title for inserted ToC: + + + opt_toc_title + + + @@ -50,47 +107,6 @@ - - - - Qt::Vertical - - - - 20 - 262 - - - - - - - - No default &cover - - - - - - - No &SVG cover - - - - - - - Insert inline &Table of Contents - - - - - - - Do not &split on page breaks - - - @@ -98,20 +114,17 @@ - - + + - &Flatten EPUB file structure + Preserve cover &aspect ratio - - + + - &Title for inserted ToC: - - - opt_toc_title + Do not &split on page breaks @@ -123,17 +136,17 @@ - + - EP&UB version: + Shrink &images larger than: - opt_epub_version + opt_epub_max_image_size - + diff --git a/src/pyj/book_list/conversion_widgets.pyj b/src/pyj/book_list/conversion_widgets.pyj index 49eb7346db..d7b94f5314 100644 --- a/src/pyj/book_list/conversion_widgets.pyj +++ b/src/pyj/book_list/conversion_widgets.pyj @@ -540,6 +540,7 @@ def epub_output(container): g.appendChild(checkbox('epub_toc_at_end', _('Put inserted Table of Contents at the &end of the book'))) g.appendChild(lineedit('toc_title', _('&Title for inserted ToC:'))) g.appendChild(int_spin('flow_size', _('Split files &larger than:'), unit='KB', max=1000000, step=20)) + g.appendChild(lineedit('epub_max_image_size', _('Shrink &images larger than:'))) g.appendChild(choices('epub_version', _('EP&UB version:'), ui_data.versions)) # }}} From cbcb17655c4740b0a821a5c3e1c8657e47449589 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Tue, 4 Apr 2023 15:29:26 +0100 Subject: [PATCH 0421/2055] Enhancement #2015114: Offer to rename enumerated values --- src/calibre/gui2/dialogs/enum_values_edit.py | 41 +++++++++++++------- src/calibre/gui2/dialogs/tag_list_editor.py | 4 ++ 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/calibre/gui2/dialogs/enum_values_edit.py b/src/calibre/gui2/dialogs/enum_values_edit.py index 564d1dca93..8a5884cf3d 100644 --- a/src/calibre/gui2/dialogs/enum_values_edit.py +++ b/src/calibre/gui2/dialogs/enum_values_edit.py @@ -1,10 +1,9 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2020, Charles Haley -from qt.core import (QDialog, QColor, QDialogButtonBox, QHeaderView, - QGridLayout, QTableWidget, - QTableWidgetItem, QVBoxLayout, QToolButton, QIcon, - QAbstractItemView, QComboBox) +from qt.core import (Qt, QDialog, QColor, QDialogButtonBox, QHeaderView, + QGridLayout, QTableWidget, QTableWidgetItem, QVBoxLayout, + QToolButton, QIcon, QAbstractItemView, QComboBox) from calibre.gui2 import error_dialog, gprefs @@ -53,12 +52,13 @@ class EnumValuesEdit(QDialog): t.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) tl.addWidget(t) + self.key = key self.fm = fm = db.field_metadata[key] permitted_values = fm.get('display', {}).get('enum_values', '') colors = fm.get('display', {}).get('enum_colors', '') t.setRowCount(len(permitted_values)) for i,v in enumerate(permitted_values): - t.setItem(i, 0, QTableWidgetItem(v)) + self.make_name_item(i, v) c = self.make_color_combobox(i, -1) if colors: c.setCurrentIndex(c.findText(colors[i])) @@ -85,10 +85,19 @@ class EnumValuesEdit(QDialog): sz.setHeight(max(sz.height(), 400)) return sz + def make_name_item(self, row, txt): + it = QTableWidgetItem(txt) + it.setData(Qt.ItemDataRole.UserRole, txt) + it.setCheckState(Qt.CheckState.Unchecked) + it.setToolTip('

' + _('Check the box if you change the value and want it renamed in books where it is used') + '

') + self.table.setItem(row, 0, it) + def make_color_combobox(self, row, dex): c = QComboBox(self) c.addItem('') c.addItems(QColor.colorNames()) + c.setToolTip('

' + _('Selects the color of the text when displayed in the book list. ' + 'Either all rows must have a color or no rows have a color') + '

') self.table.setCellWidget(row, 1, c) if dex >= 0: c.setCurrentIndex(dex) @@ -105,12 +114,12 @@ class EnumValuesEdit(QDialog): self.move_row(row, -1) def move_row(self, row, direction): - t = self.table.item(row, 0).text() + t = self.table.takeItem(row, 0) c = self.table.cellWidget(row, 1).currentIndex() self.table.removeRow(row) row += direction self.table.insertRow(row) - self.table.setItem(row, 0, QTableWidgetItem(t)) + self.table.setItem(row, 0, t) self.make_color_combobox(row, c) self.table.setCurrentCell(row, 0) @@ -135,11 +144,8 @@ class EnumValuesEdit(QDialog): _('Select a cell before clicking the button'), show=True) return self.table.insertRow(row) - self.table.setItem(row, 0, QTableWidgetItem()) - c = QComboBox(self) - c.addItem('') - c.addItems(QColor.colorNames()) - self.table.setCellWidget(row, 1, c) + self.make_name_item(row, '') + self.make_color_combobox(row, -1) def save_geometry(self): super().save_geometry(gprefs, 'enum-values-edit-geometry') @@ -148,12 +154,18 @@ class EnumValuesEdit(QDialog): disp = self.fm['display'] values = [] colors = [] + id_map = {} for i in range(0, self.table.rowCount()): - v = str(self.table.item(i, 0).text()) + it = self.table.item(i, 0) + v = str(it.text()) if not v: error_dialog(self, _('Empty value'), _('Empty values are not allowed'), show=True) return + ov = str(it.data(Qt.ItemDataRole.UserRole)) + if v != ov and it.checkState() == Qt.CheckState.Checked: + fid = self.db.new_api.get_item_id(self.key, ov) + id_map[fid] = v values.append(v) c = str(self.table.cellWidget(i, 1).currentText()) if c: @@ -177,8 +189,11 @@ class EnumValuesEdit(QDialog): disp['enum_colors'] = colors self.db.set_custom_column_metadata(self.fm['colnum'], display=disp, update_last_modified=True) + if id_map: + self.db.new_api.rename_items(self.key, id_map) self.save_geometry() return QDialog.accept(self) def reject(self): + self.save_geometry() return QDialog.reject(self) diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index f7a827bc12..f3236b3eb4 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -213,6 +213,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText(_('&OK')) self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText(_('&Cancel')) self.buttonBox.accepted.connect(self.accepted) + self.buttonBox.rejected.connect(self.rejected) self.search_box.initialize('tag_list_search_box_' + cat_name) le = self.search_box.lineEdit() @@ -682,3 +683,6 @@ class TagListEditor(QDialog, Ui_TagListEditor): def accepted(self): self.links = {self.table.item(r, 0).text():self.table.item(r, 3).text() for r in range(self.table.rowCount())} self.save_geometry() + + def rejected(self): + self.save_geometry() From 328d0b57586b6be414aedfbcbcf8fee50bff8b42 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 5 Apr 2023 16:39:47 +0530 Subject: [PATCH 0422/2055] Add zstd as a dependency Fixes #1826 (add zstd as a dependency) --- bypy/linux/__main__.py | 2 +- bypy/macos/__main__.py | 2 +- bypy/sources.json | 12 ++++++++++-- src/calibre/test_build.py | 6 ++++++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/bypy/linux/__main__.py b/bypy/linux/__main__.py index 025a47e02c..1c13f5b53c 100644 --- a/bypy/linux/__main__.py +++ b/bypy/linux/__main__.py @@ -46,7 +46,7 @@ def binary_includes(): ('usb-1.0 mtp expat sqlite3 ffi z lzma openjp2 poppler dbus-1 iconv xml2 xslt jpeg png16' ' webp webpmux webpdemux exslt ncursesw readline chm hunspell-1.7 hyphen' ' icudata icui18n icuuc icuio stemmer gcrypt gpg-error uchardet graphite2' - ' brotlicommon brotlidec brotlienc' + ' brotlicommon brotlidec brotlienc zstd' ' gobject-2.0 glib-2.0 gthread-2.0 gmodule-2.0 gio-2.0 dbus-glib-1').split() )) + [ # debian/ubuntu for for some typical stupid reason use libpcre.so.3 diff --git a/bypy/macos/__main__.py b/bypy/macos/__main__.py index 497ea0828f..d0cef9a15e 100644 --- a/bypy/macos/__main__.py +++ b/bypy/macos/__main__.py @@ -529,7 +529,7 @@ class Freeze: 'usb-1.0.0', 'mtp.9', 'chm.0', 'sqlite3.0', 'hunspell-1.7.0', 'icudata.70', 'icui18n.70', 'icuio.70', 'icuuc.70', 'hyphen.0', 'uchardet.0', 'stemmer.0', 'xslt.1', 'exslt.0', 'xml2.2', 'z.1', 'unrar', 'lzma.5', - 'brotlicommon.1', 'brotlidec.1', 'brotlienc.1', + 'brotlicommon.1', 'brotlidec.1', 'brotlienc.1', 'zstd.1', 'crypto.1.1', 'ssl.1.1', 'iconv.2', # 'ltdl.7' ): print('\nAdding', x) diff --git a/bypy/sources.json b/bypy/sources.json index 4580ec79f7..a8cc0dbb9d 100644 --- a/bypy/sources.json +++ b/bypy/sources.json @@ -106,6 +106,14 @@ } }, + { + "name": "zstd", + "unix": { + "filename": "zstd-1.5.5.tar.gz", + "hash": "sha256:9c4396cc829cfae319a6e2615202e82aad41372073482fce286fac78646d3ee4", + "urls": ["https://github.com/facebook/zstd/releases/download/v1.5.5/{filename}"] + } + }, { "name": "expat", @@ -853,8 +861,8 @@ "name": "pyzstd", "comment": "Needed by py7zr", "unix": { - "filename": "pyzstd-0.15.0.tar.gz", - "hash": "sha256:bf15a39cb3c9b662775e22ffa8c4da09fdde6a15ece5e0ed710b6d3b4329cf36", + "filename": "pyzstd-0.15.6.tar.gz", + "hash": "sha256:32a1b67d5340d8df381e718a788417455edd76bed7e8a4cbd259acdc30b5e17e", "urls": ["pypi"] } }, diff --git a/src/calibre/test_build.py b/src/calibre/test_build.py index ad1e223347..d504d748d6 100644 --- a/src/calibre/test_build.py +++ b/src/calibre/test_build.py @@ -96,6 +96,12 @@ class BuildTest(unittest.TestCase): import lzma lzma.open + def test_zstd(self): + from pyzstd import compress, decompress + data = os.urandom(4096) + cdata = compress(data) + self.assertEqual(data, decompress(cdata)) + def test_html5lib(self): import html5lib.html5parser # noqa from html5lib import parse # noqa From 4374bb034a6839c21e67de6f3e2401b501adeaa1 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 5 Apr 2023 12:13:12 +0100 Subject: [PATCH 0423/2055] Enhancement #2015317: rename user category tag browser icons when a category is renamed --- src/calibre/gui2/tag_browser/model.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/tag_browser/model.py b/src/calibre/gui2/tag_browser/model.py index 87a0ff48c6..2184501527 100644 --- a/src/calibre/gui2/tag_browser/model.py +++ b/src/calibre/gui2/tag_browser/model.py @@ -370,6 +370,22 @@ class TagsModel(QAbstractItemModel): # {{{ def gui_parent(self): return QObject.parent(self) + def rename_user_category_icon(self, old_key, new_key): + ''' + This is required for user categories because the key (lookup name) changes + on rename. We must rename the old icon to use the new key then update + the preferences and internal tables. + ''' + old_icon = self.prefs['tags_browser_category_icons'].get(old_key, None) + if old_icon is not None: + old_path = os.path.join(config_dir, 'tb_icons', old_icon) + _, ext = os.path.splitext(old_path) + new_icon = new_key + ext + new_path = os.path.join(config_dir, 'tb_icons', new_icon) + os.rename(old_path, new_path) + self.set_custom_category_icon(new_key, new_path) + self.set_custom_category_icon(old_key, None) + def set_custom_category_icon(self, key, path): d = self.prefs['tags_browser_category_icons'] if path: @@ -1351,14 +1367,16 @@ class TagsModel(QAbstractItemModel): # {{{ return self.show_error_after_event_loop_tick(_('Rename User category'), _('The name %s is already used')%nkey) user_cats[nkey] = user_cats[ckey] + self.rename_user_category_icon('@' + c, '@' + nkey) del user_cats[ckey] elif c[len(ckey)] == '.': rest = c[len(ckey):] if strcmp(ckey, nkey) != 0 and \ icu_lower(nkey + rest) in user_cat_keys_lower: return self.show_error_after_event_loop_tick(_('Rename User category'), - _('The name %s is already used')%(nkey+rest)) + _('The name %s is already used')%(nkey + rest)) user_cats[nkey + rest] = user_cats[ckey + rest] + self.rename_user_category_icon('@' + ckey + rest, '@' + nkey + rest) del user_cats[ckey + rest] self.user_categories_edited.emit(user_cats, nkey) # Does a refresh self.use_position_based_index_on_next_recount = True From 31b92d6ab8a8d3a2ceb5b7fe9ff6401ad7fdf944 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 5 Apr 2023 17:57:31 +0530 Subject: [PATCH 0424/2055] Comic Input: When converting grayscaled PNG images to PNG ensure output images are stored as indexed PNG. Fixes #1846 (Better implied comic conversion of grayscale images) --- src/calibre/ebooks/comic/input.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/comic/input.py b/src/calibre/ebooks/comic/input.py index 2c3b70709f..3c28346a5e 100644 --- a/src/calibre/ebooks/comic/input.py +++ b/src/calibre/ebooks/comic/input.py @@ -104,9 +104,11 @@ class PageProcessor(list): # {{{ self.num = num self.dest = dest self.rotate = False + self.src_img_was_grayscaled = False self.render() def render(self): + from qt.core import QImage from calibre.utils.img import image_from_data, scale_image, crop_image with open(self.path_to_page, 'rb') as f: img = image_from_data(f.read()) @@ -114,6 +116,8 @@ class PageProcessor(list): # {{{ if self.num == 0: # First image so create a thumbnail from it with open(os.path.join(self.dest, 'thumbnail.png'), 'wb') as f: f.write(scale_image(img, as_png=True)[-1]) + self.src_img_was_grayscaled = img.format() in (QImage.Format.Format_Grayscale8, QImage.Format.Format_Grayscale16) or ( + img.format() == QImage.Format.Format_Indexed8 and img.allGray()) self.pages = [img] if width > height: if self.opts.landscape: @@ -126,6 +130,7 @@ class PageProcessor(list): # {{{ self.process_pages() def process_pages(self): + from qt.core import QImage from calibre.utils.img import ( image_to_data, rotate_image, remove_borders_from_image, normalize_image, add_borders_to_image, resize_image, gaussian_sharpen_image, grayscale_image, @@ -206,8 +211,11 @@ class PageProcessor(list): # {{{ if self.opts.despeckle: img = despeckle_image(img) - if self.opts.output_format.lower() == 'png' and self.opts.colors: - img = quantize_image(img, max_colors=min(256, self.opts.colors)) + if self.opts.output_format.lower() == 'png': + if self.opts.colors: + img = quantize_image(img, max_colors=min(256, self.opts.colors)) + elif self.src_img_was_grayscaled: + img = img.convertToFormat(QImage.Format.Format_Grayscale8) dest = '%d_%d.%s'%(self.num, i, self.opts.output_format) dest = os.path.join(self.dest, dest) with open(dest, 'wb') as f: From dd186d060ddef1890df0300b69710b3bba52fe26 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 6 Apr 2023 11:08:03 +0530 Subject: [PATCH 0425/2055] Update paths and filenames in db in a single transaction --- src/calibre/db/backend.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index f9b3c06208..a15aa820cd 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1775,9 +1775,10 @@ class DB: self.copy_format_to(book_id, fmt, ofmt_fname, current_path, dest, windows_atomic_move=wam, use_hardlink=True) # Update db to reflect new file locations - for fmt in formats: - formats_field.table.set_fname(book_id, fmt, fname, self) - path_field.table.set_path(book_id, path, self) + with self.conn: + for fmt in formats: + formats_field.table.set_fname(book_id, fmt, fname, self) + path_field.table.set_path(book_id, path, self) # Delete not needed files and directories if source_ok: From acc1629efbf925f76ce0445151d95ad9e94f2233 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 6 Apr 2023 13:51:35 +0530 Subject: [PATCH 0426/2055] Function to get a best guess book path from a book id --- src/calibre/db/backend.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index a15aa820cd..4a29f9923c 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -55,6 +55,7 @@ from polyglot.builtins import ( # }}} +BOOK_ID_PATH_TEMPLATE = ' ({})' CUSTOM_DATA_TYPES = frozenset(('rating', 'text', 'comments', 'datetime', 'int', 'float', 'bool', 'series', 'composite', 'enumeration')) WINDOWS_RESERVED_NAMES = frozenset('CON PRN AUX NUL COM1 COM2 COM3 COM4 COM5 COM6 COM7 COM8 COM9 LPT1 LPT2 LPT3 LPT4 LPT5 LPT6 LPT7 LPT8 LPT9'.split()) @@ -425,13 +426,8 @@ class DB: restore_all_prefs=False, progress_callback=lambda x, y:True, load_user_formatter_functions=True): self.is_closed = False - try: - if isbytestring(library_path): - library_path = library_path.decode(filesystem_encoding) - except: - import traceback - traceback.print_exc() - + if isbytestring(library_path): + library_path = library_path.decode(filesystem_encoding) self.field_metadata = FieldMetadata() self.library_path = os.path.abspath(library_path) @@ -1338,7 +1334,7 @@ class DB: ''' Construct the directory name for this book based on its metadata. ''' - book_id = ' (%d)' % book_id + book_id = BOOK_ID_PATH_TEMPLATE.format(book_id) l = self.PATH_LIMIT - (len(book_id) // 2) - 2 author = ascii_filename(author)[:l] title = ascii_filename(title.lstrip())[:l].rstrip() @@ -1433,8 +1429,22 @@ class DB: pprint.pprint(table.metadata) raise - def format_abspath(self, book_id, fmt, fname, path): - path = os.path.join(self.library_path, path) + def find_path_for_book(self, book_id): + q = BOOK_ID_PATH_TEMPLATE.format(book_id) + for author_dir in os.scandir(self.library_path): + if not author_dir.is_dir(): + continue + try: + book_dir_iter = os.scandir(author_dir.path) + except OSError: + pass + else: + for book_dir in book_dir_iter: + if book_dir.name.endswith(q) and book_dir.is_dir(): + return book_dir.path + + def format_abspath(self, book_id, fmt, fname, book_path): + path = os.path.join(self.library_path, book_path) fmt = ('.' + fmt.lower()) if fmt else '' fmt_path = os.path.join(path, fname+fmt) if os.path.exists(fmt_path): From da5a646e600a024eb9c285c43c2da08bca2504aa Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Thu, 6 Apr 2023 11:56:22 +0100 Subject: [PATCH 0427/2055] Bug #2015427: Dragging cell to user category causes search filter to break --- src/calibre/gui2/tag_browser/ui.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/tag_browser/ui.py b/src/calibre/gui2/tag_browser/ui.py index 51d49f3bd6..58fc3ce890 100644 --- a/src/calibre/gui2/tag_browser/ui.py +++ b/src/calibre/gui2/tag_browser/ui.py @@ -110,7 +110,11 @@ class TagBrowserMixin: # {{{ self.library_view.model().count_changed() def user_categories_edited(self): - self.library_view.model().refresh() + current_row_id = self.library_view.current_id + self.library_view.model().refresh(reset=True) + self.library_view.model().research(reset=False) + self.library_view.current_id = current_row_id # the setter checks for None + def do_restriction_error(self, e): error_dialog(self.tags_view, _('Invalid search restriction'), From c3d7b84386e112f7d2f7b4d907893dca1d0de88f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 6 Apr 2023 19:26:27 +0530 Subject: [PATCH 0428/2055] Start refactoring atomic move code --- src/calibre/utils/copy_files.py | 198 +++++++++++++++++++++++++++ src/calibre/utils/copy_files_test.py | 24 ++++ src/calibre/utils/run_tests.py | 2 + 3 files changed, 224 insertions(+) create mode 100644 src/calibre/utils/copy_files.py create mode 100644 src/calibre/utils/copy_files_test.py diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py new file mode 100644 index 0000000000..71c057527c --- /dev/null +++ b/src/calibre/utils/copy_files.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python +# License: GPLv3 Copyright: 2023, Kovid Goyal + +import errno +import os +import shutil +import stat +import time +from collections import defaultdict +from contextlib import suppress +from typing import Callable, Dict, Set, Tuple, Union + +from calibre.constants import filesystem_encoding, iswindows +from calibre.utils.filenames import make_long_path_useable, samefile, windows_hardlink + +if iswindows: + from calibre_extensions import winutil + +WINDOWS_SLEEP_FOR_RETRY_TIME = 2 # seconds +WindowsFileId = Tuple[int, int, int] + +class UnixFileCopier: + + def __init__(self): + self.copy_map: Dict[str, str] = {} + + def register(self, path: str, dest: str) -> None: + self.copy_map[path] = dest + + def __enter__(self) -> None: + pass + + def __exit__(self, *a) -> None: + pass + + def copy_all(self) -> None: + for src_path, dest_path in self.copy_map.items(): + with suppress(OSError): + os.link(src_path, dest_path, follow_symlinks=False) + shutil.copystat(src_path, dest_path, follow_symlinks=False) + return + shutil.copy2(src_path, dest_path, follow_symlinks=False) + + def delete_all_source_files(self) -> None: + for src_path in self.copy_map: + os.unlink(src_path) + + +class WindowsFileCopier: + + ''' + Locks all files before starting the copy, ensuring other processes cannot interfere + ''' + + def __init__(self): + self.path_to_fileid_map : Dict[str, WindowsFileId] = {} + self.fileid_to_paths_map: Dict[WindowsFileId, Set[str]] = defaultdict(set) + self.path_to_handle_map: Dict[str, 'winutil.Handle'] = {} + self.copy_map: Dict[str, str] = {} + + def register(self, path: str, dest: str) -> None: + with suppress(OSError): + # Ensure the file is not read-only + winutil.set_file_attributes(make_long_path_useable(path), winutil.FILE_ATTRIBUTE_NORMAL) + self.path_to_fileid_map[path] = winutil.get_file_id(make_long_path_useable(path)) + self.copy_map[path] = dest + + def _open_file(self, path: str, retry_on_sharing_violation: bool = True) -> 'winutil.Handle': + try: + return winutil.create_file(path, winutil.GENERIC_READ, + winutil.FILE_SHARE_DELETE, + winutil.OPEN_EXISTING, winutil.FILE_FLAG_SEQUENTIAL_SCAN) + except OSError as e: + if e.winerror == winutil.ERROR_SHARING_VIOLATION: + # The file could be a hardlink to an already opened file, + # in which case we use the same handle for both files + fileid = self.path_to_fileid_map[path] + for other in self.fileid_to_paths_map[fileid]: + if other in self.path_to_handle_map: + return self.path_to_handle_map[other] + if retry_on_sharing_violation: + time.sleep(WINDOWS_SLEEP_FOR_RETRY_TIME) + return self._open_file(path, False) + err = IOError(errno.EACCES, + _('File is open in another program')) + err.filename = path + raise err from e + raise + + def __enter__(self) -> None: + for path, file_id in self.path_to_fileid_map.items(): + self.fileid_to_paths_map[file_id].add(path) + for src in self.copy_map: + self.path_to_handle_map = self._open_file(src) + + def __exit__(self, *a) -> None: + for h in self.path_to_handle_map.values(): + h.close() + + def copy_all(self) -> None: + for src_path, dest_path in self.copy_map.items(): + with suppress(Exception): + windows_hardlink(src_path, dest_path) + shutil.copystat(src_path, dest_path, follow_symlinks=False) + handle = self.path_to_handle_map[src_path] + winutil.set_file_pointer(handle, 0, winutil.FILE_BEGIN) + with open(dest_path, 'wb') as f: + sz = 1024 * 1024 + while True: + raw = winutil.read_file(handle, sz) + if not raw: + break + f.write(raw) + shutil.copystat(src_path, dest_path, follow_symlinks=False) + + def delete_all_source_files(self) -> None: + for src_path in self.copy_map: + winutil.delete_file(make_long_path_useable(src_path)) + + +def get_copier() -> Union[UnixFileCopier | WindowsFileCopier]: + return WindowsFileCopier() if iswindows else UnixFileCopier() + + +def copy_files(src_to_dest_map: Dict[str, str], delete_source: bool = False) -> None: + copier = get_copier() + for s, d in src_to_dest_map.items(): + if not samefile(s, d): + copier.register(s, d) + with copier: + copier.copy_all() + if delete_source: + copier.delete_all_source_files() + + +def copy_tree( + src: str, dest: str, + transform_destination_filename: Callable[[str, str], str] = lambda src_path, dest_path : dest_path, + delete_source: bool = False +) -> None: + ''' + Copy all files in the tree over. On Windows locks all files before starting the copy to ensure that + other processes cannot interfere once the copy starts. + ''' + if iswindows: + if isinstance(src, bytes): + src = src.decode(filesystem_encoding) + if isinstance(dest, bytes): + dest = dest.decode(filesystem_encoding) + + dest = os.path.abspath(dest) + os.makedirs(dest, exist_ok=True) + if samefile(src, dest): + raise ValueError(f'Cannot copy tree if the source and destination are the same: {src!r} == {dest!r}') + + def raise_error(e: OSError) -> None: + raise e + + def dest_from_entry(dirpath: str, x: str) -> str: + path = os.path.join(dirpath, d) + rel = os.path.relpath(path, src) + return os.path.join(dest, rel) + + + copier = get_copier() + for (dirpath, dirnames, filenames) in os.walk(src, onerror=raise_error): + for d in dirnames: + path = os.path.join(dirpath, d) + dest = dest_from_entry(dirpath, d) + os.makedirs(make_long_path_useable(dest), exist_ok=True) + shutil.copystat(make_long_path_useable(path), make_long_path_useable(dest), follow_symlinks=False) + for f in filenames: + path = os.path.join(dirpath, f) + dest = dest_from_entry(dirpath, d) + dest = transform_destination_filename(path, dest) + if not iswindows: + s = os.stat(path, follow_symlinks=False) + if stat.S_ISLNK(s.st_mode): + link_dest = os.readlink(path) + os.symlink(link_dest, dest) + continue + copier.register(path, dest) + + + with copier: + copier.copy_all() + if delete_source: + copier.delete_all_source_files() + + if delete_source: + try: + shutil.rmtree(make_long_path_useable(src)) + except OSError: + if iswindows: + time.sleep(WINDOWS_SLEEP_FOR_RETRY_TIME) + shutil.rmtree(make_long_path_useable(src)) + else: + raise diff --git a/src/calibre/utils/copy_files_test.py b/src/calibre/utils/copy_files_test.py new file mode 100644 index 0000000000..62358bee55 --- /dev/null +++ b/src/calibre/utils/copy_files_test.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# License: GPLv3 Copyright: 2023, Kovid Goyal + +import shutil +import tempfile +import unittest + + +class TestCopyFiles(unittest.TestCase): + + ae = unittest.TestCase.assertEqual + + def setUp(self): + self.tdir = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.tdir) + + def test_copy_files(self): + pass + + +def find_tests(): + return unittest.defaultTestLoader.loadTestsFromTestCase(TestCopyFiles) diff --git a/src/calibre/utils/run_tests.py b/src/calibre/utils/run_tests.py index 9468453029..a38815d5b2 100644 --- a/src/calibre/utils/run_tests.py +++ b/src/calibre/utils/run_tests.py @@ -291,6 +291,8 @@ def find_tests(which_tests=None, exclude_tests=None): a(find_tests()) from calibre.live import find_tests a(find_tests()) + from calibre.utils.copy_files_test import find_tests + a(find_tests()) if iswindows: from calibre.utils.windows.wintest import find_tests a(find_tests()) From 5a20841bcbcc9adc5809fc1fdfa99e0d82bf569b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 6 Apr 2023 19:39:03 +0530 Subject: [PATCH 0429/2055] Cleanup docstrings for the link_map functions --- src/calibre/db/cache.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index cca798bd4a..591573d907 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2350,9 +2350,9 @@ class Cache: ''' Return a dict of links for the supplied field. - field: the lookup name of the field for which the link map is desired + :param for_field: the lookup name of the field for which the link map is desired - returns {field_value:link_value, ...} for non-empty links + :return: {field_value:link_value, ...} for non-empty links ''' if for_field not in self.fields: raise ValueError(f'Lookup name {for_field} is not a valid name') @@ -2369,17 +2369,16 @@ class Cache: ''' Returns all links for all fields referenced by book identified by book_id - book_id: the book id in question. - - returns: - {field: {field_value, link_value}, ... - for all fields that have a non-empty link value for that book - Example: Assume author A has link X, author B has link Y, tag S has link F, and tag T has link G. IF book 1 has author A and tag T, this method returns {'authors':{'A':'X'}, 'tags':{'T', 'G'}} If book 2's author is neither A nor B and has no tags, this method returns {} + + :param book_id: the book id in question. + + :return: {field: {field_value, link_value}, ... for all fields that have a non-empty link value for that book + ''' cached = self.link_maps_cache.get(book_id) if cached is not None: @@ -2408,14 +2407,13 @@ class Cache: def set_link_map(self, field, value_to_link_map, only_set_if_no_existing_link=False): ''' Sets links for item values in field - - field: the lookup name - value_to_link_map: dict(field_value:link, ...). Note that these are - values, not field ids. - - returns books changed by setting the link - Note: this method doesn't change values not in the value_to_link_map + + :param field: the lookup name + :param value_to_link_map: dict(field_value:link, ...). Note that these are values, not field ids. + + :return: books changed by setting the link + ''' if field not in self.fields: raise ValueError(f'Lookup name {field} is not a valid name') From 1495c4e185c587ca5e42da85401895fc0e474dc9 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Thu, 6 Apr 2023 16:25:29 +0100 Subject: [PATCH 0430/2055] Make the formatter function series_sort() respect the book's language. --- src/calibre/utils/formatter_functions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 89e0150693..bf160818c7 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -1389,7 +1389,9 @@ class BuiltinSeriesSort(BuiltinFormatterFunction): def evaluate(self, formatter, kwargs, mi, locals): if mi.series: - return title_sort(mi.series) + langs = mi.languages + lang = langs[0] if len(langs) > 0 else None + return title_sort(mi.series, lang=lang) return '' From 1831407a2d1a516ed4b1d5528606fd92a61cad81 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 7 Apr 2023 08:26:36 +0530 Subject: [PATCH 0431/2055] ... --- src/calibre/gui2/tag_mapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/tag_mapper.py b/src/calibre/gui2/tag_mapper.py index d3e990b2e1..001aa2324f 100644 --- a/src/calibre/gui2/tag_mapper.py +++ b/src/calibre/gui2/tag_mapper.py @@ -392,7 +392,7 @@ class Rules(QWidget): @rules.setter def rules(self, rules): self.rule_list.clear() - for rule in rules: + for rule in (rules or ()): if self.ACTION_KEY in rule and 'match_type' in rule and 'query' in rule: self.RuleItemClass(rule, self.rule_list) From 3b0f2381410c141465cfe64c3b371bb19cb36acf Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 7 Apr 2023 08:29:48 +0530 Subject: [PATCH 0432/2055] Fix sorting on link column in category editor --- src/calibre/gui2/dialogs/tag_list_editor.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index f3236b3eb4..3193c09d76 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -192,6 +192,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.name_order = 0 self.count_order = 1 self.was_order = 1 + self.link_order = 0 self.edit_delegate = EditColumnDelegate(self.table) self.edit_delegate.editing_finished.connect(self.stop_editing) @@ -663,7 +664,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.table.scrollToItem(self.table.item(row, 0)) def do_sort(self, section): - (self.do_sort_by_name, self.do_sort_by_count, self.do_sort_by_was)[section]() + (self.do_sort_by_name, self.do_sort_by_count, self.do_sort_by_was, self.do_sort_by_was_link)[section]() def do_sort_by_name(self): self.name_order = 1 - self.name_order @@ -680,6 +681,11 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.last_sorted_by = 'count' self.table.sortByColumn(2, Qt.SortOrder(self.was_order)) + def do_sort_by_link(self): + self.link_order = 1 - self.link_order + self.last_sorted_by = 'link' + self.table.sortByColumn(3, Qt.SortOrder(self.link_order)) + def accepted(self): self.links = {self.table.item(r, 0).text():self.table.item(r, 3).text() for r in range(self.table.rowCount())} self.save_geometry() From fe18bd2a01435c08861cf5f28eb17bf9879eae7c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 7 Apr 2023 08:31:18 +0530 Subject: [PATCH 0433/2055] version 6.15.0 --- Changelog.txt | 52 ++++++++++++++++++++++++++++++++++++++++ src/calibre/constants.py | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/Changelog.txt b/Changelog.txt index 5a90e8bde1..49175caa1f 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -23,6 +23,58 @@ # - title by author # }}} +{{{ 6.15.0 2023-04-07 + +:: new features + +- Allow adding external links to tags, series, publishers, etcetera in addition to authors + + The links show up as click-able icons in the book details panel. They can be set by right clicking the + author/tag/etc. in the Tag browser and choosing "Manage" + +- calibre:// URL scheme: Add support for a new type of URL that pops up the metadata of the specified book in a new window + + Works even with books not in the current library. See https://manual.calibre-ebook.com/url_scheme.html#open-a-book-details-window-on-a-book-in-some-library for details + +- EPUB Output: Do not shrink images to fit the screen size by default, as modern readers do this themselves well enough. Can be controlled via an option in the EPUB Output section of the conversion dialog + +- Edit metadata dialog: Add buttons to pop up the category editing windows easily + +- [2012304] Metadata download: Allow specifying rules to transform publisher names in addition to author and tag names + +- [2007764] Edit metadata dialog: Use both a colored border and an icon to indicate errors in line edits + +- A new tweak in Preferences->Tweaks to control what program is run when clicking on URLs in calibre + +:: bug fixes + +- [2009586] E-book viewer: Fix a regression that caused incorrect highlight collision detection in some books + +- E-book viewer: Fix images embedded inside SVG tags not available for viewing in a pop-up + +- [2013972] DOCX Input: Do not ignore images that are present as fallbacks for a word drawing object + +- Comic Input: When converting grayscaled PNG images to PNG ensure output images are stored as indexed PNG + +- [2012797] Fix active tab not easy to distinguish in dark mode + +- [2011755] Content server: Fix re-opening book from home page after making progress not opening to correct last read position when a user is logged in + +- [2012760] Comments editor: When copying to clipboard, copy clean HTML rather than the junk Qt produces + +:: improved recipes +- Saechsische Zeitung +- LA Times +- Mediapart +- Live Mint +- The Hindu + +:: new recipes +- Tehelka by Areet Mahadevan +- The Wire by unkn0wn + +}}} + {{{ 6.14.1 2023-03-16 :: new features diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 4e9b0de30a..56d6aa8e37 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -5,7 +5,7 @@ from functools import lru_cache import sys, locale, codecs, os, collections, collections.abc __appname__ = 'calibre' -numeric_version = (6, 14, 1) +numeric_version = (6, 15, 0) __version__ = '.'.join(map(str, numeric_version)) git_version = None __author__ = "Kovid Goyal " From 4c0961c1c07e8e4be6d56d50d6fe7a4839565ede Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 7 Apr 2023 08:34:50 +0530 Subject: [PATCH 0434/2055] ... --- src/calibre/gui2/dialogs/tag_list_editor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index 3193c09d76..26bf56d313 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -479,6 +479,8 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.table.sortByColumn(0, Qt.SortOrder(self.name_order)) elif self.last_sorted_by == 'count': self.table.sortByColumn(1, Qt.SortOrder(self.count_order)) + elif self.last_sorted_by == 'link': + self.table.sortByColumn(3, Qt.SortOrder(self.link_order)) else: self.table.sortByColumn(2, Qt.SortOrder(self.was_order)) @@ -664,7 +666,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.table.scrollToItem(self.table.item(row, 0)) def do_sort(self, section): - (self.do_sort_by_name, self.do_sort_by_count, self.do_sort_by_was, self.do_sort_by_was_link)[section]() + (self.do_sort_by_name, self.do_sort_by_count, self.do_sort_by_was, self.do_sort_by_link)[section]() def do_sort_by_name(self): self.name_order = 1 - self.name_order From 24626ceb0d8a24a3dd6d4bd333793e4144dd9c36 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Fri, 7 Apr 2023 10:08:30 +0100 Subject: [PATCH 0435/2055] Undo commit e11b8c8. book-details links should appear in the special window (no search links, etc) even if in the current library. The LibraryBroker deals with not opening the database twice. This might be worth a point release. See https://www.mobileread.com/forums/showthread.php?p=4312453#post4312453 and later. --- src/calibre/gui2/actions/show_book_details.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/calibre/gui2/actions/show_book_details.py b/src/calibre/gui2/actions/show_book_details.py index 00e993f0b0..164feb7958 100644 --- a/src/calibre/gui2/actions/show_book_details.py +++ b/src/calibre/gui2/actions/show_book_details.py @@ -28,8 +28,6 @@ class ShowBookDetailsAction(InterfaceAction): library_path = kwargs.get('library_path', None) book_id = kwargs.get('book_id', None) library_id = kwargs.get('library_id', None) - if library_path is not None and self.gui.library_broker.is_gui_library(library_path): - library_path = library_id = None query = kwargs.get('query', None) index = self.gui.library_view.currentIndex() if self.gui.current_view() is not self.gui.library_view and not library_path: From 47d446f799921faab2bea9f59d6355d3029a6e50 Mon Sep 17 00:00:00 2001 From: chocolatechipcats <47759676+chocolatechipcats@users.noreply.github.com> Date: Fri, 7 Apr 2023 17:13:40 -0300 Subject: [PATCH 0436/2055] Plugboard section in template_lang Moved the plugboard section up (just below the custom column section) and clarified that all modes can be used for it. --- manual/template_lang.rst | 49 +++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/manual/template_lang.rst b/manual/template_lang.rst index 93d7880447..1a467f0e6d 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -116,6 +116,32 @@ Composite columns can use any template option, including formatting. Note: You cannot edit the data displayed in a composite column. Instead you edit the source columns. If you edit a composite column, for example by double-clicking it, calibre will open the template for editing, not the underlying data. + +Templates and plugboards +--------------------------- + +Plugboards are used for changing the metadata written into books during send-to-device and save-to-disk operations. A plugboard permits you to specify a template to provide the data to write into the book's metadata. You can use plugboards to modify the following fields: authors, author_sort, language, publisher, tags, title, title_sort. This feature helps people who want to use different metadata in books on devices to solve sorting or display issues. + +When you create a plugboard, you specify the format and device for which the plugboard is to be used. A special device is provided, ``save_to_disk``, that is used when saving formats (as opposed to sending them to a device). Once you have chosen the format and device, you choose the metadata fields to change, providing templates to supply the new values. These templates are `connected` to their destination fields, hence the name `plugboards`. You can of course use composite columns in these templates. + +Plugboards are quite flexible and can be written in Single Function Mode, Template Program Mode, General Program Mode, or Python Template mode. + +When a plugboard might apply (Content server, save to disk, or send to device), calibre searches the +defined plugboards to choose the correct one for the given format and device. For example, to find the appropriate plugboard for an EPUB book being sent to an ANDROID device, calibre searches +the plugboards using the following search order: + +* a plugboard with an exact match on format and device, e.g., ``EPUB`` and ``ANDROID`` +* a plugboard with an exact match on format and the special ``any device`` choice, e.g., ``EPUB`` and ``any device`` +* a plugboard with the special ``any format`` choice and an exact match on device, e.g., ``any format`` and ``ANDROID`` +* a plugboard with ``any format`` and ``any device`` + +The tags and authors fields have special treatment, because both of these fields can hold more than one item. A book can have many tags and many authors. When you specify that one of these two fields is to be changed, the template's result is examined to see if more than one item is there. For tags, the result is cut apart wherever calibre finds a comma. For example, if the template produces +the value ``Thriller, Horror``, then the result will be two tags, ``Thriller`` and ``Horror``. There is no way to put a comma in the middle of a tag. + +The same thing happens for authors, but using a different character for the cut, a `&` (ampersand) instead of a comma. For example, if the template produces the value ``Blogs, Joe&Posts, Susan``, then the book will end up with two authors, ``Blogs, Joe`` and ``Posts, Susan``. If the template produces the value ``Blogs, Joe;Posts, Susan``, then the book will have one author with a rather strange name. + +Plugboards affect the metadata written into the book when it is saved to disk or written to the device. Plugboards do not affect the metadata used by ``save to disk`` and ``send to device`` to create the file names. Instead, file names are constructed using the templates entered on the appropriate preferences window. + .. _single_mode: Using functions in templates - Single Function Mode @@ -823,29 +849,6 @@ To accomplish this, we: 2. Create a composite field (give it lookup name #bb) containing ``{#genre:ifempty(Unknown)}/{author_sort}/{title}``. This template produces `genre/author_sort/title`, where an empty genre is replaced with `Unknown`. 3. Set the save template to ``{series:lookup(.,#aa,#bb}``. This template chooses composite field ``#aa`` if series is not empty and composite field ``#bb`` if series is empty. We therefore have two completely different save paths, depending on whether or not `series` is empty. -Templates and plugboards ---------------------------- - -Plugboards are used for changing the metadata written into books during send-to-device and save-to-disk operations. A plugboard permits you to specify a template to provide the data to write into the book's metadata. You can use plugboards to modify the following fields: authors, author_sort, language, publisher, tags, title, title_sort. This feature helps people who want to use different metadata in books on devices to solve sorting or display issues. - -When you create a plugboard, you specify the format and device for which the plugboard is to be used. A special device is provided, ``save_to_disk``, that is used when saving formats (as opposed to sending them to a device). Once you have chosen the format and device, you choose the metadata fields to change, providing templates to supply the new values. These templates are `connected` to their destination fields, hence the name `plugboards`. You can of course use composite columns in these templates. - -When a plugboard might apply (Content server, save to disk, or send to device), calibre searches the -defined plugboards to choose the correct one for the given format and device. For example, to find the appropriate plugboard for an EPUB book being sent to an ANDROID device, calibre searches -the plugboards using the following search order: - -* a plugboard with an exact match on format and device, e.g., ``EPUB`` and ``ANDROID`` -* a plugboard with an exact match on format and the special ``any device`` choice, e.g., ``EPUB`` and ``any device`` -* a plugboard with the special ``any format`` choice and an exact match on device, e.g., ``any format`` and ``ANDROID`` -* a plugboard with ``any format`` and ``any device`` - -The tags and authors fields have special treatment, because both of these fields can hold more than one item. A book can have many tags and many authors. When you specify that one of these two fields is to be changed, the template's result is examined to see if more than one item is there. For tags, the result is cut apart wherever calibre finds a comma. For example, if the template produces -the value ``Thriller, Horror``, then the result will be two tags, ``Thriller`` and ``Horror``. There is no way to put a comma in the middle of a tag. - -The same thing happens for authors, but using a different character for the cut, a `&` (ampersand) instead of a comma. For example, if the template produces the value ``Blogs, Joe&Posts, Susan``, then the book will end up with two authors, ``Blogs, Joe`` and ``Posts, Susan``. If the template produces the value ``Blogs, Joe;Posts, Susan``, then the book will have one author with a rather strange name. - -Plugboards affect the metadata written into the book when it is saved to disk or written to the device. Plugboards do not affect the metadata used by ``save to disk`` and ``send to device`` to create the file names. Instead, file names are constructed using the templates entered on the appropriate preferences window. - Tips ----- From 800734915561e160f2ba62e9fcba81d31943f99e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 8 Apr 2023 08:46:56 +0530 Subject: [PATCH 0437/2055] version 6.15.1 --- Changelog.txt | 4 +++- src/calibre/constants.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changelog.txt b/Changelog.txt index 49175caa1f..313ade7cb3 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -23,7 +23,7 @@ # - title by author # }}} -{{{ 6.15.0 2023-04-07 +{{{ 6.15.1 2023-04-07 :: new features @@ -62,6 +62,8 @@ - [2012760] Comments editor: When copying to clipboard, copy clean HTML rather than the junk Qt produces +- Version 6.15.1 fixes an issue with the new URL scheme popping up incorrect book details windows + :: improved recipes - Saechsische Zeitung - LA Times diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 56d6aa8e37..54aa9fe02f 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -5,7 +5,7 @@ from functools import lru_cache import sys, locale, codecs, os, collections, collections.abc __appname__ = 'calibre' -numeric_version = (6, 15, 0) +numeric_version = (6, 15, 1) __version__ = '.'.join(map(str, numeric_version)) git_version = None __author__ = "Kovid Goyal " From 42cabadccbd3e7c33bdad77ed71ab22cb3dc8948 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 8 Apr 2023 11:25:42 +0530 Subject: [PATCH 0438/2055] Add some missing file attribute constants --- src/calibre/utils/windows/winutil.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/calibre/utils/windows/winutil.cpp b/src/calibre/utils/windows/winutil.cpp index 143e168a19..6f7a5dab4f 100644 --- a/src/calibre/utils/windows/winutil.cpp +++ b/src/calibre/utils/windows/winutil.cpp @@ -1402,6 +1402,10 @@ exec_module(PyObject *m) { PyModule_AddIntConstant(m, "FILE_SHARE_VALID_FLAGS", FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE); A(FILE_ATTRIBUTE_READONLY); A(FILE_ATTRIBUTE_NORMAL); + A(FILE_ATTRIBUTE_HIDDEN); + A(FILE_ATTRIBUTE_NOT_CONTENT_INDEXED); + A(FILE_ATTRIBUTE_OFFLINE); + A(FILE_ATTRIBUTE_SYSTEM); A(FILE_ATTRIBUTE_TEMPORARY); A(FILE_FLAG_DELETE_ON_CLOSE); A(FILE_FLAG_SEQUENTIAL_SCAN); From e25f8bf61c651a2d7f490133f431d4e25d0db73b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 8 Apr 2023 13:02:17 +0530 Subject: [PATCH 0439/2055] Add some tests for the new copy tree functionality --- src/calibre/utils/copy_files.py | 13 ++++-- src/calibre/utils/copy_files_test.py | 68 ++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index 71c057527c..7cddf9b9b0 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -38,7 +38,7 @@ class UnixFileCopier: with suppress(OSError): os.link(src_path, dest_path, follow_symlinks=False) shutil.copystat(src_path, dest_path, follow_symlinks=False) - return + continue shutil.copy2(src_path, dest_path, follow_symlinks=False) def delete_all_source_files(self) -> None: @@ -102,6 +102,7 @@ class WindowsFileCopier: with suppress(Exception): windows_hardlink(src_path, dest_path) shutil.copystat(src_path, dest_path, follow_symlinks=False) + continue handle = self.path_to_handle_map[src_path] winutil.set_file_pointer(handle, 0, winutil.FILE_BEGIN) with open(dest_path, 'wb') as f: @@ -140,7 +141,8 @@ def copy_tree( ) -> None: ''' Copy all files in the tree over. On Windows locks all files before starting the copy to ensure that - other processes cannot interfere once the copy starts. + other processes cannot interfere once the copy starts. Uses hardlinks, falling back to actual file copies + only if hardlinking fails. ''' if iswindows: if isinstance(src, bytes): @@ -152,14 +154,15 @@ def copy_tree( os.makedirs(dest, exist_ok=True) if samefile(src, dest): raise ValueError(f'Cannot copy tree if the source and destination are the same: {src!r} == {dest!r}') + dest_dir = dest def raise_error(e: OSError) -> None: raise e def dest_from_entry(dirpath: str, x: str) -> str: - path = os.path.join(dirpath, d) + path = os.path.join(dirpath, x) rel = os.path.relpath(path, src) - return os.path.join(dest, rel) + return os.path.join(dest_dir, rel) copier = get_copier() @@ -171,7 +174,7 @@ def copy_tree( shutil.copystat(make_long_path_useable(path), make_long_path_useable(dest), follow_symlinks=False) for f in filenames: path = os.path.join(dirpath, f) - dest = dest_from_entry(dirpath, d) + dest = dest_from_entry(dirpath, f) dest = transform_destination_filename(path, dest) if not iswindows: s = os.stat(path, follow_symlinks=False) diff --git a/src/calibre/utils/copy_files_test.py b/src/calibre/utils/copy_files_test.py index 62358bee55..2b0c979612 100644 --- a/src/calibre/utils/copy_files_test.py +++ b/src/calibre/utils/copy_files_test.py @@ -1,23 +1,83 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2023, Kovid Goyal +import os import shutil import tempfile +import time import unittest +from calibre.constants import iswindows + +from .copy_files import copy_tree +from .filenames import nlinks_file + class TestCopyFiles(unittest.TestCase): ae = unittest.TestCase.assertEqual def setUp(self): - self.tdir = tempfile.mkdtemp() + self.tdir = t = tempfile.mkdtemp() + def wf(*parts): + d = os.path.join(t, *parts) + os.makedirs(os.path.dirname(d), exist_ok=True) + with open(d, 'w') as f: + f.write(' '.join(parts)) + wf('base'), wf('src/one'), wf('src/sub/a') + if not iswindows: + os.symlink('sub/a', os.path.join(t, 'src/link')) def tearDown(self): - shutil.rmtree(self.tdir) + if self.tdir: + try: + shutil.rmtree(self.tdir) + except OSError: + time.sleep(1) + shutil.rmtree(self.tdir) + self.tdir = '' - def test_copy_files(self): - pass + def s(self, *path): + return os.path.abspath(os.path.join(self.tdir, 'src', *path)) + + def d(self, *path): + return os.path.abspath(os.path.join(self.tdir, 'dest', *path)) + + def file_data_eq(self, path): + with open(self.s(path)) as src, open(self.d(path)) as dest: + self.ae(src.read(), dest.read()) + + def reset(self): + self.tearDown() + self.setUp() + + def test_copying_of_trees(self): + src, dest = self.s(), self.d() + copy_tree(src, dest) + eq = self.file_data_eq + eq('one') + eq('sub/a') + if not iswindows: + eq('link') + self.ae(os.readlink(self.d('link')), 'sub/a') + self.ae(nlinks_file(self.s('one')), 2) + self.ae(set(os.listdir(self.tdir)), {'src', 'dest', 'base'}) + self.reset() + src, dest = self.s(), self.d() + copy_tree(src, dest, delete_source=True) + self.ae(set(os.listdir(self.tdir)), {'dest', 'base'}) + self.ae(nlinks_file(self.d('one')), 1) + + def transform_destination_filename(src, dest): + return dest + '.extra' + + self.reset() + src, dest = self.s(), self.d() + copy_tree(src, dest, transform_destination_filename=transform_destination_filename) + with open(self.d('sub/a.extra')) as d: + self.ae(d.read(), 'src/sub/a') + if not iswindows: + self.ae(os.readlink(self.d('link.extra')), 'sub/a') def find_tests(): From 5db45f0563c1ed7868a9bbcdff64ea5f9658b87b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 8 Apr 2023 13:07:42 +0530 Subject: [PATCH 0440/2055] Add a test with locked file on windows --- src/calibre/utils/copy_files.py | 2 +- src/calibre/utils/copy_files_test.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index 7cddf9b9b0..540b96bf31 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -91,7 +91,7 @@ class WindowsFileCopier: for path, file_id in self.path_to_fileid_map.items(): self.fileid_to_paths_map[file_id].add(path) for src in self.copy_map: - self.path_to_handle_map = self._open_file(src) + self.path_to_handle_map[src] = self._open_file(src) def __exit__(self, *a) -> None: for h in self.path_to_handle_map.values(): diff --git a/src/calibre/utils/copy_files_test.py b/src/calibre/utils/copy_files_test.py index 2b0c979612..1d7394bb39 100644 --- a/src/calibre/utils/copy_files_test.py +++ b/src/calibre/utils/copy_files_test.py @@ -7,6 +7,7 @@ import tempfile import time import unittest +from calibre import walk from calibre.constants import iswindows from .copy_files import copy_tree @@ -79,6 +80,14 @@ class TestCopyFiles(unittest.TestCase): if not iswindows: self.ae(os.readlink(self.d('link.extra')), 'sub/a') + self.reset() + src, dest = self.s(), self.d() + if iswindows: + with open(self.s('sub/a')) as locked: + locked + self.assertRaises(IOError, copy_tree, src, dest) + self.ae(os.listdir(self.d()), ['sub']) + self.assertFalse(tuple(walk(self.d()))) def find_tests(): return unittest.defaultTestLoader.loadTestsFromTestCase(TestCopyFiles) From 817384f997066ff9573e79662e929fbeecb500a5 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 8 Apr 2023 13:31:46 +0100 Subject: [PATCH 0441/2055] Fix custom columns not showing in book-details links from other libraries --- src/calibre/gui2/dialogs/book_info.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index c56ec17900..bf34003549 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -214,6 +214,7 @@ class BookInfo(QDialog): mi.format_files = dict() mi.formats = list() mi.marked = '' + mi.field_metadata = db.field_metadata mi.external_library_path = library_path self.refresh(row, mi) else: From a73a36703e153b2749f9dd0a95f80a95abb36641 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 9 Apr 2023 11:44:16 +0100 Subject: [PATCH 0442/2055] For accessibility, add a context menu item to book details links to click the link. --- src/calibre/gui2/book_details.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index ac0f75fbe6..e59cf2dad6 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -508,6 +508,7 @@ def details_context_menu_event(view, ev, book_info, add_popup_action=False, edit ac.current_url = url ac.setText(_('Copy link location')) menu.addAction(ac) + menu.addAction(QIcon.ic('external-link'), _('Click link'), lambda : book_info.link_clicked.emit(url)) if not copy_links_added: create_copy_links(copy_menu) From 51ed2261b9bca0a9c7d61db41d1f0925c698dbc9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 9 Apr 2023 17:13:13 +0530 Subject: [PATCH 0443/2055] string changes --- src/calibre/db/cache.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 591573d907..9201cc1062 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2370,10 +2370,9 @@ class Cache: Returns all links for all fields referenced by book identified by book_id Example: Assume author A has link X, author B has link Y, tag S has link - F, and tag T has link G. IF book 1 has author A and - tag T, this method returns {'authors':{'A':'X'}, 'tags':{'T', 'G'}} - If book 2's author is neither A nor B and has no tags, this - method returns {} + F, and tag T has link G. IF book 1 has author A and tag T, + this method returns {'authors':{'A':'X'}, 'tags':{'T', 'G'}} + If book 2's author is neither A nor B and has no tags, this method returns {} :param book_id: the book id in question. From b999883f0b238e19cba808e4e28824ea624df6c7 Mon Sep 17 00:00:00 2001 From: chocolatechipcats <47759676+chocolatechipcats@users.noreply.github.com> Date: Mon, 10 Apr 2023 04:09:58 -0300 Subject: [PATCH 0444/2055] Added entries to author_name_copywords Added several entries (Software, Games, Entertainment, Media, and Studios) commonly seen in video game manuals. --- resources/default_tweaks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 137aa54dc4..ba018fd034 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -77,7 +77,7 @@ author_name_suffixes = ('Jr', 'Sr', 'Inc', 'Ph.D', 'Phd', author_name_prefixes = ('Mr', 'Mrs', 'Ms', 'Dr', 'Prof') author_name_copywords = ('Agency', 'Corporation', 'Company', 'Co.', 'Council', 'Committee', 'Inc.', 'Institute', 'National', - 'Society', 'Club', 'Team') + 'Society', 'Club', 'Team','Software','Games','Entertainment','Media','Studios') author_use_surname_prefixes = False author_surname_prefixes = ('da', 'de', 'di', 'la', 'le', 'van', 'von') From ae7bd581fc7ddd692777ab23eb7159023b972f14 Mon Sep 17 00:00:00 2001 From: chocolatechipcats <47759676+chocolatechipcats@users.noreply.github.com> Date: Mon, 10 Apr 2023 04:15:53 -0300 Subject: [PATCH 0445/2055] Split to new line Split new entries onto a new line --- resources/default_tweaks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index ba018fd034..6b49b2f602 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -77,7 +77,8 @@ author_name_suffixes = ('Jr', 'Sr', 'Inc', 'Ph.D', 'Phd', author_name_prefixes = ('Mr', 'Mrs', 'Ms', 'Dr', 'Prof') author_name_copywords = ('Agency', 'Corporation', 'Company', 'Co.', 'Council', 'Committee', 'Inc.', 'Institute', 'National', - 'Society', 'Club', 'Team','Software','Games','Entertainment','Media','Studios') + 'Society', 'Club', 'Team','Software', + 'Games','Entertainment','Media','Studios') author_use_surname_prefixes = False author_surname_prefixes = ('da', 'de', 'di', 'la', 'le', 'van', 'von') From 03c539299c79f8c9c0898b6fced89e90f416a84b Mon Sep 17 00:00:00 2001 From: chocolatechipcats <47759676+chocolatechipcats@users.noreply.github.com> Date: Mon, 10 Apr 2023 04:17:19 -0300 Subject: [PATCH 0446/2055] made an oops... --- resources/default_tweaks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 6b49b2f602..6fdb196a90 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -77,8 +77,8 @@ author_name_suffixes = ('Jr', 'Sr', 'Inc', 'Ph.D', 'Phd', author_name_prefixes = ('Mr', 'Mrs', 'Ms', 'Dr', 'Prof') author_name_copywords = ('Agency', 'Corporation', 'Company', 'Co.', 'Council', 'Committee', 'Inc.', 'Institute', 'National', - 'Society', 'Club', 'Team','Software', - 'Games','Entertainment','Media','Studios') + 'Society', 'Club', 'Team', + 'Software', 'Games','Entertainment','Media','Studios') author_use_surname_prefixes = False author_surname_prefixes = ('da', 'de', 'di', 'la', 'le', 'van', 'von') From 71c04502214d90fe053feb45e00403ab1b47f483 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 10 Apr 2023 10:12:44 +0100 Subject: [PATCH 0447/2055] Bug #2015719: Sorting in user category editor --- src/calibre/gui2/dialogs/tag_categories.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_categories.py b/src/calibre/gui2/dialogs/tag_categories.py index f498b47005..13172b73c7 100644 --- a/src/calibre/gui2/dialogs/tag_categories.py +++ b/src/calibre/gui2/dialogs/tag_categories.py @@ -126,6 +126,11 @@ class TagCategories(QDialog, Ui_TagCategories): def category_name_tuple(self, key, name): return self.CategoryNameTuple(name, key) + def item_sort_key(self, v): + # Add the key so the order of identical items is predictable. + # The tab ensures that the values sort together regardless of key + return primary_sort_key(v.v + '\t ' + (v.k[1:] if v.k.startswith('#') else v.k)) + def initialize_category_lists(self): cfb = self.category_filter_box current_cat_filter = (self.category_labels[cfb.currentIndex()] @@ -154,11 +159,9 @@ class TagCategories(QDialog, Ui_TagCategories): self.available_items[key] = av sorted_categories.append(self.category_name_tuple(key, self.all_items[key]['name'])) - # Sort the items - self.sorted_items.sort(key=lambda v: primary_sort_key(v.v + v.k)) + self.sorted_items.sort(key=self.item_sort_key) + sorted_categories.sort(key=lambda v: primary_sort_key(v.n)) - # Fill in the category names with visible (not hidden) lookup keys - sorted_categories.sort(key=lambda v: primary_sort_key(v.n + v.k)) cfb.blockSignals(True) cfb.clear() cfb.addItem('', '') @@ -227,7 +230,7 @@ class TagCategories(QDialog, Ui_TagCategories): ccn = self.current_cat_name if ccn: self.applied_items = [v for v in self.user_categories[ccn]] - self.applied_items.sort(key=lambda x:primary_sort_key(x.v + x.k)) + self.applied_items.sort(key=self.item_sort_key) else: self.applied_items = [] self.applied_items_box.clear() From 798a74babf317c1dc84216d69b1983679dadeef6 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 10 Apr 2023 10:15:05 +0100 Subject: [PATCH 0448/2055] Book details: if an item has an associated link then offer that link in the item's context menu. --- src/calibre/db/cache.py | 10 +++++++--- src/calibre/ebooks/metadata/book/render.py | 11 ++++++----- src/calibre/gui2/book_details.py | 12 ++++++++++++ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 9201cc1062..ce12e7f2e5 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2367,18 +2367,22 @@ class Cache: @read_api def get_all_link_maps_for_book(self, book_id): ''' - Returns all links for all fields referenced by book identified by book_id + Returns all links for all fields referenced by book identified by book_id. + If book_id is None or doesn't exist then the method returns {}. Example: Assume author A has link X, author B has link Y, tag S has link - F, and tag T has link G. IF book 1 has author A and tag T, + F, and tag T has link G. If book 1 has author A and tag T, this method returns {'authors':{'A':'X'}, 'tags':{'T', 'G'}} If book 2's author is neither A nor B and has no tags, this method returns {} :param book_id: the book id in question. - :return: {field: {field_value, link_value}, ... for all fields that have a non-empty link value for that book + :return: {field: {field_value, link_value}, ... for all fields with a field_value having a non-empty link value for that book ''' + if not self.has_id(book_id): + # Works for book_id is None. + return {} cached = self.link_maps_cache.get(book_id) if cached is not None: return cached diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index 48a8f43cef..398f0cabc7 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -183,14 +183,14 @@ def mi_to_html( else: if not metadata['is_multiple']: val = '{}'.format( - search_action(field, val), + search_action(field, val, book_id=book_id), _('Click to see books with {0}: {1}').format(metadata['name'], a(val)), p(val)) else: all_vals = [v.strip() for v in val.split(metadata['is_multiple']['cache_to_list']) if v.strip()] if show_links: links = ['{}'.format( - search_action(field, x), _('Click to see books with {0}: {1}').format( + search_action(field, x, book_id=book_id), _('Click to see books with {0}: {1}').format( metadata['name'], a(x)), p(x)) for x in all_vals] else: links = all_vals @@ -210,7 +210,7 @@ def mi_to_html( extra = '
%s'%( prepare_string_for_xml(durl)) if show_links: - link = '{}{}'.format(action(scheme, loc=loc), + link = '{}{}'.format(action(scheme, book_id=book_id, loc=loc), prepare_string_for_xml(path, True), pathstr, extra) else: link = prepare_string_for_xml(path, True) @@ -238,7 +238,7 @@ def mi_to_html( if show_links: links = [ '{}'.format( - action('identifier', url=url, name=namel, id_type=id_typ, value=id_val, field='identifiers', book_id=book_id), + action('identifier', book_id=book_id, url=url, name=namel, id_type=id_typ, value=id_val, field='identifiers'), a(id_typ), a(id_val), p(namel)) for namel, id_typ, id_val, url in urls] links = value_list(', ', links) @@ -266,7 +266,8 @@ def mi_to_html( else: aut = p(aut) if link: - val = '%s'%(a(lt), action('author', url=link, name=aut, title=lt), aut) + val = '%s'%(a(lt), action('author', book_id=book_id, + url=link, name=aut, title=lt), aut) else: val = aut val += add_other_link('authors', aut) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 7a046932d8..99f7d380fa 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -391,6 +391,12 @@ def add_item_specific_entries(menu, data, book_info, copy_menu, search_menu): ac.data = ('authors', author, book_id) ac.setText(_('Remove %s from this book') % escape_for_menu(author)) menu.addAction(ac) + # See if we need to add a click associated link menu line for the author + link_map = get_gui().current_db.new_api.get_all_link_maps_for_book(data.get('book_id', None)) + link = link_map.get("authors", {}).get(author, None) + if link: + menu.addAction(QIcon.ic('external-link'), _('Open associated link'), + lambda : book_info.link_clicked.emit(link)) elif dt in ('path', 'devpath'): path = data['loc'] ac = book_info.copy_link_action @@ -435,6 +441,12 @@ def add_item_specific_entries(menu, data, book_info, copy_menu, search_menu): ac.data = (field, remove_value, book_id) ac.setText(_('Remove %s from this book') % escape_for_menu(remove_name or data.get('original_value') or value)) menu.addAction(ac) + # See if we need to add a click associated link menu line + link_map = get_gui().current_db.new_api.get_all_link_maps_for_book(data.get('book_id', None)) + link = link_map.get(field, {}).get(value, None) + if link: + menu.addAction(QIcon.ic('external-link'), _('Open associated link'), + lambda : book_info.link_clicked.emit(link)) else: v = data.get('original_value') or data.get('value') copy_menu.addAction(QIcon.ic('edit-copy.png'), _('The text: {}').format(v), From 94eee05a2ef7856f6a0e26fd9a0abd21e368de7f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 10 Apr 2023 15:26:42 +0530 Subject: [PATCH 0449/2055] Cleanup previous PR --- src/calibre/db/cache.py | 4 ++-- src/calibre/gui2/book_details.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index ce12e7f2e5..8d4230db78 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2368,7 +2368,7 @@ class Cache: def get_all_link_maps_for_book(self, book_id): ''' Returns all links for all fields referenced by book identified by book_id. - If book_id is None or doesn't exist then the method returns {}. + If book_id doesn't exist then the method returns {}. Example: Assume author A has link X, author B has link Y, tag S has link F, and tag T has link G. If book 1 has author A and tag T, @@ -2380,7 +2380,7 @@ class Cache: :return: {field: {field_value, link_value}, ... for all fields with a field_value having a non-empty link value for that book ''' - if not self.has_id(book_id): + if not self._has_id(book_id): # Works for book_id is None. return {} cached = self.link_maps_cache.get(book_id) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 99f7d380fa..33aac40dd7 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -392,8 +392,8 @@ def add_item_specific_entries(menu, data, book_info, copy_menu, search_menu): ac.setText(_('Remove %s from this book') % escape_for_menu(author)) menu.addAction(ac) # See if we need to add a click associated link menu line for the author - link_map = get_gui().current_db.new_api.get_all_link_maps_for_book(data.get('book_id', None)) - link = link_map.get("authors", {}).get(author, None) + link_map = get_gui().current_db.new_api.get_all_link_maps_for_book(data.get('book_id', -1)) + link = link_map.get("authors", {}).get(author) if link: menu.addAction(QIcon.ic('external-link'), _('Open associated link'), lambda : book_info.link_clicked.emit(link)) @@ -442,8 +442,8 @@ def add_item_specific_entries(menu, data, book_info, copy_menu, search_menu): ac.setText(_('Remove %s from this book') % escape_for_menu(remove_name or data.get('original_value') or value)) menu.addAction(ac) # See if we need to add a click associated link menu line - link_map = get_gui().current_db.new_api.get_all_link_maps_for_book(data.get('book_id', None)) - link = link_map.get(field, {}).get(value, None) + link_map = get_gui().current_db.new_api.get_all_link_maps_for_book(data.get('book_id', -1)) + link = link_map.get(field, {}).get(value) if link: menu.addAction(QIcon.ic('external-link'), _('Open associated link'), lambda : book_info.link_clicked.emit(link)) @@ -520,7 +520,7 @@ def details_context_menu_event(view, ev, book_info, add_popup_action=False, edit ac.current_url = url ac.setText(_('Copy link location')) menu.addAction(ac) - menu.addAction(QIcon.ic('external-link'), _('Open this link'), lambda : book_info.link_clicked.emit(url)) + menu.addAction(QIcon.ic('external-link'), _('Open associated link'), lambda : book_info.link_clicked.emit(url)) if not copy_links_added: create_copy_links(copy_menu) From 444f8e9eda806b30eee49fd605b36ff1c3ec6eaa Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Tue, 11 Apr 2023 12:49:30 +0100 Subject: [PATCH 0450/2055] Add a check for periods in grouped search names --- src/calibre/gui2/preferences/search.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/preferences/search.py b/src/calibre/gui2/preferences/search.py index d0264dfd2c..9ee4eec543 100644 --- a/src/calibre/gui2/preferences/search.py +++ b/src/calibre/gui2/preferences/search.py @@ -158,9 +158,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): return error_dialog(self.gui, _('Grouped search terms'), _('The search term name cannot be blank'), show=True) - if ' ' in name: + if ' ' in name or '.' in name: return error_dialog(self.gui, _('Invalid grouped search name'), - _('The grouped search term name cannot contain spaces'), show=True) + _('The grouped search term name cannot contain spaces or periods'), show=True) if idx != 0: orig_name = str(self.gst_names.itemData(idx) or '') else: From 53ef74ec85637a5d8ade791b57369694d793450f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 10 Apr 2023 12:04:54 +0530 Subject: [PATCH 0451/2055] Start work on supporting undo for book delete --- src/calibre/db/backend.py | 93 +++++++++++--- src/calibre/db/cache.py | 11 +- src/calibre/db/cli/cmd_remove.py | 3 - src/calibre/db/cli/cmd_remove_format.py | 2 - src/calibre/db/delete_service.py | 164 ------------------------ src/calibre/db/restore.py | 4 +- src/calibre/db/tests/add_remove.py | 2 - src/calibre/gui2/ui.py | 10 -- src/calibre/library/check_library.py | 3 +- src/calibre/srv/standalone.py | 6 +- 10 files changed, 87 insertions(+), 211 deletions(-) delete mode 100644 src/calibre/db/delete_service.py diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 4a29f9923c..10d2695185 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -24,7 +24,6 @@ from calibre.constants import ( ) from calibre.db import SPOOL_SIZE, FTSQueryError from calibre.db.annotations import annot_db_data, unicode_normalize -from calibre.db.delete_service import delete_service from calibre.db.errors import NoSuchFormat from calibre.db.schema_upgrades import SchemaUpgrade from calibre.db.tables import ( @@ -36,6 +35,7 @@ from calibre.library.field_metadata import FieldMetadata from calibre.ptempfile import PersistentTemporaryFile, TemporaryFile from calibre.utils import pickle_binary_string, unpickle_binary_string from calibre.utils.config import from_json, prefs, to_json, tweaks +from calibre.utils.copy_files import copy_tree, copy_files from calibre.utils.date import EPOCH, parse_date, utcfromtimestamp, utcnow from calibre.utils.filenames import ( WindowsAtomicFolderMove, ascii_filename, atomic_rename, copyfile_using_links, @@ -55,6 +55,7 @@ from polyglot.builtins import ( # }}} +TRASH_DIR_NAME = '.caltrash' BOOK_ID_PATH_TEMPLATE = ' ({})' CUSTOM_DATA_TYPES = frozenset(('rating', 'text', 'comments', 'datetime', 'int', 'float', 'bool', 'series', 'composite', 'enumeration')) @@ -501,6 +502,14 @@ class DB: if load_user_formatter_functions: set_global_state(self) + @property + def last_expired_trash_at(self) -> float: + return float(self.prefs['last_expired_trash_at']) + + @last_expired_trash_at.setter + def last_expired_trash_at(self, val: float) -> None: + self.prefs['last_expired_trash_at'] = float(val) + def get_template_functions(self): return self._template_functions @@ -549,6 +558,8 @@ class DB: defs['similar_tags_match_kind'] = 'match_all' defs['similar_series_search_key'] = 'series' defs['similar_series_match_kind'] = 'match_any' + defs['last_expired_trash_at'] = 0.0 + defs['expire_old_trash_after'] = 7 * 86400 defs['book_display_fields'] = [ ('title', False), ('authors', True), ('series', True), ('identifiers', True), ('tags', True), ('formats', True), @@ -1519,17 +1530,14 @@ class DB: return os.path.getsize(dest_path) def remove_formats(self, remove_map): - paths = [] + self.ensure_trash_dir() + paths = set() for book_id, removals in iteritems(remove_map): for fmt, fname, path in removals: path = self.format_abspath(book_id, fmt, fname, path) - if path is not None: - paths.append(path) - try: - delete_service().delete_files(paths, self.library_path) - except: - import traceback - traceback.print_exc() + if path: + paths.add(path) + self.move_book_files_to_trash(book_id, paths) def cover_last_modified(self, path): path = os.path.abspath(os.path.join(self.library_path, path, 'cover.jpg')) @@ -1856,17 +1864,68 @@ class DB: with open(path, 'rb') as f: return f.read() + @property + def trash_dir(self): + return os.path.abspath(os.path.join(self.library_path, TRASH_DIR_NAME)) + + def ensure_trash_dir(self): + tdir = self.trash_dir + os.makedirs(os.path.join(tdir, 'b'), exist_ok=True) + os.makedirs(os.path.join(tdir, 'f'), exist_ok=True) + if iswindows: + import calibre_extensions.winutil as winutil + winutil.set_file_attributes(tdir, getattr(winutil, 'FILE_ATTRIBUTE_HIDDEN', 2) | getattr(winutil, 'FILE_ATTRIBUTE_NOT_CONTENT_INDEXED', 8192)) + if time.monotonic() - self.last_expired_trash_at >= 3600: + self.expire_old_trash() + + def expire_old_trash(self, expire_age_in_seconds=-1): + if expire_age_in_seconds < 0: + expire_age_in_seconds = max(1 * 24 * 3600, float(self.prefs['expire_old_trash_after'])) + self.last_expired_trash_at = now = time.time() + removals = [] + for base in ('b', 'f'): + base = os.path.join(self.trash_dir, base) + for entries in os.scandir(base): + for x in entries: + try: + st = x.stat(follow_symlinks=False) + mtime = st.st_mtime + except OSError: + mtime = 0 + if mtime + expire_age_in_seconds < now: + removals.append(x.path) + for x in removals: + rmtree_with_retry(x) + + def move_book_to_trash(self, book_id, book_dir_abspath): + dest = os.path.join(self.trash_dir, 'b', str(book_id)) + if os.path.exists(dest): + rmtree_with_retry(dest) + copy_tree(book_dir_abspath, dest, delete_source=True) + + def move_book_files_to_trash(self, book_id, format_abspaths): + dest = os.path.join(self.trash_dir, 'f', str(book_id)) + if not os.path.exists(dest): + os.makedirs(dest) + fmap = {} + for path in format_abspaths: + ext = path.rpartition('.')[-1].lower() + fmap[path] = os.path.join(dest, ext) + copy_files(fmap, delete_source=True) + def remove_books(self, path_map, permanent=False): + self.ensure_trash_dir() self.executemany( 'DELETE FROM books WHERE id=?', [(x,) for x in path_map]) - paths = {os.path.join(self.library_path, x) for x in itervalues(path_map) if x} - paths = {x for x in paths if os.path.exists(x) and self.is_deletable(x)} - if permanent: - for path in paths: - self.rmtree(path) - remove_dir_if_empty(os.path.dirname(path), ignore_metadata_caches=True) - else: - delete_service().delete_books(paths, self.library_path) + parent_paths = set() + for book_id, path in path_map.items(): + if path: + path = os.path.abspath(os.path.join(self.library_path, path)) + if os.path.exists(path) and self.is_deletable(path): + self.rmtree(path) if permanent else self.move_book_to_trash(book_id, path) + parent_paths.add(os.path.dirname(path)) + for path in parent_paths: + remove_dir_if_empty(path, ignore_metadata_caches=True) def add_custom_data(self, name, val_map, delete_first): if delete_first: diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 8d4230db78..bb88c83143 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2038,18 +2038,17 @@ class Cache: def remove_books(self, book_ids, permanent=False): ''' Remove the books specified by the book_ids from the database and delete their format files. If ``permanent`` is False, then the format files - are placed in the recycle bin. ''' + are placed in the per-library trash directory. ''' path_map = {} for book_id in book_ids: try: path = self._field_for('path', book_id).replace('/', os.sep) - except: + except Exception: path = None path_map[book_id] = path - if iswindows: - paths = (x.replace(os.sep, '/') for x in itervalues(path_map) if x) - self.backend.windows_check_if_files_in_use(paths) - + # ensure metadata.opf is written so we can restore the book + if not permanent: + self._dump_metadata(book_ids=tuple(bid for bid, path in path_map.items() if path)) self.backend.remove_books(path_map, permanent=permanent) for field in itervalues(self.fields): try: diff --git a/src/calibre/db/cli/cmd_remove.py b/src/calibre/db/cli/cmd_remove.py index 7c5aae9df3..e940765540 100644 --- a/src/calibre/db/cli/cmd_remove.py +++ b/src/calibre/db/cli/cmd_remove.py @@ -4,7 +4,6 @@ from calibre.constants import trash_name from calibre.db.cli import integers_from_string -from calibre.db.delete_service import delete_service from calibre.srv.changes import books_deleted readonly = False @@ -13,8 +12,6 @@ version = 0 # change this if you change signature of implementation() def implementation(db, notify_changes, ids, permanent): db.remove_books(ids, permanent=permanent) - if not permanent: - delete_service().wait() if notify_changes is not None: notify_changes(books_deleted(ids)) diff --git a/src/calibre/db/cli/cmd_remove_format.py b/src/calibre/db/cli/cmd_remove_format.py index d9cf715beb..9710793fc6 100644 --- a/src/calibre/db/cli/cmd_remove_format.py +++ b/src/calibre/db/cli/cmd_remove_format.py @@ -2,7 +2,6 @@ # License: GPLv3 Copyright: 2017, Kovid Goyal -from calibre.db.delete_service import delete_service from calibre.srv.changes import formats_removed readonly = False @@ -13,7 +12,6 @@ def implementation(db, notify_changes, book_id, fmt): is_remote = notify_changes is not None fmt_map = {book_id: (fmt, )} db.remove_formats(fmt_map) - delete_service().wait() if is_remote: notify_changes(formats_removed(fmt_map)) diff --git a/src/calibre/db/delete_service.py b/src/calibre/db/delete_service.py deleted file mode 100644 index 8fff63d69c..0000000000 --- a/src/calibre/db/delete_service.py +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env python - - -__license__ = 'GPL v3' -__copyright__ = '2013, Kovid Goyal ' - -import os, tempfile, shutil, errno, time, atexit -from threading import Thread - -from calibre.constants import ismacos -from calibre.ptempfile import remove_dir -from calibre.utils.filenames import remove_dir_if_empty -from calibre.utils.recycle_bin import delete_tree, delete_file -from polyglot.queue import Queue - - -class DeleteService(Thread): - - ''' Provide a blocking file delete implementation with support for the - recycle bin. On windows, deleting files to the recycle bin spins the event - loop, which can cause locking errors in the main thread. We get around this - by only moving the files/folders to be deleted out of the library in the - main thread, they are deleted to recycle bin in a separate worker thread. - - This has the added advantage that doing a restore from the recycle bin won't - cause metadata.db and the file system to get out of sync. Also, deleting - becomes much faster, since in the common case, the move is done by a simple - os.rename(). The downside is that if the user quits calibre while a long - move to recycle bin is happening, the files may not all be deleted.''' - - daemon = True - - def __init__(self): - Thread.__init__(self) - self.requests = Queue() - if ismacos: - from calibre_extensions.cocoa import enable_cocoa_multithreading - enable_cocoa_multithreading() - - def shutdown(self, timeout=20): - self.requests.put(None) - self.join(timeout) - - def create_staging(self, library_path): - base_path = os.path.dirname(library_path) - base = os.path.basename(library_path) - try: - ans = tempfile.mkdtemp(prefix=base+' deleted ', dir=base_path) - except OSError: - ans = tempfile.mkdtemp(prefix=base+' deleted ') - atexit.register(remove_dir, ans) - return ans - - def remove_dir_if_empty(self, path): - try: - os.rmdir(path) - except OSError as e: - if e.errno == errno.ENOTEMPTY or len(os.listdir(path)) > 0: - # Some linux systems appear to raise an EPERM instead of an - # ENOTEMPTY, see https://bugs.launchpad.net/bugs/1240797 - return - raise - - def delete_books(self, paths, library_path): - tdir = self.create_staging(library_path) - self.queue_paths(tdir, paths, delete_empty_parent=True) - - def queue_paths(self, tdir, paths, delete_empty_parent=True): - try: - self._queue_paths(tdir, paths, delete_empty_parent=delete_empty_parent) - except: - if os.path.exists(tdir): - shutil.rmtree(tdir, ignore_errors=True) - raise - - def _queue_paths(self, tdir, paths, delete_empty_parent=True): - requests = [] - for path in paths: - if os.path.exists(path): - basename = os.path.basename(path) - c = 0 - while True: - dest = os.path.join(tdir, basename) - if not os.path.exists(dest): - break - c += 1 - basename = '%d - %s' % (c, os.path.basename(path)) - try: - shutil.move(path, dest) - except OSError: - if os.path.isdir(path): - # shutil.move may have partially copied the directory, - # so the subsequent call to move() will fail as the - # destination directory already exists - raise - # Wait a little in case something has locked a file - time.sleep(1) - shutil.move(path, dest) - if delete_empty_parent: - remove_dir_if_empty(os.path.dirname(path), ignore_metadata_caches=True) - requests.append(dest) - if not requests: - remove_dir_if_empty(tdir) - else: - self.requests.put(tdir) - - def delete_files(self, paths, library_path): - tdir = self.create_staging(library_path) - self.queue_paths(tdir, paths, delete_empty_parent=False) - - def run(self): - while True: - x = self.requests.get() - try: - if x is None: - break - try: - self.do_delete(x) - except: - import traceback - traceback.print_exc() - finally: - self.requests.task_done() - - def wait(self): - 'Blocks until all pending deletes have completed' - self.requests.join() - - def do_delete(self, tdir): - if os.path.exists(tdir): - try: - for x in os.listdir(tdir): - x = os.path.join(tdir, x) - if os.path.isdir(x): - delete_tree(x) - else: - delete_file(x) - finally: - shutil.rmtree(tdir) - - -__ds = None - - -def delete_service(): - global __ds - if __ds is None: - __ds = DeleteService() - __ds.start() - return __ds - - -def shutdown(timeout=20): - global __ds - if __ds is not None: - __ds.shutdown(timeout) - __ds = None - - -def has_jobs(): - global __ds - if __ds is not None: - return (not __ds.requests.empty()) or __ds.requests.unfinished_tasks - return False diff --git a/src/calibre/db/restore.py b/src/calibre/db/restore.py index 2e03435713..e6269e2cdc 100644 --- a/src/calibre/db/restore.py +++ b/src/calibre/db/restore.py @@ -16,7 +16,7 @@ from threading import Thread from calibre import force_unicode, isbytestring from calibre.constants import filesystem_encoding -from calibre.db.backend import DB, DBPrefs +from calibre.db.backend import DB, TRASH_DIR_NAME, DBPrefs from calibre.db.cache import Cache from calibre.ebooks.metadata.opf2 import OPF from calibre.ptempfile import TemporaryDirectory @@ -160,6 +160,8 @@ class Restore(Thread): def scan_library(self): for dirpath, dirnames, filenames in os.walk(self.src_library_path): + with suppress(ValueError): + dirnames.remove(TRASH_DIR_NAME) leaf = os.path.basename(dirpath) m = self.db_id_regexp.search(leaf) if m is None or 'metadata.opf' not in filenames: diff --git a/src/calibre/db/tests/add_remove.py b/src/calibre/db/tests/add_remove.py index 7b6f678064..290962d250 100644 --- a/src/calibre/db/tests/add_remove.py +++ b/src/calibre/db/tests/add_remove.py @@ -261,7 +261,6 @@ class AddRemoveTest(BaseTest): self.assertFalse(table.col_book_map) # Test the delete service - from calibre.db.delete_service import delete_service cache = self.init_cache(cl) # Check that files are removed fmtpath = cache.format_abspath(1, 'FMT1') @@ -269,7 +268,6 @@ class AddRemoveTest(BaseTest): authorpath = os.path.dirname(bookpath) item_id = {v:k for k, v in iteritems(cache.fields['#series'].table.id_map)}['My Series Two'] cache.remove_books((1,)) - delete_service().wait() for x in (fmtpath, bookpath, authorpath): af(os.path.exists(x), 'The file %s exists, when it should not' % x) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 93013cf814..efceb8843f 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -1141,14 +1141,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ if not question_dialog(self, _('Library updates waiting'), msg): return False - from calibre.db.delete_service import has_jobs - if has_jobs(): - msg = _('Some deleted books are still being moved to the recycle ' - 'bin, if you quit now, they will be left behind. Are you ' - 'sure you want to quit?') - if not question_dialog(self, _('Active jobs'), msg): - return False - return True def shutdown(self, write_settings=True): @@ -1229,8 +1221,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self._spare_pool.shutdown() from calibre.scraper.simple import cleanup_overseers wait_for_cleanup = cleanup_overseers() - from calibre.db.delete_service import shutdown - shutdown() from calibre.live import async_stop_worker wait_for_stop = async_stop_worker() time.sleep(2) diff --git a/src/calibre/library/check_library.py b/src/calibre/library/check_library.py index dedbfef5db..09799d3959 100644 --- a/src/calibre/library/check_library.py +++ b/src/calibre/library/check_library.py @@ -15,10 +15,11 @@ from calibre.constants import filesystem_encoding from calibre.ebooks import BOOK_EXTENSIONS from calibre.utils.localization import _ from polyglot.builtins import iteritems +from calibre.db.backend import TRASH_DIR_NAME EBOOK_EXTENSIONS = frozenset(BOOK_EXTENSIONS) NORMALS = frozenset({'metadata.opf', 'cover.jpg'}) -IGNORE_AT_TOP_LEVEL = frozenset({'metadata.db', 'metadata_db_prefs_backup.json', 'metadata_pre_restore.db', 'full-text-search.db'}) +IGNORE_AT_TOP_LEVEL = frozenset({'metadata.db', 'metadata_db_prefs_backup.json', 'metadata_pre_restore.db', 'full-text-search.db', TRASH_DIR_NAME}) ''' Checks fields: diff --git a/src/calibre/srv/standalone.py b/src/calibre/srv/standalone.py index 5ad690e576..45276a555d 100644 --- a/src/calibre/srv/standalone.py +++ b/src/calibre/srv/standalone.py @@ -9,7 +9,6 @@ import sys from calibre import as_unicode from calibre.constants import is_running_from_develop, ismacos, iswindows -from calibre.db.delete_service import shutdown as shutdown_delete_service from calibre.db.legacy import LibraryDatabase from calibre.srv.bonjour import BonJour from calibre.srv.handler import Handler @@ -243,7 +242,4 @@ def main(args=sys.argv): from calibre.gui2 import ensure_app, load_builtin_fonts ensure_app(), load_builtin_fonts() with HandleInterrupt(server.stop): - try: - server.serve_forever() - finally: - shutdown_delete_service() + server.serve_forever() From 5c5ac19935486d9f488c5f344ba33b27488f6d0e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 10 Apr 2023 15:16:38 +0530 Subject: [PATCH 0452/2055] Code to list entries in trash --- src/calibre/db/backend.py | 56 +++++++++++++++++++++++++----- src/calibre/db/tests/add_remove.py | 7 +++- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 10d2695185..2e5a3f783f 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -16,7 +16,9 @@ import sys import time import uuid from contextlib import closing, suppress +from dataclasses import dataclass from functools import partial +from typing import Sequence from calibre import as_unicode, force_unicode, isbytestring, prints from calibre.constants import ( @@ -35,7 +37,7 @@ from calibre.library.field_metadata import FieldMetadata from calibre.ptempfile import PersistentTemporaryFile, TemporaryFile from calibre.utils import pickle_binary_string, unpickle_binary_string from calibre.utils.config import from_json, prefs, to_json, tweaks -from calibre.utils.copy_files import copy_tree, copy_files +from calibre.utils.copy_files import copy_files, copy_tree from calibre.utils.date import EPOCH, parse_date, utcfromtimestamp, utcnow from calibre.utils.filenames import ( WindowsAtomicFolderMove, ascii_filename, atomic_rename, copyfile_using_links, @@ -54,7 +56,7 @@ from polyglot.builtins import ( # }}} - +COVER_FILE_NAME = 'cover.jpg' TRASH_DIR_NAME = '.caltrash' BOOK_ID_PATH_TEMPLATE = ' ({})' CUSTOM_DATA_TYPES = frozenset(('rating', 'text', 'comments', 'datetime', @@ -62,6 +64,14 @@ CUSTOM_DATA_TYPES = frozenset(('rating', 'text', 'comments', 'datetime', WINDOWS_RESERVED_NAMES = frozenset('CON PRN AUX NUL COM1 COM2 COM3 COM4 COM5 COM6 COM7 COM8 COM9 LPT1 LPT2 LPT3 LPT4 LPT5 LPT6 LPT7 LPT8 LPT9'.split()) +@dataclass +class TrashEntry: + book_id: int + book_dir: str + mtime: float + formats: Sequence[str] = () + + class DynamicFilter: # {{{ 'No longer used, present for legacy compatibility' @@ -1482,7 +1492,7 @@ class DB: def cover_abspath(self, book_id, path): path = os.path.join(self.library_path, path) - fmt_path = os.path.join(path, 'cover.jpg') + fmt_path = os.path.join(path, COVER_FILE_NAME) if os.path.exists(fmt_path): return fmt_path @@ -1540,14 +1550,14 @@ class DB: self.move_book_files_to_trash(book_id, paths) def cover_last_modified(self, path): - path = os.path.abspath(os.path.join(self.library_path, path, 'cover.jpg')) + path = os.path.abspath(os.path.join(self.library_path, path, COVER_FILE_NAME)) try: return utcfromtimestamp(os.stat(path).st_mtime) except OSError: pass # Cover doesn't exist def copy_cover_to(self, path, dest, windows_atomic_move=None, use_hardlink=False, report_file_size=None): - path = os.path.abspath(os.path.join(self.library_path, path, 'cover.jpg')) + path = os.path.abspath(os.path.join(self.library_path, path, COVER_FILE_NAME)) if windows_atomic_move is not None: if not isinstance(dest, string_or_bytes): raise Exception('Error, you must pass the dest as a path when' @@ -1590,7 +1600,7 @@ class DB: return False def cover_or_cache(self, path, timestamp): - path = os.path.abspath(os.path.join(self.library_path, path, 'cover.jpg')) + path = os.path.abspath(os.path.join(self.library_path, path, COVER_FILE_NAME)) try: stat = os.stat(path) except OSError: @@ -1611,7 +1621,7 @@ class DB: def progress_callback(book_id, old_sz, new_sz): return None for book_id, path in path_map.items(): - path = os.path.abspath(os.path.join(self.library_path, path, 'cover.jpg')) + path = os.path.abspath(os.path.join(self.library_path, path, COVER_FILE_NAME)) try: sz = os.path.getsize(path) except OSError: @@ -1625,7 +1635,7 @@ class DB: path = os.path.abspath(os.path.join(self.library_path, path)) if not os.path.exists(path): os.makedirs(path) - path = os.path.join(path, 'cover.jpg') + path = os.path.join(path, COVER_FILE_NAME) if callable(getattr(data, 'save', None)): from calibre.gui2 import pixmap_to_data data = pixmap_to_data(data) @@ -1782,7 +1792,7 @@ class DB: os.makedirs(tpath) if source_ok: # Migrate existing files - dest = os.path.join(tpath, 'cover.jpg') + dest = os.path.join(tpath, COVER_FILE_NAME) self.copy_cover_to(current_path, dest, windows_atomic_move=wam, use_hardlink=True) for fmt in formats: @@ -1913,6 +1923,34 @@ class DB: fmap[path] = os.path.join(dest, ext) copy_files(fmap, delete_source=True) + def list_trash_entries(self): + self.ensure_trash_dir() + books, files = [], [] + base = os.path.join(self.trash_dir, 'b') + for x in os.scandir(base): + if x.is_dir(follow_symlinks=False): + try: + book_id = int(x.name) + mtime = x.stat(follow_symlinks=False).st_mtime + except Exception: + continue + books.append(TrashEntry(book_id, x.path, mtime)) + base = os.path.join(self.trash_dir, 'f') + for x in os.scandir(base): + if x.is_dir(follow_symlinks=False): + try: + book_id = int(x.name) + mtime = x.stat(follow_symlinks=False).st_mtime + except Exception: + continue + formats = set() + for f in os.scandir(x.path): + if f.is_file(follow_symlinks=False): + formats.add(f.name.upper()) + if formats: + files.append(TrashEntry(book_id, x.path, mtime, tuple(formats))) + return books, files + def remove_books(self, path_map, permanent=False): self.ensure_trash_dir() self.executemany( diff --git a/src/calibre/db/tests/add_remove.py b/src/calibre/db/tests/add_remove.py index 290962d250..49e25cbc5d 100644 --- a/src/calibre/db/tests/add_remove.py +++ b/src/calibre/db/tests/add_remove.py @@ -265,12 +265,17 @@ class AddRemoveTest(BaseTest): # Check that files are removed fmtpath = cache.format_abspath(1, 'FMT1') bookpath = os.path.dirname(fmtpath) + os.mkdir(os.path.join(bookpath, 'xyz')) + open(os.path.join(bookpath, 'xyz', 'abc'), 'w').close() authorpath = os.path.dirname(bookpath) item_id = {v:k for k, v in iteritems(cache.fields['#series'].table.id_map)}['My Series Two'] cache.remove_books((1,)) for x in (fmtpath, bookpath, authorpath): af(os.path.exists(x), 'The file %s exists, when it should not' % x) - + b, f = cache.backend.list_trash_entries() + self.assertEqual(len(b), 1) + self.assertEqual(len(f), 0) + self.assertTrue(os.path.exists(os.path.join(b[0].book_dir, 'metadata.opf'))) # }}} def test_original_fmt(self): # {{{ From aeae26d05339df179cd9a498cf3315d449f54230 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 11 Apr 2023 15:26:42 +0530 Subject: [PATCH 0453/2055] Implement undelete of book from trash --- src/calibre/db/backend.py | 77 ++++++++++++++++++++++-------- src/calibre/db/cache.py | 40 ++++++++++++++-- src/calibre/db/restore.py | 43 +++++++++-------- src/calibre/db/tests/add_remove.py | 45 ++++++++++++++--- 4 files changed, 154 insertions(+), 51 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 2e5a3f783f..da9f079675 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -67,6 +67,8 @@ WINDOWS_RESERVED_NAMES = frozenset('CON PRN AUX NUL COM1 COM2 COM3 COM4 COM5 COM @dataclass class TrashEntry: book_id: int + title: str + author: str book_dir: str mtime: float formats: Sequence[str] = () @@ -509,6 +511,8 @@ class DB: self.initialize_tables() self.set_user_template_functions(compile_user_template_functions( self.prefs.get('user_template_functions', []))) + if self.prefs['last_expired_trash_at'] > 0: + self.ensure_trash_dir() if load_user_formatter_functions: set_global_state(self) @@ -1539,15 +1543,16 @@ class DB: atomic_rename(src_path, dest_path) return os.path.getsize(dest_path) - def remove_formats(self, remove_map): + def remove_formats(self, remove_map, metadata_map): self.ensure_trash_dir() - paths = set() for book_id, removals in iteritems(remove_map): + paths = set() for fmt, fname, path in removals: path = self.format_abspath(book_id, fmt, fname, path) if path: paths.add(path) - self.move_book_files_to_trash(book_id, paths) + if paths: + self.move_book_files_to_trash(book_id, paths, metadata_map[book_id]) def cover_last_modified(self, path): path = os.path.abspath(os.path.join(self.library_path, path, COVER_FILE_NAME)) @@ -1895,15 +1900,14 @@ class DB: removals = [] for base in ('b', 'f'): base = os.path.join(self.trash_dir, base) - for entries in os.scandir(base): - for x in entries: - try: - st = x.stat(follow_symlinks=False) - mtime = st.st_mtime - except OSError: - mtime = 0 - if mtime + expire_age_in_seconds < now: - removals.append(x.path) + for x in os.scandir(base): + try: + st = x.stat(follow_symlinks=False) + mtime = st.st_mtime + except OSError: + mtime = 0 + if mtime + expire_age_in_seconds <= now: + removals.append(x.path) for x in removals: rmtree_with_retry(x) @@ -1913,7 +1917,7 @@ class DB: rmtree_with_retry(dest) copy_tree(book_dir_abspath, dest, delete_source=True) - def move_book_files_to_trash(self, book_id, format_abspaths): + def move_book_files_to_trash(self, book_id, format_abspaths, metadata): dest = os.path.join(self.trash_dir, 'f', str(book_id)) if not os.path.exists(dest): os.makedirs(dest) @@ -1921,12 +1925,41 @@ class DB: for path in format_abspaths: ext = path.rpartition('.')[-1].lower() fmap[path] = os.path.join(dest, ext) + with open(os.path.join(dest, 'metadata.json'), 'wb') as f: + f.write(json.dumps(metadata).encode('utf-8')) copy_files(fmap, delete_source=True) + def get_metadata_for_trash_book(self, book_id, read_annotations=True): + from .restore import read_opf + bdir = os.path.join(self.trash_dir, 'b', str(book_id)) + if not os.path.isdir(bdir): + raise ValueError(f'The book {book_id} not present in the trash folder') + mi, _, annotations = read_opf(bdir, read_annotations=read_annotations) + formats = [] + for x in os.scandir(bdir): + if x.is_file() and x.name not in (COVER_FILE_NAME, 'metadata.opf') and '.' in x.name: + try: + size = x.stat(follow_symlinks=False).st_size + except OSError: + continue + fname, ext = os.path.splitext(x.name) + formats.append((ext[1:].upper(), size, fname)) + return mi, annotations, formats + + def move_book_from_trash(self, book_id, path): + bdir = os.path.join(self.trash_dir, 'b', str(book_id)) + if not os.path.isdir(bdir): + raise ValueError(f'The book {book_id} not present in the trash folder') + dest = os.path.abspath(os.path.join(self.library_path, path)) + copy_tree(bdir, dest, delete_source=True) + def list_trash_entries(self): + from calibre.ebooks.metadata.opf2 import OPF self.ensure_trash_dir() books, files = [], [] base = os.path.join(self.trash_dir, 'b') + unknown = _('Unknown') + au = (unknown,) for x in os.scandir(base): if x.is_dir(follow_symlinks=False): try: @@ -1934,8 +1967,10 @@ class DB: mtime = x.stat(follow_symlinks=False).st_mtime except Exception: continue - books.append(TrashEntry(book_id, x.path, mtime)) + opf = OPF(os.path.join(x.path, 'metadata.opf'), basedir=x.path) + books.append(TrashEntry(book_id, opf.title or unknown, (opf.authors or au)[0], x.path, mtime)) base = os.path.join(self.trash_dir, 'f') + um = {'title': unknown, 'authors': au} for x in os.scandir(base): if x.is_dir(follow_symlinks=False): try: @@ -1944,11 +1979,16 @@ class DB: except Exception: continue formats = set() + metadata = um for f in os.scandir(x.path): if f.is_file(follow_symlinks=False): - formats.add(f.name.upper()) + if f.name == 'metadata.json': + with open(f.path, 'rb') as mf: + metadata = json.loads(mf.read()) + else: + formats.add(f.name.upper()) if formats: - files.append(TrashEntry(book_id, x.path, mtime, tuple(formats))) + files.append(TrashEntry(book_id, metadata.get('title') or unknown, (metadata.get('authors') or au)[0], x.path, mtime, tuple(formats))) return books, files def remove_books(self, path_map, permanent=False): @@ -2302,11 +2342,6 @@ class DB: self.conn # Connect to the moved metadata.db progress(_('Completed'), total, total) - def restore_book(self, book_id, path, formats): - self.execute('UPDATE books SET path=? WHERE id=?', (path.replace(os.sep, '/'), book_id)) - vals = [(book_id, fmt, size, name) for fmt, size, name in formats] - self.executemany('INSERT INTO data (book,format,uncompressed_size,name) VALUES (?,?,?,?)', vals) - def backup_database(self, path): with closing(apsw.Connection(path)) as dest_db: with dest_db.backup('main', self.conn, 'main') as b: diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index bb88c83143..3727840c92 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -1858,6 +1858,7 @@ class Cache: if not db_only: removes = defaultdict(set) + metadata_map = {} for book_id, fmts in iteritems(formats_map): try: path = self._field_for('path', book_id).replace('/', os.sep) @@ -1870,8 +1871,10 @@ class Cache: continue if name and path: removes[book_id].add((fmt, name, path)) + if removes[book_id]: + metadata_map[book_id] = {'title': self._field_for('title', book_id), 'authors': self._field_for('authors', book_id)} if removes: - self.backend.remove_formats(removes) + self.backend.remove_formats(removes, metadata_map) size_map = table.remove_formats(formats_map, self.backend) self.fields['size'].table.update_sizes(size_map) @@ -2660,17 +2663,46 @@ class Cache: def is_closed(self): return self.backend.is_closed + @read_api + def list_trash_entries(self): + return self.backend.list_trash_entries() + + @write_api + def move_book_from_trash(self, book_id): + ''' Undelete a book from the trash directory ''' + if self._has_id(book_id): + raise ValueError(f'A book with the id {book_id} already exists') + mi, annotations, formats = self.backend.get_metadata_for_trash_book(book_id) + mi.cover = None + self._create_book_entry(mi, add_duplicates=True, + force_id=book_id, apply_import_tags=False, preserve_uuid=True) + path = self._field_for('path', book_id).replace('/', os.sep) + self.backend.move_book_from_trash(book_id, path) + self.format_metadata_cache.pop(book_id, None) + f = self.fields['formats'].table + max_size = 0 + for (fmt, size, fname) in formats: + max_size = max(max_size, f.update_fmt(book_id, fmt, fname, size, self.backend)) + self.fields['size'].table.update_sizes({book_id: max_size}) + cover = self.backend.cover_abspath(book_id, path) + if cover and os.path.exists(cover): + self._set_field('cover', {book_id:1}) + if annotations: + self._restore_annotations(book_id, annotations) + @write_api def restore_book(self, book_id, mi, last_modified, path, formats, annotations=()): ''' Restore the book entry in the database for a book that already exists on the filesystem ''' - cover = mi.cover - mi.cover = None + cover, mi.cover = mi.cover, None self._create_book_entry(mi, add_duplicates=True, force_id=book_id, apply_import_tags=False, preserve_uuid=True) self._update_last_modified((book_id,), last_modified) if cover and os.path.exists(cover): self._set_field('cover', {book_id:1}) - self.backend.restore_book(book_id, path, formats) + f = self.fields['formats'].table + for (fmt, size, fname) in formats: + f.update_fmt(book_id, fmt, fname, size, self.backend) + self.fields['path'].table.set_path(book_id, path, self.backend) if annotations: self._restore_annotations(book_id, annotations) diff --git a/src/calibre/db/restore.py b/src/calibre/db/restore.py index e6269e2cdc..3bc736de3c 100644 --- a/src/calibre/db/restore.py +++ b/src/calibre/db/restore.py @@ -28,6 +28,26 @@ NON_EBOOK_EXTENSIONS = frozenset(( )) +def read_opf(dirpath, read_annotations=True): + opf = os.path.join(dirpath, 'metadata.opf') + parsed_opf = OPF(opf, basedir=dirpath) + mi = parsed_opf.to_book_metadata() + annotations = tuple(parsed_opf.read_annotations()) if read_annotations else () + timestamp = os.path.getmtime(opf) + return mi, timestamp, annotations + + +def is_ebook_file(filename): + ext = os.path.splitext(filename)[1] + if not ext: + return False + ext = ext[1:].lower() + bad_ext_pat = re.compile(r'[^a-z0-9_]+') + if ext in NON_EBOOK_EXTENSIONS or bad_ext_pat.search(ext) is not None: + return False + return True + + class Restorer(Cache): def __init__(self, library_path, default_prefs=None, restore_all_prefs=False, progress_callback=lambda x, y:True): @@ -51,7 +71,6 @@ class Restore(Thread): self.src_library_path = os.path.abspath(library_path) self.progress_callback = progress_callback self.db_id_regexp = re.compile(r'^.* \((\d+)\)$') - self.bad_ext_pat = re.compile(r'[^a-z0-9_]+') if not callable(self.progress_callback): self.progress_callback = lambda x, y: x self.dirs = [] @@ -178,29 +197,15 @@ class Restore(Thread): self.failed_dirs.append((dirpath, traceback.format_exc())) self.progress_callback(_('Processed') + ' ' + dirpath, i+1) - def is_ebook_file(self, filename): - ext = os.path.splitext(filename)[1] - if not ext: - return False - ext = ext[1:].lower() - if ext in NON_EBOOK_EXTENSIONS or \ - self.bad_ext_pat.search(ext) is not None: - return False - return True - def process_dir(self, dirpath, filenames, book_id): book_id = int(book_id) - formats = list(filter(self.is_ebook_file, filenames)) + formats = list(filter(is_ebook_file, filenames)) fmts = [os.path.splitext(x)[1][1:].upper() for x in formats] sizes = [os.path.getsize(os.path.join(dirpath, x)) for x in formats] names = [os.path.splitext(x)[0] for x in formats] - opf = os.path.join(dirpath, 'metadata.opf') - parsed_opf = OPF(opf, basedir=dirpath) - mi = parsed_opf.to_book_metadata() - annotations = tuple(parsed_opf.read_annotations()) - timestamp = os.path.getmtime(opf) - path = os.path.relpath(dirpath, self.src_library_path).replace(os.sep, - '/') + + mi, timestamp, annotations = read_opf(dirpath) + path = os.path.relpath(dirpath, self.src_library_path).replace(os.sep, '/') if int(mi.application_id) == book_id: self.books.append({ diff --git a/src/calibre/db/tests/add_remove.py b/src/calibre/db/tests/add_remove.py index 49e25cbc5d..f0dba2f2b1 100644 --- a/src/calibre/db/tests/add_remove.py +++ b/src/calibre/db/tests/add_remove.py @@ -5,14 +5,17 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, glob +import glob +import os +from datetime import timedelta from io import BytesIO from tempfile import NamedTemporaryFile -from datetime import timedelta -from calibre.db.tests.base import BaseTest, IMG +from calibre.db.tests.base import IMG, BaseTest from calibre.ptempfile import PersistentTemporaryFile -from calibre.utils.date import now, UNDEFINED_DATE +from calibre.utils.date import UNDEFINED_DATE, now, utcnow +from calibre.utils.img import image_from_path +from calibre.utils.resources import get_image_path from polyglot.builtins import iteritems, itervalues @@ -215,6 +218,7 @@ class AddRemoveTest(BaseTest): def test_remove_books(self): # {{{ 'Test removal of books' cl = self.cloned_library + cl2 = self.cloned_library cache = self.init_cache() af, ae = self.assertFalse, self.assertEqual authors = cache.fields['authors'].table @@ -261,10 +265,11 @@ class AddRemoveTest(BaseTest): self.assertFalse(table.col_book_map) # Test the delete service + # test basic delete book and cache expiry cache = self.init_cache(cl) - # Check that files are removed fmtpath = cache.format_abspath(1, 'FMT1') bookpath = os.path.dirname(fmtpath) + title = cache.field_for('title', 1) os.mkdir(os.path.join(bookpath, 'xyz')) open(os.path.join(bookpath, 'xyz', 'abc'), 'w').close() authorpath = os.path.dirname(bookpath) @@ -272,10 +277,36 @@ class AddRemoveTest(BaseTest): cache.remove_books((1,)) for x in (fmtpath, bookpath, authorpath): af(os.path.exists(x), 'The file %s exists, when it should not' % x) - b, f = cache.backend.list_trash_entries() + b, f = cache.list_trash_entries() self.assertEqual(len(b), 1) self.assertEqual(len(f), 0) + self.assertEqual(b[0].title, title) self.assertTrue(os.path.exists(os.path.join(b[0].book_dir, 'metadata.opf'))) + cache.backend.expire_old_trash(1000) + self.assertTrue(os.path.exists(os.path.join(b[0].book_dir, 'metadata.opf'))) + cache.backend.expire_old_trash(0) + self.assertFalse(os.path.exists(os.path.join(b[0].book_dir, 'metadata.opf'))) + + # test restoring of books + cache = self.init_cache(cl2) + cache.set_cover({1: image_from_path(get_image_path('lt.png', allow_user_override=False))}) + fmtpath = cache.format_abspath(1, 'FMT1') + bookpath = os.path.dirname(fmtpath) + cache.set_annotations_for_book(1, 'FMT1', [({'title': 'else', 'type': 'bookmark', 'timestamp': utcnow().isoformat()}, 1)]) + annots_before = cache.all_annotations_for_book(1) + fm_before = cache.format_metadata(1, 'FMT1', allow_cache=False), cache.format_metadata(1, 'FMT2', allow_cache=False) + os.mkdir(os.path.join(bookpath, 'xyz')) + open(os.path.join(bookpath, 'xyz', 'abc'), 'w').close() + cache.remove_books((1,)) + cache.move_book_from_trash(1) + b, f = cache.list_trash_entries() + self.assertEqual(len(b), 0) + self.assertEqual(len(f), 0) + self.assertEqual(fmtpath, cache.format_abspath(1, 'FMT1')) + self.assertEqual(fm_before, (cache.format_metadata(1, 'FMT1', allow_cache=False), cache.format_metadata(1, 'FMT2', allow_cache=False))) + self.assertEqual(annots_before, cache.all_annotations_for_book(1)) + self.assertTrue(cache.cover(1)) + self.assertTrue(os.path.exists(os.path.join(bookpath, 'xyz', 'abc'))) # }}} def test_original_fmt(self): # {{{ @@ -315,7 +346,7 @@ class AddRemoveTest(BaseTest): def test_copy_to_library(self): # {{{ from calibre.db.copy_to_library import copy_one_book from calibre.ebooks.metadata import authors_to_string - from calibre.utils.date import utcnow, EPOCH + from calibre.utils.date import EPOCH, utcnow src_db = self.init_cache() dest_db = self.init_cache(self.cloned_library) From f2f49c9d8e815e8de5b9cf46640aac0a6d720583 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 11 Apr 2023 19:41:37 +0530 Subject: [PATCH 0454/2055] Add libxcb-xcursor0 to linux build env for Qt 6.5 https://codereview.qt-project.org/c/qt/qtbase/+/325414 --- bypy/linux.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bypy/linux.conf b/bypy/linux.conf index 7b2bb31d76..c2f0690fd4 100644 --- a/bypy/linux.conf +++ b/bypy/linux.conf @@ -2,4 +2,4 @@ image 'https://cloud-images.ubuntu.com/releases/focal/release/ubuntu-20.04-serve # Build time deps for Qt. See https://doc.qt.io/qt-6/linux-requirements.html and # https://doc.qt.io/qt-6/qtwebengine-platform-notes.html -deps 'flex bison gperf ruby python2 libx11-dev libxext-dev libxfixes-dev libxi-dev libxrender-dev libxcb1-dev libx11-xcb-dev libxcb-glx0-dev libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev libxcb-sync0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-randr0-dev libxcb-render-util0-dev libxcb-xinerama0-dev libxcb-util-dev xkb-data libglu1-mesa-dev libxkbcommon-dev libinput-dev libxkbcommon-x11-dev libxkbfile-dev libgtk2.0-dev libvulkan-dev libwayland-dev libwayland-egl1-mesa libxcb-xkb-dev libegl1-mesa-dev libxtst-dev libnss3-dev libfreetype6-dev libfontconfig-dev libdrm-dev libxshmfence-dev libcups2-dev' +deps 'flex bison gperf ruby python2 libx11-dev libxext-dev libxfixes-dev libxi-dev libxrender-dev libxcb1-dev libx11-xcb-dev libxcb-glx0-dev libxcb-keysyms1-dev libxcb-image0-dev libxcb-shm0-dev libxcb-icccm4-dev libxcb-sync0-dev libxcb-xfixes0-dev libxcb-shape0-dev libxcb-randr0-dev libxcb-render-util0-dev libxcb-xinerama0-dev libxcb-util-dev xkb-data libglu1-mesa-dev libxkbcommon-dev libinput-dev libxkbcommon-x11-dev libxkbfile-dev libgtk2.0-dev libvulkan-dev libwayland-dev libwayland-egl1-mesa libxcb-xkb-dev libegl1-mesa-dev libxtst-dev libnss3-dev libfreetype6-dev libfontconfig-dev libdrm-dev libxshmfence-dev libcups2-dev libxcb-cursor0' From fff9686192a545c5eaa00829cad1a184ea599479 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 11 Apr 2023 19:52:05 +0530 Subject: [PATCH 0455/2055] Dont expose the trash book dir when listing entries --- src/calibre/db/backend.py | 6 +++--- src/calibre/db/cache.py | 8 +++++++- src/calibre/db/tests/add_remove.py | 6 +++--- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index da9f079675..76cf7168b9 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -69,7 +69,7 @@ class TrashEntry: book_id: int title: str author: str - book_dir: str + cover_path: str mtime: float formats: Sequence[str] = () @@ -1968,7 +1968,7 @@ class DB: except Exception: continue opf = OPF(os.path.join(x.path, 'metadata.opf'), basedir=x.path) - books.append(TrashEntry(book_id, opf.title or unknown, (opf.authors or au)[0], x.path, mtime)) + books.append(TrashEntry(book_id, opf.title or unknown, (opf.authors or au)[0], os.path.join(x.path, COVER_FILE_NAME), mtime)) base = os.path.join(self.trash_dir, 'f') um = {'title': unknown, 'authors': au} for x in os.scandir(base): @@ -1988,7 +1988,7 @@ class DB: else: formats.add(f.name.upper()) if formats: - files.append(TrashEntry(book_id, metadata.get('title') or unknown, (metadata.get('authors') or au)[0], x.path, mtime, tuple(formats))) + files.append(TrashEntry(book_id, metadata.get('title') or unknown, (metadata.get('authors') or au)[0], '', mtime, tuple(formats))) return books, files def remove_books(self, path_map, permanent=False): diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 3727840c92..2160a92ede 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2665,7 +2665,13 @@ class Cache: @read_api def list_trash_entries(self): - return self.backend.list_trash_entries() + books, formats = self.backend.list_trash_entries() + ff = [] + for e in formats: + if self._has_id(e.book_id): + ff.append(e) + e.cover_path = self.format_abspath(e.book_id, '__COVER_INTERNAL__') + return books, formats @write_api def move_book_from_trash(self, book_id): diff --git a/src/calibre/db/tests/add_remove.py b/src/calibre/db/tests/add_remove.py index f0dba2f2b1..935ba2f8d2 100644 --- a/src/calibre/db/tests/add_remove.py +++ b/src/calibre/db/tests/add_remove.py @@ -281,11 +281,11 @@ class AddRemoveTest(BaseTest): self.assertEqual(len(b), 1) self.assertEqual(len(f), 0) self.assertEqual(b[0].title, title) - self.assertTrue(os.path.exists(os.path.join(b[0].book_dir, 'metadata.opf'))) + self.assertTrue(os.path.exists(b[0].cover_path)) cache.backend.expire_old_trash(1000) - self.assertTrue(os.path.exists(os.path.join(b[0].book_dir, 'metadata.opf'))) + self.assertTrue(os.path.exists(b[0].cover_path)) cache.backend.expire_old_trash(0) - self.assertFalse(os.path.exists(os.path.join(b[0].book_dir, 'metadata.opf'))) + self.assertFalse(os.path.exists(b[0].cover_path)) # test restoring of books cache = self.init_cache(cl2) From 7a5176e1b497304b97dde1090143281c7f151f0f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 11 Apr 2023 21:41:49 +0530 Subject: [PATCH 0456/2055] Code to restore deleted format files --- src/calibre/db/backend.py | 28 ++++++++++++++++++++++++++-- src/calibre/db/cache.py | 20 ++++++++++++++++++++ src/calibre/db/tests/add_remove.py | 16 ++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 76cf7168b9..5514010db1 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1746,7 +1746,7 @@ class DB: # rename rather than remove, so that if something goes # wrong in the rest of this function, at least the file is # not deleted - os.rename(old_path, dest) + os.replace(old_path, dest) except OSError as e: if getattr(e, 'errno', None) != errno.ENOENT: # Failing to rename the old format will at worst leave a @@ -1754,7 +1754,17 @@ class DB: import traceback traceback.print_exc() - if (not getattr(stream, 'name', False) or not samefile(dest, stream.name)): + if isinstance(stream, str) and stream: + try: + os.replace(stream, dest) + except OSError: + if iswindows: + time.sleep(1) + os.replace(stream, dest) + else: + raise + size = os.path.getsize(dest) + elif (not getattr(stream, 'name', False) or not samefile(dest, stream.name)): with open(dest, 'wb') as f: shutil.copyfileobj(stream, f) size = f.tell() @@ -1953,6 +1963,20 @@ class DB: dest = os.path.abspath(os.path.join(self.library_path, path)) copy_tree(bdir, dest, delete_source=True) + def path_for_trash_format(self, book_id, fmt): + bdir = os.path.join(self.trash_dir, 'f', str(book_id)) + if not os.path.isdir(bdir): + return '' + path = os.path.join(bdir, fmt.lower()) + if not os.path.exists(path): + path = '' + return path + + def remove_trash_formats_dir_if_empty(self, book_id): + bdir = os.path.join(self.trash_dir, 'f', str(book_id)) + if os.path.isdir(bdir) and len(os.listdir(bdir)) <= 1: # dont count metadata.json + self.rmtree(bdir) + def list_trash_entries(self): from calibre.ebooks.metadata.opf2 import OPF self.ensure_trash_dir() diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 2160a92ede..9457cd2d0d 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2673,6 +2673,26 @@ class Cache: e.cover_path = self.format_abspath(e.book_id, '__COVER_INTERNAL__') return books, formats + @write_api + def move_format_from_trash(self, book_id, fmt): + ''' Undelete a format from the trash directory ''' + if not self._has_id(book_id): + raise ValueError(f'A book with the id {book_id} does not exist') + fmt = fmt.upper() + try: + name = self.fields['formats'].format_fname(book_id, fmt) + except Exception: + name = None + fpath = self.backend.path_for_trash_format(book_id, fmt) + if not fpath: + raise ValueError(f'No format {fmt} found in book {book_id}') + size, fname = self._do_add_format(book_id, fmt, fpath, name) + self.format_metadata_cache.pop(book_id, None) + max_size = self.fields['formats'].table.update_fmt(book_id, fmt, fname, size, self.backend) + self.fields['size'].table.update_sizes({book_id: max_size}) + self.event_dispatcher(EventType.format_added, book_id, fmt) + self.backend.remove_trash_formats_dir_if_empty(book_id) + @write_api def move_book_from_trash(self, book_id): ''' Undelete a book from the trash directory ''' diff --git a/src/calibre/db/tests/add_remove.py b/src/calibre/db/tests/add_remove.py index 935ba2f8d2..33e069e5bf 100644 --- a/src/calibre/db/tests/add_remove.py +++ b/src/calibre/db/tests/add_remove.py @@ -219,6 +219,7 @@ class AddRemoveTest(BaseTest): 'Test removal of books' cl = self.cloned_library cl2 = self.cloned_library + cl3 = self.cloned_library cache = self.init_cache() af, ae = self.assertFalse, self.assertEqual authors = cache.fields['authors'].table @@ -307,6 +308,21 @@ class AddRemoveTest(BaseTest): self.assertEqual(annots_before, cache.all_annotations_for_book(1)) self.assertTrue(cache.cover(1)) self.assertTrue(os.path.exists(os.path.join(bookpath, 'xyz', 'abc'))) + + # test restoring of formats + cache = self.init_cache(cl3) + all_formats = cache.formats(1) + cache.remove_formats({1: all_formats}) + self.assertFalse(cache.formats(1)) + b, f = cache.list_trash_entries() + self.assertEqual(len(b), 0) + self.assertEqual(len(f), 1) + self.assertEqual(f[0].title, title) + self.assertTrue(f[0].cover_path) + for fmt in all_formats: + cache.move_format_from_trash(1, fmt) + self.assertEqual(all_formats, cache.formats(1)) + self.assertFalse(os.listdir(os.path.join(cache.backend.trash_dir, 'f'))) # }}} def test_original_fmt(self): # {{{ From 7fe6b7dfe5199243f897924475b1e000fca00f2c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 12 Apr 2023 11:24:41 +0530 Subject: [PATCH 0457/2055] Allow MessagePopup to store data with the undo URL --- src/calibre/gui2/tweak_book/boss.py | 2 +- src/calibre/gui2/tweak_book/ui.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index 3b397203e4..dbadd4fde1 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -825,7 +825,7 @@ class Boss(QObject): # }}} # Global history {{{ - def do_global_undo(self): + def do_global_undo(self, *a): container = self.global_undo.undo() if container is not None: set_current_container(container) diff --git a/src/calibre/gui2/tweak_book/ui.py b/src/calibre/gui2/tweak_book/ui.py index 13a76fe119..fd105ad401 100644 --- a/src/calibre/gui2/tweak_book/ui.py +++ b/src/calibre/gui2/tweak_book/ui.py @@ -262,7 +262,7 @@ def install_new_plugins(): class MessagePopup(QLabel): - undo_requested = pyqtSignal() + undo_requested = pyqtSignal(object) def __init__(self, parent): QLabel.__init__(self, parent) @@ -294,12 +294,16 @@ class MessagePopup(QLabel): def link_activated(self, link): self.hide() if link.startswith('undo://'): - self.undo_requested.emit() + import base64, json + data = base64.urlsafe_b64decode(link.rpartition('/')[-1]) + self.undo_requested.emit(json.loads(data)) def __call__(self, text='Testing message popup', show_undo=True, timeout=5000, has_markup=False): text = '

' + (text if has_markup else prepare_string_for_xml(text)) if show_undo: - text += '\xa0\xa0{}'.format(_('Undo')) + import base64, json + data = base64.urlsafe_b64encode(json.dumps(show_undo).encode('utf-8')).decode('ascii') + text += '\xa0\xa0{}'.format(data, _('Undo')) text += f'\xa0\xa0' self.setText(text) self.resize(self.sizeHint()) @@ -317,7 +321,7 @@ class Main(MainWindow): APP_NAME = _('Edit book') STATE_VERSION = 0 - undo_requested = pyqtSignal() + undo_requested = pyqtSignal(object) def __init__(self, opts, notify=None): MainWindow.__init__(self, opts, disable_automatic_gc=True) From 019e9435fbaf271745af9f77f7697fb73c17dc09 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 12 Apr 2023 18:04:34 +0530 Subject: [PATCH 0458/2055] Allow auto hiding popup questions --- src/calibre/gui2/proceed.py | 57 ++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/src/calibre/gui2/proceed.py b/src/calibre/gui2/proceed.py index 0251638bdf..57412f1669 100644 --- a/src/calibre/gui2/proceed.py +++ b/src/calibre/gui2/proceed.py @@ -6,11 +6,12 @@ __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' from collections import namedtuple - from qt.core import ( - QWidget, Qt, QLabel, QVBoxLayout, QDialogButtonBox, QApplication, QTimer, QPixmap, QEvent, - QSize, pyqtSignal, QIcon, QPlainTextEdit, QCheckBox, QPainter, QHBoxLayout, QFontMetrics, - QPainterPath, QRectF, pyqtProperty, QPropertyAnimation, QEasingCurve, QSizePolicy, QImage, QPalette) + QApplication, QCheckBox, QDialogButtonBox, QEasingCurve, QEvent, QFontMetrics, + QHBoxLayout, QIcon, QImage, QLabel, QPainter, QPainterPath, QPalette, QPixmap, + QPlainTextEdit, QPropertyAnimation, QRectF, QSize, QSizePolicy, Qt, QTimer, + QVBoxLayout, QWidget, pyqtProperty, pyqtSignal, sip, +) from calibre.constants import __version__ from calibre.gui2.dialogs.message_box import ViewLog @@ -19,7 +20,7 @@ Question = namedtuple('Question', 'payload callback cancel_callback ' 'title msg html_log log_viewer_title log_is_file det_msg ' 'show_copy_button checkbox_msg checkbox_checked action_callback ' 'action_label action_icon focus_action show_det show_ok icon ' - 'log_viewer_unique_name') + 'log_viewer_unique_name auto_hide_after') class Icon(QWidget): @@ -95,6 +96,7 @@ class ProceedQuestion(QWidget): def __init__(self, parent): QWidget.__init__(self, parent) self.setVisible(False) + self.auto_hide_timer = None parent.installEventFilter(self) self._show_fraction = 0.0 @@ -181,6 +183,7 @@ class ProceedQuestion(QWidget): self.accept() def accept(self): + self.cancel_auto_hide() if self.questions: payload, callback, cancel_callback = self.questions[0][:3] self.questions = self.questions[1:] @@ -191,6 +194,7 @@ class ProceedQuestion(QWidget): self.hide() def reject(self): + self.cancel_auto_hide() if self.questions: payload, callback, cancel_callback = self.questions[0][:3] self.questions = self.questions[1:] @@ -223,10 +227,16 @@ class ProceedQuestion(QWidget): self.resize(sz) self.position_widget() + def cancel_auto_hide(self): + if self.auto_hide_timer is not None: + self.auto_hide_timer.stop() + self.auto_hide_timer = None + def show_question(self): if not self.questions: return if not self.isVisible(): + self.cancel_auto_hide() question = self.questions[0] self.msg_label.setText(question.msg) self.icon.set_icon(question.icon) @@ -260,6 +270,16 @@ class ProceedQuestion(QWidget): button.setDefault(True) self.raise_() self.start_show_animation() + if question.auto_hide_after > 0: + self.auto_hide_timer = t = QTimer(self) + t.setSingleShot(True) + t.timeout.connect(self.auto_hide) + t.start(1000 * question.auto_hide_after) + + def auto_hide(self): + self.auto_hide_timer = None + if not sip.isdeleted(self) and self.isVisible(): + self.reject() def start_show_animation(self): if self.rendered_pixmap is not None: @@ -307,18 +327,21 @@ class ProceedQuestion(QWidget): self.show() self.position_widget() - def dummy_question(self, action_label=None): + def dummy_question(self, action_label=None, auto_hide_after=0): self(lambda *args:args, (), 'dummy log', 'Log Viewer', 'A Dummy Popup', 'This is a dummy popup to easily test things, with a long line of text that should wrap. ' 'This is a dummy popup to easily test things, with a long line of text that should wrap', - checkbox_msg='A dummy checkbox', + checkbox_msg='A dummy checkbox', auto_hide_after=auto_hide_after, action_callback=lambda *args: args, action_label=action_label or 'An action') - def __call__(self, callback, payload, html_log, log_viewer_title, title, - msg, det_msg='', show_copy_button=False, cancel_callback=None, - log_is_file=False, checkbox_msg=None, checkbox_checked=False, - action_callback=None, action_label=None, action_icon=None, focus_action=False, - show_det=False, show_ok=False, icon=None, log_viewer_unique_name=None, **kw): + def __call__( + self, callback, payload, html_log, log_viewer_title, title, + msg, det_msg='', show_copy_button=False, cancel_callback=None, + log_is_file=False, checkbox_msg=None, checkbox_checked=False, auto_hide_after=0, + action_callback=None, action_label=None, action_icon=None, focus_action=False, + show_det=False, show_ok=False, icon=None, log_viewer_unique_name=None, + **kw + ): ''' A non modal popup that notifies the user that a background task has been completed. This class guarantees that only a single popup is @@ -343,6 +366,7 @@ class ProceedQuestion(QWidget): called with both the payload and the state of the checkbox as arguments. :param checkbox_checked: If True the checkbox is checked by default. + :param auto_hide_after: Number of seconds to automatically cancel this question after. Zero or less for no auto hide. :param action_callback: If not None, an extra button is added, which when clicked will cause action_callback to be called instead of callback. action_callback is called in @@ -359,7 +383,7 @@ class ProceedQuestion(QWidget): payload, callback, cancel_callback, title, msg, html_log, log_viewer_title, log_is_file, det_msg, show_copy_button, checkbox_msg, checkbox_checked, action_callback, action_label, - action_icon, focus_action, show_det, show_ok, icon, log_viewer_unique_name) + action_icon, focus_action, show_det, show_ok, icon, log_viewer_unique_name, auto_hide_after) self.questions.append(question) self.show_question() @@ -403,8 +427,9 @@ class ProceedQuestion(QWidget): def main(): - from calibre.gui2 import Application from qt.core import QMainWindow, QStatusBar, QTimer + + from calibre.gui2 import Application app = Application([]) w = QMainWindow() s = QStatusBar(w) @@ -414,6 +439,10 @@ def main(): p = ProceedQuestion(w) def doit(): + p( + lambda p:None, None, 'ass2', 'ass2', 'testing auto hide', 'this popup will auto hide after 2 seconds', + auto_hide_after=2, + ) p.dummy_question() p.dummy_question(action_label='A very long button for testing relayout (indeed)') p( From ebea37e7e3391f1727a518eb1ea68d77bb03fc3e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 12 Apr 2023 18:07:21 +0530 Subject: [PATCH 0459/2055] More efficient undo link data storage --- src/calibre/gui2/tweak_book/ui.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/tweak_book/ui.py b/src/calibre/gui2/tweak_book/ui.py index fd105ad401..715ee4346f 100644 --- a/src/calibre/gui2/tweak_book/ui.py +++ b/src/calibre/gui2/tweak_book/ui.py @@ -267,6 +267,7 @@ class MessagePopup(QLabel): def __init__(self, parent): QLabel.__init__(self, parent) self.setFocusPolicy(Qt.FocusPolicy.NoFocus) + self.undo_data = None if QApplication.instance().is_dark_theme: c = builtin_colors_dark['green'] else: @@ -294,16 +295,13 @@ class MessagePopup(QLabel): def link_activated(self, link): self.hide() if link.startswith('undo://'): - import base64, json - data = base64.urlsafe_b64decode(link.rpartition('/')[-1]) - self.undo_requested.emit(json.loads(data)) + self.undo_requested.emit(self.undo_data) def __call__(self, text='Testing message popup', show_undo=True, timeout=5000, has_markup=False): text = '

' + (text if has_markup else prepare_string_for_xml(text)) if show_undo: - import base64, json - data = base64.urlsafe_b64encode(json.dumps(show_undo).encode('utf-8')).decode('ascii') - text += '\xa0\xa0{}'.format(data, _('Undo')) + self.undo_data = show_undo + text += '\xa0\xa0{}'.format(_('Undo')) text += f'\xa0\xa0' self.setText(text) self.resize(self.sizeHint()) From 614585db0ed0547589f99b563b05715fc44e890c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 12 Apr 2023 20:34:49 +0530 Subject: [PATCH 0460/2055] Make MessagePopup resueable --- src/calibre/gui2/tweak_book/ui.py | 72 +++---------------------------- src/calibre/gui2/widgets2.py | 62 +++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 67 deletions(-) diff --git a/src/calibre/gui2/tweak_book/ui.py b/src/calibre/gui2/tweak_book/ui.py index 715ee4346f..dc1157a3d2 100644 --- a/src/calibre/gui2/tweak_book/ui.py +++ b/src/calibre/gui2/tweak_book/ui.py @@ -8,16 +8,12 @@ import os from functools import partial from itertools import product from qt.core import ( - QAction, QApplication, QColor, QDockWidget, QEvent, QHBoxLayout, QIcon, QLabel, - QMenu, QMenuBar, QPalette, QSize, QStackedWidget, Qt, QTabWidget, QTimer, QUrl, - QVBoxLayout, QWidget, pyqtSignal + QAction, QDockWidget, QEvent, QHBoxLayout, QIcon, QLabel, QMenu, QMenuBar, QSize, + QStackedWidget, Qt, QTabWidget, QTimer, QUrl, QVBoxLayout, QWidget, pyqtSignal, ) -from calibre import prepare_string_for_xml, prints -from calibre.constants import ( - DEBUG, __appname__, builtin_colors_dark, builtin_colors_light, get_version, - ismacos -) +from calibre import prints +from calibre.constants import DEBUG, __appname__, get_version, ismacos from calibre.customize.ui import find_plugin from calibre.gui2 import elided_text, open_url from calibre.gui2.keyboard import Manager as KeyboardManager @@ -25,7 +21,7 @@ from calibre.gui2.main_window import MainWindow from calibre.gui2.throbber import ThrobbingButton from calibre.gui2.tweak_book import ( actions, capitalize, current_container, editors, toolbar_actions, tprefs, - update_mark_text_action + update_mark_text_action, ) from calibre.gui2.tweak_book.boss import Boss from calibre.gui2.tweak_book.char_select import CharSelect @@ -46,9 +42,10 @@ from calibre.gui2.tweak_book.spell import SpellCheck from calibre.gui2.tweak_book.text_search import TextSearch from calibre.gui2.tweak_book.toc import TOCViewer from calibre.gui2.tweak_book.undo import CheckpointView +from calibre.gui2.widgets2 import MessagePopup from calibre.utils.icu import ord_string, sort_key from calibre.utils.localization import ( - localize_user_manual_link, localize_website_link, pgettext + localize_user_manual_link, localize_website_link, pgettext, ) from calibre.utils.unicode_names import character_name_from_code from polyglot.builtins import iteritems, itervalues @@ -260,61 +257,6 @@ def install_new_plugins(): prefs['newly_installed_plugins'] = [] -class MessagePopup(QLabel): - - undo_requested = pyqtSignal(object) - - def __init__(self, parent): - QLabel.__init__(self, parent) - self.setFocusPolicy(Qt.FocusPolicy.NoFocus) - self.undo_data = None - if QApplication.instance().is_dark_theme: - c = builtin_colors_dark['green'] - else: - c = builtin_colors_light['green'] - self.color = self.palette().color(QPalette.ColorRole.WindowText).name() - bg = QColor(c).getRgb() - self.setStyleSheet(f'''QLabel {{ - background-color: rgba({bg[0]}, {bg[1]}, {bg[2]}, 0.85); - border-radius: 4px; - color: {self.color}; - padding: 0.5em; - }}''' - ) - self.linkActivated.connect(self.link_activated) - self.close_timer = t = QTimer() - t.setSingleShot(True) - t.timeout.connect(self.hide) - self.setMouseTracking(True) - self.hide() - - def mouseMoveEvent(self, ev): - self.close_timer.start() - return super().mouseMoveEvent(ev) - - def link_activated(self, link): - self.hide() - if link.startswith('undo://'): - self.undo_requested.emit(self.undo_data) - - def __call__(self, text='Testing message popup', show_undo=True, timeout=5000, has_markup=False): - text = '

' + (text if has_markup else prepare_string_for_xml(text)) - if show_undo: - self.undo_data = show_undo - text += '\xa0\xa0{}'.format(_('Undo')) - text += f'\xa0\xa0' - self.setText(text) - self.resize(self.sizeHint()) - self.position_in_parent() - self.show() - self.raise_() - self.close_timer.start(timeout) - - def position_in_parent(self): - p = self.parent() - self.move((p.width() - self.width()) // 2, 25) - - class Main(MainWindow): APP_NAME = _('Edit book') diff --git a/src/calibre/gui2/widgets2.py b/src/calibre/gui2/widgets2.py index 4768b384e7..986ab42a3a 100644 --- a/src/calibre/gui2/widgets2.py +++ b/src/calibre/gui2/widgets2.py @@ -9,10 +9,12 @@ from qt.core import ( QFontInfo, QFontMetrics, QFrame, QIcon, QKeySequence, QLabel, QLayout, QMenu, QMimeData, QPainter, QPalette, QPixmap, QPoint, QPushButton, QRect, QScrollArea, QSize, QSizePolicy, QStyle, QStyledItemDelegate, QStyleOptionToolButton, - QStylePainter, Qt, QTabWidget, QTextBrowser, QTextCursor, QToolButton, QUndoCommand, - QUndoStack, QUrl, QWidget, pyqtSignal, + QStylePainter, Qt, QTabWidget, QTextBrowser, QTextCursor, QTimer, QToolButton, + QUndoCommand, QUndoStack, QUrl, QWidget, pyqtSignal, ) +from calibre import prepare_string_for_xml +from calibre.constants import builtin_colors_dark, builtin_colors_light from calibre.ebooks.metadata import rating_to_stars from calibre.gui2 import UNDEFINED_QDATETIME, gprefs, rating_font from calibre.gui2.complete2 import EditWithComplete, LineEdit @@ -724,6 +726,62 @@ class DateTimeEdit(QDateTimeEdit): return QDateTimeEdit.keyPressEvent(self, ev) +class MessagePopup(QLabel): + + undo_requested = pyqtSignal(object) + + def __init__(self, parent): + QLabel.__init__(self, parent) + self.setFocusPolicy(Qt.FocusPolicy.NoFocus) + self.undo_data = None + if QApplication.instance().is_dark_theme: + c = builtin_colors_dark['green'] + else: + c = builtin_colors_light['green'] + self.color = self.palette().color(QPalette.ColorRole.WindowText).name() + bg = QColor(c).getRgb() + self.setStyleSheet(f'''QLabel {{ + background-color: rgba({bg[0]}, {bg[1]}, {bg[2]}, 0.85); + border-radius: 4px; + color: {self.color}; + padding: 0.5em; + }}''' + ) + self.linkActivated.connect(self.link_activated) + self.close_timer = t = QTimer() + t.setSingleShot(True) + t.timeout.connect(self.hide) + self.setMouseTracking(True) + self.hide() + + def mouseMoveEvent(self, ev): + self.close_timer.start() + return super().mouseMoveEvent(ev) + + def link_activated(self, link): + self.hide() + if link.startswith('undo://'): + self.undo_requested.emit(self.undo_data) + + def __call__(self, text='Testing message popup', show_undo=True, timeout=5000, has_markup=False): + text = '

' + (text if has_markup else prepare_string_for_xml(text)) + if show_undo: + self.undo_data = show_undo + text += '\xa0\xa0{}'.format(_('Undo')) + text += f'\xa0\xa0' + self.setText(text) + self.resize(self.sizeHint()) + self.position_in_parent() + self.show() + self.raise_() + self.close_timer.start(timeout) + + def position_in_parent(self): + p = self.parent() + self.move((p.width() - self.width()) // 2, 25) + + + if __name__ == '__main__': from calibre.gui2 import Application app = Application([]) From 78904f6b20341ddc0f8b550cdbcca63c3b1df667 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 12 Apr 2023 21:40:18 +0530 Subject: [PATCH 0461/2055] Implement an undo popup for book deletion --- src/calibre/gui2/actions/delete.py | 51 +++++++++++++++++++----------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/calibre/gui2/actions/delete.py b/src/calibre/gui2/actions/delete.py index 0f996c3f70..26b4f5856f 100644 --- a/src/calibre/gui2/actions/delete.py +++ b/src/calibre/gui2/actions/delete.py @@ -11,16 +11,17 @@ from collections import Counter from functools import partial from qt.core import QDialog, QModelIndex, QObject, QTimer -from calibre.constants import ismacos, trash_name -from calibre.gui2 import Aborted, error_dialog, question_dialog +from calibre.constants import ismacos +from calibre.gui2 import Aborted, error_dialog from calibre.gui2.actions import InterfaceAction from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.confirm_delete_location import confirm_location from calibre.gui2.dialogs.delete_matching_from_device import ( DeleteMatchingFromDeviceDialog, ) +from calibre.gui2.widgets import BusyCursor +from calibre.gui2.widgets2 import MessagePopup from calibre.utils.localization import ngettext -from calibre.utils.recycle_bin import can_recycle single_shot = partial(QTimer.singleShot, 10) @@ -33,16 +34,6 @@ class MultiDeleter(QObject): # {{{ self.model = gui.library_view.model() self.ids = ids self.permanent = False - if can_recycle and len(ids) > 100: - if question_dialog(gui, _('Are you sure?'), '

'+ - _('You are trying to delete {0} books. ' - 'Sending so many files to the {1}' - ' can be slow. Should calibre skip the' - ' {1}? If you click Yes the files' - ' will be permanently deleted.').format(len(ids), trash_name()), - add_abort_button=True - ): - self.permanent = True self.gui = gui self.failures = [] self.deleted_ids = [] @@ -64,7 +55,7 @@ class MultiDeleter(QObject): # {{{ if title_: title = title_ self.model.db.delete_book(id_, notify=False, commit=False, - permanent=self.permanent) + permanent=False) self.deleted_ids.append(id_) except: import traceback @@ -365,13 +356,35 @@ class DeleteAction(InterfaceAction): if view.model().rowCount(QModelIndex()) < 1: self.gui.book_details.reset_info() - def library_ids_deleted2(self, ids_deleted, next_id=None): + def library_ids_deleted2(self, ids_deleted, next_id=None, can_undo=False): view = self.gui.library_view current_row = None if next_id is not None: rmap = view.ids_to_rows([next_id]) current_row = rmap.get(next_id, None) self.library_ids_deleted(ids_deleted, current_row=current_row) + if can_undo: + if not hasattr(self, 'message_popup'): + self.message_popup = MessagePopup(self.gui) + self.message_popup.undo_requested.connect(self.undelete) + self.message_popup(ngettext('One book deleted.', '{} books deleted.', len(ids_deleted)).format(len(ids_deleted)), + show_undo=(self.gui.current_db.new_api.library_id, ids_deleted)) + + def library_changed(self, db): + if hasattr(self, 'message_popup'): + self.message_popup.hide() + + def undelete(self, what): + library_id, book_ids = what + db = self.gui.current_db.new_api + if library_id == db.library_id: + with BusyCursor(): + for book_id in book_ids: + db.move_book_from_trash(book_id) + self.gui.current_db.data.books_added(book_ids) + self.gui.iactions['Add Books'].refresh_gui(len(book_ids)) + self.gui.library_view.resort() + self.gui.library_view.select_rows(set(book_ids), using_ids=True) def do_library_delete(self, to_delete_ids): view = self.gui.current_view() @@ -400,9 +413,9 @@ class DeleteAction(InterfaceAction): # The following will run if the selected books are not on a connected device. # The user has selected to delete from the library or the device and library. if not confirm('

'+ngettext( - 'The selected book will be permanently deleted and the files ' + 'The selected book will be deleted and the files ' 'removed from your calibre library. Are you sure?', - 'The {} selected books will be permanently deleted and the files ' + 'The {} selected books will be deleted and the files ' 'removed from your calibre library. Are you sure?', len(to_delete_ids)).format(len(to_delete_ids)), 'library_delete_books', self.gui): return @@ -418,11 +431,11 @@ class DeleteAction(InterfaceAction): ' program? Click "Show details" for more information.')%fname, det_msg=traceback.format_exc(), show=True) raise - self.library_ids_deleted2(to_delete_ids, next_id=next_id) + self.library_ids_deleted2(to_delete_ids, next_id=next_id, can_undo=True) else: try: self.__md = MultiDeleter(self.gui, to_delete_ids, - partial(self.library_ids_deleted2, next_id=next_id)) + partial(self.library_ids_deleted2, next_id=next_id, can_undo=True)) except Aborted: pass From 5470d311a3adaeb84d8bf1681ee66dcbb6e703df Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 13 Apr 2023 13:31:38 +0530 Subject: [PATCH 0462/2055] Implement undo popup for book format deletion --- src/calibre/db/backend.py | 4 ++++ src/calibre/db/cache.py | 5 ++++- src/calibre/gui2/actions/delete.py | 36 ++++++++++++++++++++++-------- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 5514010db1..0db99b22af 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1545,14 +1545,18 @@ class DB: def remove_formats(self, remove_map, metadata_map): self.ensure_trash_dir() + removed_map = {} for book_id, removals in iteritems(remove_map): paths = set() + removed_map[book_id] = set() for fmt, fname, path in removals: path = self.format_abspath(book_id, fmt, fname, path) if path: paths.add(path) + removed_map[book_id].add(fmt.upper()) if paths: self.move_book_files_to_trash(book_id, paths, metadata_map[book_id]) + return removed_map def cover_last_modified(self, path): path = os.path.abspath(os.path.join(self.library_path, path, COVER_FILE_NAME)) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 9457cd2d0d..545241be72 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -1848,9 +1848,11 @@ class Cache: :param formats_map: A mapping of book_id to a list of formats to be removed from the book. :param db_only: If True, only remove the record for the format from the db, do not delete the actual format file from the filesystem. + :return: A map of book id to set of formats actually deleted from the filesystem for that book ''' table = self.fields['formats'].table formats_map = {book_id:frozenset((f or '').upper() for f in fmts) for book_id, fmts in iteritems(formats_map)} + removed_map = {} for book_id, fmts in iteritems(formats_map): for fmt in fmts: @@ -1874,7 +1876,7 @@ class Cache: if removes[book_id]: metadata_map[book_id] = {'title': self._field_for('title', book_id), 'authors': self._field_for('authors', book_id)} if removes: - self.backend.remove_formats(removes, metadata_map) + removed_map = self.backend.remove_formats(removes, metadata_map) size_map = table.remove_formats(formats_map, self.backend) self.fields['size'].table.update_sizes(size_map) @@ -1884,6 +1886,7 @@ class Cache: self._update_last_modified(tuple(formats_map)) self.event_dispatcher(EventType.formats_removed, formats_map) + return removed_map @read_api def get_next_series_num_for(self, series, field='series', current_indices=False): diff --git a/src/calibre/gui2/actions/delete.py b/src/calibre/gui2/actions/delete.py index 26b4f5856f..7989a75ad5 100644 --- a/src/calibre/gui2/actions/delete.py +++ b/src/calibre/gui2/actions/delete.py @@ -166,7 +166,7 @@ class DeleteAction(InterfaceAction): return set(map(self.gui.library_view.model().id, rows)) def _remove_formats_from_ids(self, fmts, ids): - self.gui.library_view.model().db.new_api.remove_formats({bid: fmts for bid in ids}) + self.show_undo_for_deleted_formats(self.gui.library_view.model().db.new_api.remove_formats({bid: fmts for bid in ids})) self.gui.library_view.model().refresh_ids(ids) self.gui.library_view.model().current_changed(self.gui.library_view.currentIndex(), self.gui.library_view.currentIndex()) @@ -174,7 +174,7 @@ class DeleteAction(InterfaceAction): def remove_format_by_id(self, book_id, fmt): title = self.gui.current_db.title(book_id, index_is_id=True) if not confirm('

'+(_( - 'The %(fmt)s format will be permanently deleted from ' + 'The %(fmt)s format will be deleted from ' '%(title)s. Are you sure?')%dict(fmt=fmt, title=title)) + '

', 'library_delete_specific_format', self.gui): return @@ -193,8 +193,8 @@ class DeleteAction(InterfaceAction): return error_dialog(self.gui, _('Format not found'), _('The {} format is not present in the selected books.').format(fmt), show=True) if not confirm( '

'+ ngettext( - _('The {fmt} format will be permanently deleted from {title}.'), - _('The {fmt} format will be permanently deleted from all {num} selected books.'), + _('The {fmt} format will be deleted from {title}.'), + _('The {fmt} format will be deleted from all {num} selected books.'), len(ids)).format(fmt=fmt.upper(), num=len(ids), title=self.gui.current_db.title(next(iter(ids)), index_is_id=True) ) + ' ' + _('Are you sure?'), 'library_delete_specific_format_from_selected', self.gui ): @@ -217,7 +217,7 @@ class DeleteAction(InterfaceAction): if not fmts: return m = self.gui.library_view.model() - m.db.new_api.remove_formats({book_id:fmts for book_id in ids}) + self.show_undo_for_deleted_formats(m.db.new_api.remove_formats({book_id:fmts for book_id in ids})) m.refresh_ids(ids) m.current_changed(self.gui.library_view.currentIndex(), self.gui.library_view.currentIndex()) @@ -247,7 +247,7 @@ class DeleteAction(InterfaceAction): # formats removals[id] = rfmts if removals: - m.db.new_api.remove_formats(removals) + self.show_undo_for_deleted_formats(m.db.new_api.remove_formats(removals)) m.refresh_ids(ids) m.current_changed(self.gui.library_view.currentIndex(), self.gui.library_view.currentIndex()) @@ -270,7 +270,7 @@ class DeleteAction(InterfaceAction): if fmts: removals[id] = fmts.split(',') if removals: - db.new_api.remove_formats(removals) + self.show_undo_for_deleted_formats(db.new_api.remove_formats(removals)) self.gui.library_view.model().refresh_ids(ids) self.gui.library_view.model().current_changed(self.gui.library_view.currentIndex(), self.gui.library_view.currentIndex()) @@ -367,9 +367,17 @@ class DeleteAction(InterfaceAction): if not hasattr(self, 'message_popup'): self.message_popup = MessagePopup(self.gui) self.message_popup.undo_requested.connect(self.undelete) - self.message_popup(ngettext('One book deleted.', '{} books deleted.', len(ids_deleted)).format(len(ids_deleted)), + self.message_popup(ngettext('One book deleted from library.', '{} books deleted from library.', len(ids_deleted)).format(len(ids_deleted)), show_undo=(self.gui.current_db.new_api.library_id, ids_deleted)) + def show_undo_for_deleted_formats(self, removed_map): + if not hasattr(self, 'message_popup'): + self.message_popup = MessagePopup(self.gui) + self.message_popup.undo_requested.connect(self.undelete) + num = sum(map(len, removed_map.values())) + self.message_popup(ngettext('One book format deleted.', '{} book formats deleted.', num).format(num), + show_undo=(self.gui.current_db.new_api.library_id, removed_map)) + def library_changed(self, db): if hasattr(self, 'message_popup'): self.message_popup.hide() @@ -377,7 +385,17 @@ class DeleteAction(InterfaceAction): def undelete(self, what): library_id, book_ids = what db = self.gui.current_db.new_api - if library_id == db.library_id: + if library_id != db.library_id: + return + current_idx = self.gui.library_view.currentIndex() + if isinstance(book_ids, dict): + with BusyCursor(): + for book_id, fmts in book_ids.items(): + for fmt in fmts: + db.move_format_from_trash(book_id, fmt) + if current_idx.isValid(): + self.gui.library_view.model().current_changed(current_idx, current_idx) + else: with BusyCursor(): for book_id in book_ids: db.move_book_from_trash(book_id) From 26f2b45f7fe470014a176bdc234b2b34a95452a8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 13 Apr 2023 13:38:01 +0530 Subject: [PATCH 0463/2055] DRYer --- src/calibre/gui2/actions/delete.py | 18 ++++++++++-------- src/calibre/gui2/widgets2.py | 3 ++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/calibre/gui2/actions/delete.py b/src/calibre/gui2/actions/delete.py index 7989a75ad5..e60758584d 100644 --- a/src/calibre/gui2/actions/delete.py +++ b/src/calibre/gui2/actions/delete.py @@ -356,6 +356,14 @@ class DeleteAction(InterfaceAction): if view.model().rowCount(QModelIndex()) < 1: self.gui.book_details.reset_info() + @property + def show_message_popup(self): + if not hasattr(self, 'message_popup'): + self.message_popup = MessagePopup(self.gui) + self.message_popup.OFFSET_FROM_TOP = 12 + self.message_popup.undo_requested.connect(self.undelete) + return self.message_popup + def library_ids_deleted2(self, ids_deleted, next_id=None, can_undo=False): view = self.gui.library_view current_row = None @@ -364,18 +372,12 @@ class DeleteAction(InterfaceAction): current_row = rmap.get(next_id, None) self.library_ids_deleted(ids_deleted, current_row=current_row) if can_undo: - if not hasattr(self, 'message_popup'): - self.message_popup = MessagePopup(self.gui) - self.message_popup.undo_requested.connect(self.undelete) - self.message_popup(ngettext('One book deleted from library.', '{} books deleted from library.', len(ids_deleted)).format(len(ids_deleted)), + self.show_message_popup(ngettext('One book deleted from library.', '{} books deleted from library.', len(ids_deleted)).format(len(ids_deleted)), show_undo=(self.gui.current_db.new_api.library_id, ids_deleted)) def show_undo_for_deleted_formats(self, removed_map): - if not hasattr(self, 'message_popup'): - self.message_popup = MessagePopup(self.gui) - self.message_popup.undo_requested.connect(self.undelete) num = sum(map(len, removed_map.values())) - self.message_popup(ngettext('One book format deleted.', '{} book formats deleted.', num).format(num), + self.show_message_popup(ngettext('One book format deleted.', '{} book formats deleted.', num).format(num), show_undo=(self.gui.current_db.new_api.library_id, removed_map)) def library_changed(self, db): diff --git a/src/calibre/gui2/widgets2.py b/src/calibre/gui2/widgets2.py index 986ab42a3a..65f58ee054 100644 --- a/src/calibre/gui2/widgets2.py +++ b/src/calibre/gui2/widgets2.py @@ -729,6 +729,7 @@ class DateTimeEdit(QDateTimeEdit): class MessagePopup(QLabel): undo_requested = pyqtSignal(object) + OFFSET_FROM_TOP = 25 def __init__(self, parent): QLabel.__init__(self, parent) @@ -778,7 +779,7 @@ class MessagePopup(QLabel): def position_in_parent(self): p = self.parent() - self.move((p.width() - self.width()) // 2, 25) + self.move((p.width() - self.width()) // 2, self.OFFSET_FROM_TOP) From 600f37f336d23716ed1f4a8139d02cd18b017b40 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 13 Apr 2023 13:38:06 +0530 Subject: [PATCH 0464/2055] String changes --- src/calibre/gui2/tweak_book/file_list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/tweak_book/file_list.py b/src/calibre/gui2/tweak_book/file_list.py index 056fc6df6c..1d06913452 100644 --- a/src/calibre/gui2/tweak_book/file_list.py +++ b/src/calibre/gui2/tweak_book/file_list.py @@ -717,7 +717,7 @@ class FileList(QTreeWidget, OpenWithHandler): m.addSeparator() if num > 1: m.addAction(QIcon.ic('modified.png'), _('&Bulk rename the selected files'), self.request_bulk_rename) - m.addAction(QIcon.ic('modified.png'), _('Change the file extension for the selected files'), self.request_change_ext) + m.addAction(QIcon.ic('modified.png'), _('Change the file extensions for the selected files'), self.request_change_ext) m.addAction(QIcon.ic('trash.png'), ngettext( '&Delete the selected file', '&Delete the {} selected files', num).format(num), self.request_delete) m.addAction(QIcon.ic('edit-copy.png'), ngettext( @@ -980,7 +980,7 @@ class FileList(QTreeWidget, OpenWithHandler): self._request_edit(item) else: error_dialog(self, _('Cannot edit'), - _('No item with the name: %s was found') % name, show=True) + _('No item with the name %s was found') % name, show=True) def edit_next_file(self, currently_editing=None, backwards=False): category = self.categories['text'] From 6725340a9f4a6fad884a5becb1506b5039343ebf Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 13 Apr 2023 13:44:47 +0530 Subject: [PATCH 0465/2055] Change default trash expiry to 14 days --- src/calibre/db/backend.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 0db99b22af..ce9c6a4a7c 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -573,7 +573,7 @@ class DB: defs['similar_series_search_key'] = 'series' defs['similar_series_match_kind'] = 'match_any' defs['last_expired_trash_at'] = 0.0 - defs['expire_old_trash_after'] = 7 * 86400 + defs['expire_old_trash_after'] = 14 * 86400 defs['book_display_fields'] = [ ('title', False), ('authors', True), ('series', True), ('identifiers', True), ('tags', True), ('formats', True), @@ -1904,7 +1904,7 @@ class DB: if iswindows: import calibre_extensions.winutil as winutil winutil.set_file_attributes(tdir, getattr(winutil, 'FILE_ATTRIBUTE_HIDDEN', 2) | getattr(winutil, 'FILE_ATTRIBUTE_NOT_CONTENT_INDEXED', 8192)) - if time.monotonic() - self.last_expired_trash_at >= 3600: + if time.time() - self.last_expired_trash_at >= 3600: self.expire_old_trash() def expire_old_trash(self, expire_age_in_seconds=-1): From 96b58acc8fdb6cf9913d2047e4c99de54c7269e9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 13 Apr 2023 20:03:55 +0530 Subject: [PATCH 0466/2055] A GUI to manage the new trash can --- manual/gui.rst | 4 +- src/calibre/db/backend.py | 9 +- src/calibre/db/cache.py | 10 ++ src/calibre/gui2/actions/delete.py | 23 ++- src/calibre/gui2/trash.py | 258 +++++++++++++++++++++++++++++ 5 files changed, 298 insertions(+), 6 deletions(-) create mode 100644 src/calibre/gui2/trash.py diff --git a/manual/gui.rst b/manual/gui.rst index fdf9057bd0..d5c217bd55 100644 --- a/manual/gui.rst +++ b/manual/gui.rst @@ -270,8 +270,10 @@ Remove books 6. **Remove matching books from device**: Allows you to remove e-book files from a connected device that match the books that are selected in the book list. + 7. **Restore recently deleted**: Allows you to undo the removal of books or formats. + .. note:: - Note that when you use :guilabel:`Remove books` to delete books from your calibre library, the book record is permanently deleted, but the files are placed into the :guilabel:`Recycle Bin/Trash`. This allows you to recover the files if you change your mind. + Note that when you use :guilabel:`Remove books` to delete books from your calibre library, the book record is deleted, but the books is temporarily stored, for a few days, in a trash folder. You can undo the delete by right clicking the :guilabel:`Remove books` button and choosing to :guilabel:`Restore recently deleted` books. .. _configuration: diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index ce9c6a4a7c..44f7adec00 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -57,6 +57,7 @@ from polyglot.builtins import ( # }}} COVER_FILE_NAME = 'cover.jpg' +DEFAULT_TRASH_EXPIRY_TIME_SECONDS = 14 * 86400 TRASH_DIR_NAME = '.caltrash' BOOK_ID_PATH_TEMPLATE = ' ({})' CUSTOM_DATA_TYPES = frozenset(('rating', 'text', 'comments', 'datetime', @@ -573,7 +574,7 @@ class DB: defs['similar_series_search_key'] = 'series' defs['similar_series_match_kind'] = 'match_any' defs['last_expired_trash_at'] = 0.0 - defs['expire_old_trash_after'] = 14 * 86400 + defs['expire_old_trash_after'] = DEFAULT_TRASH_EXPIRY_TIME_SECONDS defs['book_display_fields'] = [ ('title', False), ('authors', True), ('series', True), ('identifiers', True), ('tags', True), ('formats', True), @@ -1907,6 +1908,12 @@ class DB: if time.time() - self.last_expired_trash_at >= 3600: self.expire_old_trash() + def delete_trash_entry(self, book_id, category): + self.ensure_trash_dir() + path = os.path.join(self.trash_dir, category, str(book_id)) + if os.path.exists(path): + self.rmtree(path) + def expire_old_trash(self, expire_age_in_seconds=-1): if expire_age_in_seconds < 0: expire_age_in_seconds = max(1 * 24 * 3600, float(self.prefs['expire_old_trash_after'])) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 545241be72..1b67a028b9 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2719,6 +2719,16 @@ class Cache: if annotations: self._restore_annotations(book_id, annotations) + @write_api + def delete_trash_entry(self, book_id, category): + " Delete an entry from the trash. Here category is 'b' for books and 'f' for formats. " + self.backend.delete_trash_entry(book_id, category) + + @write_api + def expire_old_trash(self): + ' Expire entries from the trash that are too old ' + self.backend.expire_old_trash() + @write_api def restore_book(self, book_id, mi, last_modified, path, formats, annotations=()): ''' Restore the book entry in the database for a book that already exists on the filesystem ''' diff --git a/src/calibre/gui2/actions/delete.py b/src/calibre/gui2/actions/delete.py index e60758584d..724e6bf585 100644 --- a/src/calibre/gui2/actions/delete.py +++ b/src/calibre/gui2/actions/delete.py @@ -134,6 +134,8 @@ class DeleteAction(InterfaceAction): m('delete-matching', _('Remove matching books from device'), triggered=self.remove_matching_books_from_device) + self.delete_menu.addSeparator() + m('delete-undelete', _('Restore recently deleted'), triggered=self.undelete_recent, icon='edit-undo.png') self.qaction.setMenu(self.delete_menu) self.delete_memory = {} @@ -401,10 +403,23 @@ class DeleteAction(InterfaceAction): with BusyCursor(): for book_id in book_ids: db.move_book_from_trash(book_id) - self.gui.current_db.data.books_added(book_ids) - self.gui.iactions['Add Books'].refresh_gui(len(book_ids)) - self.gui.library_view.resort() - self.gui.library_view.select_rows(set(book_ids), using_ids=True) + self.refresh_after_undelete(book_ids) + + def refresh_after_undelete(self, book_ids): + self.gui.current_db.data.books_added(book_ids) + self.gui.iactions['Add Books'].refresh_gui(len(book_ids)) + self.gui.library_view.resort() + self.gui.library_view.select_rows(set(book_ids), using_ids=True) + + def undelete_recent(self): + from calibre.gui2.trash import TrashView + current_idx = self.gui.library_view.currentIndex() + d = TrashView(self.gui.current_db, self.gui) + d.books_restored.connect(self.refresh_after_undelete) + d.exec() + if d.formats_restored: + if current_idx.isValid(): + self.gui.library_view.model().current_changed(current_idx, current_idx) def do_library_delete(self, to_delete_ids): view = self.gui.current_view() diff --git a/src/calibre/gui2/trash.py b/src/calibre/gui2/trash.py new file mode 100644 index 0000000000..2fd7f900a7 --- /dev/null +++ b/src/calibre/gui2/trash.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python +# License: GPLv3 Copyright: 2023, Kovid Goyal + + +import time +import traceback +from qt.core import ( + QAbstractItemView, QDialogButtonBox, QHBoxLayout, QIcon, QLabel, QListWidget, + QListWidgetItem, QPainter, QPalette, QPixmap, QRectF, QSize, QSpinBox, QStyle, + QStyledItemDelegate, Qt, QTabWidget, QVBoxLayout, pyqtSignal, +) +from typing import Iterator, List + +from calibre import fit_image +from calibre.db.backend import DEFAULT_TRASH_EXPIRY_TIME_SECONDS, TrashEntry +from calibre.gui2 import error_dialog +from calibre.gui2.widgets import BusyCursor +from calibre.gui2.widgets2 import Dialog + +THUMBNAIL_SIZE = 60, 80 +MARGIN_SIZE = 8 + + +def time_spec(mtime: float) -> str: + delta = time.time() - mtime + if delta <= 86400: + if delta <= 3600: + return _('less than an hour ago') + return _('{} hours ago').format(int(delta) // 3600) + else: + return _('{} days ago').format(int(delta) // 86400) + + +class TrashItemDelegate(QStyledItemDelegate): + + def __init__(self, parent): + super().__init__(parent) + self.pixmap_cache = {} + + def sizeHint(self, option, index): + return QSize(THUMBNAIL_SIZE[0] + MARGIN_SIZE + 256, THUMBNAIL_SIZE[1] + MARGIN_SIZE) + + def paint(self, painter: QPainter, option, index): + super().paint(painter, option, index) + painter.save() + entry: TrashEntry = index.data(Qt.ItemDataRole.UserRole) + if option is not None and option.state & QStyle.StateFlag.State_Selected: + p = option.palette + group = (QPalette.ColorGroup.Active if option.state & QStyle.StateFlag.State_Active else + QPalette.ColorGroup.Inactive) + c = p.color(group, QPalette.ColorRole.HighlightedText) + painter.setPen(c) + + text = entry.title + '\n' + entry.author + '\n' + _('Deleted: {}').format(time_spec(entry.mtime)) + if entry.formats: + text += '\n' + ', '.join(sorted(entry.formats)) + r = QRectF(option.rect) + if entry.cover_path: + dp = self.parent().devicePixelRatioF() + p = self.pixmap_cache.get(entry.cover_path) + if p is None: + p = QPixmap() + p.load(entry.cover_path) + scaled, w, h = fit_image(p.width(), p.height(), int(THUMBNAIL_SIZE[0] * dp), int(THUMBNAIL_SIZE[1] * dp)) + if scaled: + p = p.scaled(w, h, transformMode=Qt.TransformationMode.SmoothTransformation) + p.setDevicePixelRatio(self.parent().devicePixelRatioF()) + self.pixmap_cache[entry.cover_path] = p + w, h = p.width() / dp, p.height() / dp + width, height = THUMBNAIL_SIZE[0] + MARGIN_SIZE, THUMBNAIL_SIZE[1] + MARGIN_SIZE + pos = r.topLeft() + if width > w: + pos.setX(pos.x() + (width - w) / 2) + if height > h: + pos.setY(pos.y() + (height - h) / 2) + painter.drawPixmap(pos, p) + r.adjust(THUMBNAIL_SIZE[0] + MARGIN_SIZE, 0, 0, 0) + painter.drawText(r, Qt.TextFlag.TextWordWrap | Qt.AlignmentFlag.AlignTop, text) + painter.restore() + + +class TrashList(QListWidget): + + restore_item = pyqtSignal(object, object) + + def __init__(self, entries: List[TrashEntry], parent: 'TrashView'): + super().__init__(parent) + self.db = parent.db + self.delegate = TrashItemDelegate(self) + self.setItemDelegate(self.delegate) + self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) + for entry in entries: + i = QListWidgetItem(self) + i.setData(Qt.ItemDataRole.UserRole, entry) + self.addItem(i) + self.itemDoubleClicked.connect(self.double_clicked) + + @property + def selected_entries(self) -> Iterator[TrashEntry]: + for i in self.selectedItems(): + yield i.data(Qt.ItemDataRole.UserRole) + + def double_clicked(self, item): + self.restore_item.emit(self, item) + + +class TrashView(Dialog): + + books_restored = pyqtSignal(object) + + def __init__(self, db, parent=None): + self.db = db.new_api + self.expire_on_close = False + self.formats_restored = set() + super().__init__(_('Recently deleted books'), 'trash-view-for-library', parent=parent, default_buttons=QDialogButtonBox.StandardButton.Close) + self.finished.connect(self.expire_old_trash) + + def setup_ui(self): + self.l = l = QVBoxLayout(self) + self.setWindowIcon(QIcon.ic('trash.png')) + + with BusyCursor(): + books, formats = self.db.list_trash_entries() + self.books = TrashList(books, self) + self.books.restore_item.connect(self.restore_item) + self.formats = TrashList(formats, self) + self.formats.restore_item.connect(self.restore_item) + + self.tabs = t = QTabWidget(self) + l.addWidget(t) + t.addTab(self.books, QIcon.ic('book.png'), 'books') + t.addTab(self.formats, QIcon.ic('mimetypes/zero.png'), 'formats') + + la = QLabel(_('&Permanently delete after:')) + self.auto_delete = ad = QSpinBox(self) + ad.setMinimum(1) + ad.setMaximum(365) + ad.setValue(int(self.db.pref('expire_old_trash_after', DEFAULT_TRASH_EXPIRY_TIME_SECONDS) / 86400)) + ad.setSuffix(_(' days')) + ad.setToolTip(_('Deleted items are permanently deleted automatically after the specified number of days')) + ad.valueChanged.connect(self.trash_expiry_time_changed) + h = QHBoxLayout() + h.addWidget(la), h.addWidget(ad), h.addStretch(10) + la.setBuddy(ad) + l.addLayout(h) + + l.addWidget(self.bb) + self.restore_button = b = self.bb.addButton(_('&Restore selected'), QDialogButtonBox.ButtonRole.ActionRole) + b.clicked.connect(self.restore_selected) + b.setIcon(QIcon.ic('edit-undo.png')) + self.delete_button = b = self.bb.addButton(_('Permanently &delete selected'), QDialogButtonBox.ButtonRole.ActionRole) + b.setToolTip(_('Remove the selected entries from the trash bin, thereby deleting them permanently')) + b.setIcon(QIcon.ic('edit-clear.png')) + b.clicked.connect(self.delete_selected) + self.update_titles() + + def update_titles(self): + self.tabs.setTabText(0, _('&Books ({})').format(self.books.count())) + self.tabs.setTabText(1, _('&Formats ({})').format(self.formats.count())) + + def trash_expiry_time_changed(self, val): + self.db.set_pref('expire_old_trash_after', 86400 * self.auto_delete.value()) + self.expire_on_close = True + + def expire_old_trash(self): + if self.expire_on_close: + self.db.expire_old_trash() + + def sizeHint(self): + return QSize(500, 650) + + def do_operation_on_selected(self, func): + ok_items, failed_items = [], [] + for i in self.tabs.currentWidget().selectedItems(): + entry = i.data(Qt.ItemDataRole.UserRole) + try: + func(entry) + except Exception as e: + failed_items.append((entry, e, traceback.format_exc())) + else: + ok_items.append(i) + return ok_items, failed_items + + @property + def books_tab_is_selected(self): + return self.tabs.currentWidget() is self.books + + def restore_item(self, which, item): + is_books = which is self.books + entry = item.data(Qt.ItemDataRole.UserRole) + if is_books: + self.db.move_book_from_trash(entry.book_id) + self.books_restored.emit({entry.book_id}) + else: + self.formats_restored.add(entry.book_id) + for fmt in entry.formats: + self.db.move_format_from_trash(entry.book_id, fmt) + self.remove_entries([item]) + + def restore_selected(self): + is_books = self.books_tab_is_selected + done = set() + + def f(entry): + if is_books: + self.db.move_book_from_trash(entry.book_id) + done.add(entry.book_id) + else: + self.formats_restored.add(entry.book_id) + for fmt in entry.formats: + self.db.move_format_from_trash(entry.book_id, fmt) + + ok, failed = self.do_operation_on_selected(f) + if done: + self.books_restored.emit(done) + self.remove_entries(ok) + self.show_failures(failed, _('restore')) + + def remove_entries(self, remove): + w = self.tabs.currentWidget() + for i in remove: + w.takeItem(w.row(i)) + self.update_titles() + + def delete_selected(self): + category = 'b' if self.books_tab_is_selected else 'f' + + def f(entry): + self.db.delete_trash_entry(entry.book_id, category) + ok, failed = self.do_operation_on_selected(f) + self.remove_entries(ok) + self.show_failures(failed, _('delete')) + + def show_failures(self, failures, operation): + if not failures: + return + det_msg = [] + for (entry, exc, tb) in failures: + det_msg.append(_('Failed for {} with error:').format(entry.title)) + det_msg.append(tb) + det_msg.append('-' * 40) + det_msg.append('') + det_msg = det_msg[:-2] + entry_type = _('Books') if self.books_tab_is_selected else _('Formats') + error_dialog( + self, _('Failed to process some {}').format(entry_type), + _('Could not {0} some {1}. Click "Show details" for details.').format(operation, entry_type), + det_msg='\n'.join(det_msg), show=True) + + + + +if __name__ == '__main__': + from calibre.gui2 import Application + from calibre.library import db + app = Application([]) + TrashView(db()).exec() + del app From d020bbcf54daa1f7ba37f7062c801c225e769508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dirk=20G=C3=B3mez?= Date: Thu, 13 Apr 2023 22:00:32 +0200 Subject: [PATCH 0467/2055] Fix zackzack.at recipe to cope with new site structure --- recipes/zackzack.recipe | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/recipes/zackzack.recipe b/recipes/zackzack.recipe index 1297318cbf..26e9b2e36b 100755 --- a/recipes/zackzack.recipe +++ b/recipes/zackzack.recipe @@ -10,7 +10,7 @@ from calibre.web.feeds.news import BasicNewsRecipe class Zackzack_at_review(BasicNewsRecipe): title = 'ZackZack.at' oldest_article = 7 - max_articles_per_feed = 100 + max_articles_per_feed = 20 use_embedded_content = False __author__ = 'Dirk Gomez' language = 'de_AT' @@ -18,10 +18,15 @@ class Zackzack_at_review(BasicNewsRecipe): no_stylesheets = True keep_only_tags = [ - dict(name='h1', attrs={'class': 'av-special-heading-tag'}), - dict(name='div', attrs={'class': 'av-subheading'}), - dict(name='div', attrs={'class': 'avia_textblock'}), - ] + dict(name='div', attrs={'class': 'tdb_title'}), + dict(name='div', attrs={'class': 'tdb_single_author'}), + dict(name='div', attrs={'class': 'tdb_single_date'}), + dict(name='div', attrs={'class': 'tdb_single_content'}), + ] + + remove_tags = [ + dict(name='figure', attrs={'class': 'wp-block-image'}), + ] feeds = [(u'Zack Zack', u'https://zackzack.at/feed/')] From b044e7043947529abffbc383ec2b1180d00d6791 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 14 Apr 2023 05:55:16 +0530 Subject: [PATCH 0468/2055] string changes --- manual/gui.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/gui.rst b/manual/gui.rst index d5c217bd55..19b57869a1 100644 --- a/manual/gui.rst +++ b/manual/gui.rst @@ -273,7 +273,7 @@ Remove books 7. **Restore recently deleted**: Allows you to undo the removal of books or formats. .. note:: - Note that when you use :guilabel:`Remove books` to delete books from your calibre library, the book record is deleted, but the books is temporarily stored, for a few days, in a trash folder. You can undo the delete by right clicking the :guilabel:`Remove books` button and choosing to :guilabel:`Restore recently deleted` books. + Note that when you use :guilabel:`Remove books` to delete books from your calibre library, the book record is deleted, but the books are temporarily stored, for a few days, in a trash folder. You can undo the delete by right clicking the :guilabel:`Remove books` button and choosing to :guilabel:`Restore recently deleted` books. .. _configuration: From 8701baf7e0cb7b33f943e370327bf83d494531be Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 14 Apr 2023 10:31:41 +0530 Subject: [PATCH 0469/2055] Ensure trash entries are sorted in recency order --- src/calibre/gui2/trash.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/trash.py b/src/calibre/gui2/trash.py index 2fd7f900a7..e666c2ff07 100644 --- a/src/calibre/gui2/trash.py +++ b/src/calibre/gui2/trash.py @@ -4,6 +4,7 @@ import time import traceback +from operator import attrgetter from qt.core import ( QAbstractItemView, QDialogButtonBox, QHBoxLayout, QIcon, QLabel, QListWidget, QListWidgetItem, QPainter, QPalette, QPixmap, QRectF, QSize, QSpinBox, QStyle, @@ -89,7 +90,7 @@ class TrashList(QListWidget): self.delegate = TrashItemDelegate(self) self.setItemDelegate(self.delegate) self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) - for entry in entries: + for entry in sorted(entries, key=attrgetter('mtime'), reverse=True): i = QListWidgetItem(self) i.setData(Qt.ItemDataRole.UserRole, entry) self.addItem(i) From c94e0b719f2a448eacb653a050fa9d5ba4fbbd10 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 15 Apr 2023 14:38:55 +0530 Subject: [PATCH 0470/2055] The Washington Post Print Edition by unkn0wn --- recipes/wash_post_print.recipe | 64 ++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 recipes/wash_post_print.recipe diff --git a/recipes/wash_post_print.recipe b/recipes/wash_post_print.recipe new file mode 100644 index 0000000000..83c4fa0cee --- /dev/null +++ b/recipes/wash_post_print.recipe @@ -0,0 +1,64 @@ +''' +washingtonpost.com +''' + +from calibre.web.feeds.news import BasicNewsRecipe, classes + +class wapoprint(BasicNewsRecipe): + title = 'The Washington Post | Print Edition' + __author__ = 'unkn0wn' + description = ( + 'Leading source for news, video and opinion on politics, business, world and national news, science,' + ' travel, entertainment and more. Our local coverage includes reporting on education, crime, weather,' + ' traffic, real estate, jobs and cars for DC, Maryland and Virginia. Offering award-winning opinion writing,' + ' entertainment information and restaurant reviews.' + ) + publisher = 'The Washington Post Company' + category = 'news, politics, USA' + no_stylesheets = True + use_embedded_content = False + encoding = 'utf-8' + language = 'en' + remove_attributes = ['style', 'height', 'width'] + publication_type = 'newspaper' + ignore_duplicate_articles = {'title', 'url'} + + keep_only_tags = [ + dict(name=['h1', 'figure']), + dict(attrs={'data-qa': 'lede-art'}), + classes('byline article-body'), + ] + + remove_tags = [ + dict(name=['meta', 'link', 'svg']), + classes('inline-video author-tooltip author-image powa-wrapper'), + dict(attrs={'data-qa': ['article-body-ad', 'subscribe-promo', 'interstitial-link-wrapper']}), + ] + + def parse_index(self): + soup = self.index_to_soup('https://www.washingtonpost.com/todays_paper/updates/') + if cover := soup.find('div', attrs={'class':lambda x: x and 'todays-content-image' in x.split()}): + self.cover_url = cover.img['src'] + main = soup.find('div', attrs={'id':'todays-paper'}) + + feeds = [] + + for div in main.findAll('div', attrs={'class': lambda x: x and 'todays-content' in x.split()}): + h2 = div.find('p', attrs={'class':lambda x: x and 'heading2' in x.split()}) + secname = self.tag_to_string(h2) + self.log(secname) + articles = [] + for a in div.findAll('a', href=True, attrs={'class':'headline'}): + url = a['href'] + title = self.tag_to_string(a) + articles.append({'title': title, 'url': url}) + self.log('\t', title) + self.log('\t\t', url) + if articles: + feeds.append((secname, articles)) + return feeds + + def preprocess_html(self, soup): + for img in soup.findAll('img', srcset=True): + img['src'] = img['srcset'].split()[0] + return soup From c0d533f7ec29e78bbf2fff4521ec9b46fb88453d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 15 Apr 2023 21:24:44 +0530 Subject: [PATCH 0471/2055] Update Frontline and Outlook Magazine --- recipes/frontline.recipe | 56 +++++++++++++++++++----------------- recipes/outlook_india.recipe | 3 ++ 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/recipes/frontline.recipe b/recipes/frontline.recipe index b3bd2de3a2..7a95524c8d 100644 --- a/recipes/frontline.recipe +++ b/recipes/frontline.recipe @@ -1,7 +1,6 @@ from collections import defaultdict from calibre.web.feeds.news import BasicNewsRecipe, classes - class Frontline(BasicNewsRecipe): title = u'Frontline' __author__ = 'unkn0wn' @@ -16,18 +15,21 @@ class Frontline(BasicNewsRecipe): remove_attributes = ['height', 'width'] resolve_internal_links = True extra_css = ''' - .overline{ font-size:small; color:#404040; } - .person-name { font-size:small; font-weight:bold; } - .lead-img-caption, .caption-cont { font-size:small; text-align:center; } + .environment, .publish-time, .author { font-size:small; color:#404040; } + .caption { font-size:small; text-align:center; } + img { display:block; margin:0 auto; } + .question {font-weight:bold;} ''' keep_only_tags = [ - classes('article') + dict(name='div', attrs={'class':'container article-section'}) ] remove_tags = [ - classes('shareicon-article articleBottomLine secheader mobilesocialicons'), - dict(name='h2', attrs={'class':'title'}) + classes( + 'breadcrumb comments-shares share-page article-video ' + 'referpara slide-mobile title-patch hide-mobile related-stories' + ), ] def preprocess_html(self, soup): @@ -36,11 +38,11 @@ class Frontline(BasicNewsRecipe): source = img.findPrevious('source', srcset=True) img.extract() if source: - source['src'] = source['srcset'] + source['src'] = source['srcset'].replace('_320','_1200') source.name = 'img' else: img['src'] = img['data-original'] - for cap in soup.findAll(**classes('caption-cont')): + for cap in soup.findAll(**classes('caption')): cap.name = 'figcaption' return soup @@ -50,30 +52,32 @@ class Frontline(BasicNewsRecipe): return soup def parse_index(self): - soup = self.index_to_soup('https://frontline.thehindu.com/magazine/') - issue = soup.find(**classes('sptar-archive-item')).find('a')['href'] - self.log(issue) - soup = self.index_to_soup(issue) - time = soup.find(**classes('date')).findNext('h3') - if time: - self.timefmt = ' ' + self.tag_to_string(time) - self.log('Downloading Issue:', self.timefmt) - self.cover_url = soup.find(**classes('sptar-cover-item')).find('img')['data-original'].replace('FREE_320', 'FREE_810') + soup = self.index_to_soup('https://frontline.thehindu.com/current-issue/') + + if cover := soup.find('div', attrs={'class':'magazine'}): + self.cover_url = cover.find(**classes('sptar-image')).img['data-original'].replace('_320', '_1200') + self.log('Cover ', self.cover_url) + if desc := cover.find(**classes('sub-text')): + self.description = self.tag_to_string(desc) + feeds_dict = defaultdict(list) - for div in soup.findAll('div', attrs={'class':'brief-list-item'}): - a = div.find(**classes('brief-title')).find('a') + + mag = soup.find(**classes('section-magazine')) + for div in mag.findAll('div', attrs={'class':'content'}): + a = div.find(**classes('title')).find('a') url = a['href'] title = self.tag_to_string(a) section = 'Articles' - cat = div.find(**classes('brief-cat')) - if cat: + if cat := div.find(**classes('label')): section = self.tag_to_string(cat) desc = '' - art = div.find(**classes('artbody')) - if art: + + if art := div.find(**classes('sub-text')): desc = self.tag_to_string(art) + if auth := div.find(**classes('author')): + desc = self.tag_to_string(auth) + ' | ' + desc if not url or not title: continue self.log(section, '\n\t', title, '\n\t', desc, '\n\t\t', url) - feeds_dict[section].append({"title": title, "url": url}) - return [(section, articles) for section, articles in feeds_dict.items()] + feeds_dict[section].append({"title": title, "url": url, "description": desc}) + return [(section, articles) for section, articles in feeds_dict.items()] \ No newline at end of file diff --git a/recipes/outlook_india.recipe b/recipes/outlook_india.recipe index f88714f386..a50f177c9d 100644 --- a/recipes/outlook_india.recipe +++ b/recipes/outlook_india.recipe @@ -32,6 +32,9 @@ class outlook(BasicNewsRecipe): ) ] + def get_browser(self): + return BasicNewsRecipe.get_browser(self, user_agent='common_words/based') + def parse_index(self): soup = self.index_to_soup('https://www.outlookindia.com/magazine') div = soup.find('div', attrs={'class':'wrapper'}) From 5fe521bcb9d7190b6c18c52eafdd33bf24454fd8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 15 Apr 2023 21:26:28 +0530 Subject: [PATCH 0472/2055] Bar and Bench by unkn0wn --- recipes/bar_and_bench.recipe | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 recipes/bar_and_bench.recipe diff --git a/recipes/bar_and_bench.recipe b/recipes/bar_and_bench.recipe new file mode 100644 index 0000000000..9d9a11ab84 --- /dev/null +++ b/recipes/bar_and_bench.recipe @@ -0,0 +1,73 @@ +from calibre.web.feeds.news import BasicNewsRecipe, prefixed_classes +from calibre.ptempfile import PersistentTemporaryFile + +class bar(BasicNewsRecipe): + title = 'Bar and Bench' + __author__ = 'unkn0wn' + description = ( + 'Bar & Bench is the premier online portal for Indian legal news. News, interviews,' + ' and columns related to the Supreme Court of India and the High Courts are published.' + ) + language = 'en_IN' + masthead_url = 'https://gumlet.assettype.com/barandbench/2019-12/7a743b15-5d5d-44d7-96c2-13616780ed95/brand_2x.png' + + no_stylesheets = True + remove_javascript = True + remove_attributes = ['height', 'width', 'style'] + + keep_only_tags = [ + prefixed_classes( + 'text-story-m_header-details__ text-story-m_hero-image__ text-story-m_story-content-inner-wrapper__' + ) + ] + + remove_tags = [ + prefixed_classes( + 'text-story-m_story-tags__ story-footer-module__metype__' + ), + dict(name = 'svg') + ] + + def preprocess_html(self, soup): + for img in soup.findAll('img', attrs={'data-src':True}): + img['src'] = img['data-src'] + return soup + + ignore_duplicate_articles = {'title'} + resolve_internal_links = True + remove_empty_feeds = True + + articles_are_obfuscated = True + + def get_obfuscated_article(self, url): + br = self.get_browser() + try: + br.open(url) + except Exception as e: + url = e.hdrs.get('location') + soup = self.index_to_soup(url) + link = soup.find('a', href=True) + skip_sections =[ # add sections you want to skip + '/video/', '/videos/', '/media/', 'podcast-' + ] + if any(x in link['href'] for x in skip_sections): + self.log('Aborting Article ', link['href']) + self.abort_article('skipping video links') + + self.log('Downloading ', link['href']) + html = br.open(link['href']).read() + pt = PersistentTemporaryFile('.html') + pt.write(html) + pt.close() + return pt.name + + feeds = [] + + sections = [ + 'news', 'columns', 'interviews', 'law-firms', 'apprentice-lawyer', 'legal-jobs' + ] + + for sec in sections: + a = 'https://news.google.com/rss/search?q=when:27h+allinurl:barandbench.com{}&hl=en-IN&gl=IN&ceid=IN:en' + feeds.append((sec.capitalize(), a.format('%2F' + sec + '%2F'))) + feeds.append(('Others', a.format(''))) From 8c8e6fda0b953f86c75b193e0ce1df34e5d6ad00 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 16 Apr 2023 11:32:57 +0530 Subject: [PATCH 0473/2055] Code to rename a group of files while keeping them locked --- src/calibre/utils/copy_files.py | 17 +++++++++++++++++ src/calibre/utils/copy_files_test.py | 11 ++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index 540b96bf31..6715c7d32a 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -33,6 +33,10 @@ class UnixFileCopier: def __exit__(self, *a) -> None: pass + def rename_all(self) -> None: + for src_path, dest_path in self.copy_map.items(): + os.replace(src_path, dest_path) + def copy_all(self) -> None: for src_path, dest_path in self.copy_map.items(): with suppress(OSError): @@ -114,6 +118,10 @@ class WindowsFileCopier: f.write(raw) shutil.copystat(src_path, dest_path, follow_symlinks=False) + def rename_all(self) -> None: + for src_path, dest_path in self.copy_map.items(): + winutil.move_file(src_path, dest_path) + def delete_all_source_files(self) -> None: for src_path in self.copy_map: winutil.delete_file(make_long_path_useable(src_path)) @@ -123,6 +131,15 @@ def get_copier() -> Union[UnixFileCopier | WindowsFileCopier]: return WindowsFileCopier() if iswindows else UnixFileCopier() +def rename_files(src_to_dest_map: Dict[str, str]) -> None: + ' Rename a bunch of files. On Windows all files are locked before renaming so no other process can interfere. ' + copier = get_copier() + for s, d in src_to_dest_map.items(): + copier.register(s, d) + with copier: + copier.rename_all() + + def copy_files(src_to_dest_map: Dict[str, str], delete_source: bool = False) -> None: copier = get_copier() for s, d in src_to_dest_map.items(): diff --git a/src/calibre/utils/copy_files_test.py b/src/calibre/utils/copy_files_test.py index 1d7394bb39..4fd17cf33f 100644 --- a/src/calibre/utils/copy_files_test.py +++ b/src/calibre/utils/copy_files_test.py @@ -10,7 +10,7 @@ import unittest from calibre import walk from calibre.constants import iswindows -from .copy_files import copy_tree +from .copy_files import copy_tree, rename_files from .filenames import nlinks_file @@ -52,6 +52,15 @@ class TestCopyFiles(unittest.TestCase): self.tearDown() self.setUp() + def test_renaming_of_files(self): + for name in 'one two'.split(): + with open(os.path.join(self.tdir, name), 'w') as f: + f.write(name) + renames = {os.path.join(self.tdir, k): os.path.join(self.tdir, v) for k, v in {'one': 'One', 'two': 'three'}.items()} + rename_files(renames) + contents = set(os.listdir(self.tdir)) - {'base', 'src'} + self.ae(contents, {'One', 'three'}) + def test_copying_of_trees(self): src, dest = self.s(), self.d() copy_tree(src, dest) From c3a83fd89122998853d8e4a35c28a048ee4043a9 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 16 Apr 2023 12:42:55 +0100 Subject: [PATCH 0474/2055] Add the ability to name the shortcut for qactions in InterfaceActionPlugin. --- src/calibre/gui2/actions/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/actions/__init__.py b/src/calibre/gui2/actions/__init__.py index 376b618734..be6fb2eadb 100644 --- a/src/calibre/gui2/actions/__init__.py +++ b/src/calibre/gui2/actions/__init__.py @@ -79,6 +79,10 @@ class InterfaceAction(QObject): #: with no default key binding. action_spec = ('text', 'icon', None, None) + #: If not none, used for the name displayed to the user when customizing + #: the keyboard shortcuts for the above action spec (self.qaction) + action_shortcut_name = None + #: If True, a menu is automatically created and added to self.qaction action_add_menu = False @@ -181,7 +185,9 @@ class InterfaceAction(QObject): if shortcut is not None: keys = ((shortcut,) if isinstance(shortcut, string_or_bytes) else tuple(shortcut)) - if shortcut_name is None and spec[0]: + if shortcut_name is None and self.action_shortcut_name is not None: + shortcut_name = self.action_shortcut_name + if not shortcut_name and spec[0]: shortcut_name = str(spec[0]) if shortcut_name and self.action_spec[0] and not ( From 3e4ce5a341bcd7c85c56ce660ed3b1f1ee2f25a8 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 16 Apr 2023 12:53:35 +0100 Subject: [PATCH 0475/2055] Several changes: 1) Remove the ability to open book-details link windows using a query. It was never documented and ui.py didn't support it. 2) Simplify management of book details windows. There can now only be three windows, all non-modal on top of calibre, all with calibre as the parent. Each window has a "purpose". The first is the normal book details slave window, the second is a locked book details window, and the third is a book-details link window. 3) Add more menu actions to Show Book Details: open a locked window and close all book details windows. Use this menu where the menuless qaction was previously used. --- src/calibre/gui2/actions/show_book_details.py | 87 +++++++++++-------- src/calibre/gui2/book_details.py | 3 +- src/calibre/gui2/dialogs/book_info.py | 26 +++--- 3 files changed, 68 insertions(+), 48 deletions(-) diff --git a/src/calibre/gui2/actions/show_book_details.py b/src/calibre/gui2/actions/show_book_details.py index 164feb7958..7469bc5572 100644 --- a/src/calibre/gui2/actions/show_book_details.py +++ b/src/calibre/gui2/actions/show_book_details.py @@ -8,69 +8,88 @@ __docformat__ = 'restructuredtext en' from qt.core import Qt, sip from calibre.gui2.actions import InterfaceAction -from calibre.gui2.dialogs.book_info import BookInfo +from calibre.gui2.dialogs.book_info import BookInfo, DialogNumbers from calibre.gui2 import error_dialog class ShowBookDetailsAction(InterfaceAction): name = 'Show Book Details' - action_spec = (_('Show Book details'), 'dialog_information.png', + action_spec = (_('Book details'), 'dialog_information.png', _('Show the detailed metadata for the current book in a separate window'), _('I')) + action_shortcut_name = _('Show Book details in a separate window') dont_add_to = frozenset(('context-menu-device',)) action_type = 'current' + action_add_menu = True + action_menu_clone_qaction = _('Show Book details in a separate window') def genesis(self): + self.dialogs = [None, None, None] + m = self.qaction.menu() + self.show_info_locked = l = self.create_menu_action(m, + 'show_locked_details', _('Show Book details in a separate locked window'), + icon='drm-locked.png', shortcut=None) + l.triggered.connect(self.open_locked_window) + l = self.create_menu_action(m, + 'close_all_details', _('Close all book details windows'), icon='close.png', shortcut=None) + l.triggered.connect(self.close_all_windows) self.qaction.triggered.connect(self.show_book_info) - self.dialogs = [None, ] def show_book_info(self, *args, **kwargs): library_path = kwargs.get('library_path', None) book_id = kwargs.get('book_id', None) library_id = kwargs.get('library_id', None) - query = kwargs.get('query', None) + locked = kwargs.get('locked', False) index = self.gui.library_view.currentIndex() if self.gui.current_view() is not self.gui.library_view and not library_path: error_dialog(self.gui, _('No detailed info available'), _('No detailed information is available for books ' 'on the device.')).exec() return - if library_path or index.isValid(): - # Window #0 is slaved to changes in the book list. As such - # it must not be used for details from other libraries. - for dn,v in enumerate(self.dialogs): - if dn == 0 and library_path: - continue - if v is None: - break - else: - self.dialogs.append(None) - dn += 1 - - try: - d = BookInfo(self.gui, self.gui.library_view, index, - self.gui.book_details.handle_click, dialog_number=dn, - library_id=library_id, library_path=library_path, book_id=book_id, query=query) - except ValueError as e: - error_dialog(self.gui, _('Book not found'), str(e)).exec() + if library_path: + dn = DialogNumbers.DetailsLink + else: + if not index.isValid(): return + dn = DialogNumbers.Locked if locked else DialogNumbers.Slaved + if self.dialogs[dn] is not None: + if dn == DialogNumbers.Slaved: + # This is the slaved window. It will update automatically + return + else: + # Replace the other windows. There is a signals race condition + # between closing the existing window and opening the new one, + # so do all the work here + d = self.dialogs[dn] + d.closed.disconnect(self.closed) + d.done(0) + self.dialogs[dn] = None + try: + d = BookInfo(self.gui, self.gui.library_view, index, + self.gui.book_details.handle_click, dialog_number=dn, + library_id=library_id, library_path=library_path, book_id=book_id) + except ValueError as e: + error_dialog(self.gui, _('Book not found'), str(e)).exec() + return - d.open_cover_with.connect(self.gui.bd_open_cover_with, type=Qt.ConnectionType.QueuedConnection) - self.dialogs[dn] = d - d.closed.connect(self.closed, type=Qt.ConnectionType.QueuedConnection) - d.show() + d.open_cover_with.connect(self.gui.bd_open_cover_with, type=Qt.ConnectionType.QueuedConnection) + self.dialogs[dn] = d + d.closed.connect(self.closed, type=Qt.ConnectionType.QueuedConnection) + d.show() + + def open_locked_window(self): + self.show_book_info(locked=True) def shutting_down(self): - for d in self.dialogs: - if d: - d.done(0) + self.close_all_windows() + + def close_all_windows(self): + for dialog in [d for d in self.dialogs if d is not None]: + dialog.done(0) def library_about_to_change(self, *args): - for i,d in enumerate(self.dialogs): - if i == 0: - continue - if d: - d.done(0) + for dialog in [d for d in self.dialogs[1:] if d is not None]: + dialog.done(0) def closed(self, d): try: diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 33aac40dd7..0f3443e702 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -541,8 +541,7 @@ def details_context_menu_event(view, ev, book_info, add_popup_action=False, edit menu.addSeparator() from calibre.gui2.ui import get_gui if add_popup_action: - ema = get_gui().iactions['Show Book Details'].menuless_qaction - menu.addAction(_('Open the Book details window') + '\t' + ema.shortcut().toString(QKeySequence.SequenceFormat.NativeText), book_info.show_book_info) + menu.addMenu(get_gui().iactions['Show Book Details'].qaction.menu()) else: ema = get_gui().iactions['Edit Metadata'].menuless_qaction menu.addAction(_('Open the Edit metadata window') + '\t' + ema.shortcut().toString(QKeySequence.SequenceFormat.NativeText), edit_metadata) diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index bf34003549..e9cebb86fd 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -2,6 +2,7 @@ # License: GPLv3 Copyright: 2008, Kovid Goyal +from enum import IntEnum import textwrap from qt.core import ( @@ -130,14 +131,20 @@ class Details(HTMLDisplay): details_context_menu_event(self, ev, self.book_info, edit_metadata=self.edit_metadata) +class DialogNumbers(IntEnum): + Slaved = 0 + Locked = 1 + DetailsLink = 2 + + class BookInfo(QDialog): closed = pyqtSignal(object) open_cover_with = pyqtSignal(object, object) def __init__(self, parent, view, row, link_delegate, dialog_number=None, - library_id=None, library_path=None, book_id=None, query=None): - QDialog.__init__(self, None, flags=Qt.WindowType.Window) + library_id=None, library_path=None, book_id=None): + QDialog.__init__(self, parent) self.dialog_number = dialog_number self.library_id = library_id self.marked = None @@ -179,7 +186,7 @@ class BookInfo(QDialog): hl.setContentsMargins(0, 0, 0, 0) l2.addLayout(hl, l2.rowCount(), 0, 1, -1) hl.addWidget(self.fit_cover), hl.addStretch() - if self.dialog_number == 0: + if self.dialog_number == DialogNumbers.Slaved: self.previous_button = QPushButton(QIcon.ic('previous.png'), _('&Previous'), self) self.previous_button.clicked.connect(self.previous) l2.addWidget(self.previous_button, l2.rowCount(), 0) @@ -201,11 +208,6 @@ class BookInfo(QDialog): if library_path is not None: self.view = None db = get_gui().library_broker.get_library(library_path) - if book_id is None: - ids = db.new_api.search(query) - if len(ids) == 0: - raise ValueError(_('Query "{}" found no books').format(query)) - book_id = sorted(ids)[0] if not db.new_api.has_id(book_id): raise ValueError(_("Book {} doesn't exist").format(book_id)) mi = db.new_api.get_metadata(book_id, get_cover=False) @@ -219,7 +221,7 @@ class BookInfo(QDialog): self.refresh(row, mi) else: self.view = view - if dialog_number == 0: + if dialog_number == DialogNumbers.Slaved: self.slave_connected = True self.view.model().new_bookdisplay_data.connect(self.slave) self.refresh(row) @@ -246,9 +248,9 @@ class BookInfo(QDialog): pass def geometry_string(self, txt): - if self.dialog_number is None or self.dialog_number == 0: + if self.dialog_number is None or self.dialog_number == DialogNumbers.Slaved: return txt - return txt + '_' + str(self.dialog_number) + return txt + '_' + str(int(self.dialog_number)) def sizeHint(self): try: @@ -379,7 +381,7 @@ class BookInfo(QDialog): # Indicates books was deleted from library, or row numbers have # changed return - if self.dialog_number == 0: + if self.dialog_number == DialogNumbers.Slaved: self.previous_button.setEnabled(False if row == 0 else True) self.next_button.setEnabled(False if row == self.view.model().rowCount(QModelIndex())-1 else True) self.setWindowTitle(mi.title + ' ' + _('(the current book)')) From eac71462861cbe45d7799cd7b5860053a64b8f70 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 16 Apr 2023 18:07:05 +0530 Subject: [PATCH 0476/2055] update_path() now preserves non format files/dirs in book directories --- src/calibre/db/backend.py | 166 ++++++++++++++++------------- src/calibre/db/tests/filesystem.py | 62 +++++++++++ 2 files changed, 152 insertions(+), 76 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 44f7adec00..034f81eb65 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -37,7 +37,7 @@ from calibre.library.field_metadata import FieldMetadata from calibre.ptempfile import PersistentTemporaryFile, TemporaryFile from calibre.utils import pickle_binary_string, unpickle_binary_string from calibre.utils.config import from_json, prefs, to_json, tweaks -from calibre.utils.copy_files import copy_files, copy_tree +from calibre.utils.copy_files import copy_files, copy_tree, rename_files from calibre.utils.date import EPOCH, parse_date, utcfromtimestamp, utcnow from calibre.utils.filenames import ( WindowsAtomicFolderMove, ascii_filename, atomic_rename, copyfile_using_links, @@ -423,6 +423,8 @@ def rmtree_with_retry(path, sleep_time=1): try: shutil.rmtree(path) except OSError as e: + if not iswindows: + raise if e.errno == errno.ENOENT and not os.path.exists(path): return time.sleep(sleep_time) # In case something has temporarily locked a file @@ -1469,7 +1471,7 @@ class DB: if book_dir.name.endswith(q) and book_dir.is_dir(): return book_dir.path - def format_abspath(self, book_id, fmt, fname, book_path): + def format_abspath(self, book_id, fmt, fname, book_path, do_file_rename=True): path = os.path.join(self.library_path, book_path) fmt = ('.' + fmt.lower()) if fmt else '' fmt_path = os.path.join(path, fname+fmt) @@ -1479,11 +1481,13 @@ class DB: return candidates = () with suppress(OSError): - candidates = os.listdir(path) + candidates = os.scandir(path) q = fmt.lower() for x in candidates: - if x.lower().endswith(q): - x = os.path.join(path, x) + if x.name.endswith(q) and x.is_file(): + if not do_file_rename: + return x.path + x = x.path with suppress(OSError): atomic_rename(x, fmt_path) return fmt_path @@ -1783,92 +1787,102 @@ class DB: return size, fname def update_path(self, book_id, title, author, path_field, formats_field): - path = self.construct_path_name(book_id, title, author) current_path = path_field.for_book(book_id, default_value='') + path = self.construct_path_name(book_id, title, author) formats = formats_field.for_book(book_id, default_value=()) try: extlen = max(len(fmt) for fmt in formats) + 1 except ValueError: extlen = 10 fname = self.construct_file_name(book_id, title, author, extlen) - # Check if the metadata used to construct paths has changed - changed = False - for fmt in formats: - name = formats_field.format_fname(book_id, fmt) - if name and name != fname: - changed = True - break - if path == current_path and not changed: - return - spath = os.path.join(self.library_path, *current_path.split('/')) - tpath = os.path.join(self.library_path, *path.split('/')) - source_ok = current_path and os.path.exists(spath) - wam = WindowsAtomicFolderMove(spath) if iswindows and source_ok else None - format_map = {} - original_format_map = {} - try: - if not os.path.exists(tpath): - os.makedirs(tpath) - - if source_ok: # Migrate existing files - dest = os.path.join(tpath, COVER_FILE_NAME) - self.copy_cover_to(current_path, dest, - windows_atomic_move=wam, use_hardlink=True) + def rename_format_files(): + changed = False + for fmt in formats: + name = formats_field.format_fname(book_id, fmt) + if name and name != fname: + changed = True + break + if changed: + rename_map = {} for fmt in formats: - dest = os.path.join(tpath, fname+'.'+fmt.lower()) - format_map[fmt] = dest - ofmt_fname = formats_field.format_fname(book_id, fmt) - original_format_map[fmt] = os.path.join(spath, ofmt_fname+'.'+fmt.lower()) - self.copy_format_to(book_id, fmt, ofmt_fname, current_path, - dest, windows_atomic_move=wam, use_hardlink=True) - # Update db to reflect new file locations + current_fname = formats_field.format_fname(book_id, fmt) + current_fmt_path = self.format_abspath(book_id, fmt, current_fname, current_path, do_file_rename=False) + if current_fmt_path: + new_fmt_path = os.path.abspath(os.path.join(os.path.dirname(current_fmt_path), fname + '.' + fmt.lower())) + if current_fmt_path != new_fmt_path: + rename_map[current_fmt_path] = new_fmt_path + if rename_map: + rename_files(rename_map) + return changed + + def update_paths_in_db(): with self.conn: for fmt in formats: formats_field.table.set_fname(book_id, fmt, fname, self) path_field.table.set_path(book_id, path, self) - # Delete not needed files and directories - if source_ok: - if os.path.exists(spath): - if samefile(spath, tpath): - # The format filenames may have changed while the folder - # name remains the same - for fmt, opath in iteritems(original_format_map): - npath = format_map.get(fmt, None) - if npath and os.path.abspath(npath.lower()) != os.path.abspath(opath.lower()) and samefile(opath, npath): - # opath and npath are different hard links to the same file - os.unlink(opath) - else: - if wam is not None: - wam.delete_originals() - self.rmtree(spath) - parent = os.path.dirname(spath) - if len(os.listdir(parent)) == 0: - self.rmtree(parent) - finally: - if wam is not None: - wam.close_handles() + if not current_path: + update_paths_in_db() + return - curpath = self.library_path - c1, c2 = current_path.split('/'), path.split('/') - if not self.is_case_sensitive and len(c1) == len(c2): - # On case-insensitive systems, title and author renames that only - # change case don't cause any changes to the directories in the file - # system. This can lead to having the directory names not match the - # title/author, which leads to trouble when libraries are copied to - # a case-sensitive system. The following code attempts to fix this - # by checking each segment. If they are different because of case, - # then rename the segment. Note that the code above correctly - # handles files in the directories, so no need to do them here. - for oldseg, newseg in zip(c1, c2): - if oldseg.lower() == newseg.lower() and oldseg != newseg: - try: - os.rename(os.path.join(curpath, oldseg), - os.path.join(curpath, newseg)) - except: - break # Fail silently since nothing catastrophic has happened - curpath = os.path.join(curpath, newseg) + if path == current_path: + # Only format paths have possibly changed + if rename_format_files(): + update_paths_in_db() + return + + spath = os.path.join(self.library_path, *current_path.split('/')) + tpath = os.path.join(self.library_path, *path.split('/')) + if samefile(spath, tpath): + # format paths changed and case of path to book folder changed + rename_format_files() + update_paths_in_db() + curpath = self.library_path + c1, c2 = current_path.split('/'), path.split('/') + if not self.is_case_sensitive and len(c1) == len(c2): + # On case-insensitive systems, title and author renames that only + # change case don't cause any changes to the directories in the file + # system. This can lead to having the directory names not match the + # title/author, which leads to trouble when libraries are copied to + # a case-sensitive system. The following code attempts to fix this + # by checking each segment. If they are different because of case, + # then rename the segment. Note that the code above correctly + # handles files in the directories, so no need to do them here. + for oldseg, newseg in zip(c1, c2): + if oldseg.lower() == newseg.lower() and oldseg != newseg: + try: + os.replace(os.path.join(curpath, oldseg), os.path.join(curpath, newseg)) + except OSError: + break # Fail silently since nothing catastrophic has happened + curpath = os.path.join(curpath, newseg) + return + + with suppress(FileNotFoundError): + self.rmtree(tpath) + + lfmts = tuple(fmt.lower() for fmt in formats) + existing_format_filenames = {} + for fmt in lfmts: + current_fname = formats_field.format_fname(book_id, fmt) + current_fmt_path = self.format_abspath(book_id, fmt, current_fname, current_path, do_file_rename=False) + if current_fmt_path: + existing_format_filenames[os.path.basename(current_fmt_path)] = fmt + + def transform_format_filenames(src_path, dest_path): + src_dir, src_filename = os.path.split(os.path.abspath(src_path)) + if src_dir != spath: + return dest_path + fmt = existing_format_filenames.get(src_filename) + if not fmt: + return dest_path + return os.path.join(os.path.dirname(dest_path), fname + '.' + fmt) + + if os.path.exists(spath): + copy_tree(os.path.abspath(spath), tpath, delete_source=True, transform_destination_filename=transform_format_filenames) + else: + os.makedirs(tpath) + update_paths_in_db() def write_backup(self, path, raw): path = os.path.abspath(os.path.join(self.library_path, path, 'metadata.opf')) diff --git a/src/calibre/db/tests/filesystem.py b/src/calibre/db/tests/filesystem.py index 363aa5af5c..0aee02d959 100644 --- a/src/calibre/db/tests/filesystem.py +++ b/src/calibre/db/tests/filesystem.py @@ -67,6 +67,68 @@ class FilesystemTest(BaseTest): part = fpath[-x:][0] self.assertIn(part, os.listdir(base)) + initial_side_data = {} + def init_cache(): + nonlocal cache, initial_side_data + cache = self.init_cache(self.cloned_library) + bookdir = os.path.dirname(cache.format_abspath(1, '__COVER_INTERNAL__')) + with open(os.path.join(bookdir, 'a.side'), 'w') as f: + f.write('a.side') + os.mkdir(os.path.join(bookdir, 'subdir')) + with open(os.path.join(bookdir, 'subdir', 'a.fmt1'), 'w') as f: + f.write('a.fmt1') + initial_side_data = side_data() + + def side_data(book_id=1): + bookdir = os.path.dirname(cache.format_abspath(book_id, '__COVER_INTERNAL__')) + return { + 'a.side': open(os.path.join(bookdir, 'a.side')).read(), + 'a.fmt1': open(os.path.join(bookdir, 'subdir', 'a.fmt1')).read(), + } + + def check_that_filesystem_and_db_entries_match(book_id): + bookdir = os.path.dirname(cache.format_abspath(book_id, '__COVER_INTERNAL__')) + if iswindows: + from calibre_extensions import winutil + bookdir = winutil.get_long_path_name(bookdir) + bookdir_contents = set(os.listdir(bookdir)) + expected_contents = {'cover.jpg', 'a.side', 'subdir'} + for fmt, fname in cache.fields['formats'].table.fname_map[book_id].items(): + expected_contents.add(fname + '.' + fmt.lower()) + ae(expected_contents, bookdir_contents) + fs_path = bookdir.split(os.sep)[-2:] + db_path = cache.field_for('path', book_id).split('/') + ae(db_path, fs_path) + ae(initial_side_data, side_data(book_id)) + + # test only formats being changed + init_cache() + fname = cache.fields['formats'].table.fname_map[1]['FMT1'] + cache.fields['formats'].table.fname_map[1]['FMT1'] = 'some thing else' + cache.fields['formats'].table.fname_map[1]['FMT2'] = fname.upper() + cache.backend.update_path(1, cache.field_for('title', 1), cache.field_for('authors', 1)[0], cache.fields['path'], cache.fields['formats']) + check_that_filesystem_and_db_entries_match(1) + + # test a case only change + init_cache() + title = cache.field_for('title', 1) + self.assertNotEqual(title, title.upper()) + cache.set_field('title', {1: title.upper()}) + check_that_filesystem_and_db_entries_match(1) + + # test a title change + init_cache() + cache.set_field('title', {1: 'new changed title'}) + check_that_filesystem_and_db_entries_match(1) + # test an author change + cache.set_field('authors', {1: ('new changed author',)}) + check_that_filesystem_and_db_entries_match(1) + # test a double change + from calibre.ebooks.metadata.book.base import Metadata + cache.set_metadata(1, Metadata('t1', ('a1', 'a2'))) + check_that_filesystem_and_db_entries_match(1) + + @unittest.skipUnless(iswindows, 'Windows only') def test_windows_atomic_move(self): 'Test book file open in another process when changing metadata' From 1a20d1e0056880acaf47268b6d4cc1d70d68d63d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 16 Apr 2023 20:15:42 +0530 Subject: [PATCH 0477/2055] Cleanup previous PR --- src/calibre/gui2/actions/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/actions/__init__.py b/src/calibre/gui2/actions/__init__.py index be6fb2eadb..7543e679e0 100644 --- a/src/calibre/gui2/actions/__init__.py +++ b/src/calibre/gui2/actions/__init__.py @@ -79,8 +79,8 @@ class InterfaceAction(QObject): #: with no default key binding. action_spec = ('text', 'icon', None, None) - #: If not none, used for the name displayed to the user when customizing - #: the keyboard shortcuts for the above action spec (self.qaction) + #: If not None, used for the name displayed to the user when customizing + #: the keyboard shortcuts for the above action spec instead of action_spec[0] action_shortcut_name = None #: If True, a menu is automatically created and added to self.qaction @@ -185,11 +185,11 @@ class InterfaceAction(QObject): if shortcut is not None: keys = ((shortcut,) if isinstance(shortcut, string_or_bytes) else tuple(shortcut)) - if shortcut_name is None and self.action_shortcut_name is not None: - shortcut_name = self.action_shortcut_name - if not shortcut_name and spec[0]: - shortcut_name = str(spec[0]) - + if shortcut_name is None: + if self.action_shortcut_name is not None: + shortcut_name = self.action_shortcut_name + elif spec[0]: + shortcut_name = str(spec[0]) if shortcut_name and self.action_spec[0] and not ( attr == 'qaction' and self.popup_type == QToolButton.ToolButtonPopupMode.InstantPopup): try: From a29e2a25373e8e2c9c909baabeb3e8d94c1bcf01 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 17 Apr 2023 11:17:30 +0530 Subject: [PATCH 0478/2055] Preserve all files in book dirs during export/import --- src/calibre/db/backend.py | 55 +++++++++++++++++++++++++----- src/calibre/db/cache.py | 22 +++++++++--- src/calibre/db/tests/filesystem.py | 9 +++++ 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 034f81eb65..b1bbd77680 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -57,6 +57,7 @@ from polyglot.builtins import ( # }}} COVER_FILE_NAME = 'cover.jpg' +METADATA_FILE_NAME = 'metadata.opf' DEFAULT_TRASH_EXPIRY_TIME_SECONDS = 14 * 86400 TRASH_DIR_NAME = '.caltrash' BOOK_ID_PATH_TEMPLATE = ' ({})' @@ -1584,7 +1585,8 @@ class DB: try: f = open(path, 'rb') except OSError: - time.sleep(0.2) + if iswindows: + time.sleep(0.2) try: f = open(path, 'rb') except OSError as e: @@ -1624,7 +1626,8 @@ class DB: try: f = open(path, 'rb') except OSError: - time.sleep(0.2) + if iswindows: + time.sleep(0.2) f = open(path, 'rb') with f: return True, f.read(), stat.st_mtime @@ -1660,7 +1663,8 @@ class DB: try: os.remove(path) except OSError: - time.sleep(0.2) + if iswindows: + time.sleep(0.2) os.remove(path) else: if no_processing: @@ -1671,7 +1675,8 @@ class DB: try: save_cover_data_to(data, path) except OSError: - time.sleep(0.2) + if iswindows: + time.sleep(0.2) save_cover_data_to(data, path) def copy_format_to(self, book_id, fmt, fname, path, dest, @@ -1884,8 +1889,42 @@ class DB: os.makedirs(tpath) update_paths_in_db() + def iter_extra_files(self, book_id, book_path, formats_field): + known_files = {COVER_FILE_NAME, METADATA_FILE_NAME} + for fmt in formats_field.for_book(book_id, default_value=()): + fname = formats_field.format_fname(book_id, fmt) + fpath = self.format_abspath(book_id, fmt, fname, book_path, do_file_rename=False) + if fpath: + known_files.add(os.path.basename(fpath)) + full_book_path = os.path.abspath(os.path.join(self.library_path, book_path)) + for dirpath, dirnames, filenames in os.walk(full_book_path): + for fname in filenames: + path = os.path.join(dirpath, fname) + if os.access(path, os.R_OK): + relpath = os.path.relpath(path, full_book_path) + relpath = relpath.replace(os.sep, '/') + if relpath not in known_files: + try: + src = open(path, 'rb') + except OSError: + if iswindows: + time.sleep(1) + src = open(path, 'rb') + with src: + yield relpath, src, os.path.getmtime(path) + + def add_extra_file(self, relpath, stream, book_path): + dest = os.path.abspath(os.path.join(self.library_path, book_path, relpath)) + try: + d = open(dest, 'wb') + except OSError: + os.makedirs(os.path.dirname(dest), exist_ok=True) + d = open(dest, 'wb') + with d: + shutil.copyfileobj(stream, d) + def write_backup(self, path, raw): - path = os.path.abspath(os.path.join(self.library_path, path, 'metadata.opf')) + path = os.path.abspath(os.path.join(self.library_path, path, METADATA_FILE_NAME)) try: with open(path, 'wb') as f: f.write(raw) @@ -1904,7 +1943,7 @@ class DB: f.write(raw) def read_backup(self, path): - path = os.path.abspath(os.path.join(self.library_path, path, 'metadata.opf')) + path = os.path.abspath(os.path.join(self.library_path, path, METADATA_FILE_NAME)) with open(path, 'rb') as f: return f.read() @@ -1972,7 +2011,7 @@ class DB: mi, _, annotations = read_opf(bdir, read_annotations=read_annotations) formats = [] for x in os.scandir(bdir): - if x.is_file() and x.name not in (COVER_FILE_NAME, 'metadata.opf') and '.' in x.name: + if x.is_file() and x.name not in (COVER_FILE_NAME, METADATA_FILE_NAME) and '.' in x.name: try: size = x.stat(follow_symlinks=False).st_size except OSError: @@ -2016,7 +2055,7 @@ class DB: mtime = x.stat(follow_symlinks=False).st_mtime except Exception: continue - opf = OPF(os.path.join(x.path, 'metadata.opf'), basedir=x.path) + opf = OPF(os.path.join(x.path, METADATA_FILE_NAME), basedir=x.path) books.append(TrashEntry(book_id, opf.title or unknown, (opf.authors or au)[0], os.path.join(x.path, COVER_FILE_NAME), mtime)) base = os.path.join(self.trash_dir, 'f') um = {'title': unknown, 'authors': au} diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 1b67a028b9..ea9c7211cc 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2927,7 +2927,8 @@ class Cache: os.remove(pt.name) format_metadata = {} - metadata = {'format_data':format_metadata, 'metadata.db':dbkey, 'total':total} + extra_files = {} + metadata = {'format_data':format_metadata, 'metadata.db':dbkey, 'total':total, 'extra_files': extra_files} if has_fts: metadata['full-text-search.db'] = ftsdbkey for i, book_id in enumerate(book_ids): @@ -2935,11 +2936,11 @@ class Cache: return if progress is not None: progress(self._field_for('title', book_id), i + poff, total) - format_metadata[book_id] = {} + format_metadata[book_id] = fm = {} for fmt in self._formats(book_id): mdata = self.format_metadata(book_id, fmt) key = f'{key_prefix}:{book_id}:{fmt}' - format_metadata[book_id][fmt] = key + fm[fmt] = key with exporter.start_file(key, mtime=mdata.get('mtime')) as dest: self._copy_format_to(book_id, fmt, dest, report_file_size=dest.ensure_space) cover_key = '{}:{}:{}'.format(key_prefix, book_id, '.cover') @@ -2947,7 +2948,15 @@ class Cache: if not self.copy_cover_to(book_id, dest, report_file_size=dest.ensure_space): dest.discard() else: - format_metadata[book_id]['.cover'] = cover_key + fm['.cover'] = cover_key + bp = self.field_for('path', book_id) + extra_files[book_id] = ef = {} + if bp: + for (relpath, fobj, mtime) in self.backend.iter_extra_files(book_id, bp, self.fields['formats']): + key = f'{key_prefix}:{book_id}:.|{relpath}' + with exporter.start_file(key, mtime=mtime) as dest: + shutil.copyfileobj(fobj, dest) + ef[relpath] = key exporter.set_metadata(library_key, metadata) if progress is not None: progress(_('Completed'), total, total) @@ -3067,6 +3076,7 @@ def import_library(library_key, importer, library_path, progress=None, abort=Non cache = Cache(DB(library_path, load_user_formatter_functions=False)) cache.init() format_data = {int(book_id):data for book_id, data in iteritems(metadata['format_data'])} + extra_files = {int(book_id):data for book_id, data in metadata.get('extra_files', {}).items()} for i, (book_id, fmt_key_map) in enumerate(iteritems(format_data)): if abort is not None and abort.is_set(): return @@ -3084,6 +3094,10 @@ def import_library(library_key, importer, library_path, progress=None, abort=Non size, fname = cache._do_add_format(book_id, fmt, stream, mtime=stream.mtime) cache.fields['formats'].table.update_fmt(book_id, fmt, fname, size, cache.backend) stream.close() + for relpath, efkey in extra_files.get(book_id, {}).items(): + stream = importer.start_file(efkey, _('{0} for {1}').format(relpath, title)) + path = cache._field_for('path', book_id).replace('/', os.sep) + cache.backend.add_extra_file(relpath, stream, path) cache.dump_metadata({book_id}) if progress is not None: progress(_('Completed'), total, total) diff --git a/src/calibre/db/tests/filesystem.py b/src/calibre/db/tests/filesystem.py index 0aee02d959..545c068e26 100644 --- a/src/calibre/db/tests/filesystem.py +++ b/src/calibre/db/tests/filesystem.py @@ -215,6 +215,12 @@ class FilesystemTest(BaseTest): from calibre.db.cache import import_library from calibre.utils.exim import Exporter, Importer cache = self.init_cache() + bookdir = os.path.dirname(cache.format_abspath(1, '__COVER_INTERNAL__')) + with open(os.path.join(bookdir, 'exf'), 'w') as f: + f.write('exf') + os.mkdir(os.path.join(bookdir, 'sub')) + with open(os.path.join(bookdir, 'sub', 'recurse'), 'w') as f: + f.write('recurse') for part_size in (1 << 30, 100, 1): with TemporaryDirectory('export_lib') as tdir, TemporaryDirectory('import_lib') as idir: exporter = Exporter(tdir, part_size=part_size) @@ -228,6 +234,9 @@ class FilesystemTest(BaseTest): for fmt in cache.formats(book_id): self.assertEqual(cache.format(book_id, fmt), ic.format(book_id, fmt)) self.assertEqual(cache.format_metadata(book_id, fmt)['mtime'], cache.format_metadata(book_id, fmt)['mtime']) + bookdir = os.path.dirname(ic.format_abspath(1, '__COVER_INTERNAL__')) + self.assertEqual('exf', open(os.path.join(bookdir, 'exf')).read()) + self.assertEqual('recurse', open(os.path.join(bookdir, 'sub', 'recurse')).read()) cache.add_format(1, 'TXT', BytesIO(b'testing exim')) cache.fts_indexing_sleep_time = 0.001 cache.enable_fts() From 6e35a30a8e32f7e0b0c9cdd8c46f42c7a5615716 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 17 Apr 2023 11:18:49 +0530 Subject: [PATCH 0479/2055] string changes --- src/calibre/devices/kobo/kobotouch_config.py | 2 +- src/calibre/gui2/actions/show_book_details.py | 2 +- src/calibre/gui2/preferences/create_custom_column.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/calibre/devices/kobo/kobotouch_config.py b/src/calibre/devices/kobo/kobotouch_config.py index cb0fb4bcdb..a65eb5c815 100644 --- a/src/calibre/devices/kobo/kobotouch_config.py +++ b/src/calibre/devices/kobo/kobotouch_config.py @@ -586,7 +586,7 @@ class MetadataGroupBox(DeviceOptionsGroupBox): self.update_core_metadata_checkbox = create_checkbox( _("Update metadata on Book Details pages"), _('This will update the metadata in the device database when the device is connected. ' - 'The metadata updated is displayed on the device in the library and the book details page. ' + 'The metadata updated is displayed on the device in the library and the Book details page. ' 'This is the title, authors, comments/synopsis, series name and number, publisher and published Date, ISBN and language. ' 'If a metadata plugboard exists for the device and book format, this will be used to set the metadata.' ), diff --git a/src/calibre/gui2/actions/show_book_details.py b/src/calibre/gui2/actions/show_book_details.py index 7469bc5572..4395b4e9ac 100644 --- a/src/calibre/gui2/actions/show_book_details.py +++ b/src/calibre/gui2/actions/show_book_details.py @@ -31,7 +31,7 @@ class ShowBookDetailsAction(InterfaceAction): icon='drm-locked.png', shortcut=None) l.triggered.connect(self.open_locked_window) l = self.create_menu_action(m, - 'close_all_details', _('Close all book details windows'), icon='close.png', shortcut=None) + 'close_all_details', _('Close all Book details windows'), icon='close.png', shortcut=None) l.triggered.connect(self.close_all_windows) self.qaction.triggered.connect(self.show_book_info) diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index 07a21141d1..01b22b2377 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -457,9 +457,9 @@ class CreateCustomColumn(QDialog): l.addStretch() add_row(None, l) l = QHBoxLayout() - self.composite_in_comments_box = cmc = QCheckBox(_("Show with comments in book details")) + self.composite_in_comments_box = cmc = QCheckBox(_("Show with comments in Book details")) cmc.setToolTip('

' + _('If you check this box then the column contents ' - 'will show in the Comments section in book details. ' + 'will show in the Comments section in Book details. ' 'You can indicate whether not to have a header or ' 'to put a header above the column. If you want a ' "header beside the data, don't check this box. " From a041737b2011aefde2ac37fa5c323ee69d1a79b8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 17 Apr 2023 11:46:51 +0530 Subject: [PATCH 0480/2055] No need to separately copy author links in copy_to_library since all links are copied via get_metadata/set_metadata already --- src/calibre/db/copy_to_library.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/calibre/db/copy_to_library.py b/src/calibre/db/copy_to_library.py index be916524c1..c9e4ee22f9 100644 --- a/src/calibre/db/copy_to_library.py +++ b/src/calibre/db/copy_to_library.py @@ -38,7 +38,7 @@ def postprocess_copy(book_id, new_book_id, new_authors, db, newdb, identical_boo return if new_authors: author_id_map = db.get_item_ids('authors', new_authors) - sort_map, link_map = {}, {} + sort_map = {} for author, aid in iteritems(author_id_map): if aid is not None: adata = db.author_data((aid,)).get(aid) @@ -48,13 +48,8 @@ def postprocess_copy(book_id, new_book_id, new_authors, db, newdb, identical_boo asv = adata.get('sort') if asv: sort_map[aid] = asv - alv = adata.get('link') - if alv: - link_map[aid] = alv if sort_map: newdb.set_sort_for_authors(sort_map, update_books=False) - if link_map: - newdb.set_link_for_authors(link_map) co = db.conversion_options(book_id) if co is not None: From ce4238e8a12f9926739701262a53a3cd9e4c3156 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 17 Apr 2023 12:11:03 +0530 Subject: [PATCH 0481/2055] copy_to_library now preserves extra files in book folders --- src/calibre/db/backend.py | 43 ++++++++++++------- src/calibre/db/copy_to_library.py | 6 +++ src/calibre/db/tests/add_remove.py | 9 ++++ .../gui2/preferences/create_custom_column.py | 2 +- 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index b1bbd77680..96557722ef 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1889,7 +1889,7 @@ class DB: os.makedirs(tpath) update_paths_in_db() - def iter_extra_files(self, book_id, book_path, formats_field): + def iter_extra_files(self, book_id, book_path, formats_field, yield_paths=False): known_files = {COVER_FILE_NAME, METADATA_FILE_NAME} for fmt in formats_field.for_book(book_id, default_value=()): fname = formats_field.format_fname(book_id, fmt) @@ -1904,24 +1904,35 @@ class DB: relpath = os.path.relpath(path, full_book_path) relpath = relpath.replace(os.sep, '/') if relpath not in known_files: - try: - src = open(path, 'rb') - except OSError: - if iswindows: - time.sleep(1) - src = open(path, 'rb') - with src: - yield relpath, src, os.path.getmtime(path) + mtime = os.path.getmtime(path) + if yield_paths: + yield relpath, path, mtime + else: + try: + src = open(path, 'rb') + except OSError: + if iswindows: + time.sleep(1) + src = open(path, 'rb') + with src: + yield relpath, src, mtime def add_extra_file(self, relpath, stream, book_path): dest = os.path.abspath(os.path.join(self.library_path, book_path, relpath)) - try: - d = open(dest, 'wb') - except OSError: - os.makedirs(os.path.dirname(dest), exist_ok=True) - d = open(dest, 'wb') - with d: - shutil.copyfileobj(stream, d) + if isinstance(stream, str): + try: + shutil.copy2(stream, dest) + except FileNotFoundError: + os.makedirs(os.path.dirname(dest), exist_ok=True) + shutil.copy2(stream, dest) + else: + try: + d = open(dest, 'wb') + except FileNotFoundError: + os.makedirs(os.path.dirname(dest), exist_ok=True) + d = open(dest, 'wb') + with d: + shutil.copyfileobj(stream, d) def write_backup(self, path, raw): path = os.path.abspath(os.path.join(self.library_path, path, METADATA_FILE_NAME)) diff --git a/src/calibre/db/copy_to_library.py b/src/calibre/db/copy_to_library.py index c9e4ee22f9..6488ec636b 100644 --- a/src/calibre/db/copy_to_library.py +++ b/src/calibre/db/copy_to_library.py @@ -102,6 +102,12 @@ def copy_one_book( new_book_id = newdb.add_books( [(mi, format_map)], add_duplicates=True, apply_import_tags=tweaks['add_new_book_tags_when_importing_books'], preserve_uuid=preserve_uuid, run_hooks=False)[0][0] + bp = db.field_for('path', book_id) + if bp: + for (relpath, src_path, mtime) in db.backend.iter_extra_files(book_id, bp, db.fields['formats'], yield_paths=True): + nbp = newdb.field_for('path', book_id) + if nbp: + newdb.backend.add_extra_file(relpath, src_path, nbp) postprocess_copy(book_id, new_book_id, new_authors, db, newdb, identical_books_data, duplicate_action) return_data['new_book_id'] = new_book_id return return_data diff --git a/src/calibre/db/tests/add_remove.py b/src/calibre/db/tests/add_remove.py index 33e069e5bf..8e08f58146 100644 --- a/src/calibre/db/tests/add_remove.py +++ b/src/calibre/db/tests/add_remove.py @@ -377,6 +377,12 @@ class AddRemoveTest(BaseTest): a(type='highlight', highlighted_text='text2', uuid='2', seq=3, notes='notes2 some word changed again'), ] src_db.set_annotations_for_book(1, 'FMT1', annot_list) + bookdir = os.path.dirname(src_db.format_abspath(1, '__COVER_INTERNAL__')) + with open(os.path.join(bookdir, 'exf'), 'w') as f: + f.write('exf') + os.mkdir(os.path.join(bookdir, 'sub')) + with open(os.path.join(bookdir, 'sub', 'recurse'), 'w') as f: + f.write('recurse') def make_rdata(book_id=1, new_book_id=None, action='add'): return { @@ -416,5 +422,8 @@ class AddRemoveTest(BaseTest): for new_book_id in (1, 4, 5): self.assertEqual(dest_db.format(new_book_id, 'FMT1'), b'replaced') self.assertEqual(dest_db.format(rdata['new_book_id'], 'FMT1'), b'second-round') + bookdir = os.path.dirname(dest_db.format_abspath(1, '__COVER_INTERNAL__')) + self.assertEqual('exf', open(os.path.join(bookdir, 'exf')).read()) + self.assertEqual('recurse', open(os.path.join(bookdir, 'sub', 'recurse')).read()) # }}} diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index 01b22b2377..627f02319b 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -459,7 +459,7 @@ class CreateCustomColumn(QDialog): l = QHBoxLayout() self.composite_in_comments_box = cmc = QCheckBox(_("Show with comments in Book details")) cmc.setToolTip('

' + _('If you check this box then the column contents ' - 'will show in the Comments section in Book details. ' + 'will show in the Comments section in the Book details. ' 'You can indicate whether not to have a header or ' 'to put a header above the column. If you want a ' "header beside the data, don't check this box. " From c049052dc9cd7158b5bc760e9963c301b414d402 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 17 Apr 2023 17:15:58 +0530 Subject: [PATCH 0482/2055] Add a test to ensure restoring a db from folders preserves extra files Also clean up the restore folder scanning logic a bit --- src/calibre/db/restore.py | 41 +++++++++++++++++++++------------ src/calibre/db/tests/writing.py | 27 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/src/calibre/db/restore.py b/src/calibre/db/restore.py index 3bc736de3c..24c0f1db37 100644 --- a/src/calibre/db/restore.py +++ b/src/calibre/db/restore.py @@ -8,6 +8,7 @@ __docformat__ = 'restructuredtext en' import os import re import shutil +import sys import time import traceback from contextlib import suppress @@ -16,7 +17,7 @@ from threading import Thread from calibre import force_unicode, isbytestring from calibre.constants import filesystem_encoding -from calibre.db.backend import DB, TRASH_DIR_NAME, DBPrefs +from calibre.db.backend import DB, METADATA_FILE_NAME, TRASH_DIR_NAME, DBPrefs from calibre.db.cache import Cache from calibre.ebooks.metadata.opf2 import OPF from calibre.ptempfile import TemporaryDirectory @@ -29,7 +30,7 @@ NON_EBOOK_EXTENSIONS = frozenset(( def read_opf(dirpath, read_annotations=True): - opf = os.path.join(dirpath, 'metadata.opf') + opf = os.path.join(dirpath, METADATA_FILE_NAME) parsed_opf = OPF(opf, basedir=dirpath) mi = parsed_opf.to_book_metadata() annotations = tuple(parsed_opf.read_annotations()) if read_annotations else () @@ -74,7 +75,6 @@ class Restore(Thread): if not callable(self.progress_callback): self.progress_callback = lambda x, y: x self.dirs = [] - self.ignored_dirs = [] self.failed_dirs = [] self.books = [] self.conflicting_custom_cols = {} @@ -183,26 +183,37 @@ class Restore(Thread): dirnames.remove(TRASH_DIR_NAME) leaf = os.path.basename(dirpath) m = self.db_id_regexp.search(leaf) - if m is None or 'metadata.opf' not in filenames: - self.ignored_dirs.append(dirpath) + if m is None or METADATA_FILE_NAME not in filenames: continue - self.dirs.append((dirpath, filenames, m.group(1))) + self.dirs.append((dirpath, list(dirnames), filenames, m.group(1))) + del dirnames[:] self.progress_callback(None, len(self.dirs)) - for i, x in enumerate(self.dirs): - dirpath, filenames, book_id = x + for i, (dirpath, dirnames, filenames, book_id) in enumerate(self.dirs): try: - self.process_dir(dirpath, filenames, book_id) - except: + self.process_dir(dirpath, dirnames, filenames, book_id) + except Exception: self.failed_dirs.append((dirpath, traceback.format_exc())) self.progress_callback(_('Processed') + ' ' + dirpath, i+1) - def process_dir(self, dirpath, filenames, book_id): + def process_dir(self, dirpath, dirnames, filenames, book_id): book_id = int(book_id) - formats = list(filter(is_ebook_file, filenames)) - fmts = [os.path.splitext(x)[1][1:].upper() for x in formats] - sizes = [os.path.getsize(os.path.join(dirpath, x)) for x in formats] - names = [os.path.splitext(x)[0] for x in formats] + def safe_mtime(path): + with suppress(OSError): + return os.path.getmtime(path) + return sys.maxsize + + filenames.sort(key=lambda f: safe_mtime(os.path.join(dirpath, filenames))) + fmt_map = {} + fmts, formats, sizes, names = [], [], [], [] + for x in filenames: + if is_ebook_file(x): + fmt = os.path.splitext(x)[1][1:].upper() + if fmt and fmt_map.setdefault(fmt, x) is x: + formats.append(x) + sizes.append(os.path.getsize(os.path.join(dirpath, x))) + names.append(os.path.splitext(x)[0]) + fmts.append(fmt) mi, timestamp, annotations = read_opf(dirpath) path = os.path.relpath(dirpath, self.src_library_path).replace(os.sep, '/') diff --git a/src/calibre/db/tests/writing.py b/src/calibre/db/tests/writing.py index ab27e50fa4..ccffb79227 100644 --- a/src/calibre/db/tests/writing.py +++ b/src/calibre/db/tests/writing.py @@ -5,6 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' __docformat__ = 'restructuredtext en' +import os from collections import namedtuple from functools import partial from io import BytesIO @@ -368,6 +369,22 @@ class WritingTest(BaseTest): af(mb.is_alive()) from calibre.ebooks.metadata.opf2 import OPF book_ids = (1,2,3) + + def read_all_formats(): + fbefore = {} + for book_id in book_ids: + ff = fbefore[book_id] = {} + for fmt in cache.formats(book_id): + ff[fmt] = cache.format(book_id, fmt) + return fbefore + + def read_all_extra_files(book_id=1): + ans = {} + bp = cache.field_for('path', book_id) + for (relpath, fobj, mtime) in cache.backend.iter_extra_files(book_id, bp, cache.fields['formats']): + ans[relpath] = fobj.read() + return ans + for book_id in book_ids: raw = cache.read_backup(book_id) opf = OPF(BytesIO(raw)) @@ -376,6 +393,14 @@ class WritingTest(BaseTest): tested_fields = 'title authors tags'.split() before = {f:cache.all_field_for(f, book_ids) for f in tested_fields} lbefore = tuple(cache.get_all_link_maps_for_book(i) for i in book_ids) + fbefore = read_all_formats() + bookdir = os.path.dirname(cache.format_abspath(1, '__COVER_INTERNAL__')) + with open(os.path.join(bookdir, 'exf'), 'w') as f: + f.write('exf') + os.mkdir(os.path.join(bookdir, 'sub')) + with open(os.path.join(bookdir, 'sub', 'recurse'), 'w') as f: + f.write('recurse') + ebefore = read_all_extra_files() cache.close() from calibre.db.restore import Restore restorer = Restore(cl) @@ -385,6 +410,8 @@ class WritingTest(BaseTest): cache = self.init_cache(cl) ae(before, {f:cache.all_field_for(f, book_ids) for f in tested_fields}) ae(lbefore, tuple(cache.get_all_link_maps_for_book(i) for i in book_ids)) + ae(fbefore, read_all_formats()) + ae(ebefore, read_all_extra_files()) # }}} def test_set_cover(self): # {{{ From 9bf5fb2d37a118a116efeb0f24141565367ec173 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 17 Apr 2023 22:18:22 +0530 Subject: [PATCH 0483/2055] ... --- src/calibre/db/cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index ea9c7211cc..51ed62aff4 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -3095,7 +3095,7 @@ def import_library(library_key, importer, library_path, progress=None, abort=Non cache.fields['formats'].table.update_fmt(book_id, fmt, fname, size, cache.backend) stream.close() for relpath, efkey in extra_files.get(book_id, {}).items(): - stream = importer.start_file(efkey, _('{0} for {1}').format(relpath, title)) + stream = importer.start_file(efkey, _('Extra file {0} for book {1}').format(relpath, title)) path = cache._field_for('path', book_id).replace('/', os.sep) cache.backend.add_extra_file(relpath, stream, path) cache.dump_metadata({book_id}) From e445d0996bec79bb99406931c97037a454e19b12 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 17 Apr 2023 18:26:00 +0100 Subject: [PATCH 0484/2055] Save_to_disk formats the undefined date as 101. See https://www.mobileread.com/forums/showthread.php?t=353375 --- src/calibre/library/save_to_disk.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py index eac71e3b75..28bd91440c 100644 --- a/src/calibre/library/save_to_disk.py +++ b/src/calibre/library/save_to_disk.py @@ -16,7 +16,7 @@ from calibre.db.errors import NoSuchFormat from calibre.db.lazy import FormatsList from calibre.ebooks.metadata import fmt_sidx, title_sort from calibre.utils.config import Config, StringConfig, tweaks -from calibre.utils.date import as_local_time +from calibre.utils.date import as_local_time, is_date_undefined from calibre.utils.filenames import ascii_filename, shorten_components_to from calibre.utils.formatter import TemplateFormatter from calibre.utils.localization import _ @@ -208,11 +208,12 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250, else: format_args['identifiers'] = '' - if hasattr(mi.timestamp, 'timetuple'): + if not is_date_undefined(mi.timestamp) and hasattr(mi.timestamp, 'timetuple'): format_args['timestamp'] = strftime(timefmt, mi.timestamp.timetuple()) - if hasattr(mi.pubdate, 'timetuple'): + if not is_date_undefined(mi.pubdate) and hasattr(mi.pubdate, 'timetuple'): format_args['pubdate'] = strftime(timefmt, mi.pubdate.timetuple()) - if hasattr(mi, 'last_modified') and hasattr(mi.last_modified, 'timetuple'): + if (hasattr(mi, 'last_modified') and not is_date_undefined(mi.last_modified) and + hasattr(mi.last_modified, 'timetuple')): format_args['last_modified'] = strftime(timefmt, mi.last_modified.timetuple()) format_args['id'] = str(id) From 11d0994878fa6e03ed11091f47679729557fdaf1 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Tue, 18 Apr 2023 12:57:03 +0100 Subject: [PATCH 0485/2055] Bug #2016711: Long-text columns missing from grouped search dropdown Also improved the explanatory text. --- src/calibre/gui2/preferences/search.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/calibre/gui2/preferences/search.py b/src/calibre/gui2/preferences/search.py index 9ee4eec543..e0ef773698 100644 --- a/src/calibre/gui2/preferences/search.py +++ b/src/calibre/gui2/preferences/search.py @@ -55,26 +55,28 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): "grouped search term in the drop-down box, enter the list of columns " "to search in the value box, then push the Save button. " "

Note: Search terms are forced to lower case; MySearch " - "and mysearch are the same term." - "

You can have your grouped search term show up as User categories in " - " the Tag browser. Just add the grouped search term names to the Make User " - "categories from box. You can add multiple terms separated by commas. " - "The new User category will be automatically " - "populated with all the items in the categories included in the grouped " - "search term.

Automatic User categories permit you to see easily " - "all the category items that " + "and mysearch are the same term. Search terms cannot be " + "hierarchical. Periods are not allowed in the term name." + "

Grouped search terms can show as User categories in the Tag browser " + "by adding the grouped search term names to the 'Make User " + "categories from' box. Multiple terms are separated by commas. " + "These 'automatic user categories' will be populated with items " + "from the categories included in the grouped search term. " + "

Automatic user categories permit you to see all the category items that " "are in the columns contained in the grouped search term. Using the above " - "allseries example, the automatically-generated User category " - "will contain all the series mentioned in series, " + "allseries example, the automatic user category " + "will contain all the series names in series, " "#myseries, and #myseries2. This " "can be useful to check for duplicates, to find which column contains " "a particular item, or to have hierarchical categories (categories " - "that contain categories).")) + "that contain categories). " + "

Note: values from non-category columns such as comments won't appear " + "in automatic user categories. ")) self.gst = db.prefs.get('grouped_search_terms', {}).copy() self.orig_gst_keys = list(self.gst.keys()) fm = db.new_api.field_metadata - categories = [x[0] for x in find_categories(fm) if fm[x[0]]['search_terms']] + categories = [x for x in fm.keys() if not x.startswith('@') and fm[x]['search_terms']] self.gst_value.update_items_cache(categories) QTimer.singleShot(0, self.fill_gst_box) From ea0d6e0f1585e91978d2fcb0fbaadf74a8bc6487 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Tue, 18 Apr 2023 12:57:49 +0100 Subject: [PATCH 0486/2055] Many changes and cleanups to the category editor. --- src/calibre/gui2/dialogs/tag_list_editor.py | 169 +++++++++++++------- src/calibre/gui2/tag_browser/ui.py | 2 +- 2 files changed, 108 insertions(+), 63 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index 26bf56d313..0a6e5cc494 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -111,20 +111,21 @@ class EditColumnDelegate(QItemDelegate): editing_finished = pyqtSignal(int) editing_started = pyqtSignal(int) - def __init__(self, table): + def __init__(self, table, check_for_deleted_items): QItemDelegate.__init__(self) self.table = table self.completion_data = None + self.check_for_deleted_items = check_for_deleted_items def set_completion_data(self, data): self.completion_data = data def createEditor(self, parent, option, index): - self.editing_started.emit(index.row()) if index.column() == 0: - self.item = self.table.itemFromIndex(index) - if self.item.is_deleted: + if self.check_for_deleted_items(show_error=True): return None + self.editing_started.emit(index.row()) + self.item = self.table.itemFromIndex(index) if self.completion_data: editor = EditWithComplete(parent) editor.set_separator(None) @@ -132,6 +133,7 @@ class EditColumnDelegate(QItemDelegate): else: editor = EnLineEdit(parent) return editor + self.editing_started.emit(index.row()) editor = EnLineEdit(parent) editor.setClearButtonEnabled(True) return editor @@ -185,19 +187,19 @@ class TagListEditor(QDialog, Ui_TagListEditor): hh = self.table.horizontalHeader() hh.sectionResized.connect(self.table_column_resized) hh.setSectionsClickable(True) - hh.sectionClicked.connect(self.do_sort) + self.table.setSortingEnabled(True) + hh.sectionClicked.connect(self.record_sort) hh.setSortIndicatorShown(True) + self.sort_names = ('name', 'count', 'was', 'link') self.last_sorted_by = 'name' - self.name_order = 0 - self.count_order = 1 - self.was_order = 1 - self.link_order = 0 + self.name_order = self.count_order = self.was_order = self.link_order = 1 - self.edit_delegate = EditColumnDelegate(self.table) + self.edit_delegate = EditColumnDelegate(self.table, self.check_for_deleted_items) self.edit_delegate.editing_finished.connect(self.stop_editing) self.edit_delegate.editing_started.connect(self.start_editing) self.table.setItemDelegateForColumn(0, self.edit_delegate) + self.table.setItemDelegateForColumn(3, self.edit_delegate) if prefs['case_sensitive']: self.string_contains = contains @@ -210,12 +212,16 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.undo_button.clicked.connect(self.undo_edit) self.table.itemDoubleClicked.connect(self._rename_tag) self.table.itemChanged.connect(self.finish_editing) + self.table.itemSelectionChanged.connect(self.selection_changed) self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText(_('&OK')) self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText(_('&Cancel')) self.buttonBox.accepted.connect(self.accepted) self.buttonBox.rejected.connect(self.rejected) + # Ensure that the selection moves with the item focus + self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectItems) + self.search_box.initialize('tag_list_search_box_' + cat_name) le = self.search_box.lineEdit() ac = le.findChild(QAction, QT_HIDDEN_CLEAR_ACTION) @@ -432,7 +438,8 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.table.setRowCount(len(tags)) for row,tag in enumerate(tags): item = NameTableWidgetItem(self.sorter) - item.set_is_deleted(self.all_tags[tag]['is_deleted']) + is_deleted = self.all_tags[tag]['is_deleted'] + item.set_is_deleted(is_deleted) _id = self.all_tags[tag]['key'] item.setData(Qt.ItemDataRole.UserRole, _id) item.set_initial_text(tag) @@ -470,19 +477,19 @@ class TagListEditor(QDialog, Ui_TagListEditor): item.setFlags(item.flags() & ~(Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable)) item.setText(_('no links available')) else: - item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsSelectable) - item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable) + if is_deleted: + item.setFlags(item.flags() & ~(Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable)) + item.setIcon(QIcon.ic('trash.png')) + else: + item.setFlags(item.flags() | (Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable)) + item.setIcon(QIcon()) item.setText(self.link_map.get(tag, '')) self.table.setItem(row, 3, item) - if self.last_sorted_by == 'name': - self.table.sortByColumn(0, Qt.SortOrder(self.name_order)) - elif self.last_sorted_by == 'count': - self.table.sortByColumn(1, Qt.SortOrder(self.count_order)) - elif self.last_sorted_by == 'link': - self.table.sortByColumn(3, Qt.SortOrder(self.link_order)) - else: - self.table.sortByColumn(2, Qt.SortOrder(self.was_order)) + # re-sort the table + column = self.sort_names.index(self.last_sorted_by) + sort_order = getattr(self, self.last_sorted_by + '_order') + self.table.sortByColumn(column, Qt.SortOrder(sort_order)) if select_item is not None: self.table.setCurrentItem(select_item) @@ -532,6 +539,11 @@ class TagListEditor(QDialog, Ui_TagListEditor): super().save_geometry(gprefs, 'tag_list_editor_dialog_geometry') def start_editing(self, on_row): + current_column = self.table.currentItem().column() + # We don't support editing multiple link rows at the same time. Use + # the current cell. + if current_column != 0: + self.table.setCurrentItem(self.table.item(on_row, current_column)) items = self.table.selectedItems() self.table.blockSignals(True) for item in items: @@ -542,6 +554,8 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.table.blockSignals(False) def stop_editing(self, on_row): + # This works because the link field doesn't support editing on multiple + # lines, so the on_row check will always be false. items = self.table.selectedItems() self.table.blockSignals(True) for item in items: @@ -551,6 +565,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): def finish_editing(self, edited_item): if edited_item.column() != 0: + # Nothing to do for link fields return if not edited_item.text(): error_dialog(self, _('Item is blank'), _( @@ -581,8 +596,8 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.table.blockSignals(False) def undo_edit(self): - indexes = self.table.selectionModel().selectedRows() - if not indexes: + col_zero_items = (self.table.item(item.row(), 0) for item in self.table.selectedItems()) + if not col_zero_items: error_dialog(self, _('No item selected'), _('You must select one item from the list of available items.')).exec() return @@ -592,17 +607,45 @@ class TagListEditor(QDialog, Ui_TagListEditor): 'tag_list_editor_undo'): return self.table.blockSignals(True) - for idx in indexes: - row = idx.row() - item = self.table.item(row, 0) - item.setText(item.initial_text()) - item.set_is_deleted(False) - self.to_delete.discard(int(item.data(Qt.ItemDataRole.UserRole))) - self.to_rename.pop(int(item.data(Qt.ItemDataRole.UserRole)), None) + for col_zero_item in col_zero_items: + col_zero_item.setText(col_zero_item.initial_text()) + col_zero_item.set_is_deleted(False) + self.to_delete.discard(int(col_zero_item.data(Qt.ItemDataRole.UserRole))) + self.to_rename.pop(int(col_zero_item.data(Qt.ItemDataRole.UserRole)), None) + row = col_zero_item.row() self.table.item(row, 2).setData(Qt.ItemDataRole.DisplayRole, '') + item = self.table.item(row, 3) + item.setFlags(item.flags() | Qt.ItemFlag.ItemIsEditable | Qt.ItemFlag.ItemIsSelectable) + item.setIcon(QIcon()) self.table.blockSignals(False) + def selection_changed(self): + col0 = tuple(item for item in self.table.selectedItems() if item.column() == 0) + col3 = tuple(item for item in self.table.selectedItems() if item.column() == 3) + if col0 and col3: + error_dialog(self, _('Cannot select in multiple columns'), + '

'+_('Selection of items in multiple columns is not supported. ' + 'The selection will be cleared')+'
', + show=True) + sm = self.table.selectionModel() + self.table.blockSignals(True) + sm.clear() + self.table.blockSignals(False) + + def check_for_deleted_items(self, show_error=False): + for col_zero_item in (self.table.item(item.row(), 0) for item in self.table.selectedItems()): + if col_zero_item.is_deleted: + if show_error: + error_dialog(self, _('Selection contains deleted items'), + '

'+_('The selection contains deleted items. You ' + 'must undelete them before editing.')+'
', + show=True) + return True + return False + def rename_tag(self): + if self.table.currentColumn() != 0: + return item = self.table.item(self.table.currentRow(), 0) self._rename_tag(item) @@ -611,14 +654,13 @@ class TagListEditor(QDialog, Ui_TagListEditor): error_dialog(self, _('No item selected'), _('You must select one item from the list of available items.')).exec() return - for col_zero_item in self.table.selectedItems(): - if col_zero_item.is_deleted: - if not question_dialog(self, _('Undelete items?'), - '

'+_('Items must be undeleted to continue. Do you want ' - 'to do this?')+'
'): - return + if self.check_for_deleted_items(): + if not question_dialog(self, _('Undelete items?'), + '

'+_('Items must be undeleted to continue. Do you want ' + 'to do this?')+'
'): + return self.table.blockSignals(True) - for col_zero_item in self.table.selectedItems(): + for col_zero_item in (self.table.item(item.row(), 0) for item in self.table.selectedItems()): # undelete any deleted items if col_zero_item.is_deleted: col_zero_item.set_is_deleted(False) @@ -631,14 +673,25 @@ class TagListEditor(QDialog, Ui_TagListEditor): def delete_pressed(self): if self.table.currentColumn() == 0: self.delete_tags() + return + if not confirm( + '

'+_('Are you sure you want to delete the selected links? ' + 'There is no undo.')+'
', + 'tag_list_editor_link_delete'): + return + for item in self.table.selectedItems(): + item.setText('') def delete_tags(self): + # This check works because we ensure that the selection is in only one column + if self.table.currentItem().column() != 0: + return + # We know the selected items are in column zero deletes = self.table.selectedItems() if not deletes: error_dialog(self, _('No items selected'), _('You must select at least one item from the list.')).exec() return - to_del = [] for item in deletes: if not item.is_deleted: @@ -657,39 +710,31 @@ class TagListEditor(QDialog, Ui_TagListEditor): id_ = int(item.data(Qt.ItemDataRole.UserRole)) self.to_delete.add(id_) item.set_is_deleted(True) - orig = self.table.item(item.row(), 2) + row = item.row() + orig = self.table.item(row, 2) orig.setData(Qt.ItemDataRole.DisplayRole, item.initial_text()) + link = self.table.item(row, 3) + link.setFlags(link.flags() & ~(Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable)) + link.setIcon(QIcon.ic('trash.png')) self.table.blockSignals(False) if row >= self.table.rowCount(): row = self.table.rowCount() - 1 if row >= 0: self.table.scrollToItem(self.table.item(row, 0)) - def do_sort(self, section): - (self.do_sort_by_name, self.do_sort_by_count, self.do_sort_by_was, self.do_sort_by_link)[section]() - - def do_sort_by_name(self): - self.name_order = 1 - self.name_order - self.last_sorted_by = 'name' - self.table.sortByColumn(0, Qt.SortOrder(self.name_order)) - - def do_sort_by_count(self): - self.count_order = 1 - self.count_order - self.last_sorted_by = 'count' - self.table.sortByColumn(1, Qt.SortOrder(self.count_order)) - - def do_sort_by_was(self): - self.was_order = 1 - self.was_order - self.last_sorted_by = 'count' - self.table.sortByColumn(2, Qt.SortOrder(self.was_order)) - - def do_sort_by_link(self): - self.link_order = 1 - self.link_order - self.last_sorted_by = 'link' - self.table.sortByColumn(3, Qt.SortOrder(self.link_order)) + def record_sort(self, section): + # Note what sort was done so we can redo it when the table is rebuilt + sort_name = self.sort_names[section] + sort_order_attr = sort_name + '_order' + setattr(self, sort_order_attr, 1 - getattr(self, sort_order_attr)) + self.last_sorted_by = sort_name def accepted(self): - self.links = {self.table.item(r, 0).text():self.table.item(r, 3).text() for r in range(self.table.rowCount())} + # We don't bother with cleaning out the deleted links because the db + # interface ignores links for values that don't exist. The caller must + # process deletes and renames first so the names are correct. + self.links = {self.table.item(r, 0).text():self.table.item(r, 3).text() + for r in range(self.table.rowCount())} self.save_geometry() def rejected(self): diff --git a/src/calibre/gui2/tag_browser/ui.py b/src/calibre/gui2/tag_browser/ui.py index 58fc3ce890..4b2e61fbfa 100644 --- a/src/calibre/gui2/tag_browser/ui.py +++ b/src/calibre/gui2/tag_browser/ui.py @@ -306,7 +306,7 @@ class TagBrowserMixin: # {{{ db.new_api.remove_items(category, to_delete) db.new_api.rename_items(category, to_rename, change_index=False) - # Must do this at the end so renames are accounted for + # Must do this at the end so renames and deletes are accounted for db.new_api.set_link_map(category, d.links) # Clean up the library view From 80c997b13eed6fa393775ab90f5c54062b52b873 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 18 Apr 2023 22:01:04 +0530 Subject: [PATCH 0487/2055] pep8 --- src/calibre/gui2/preferences/search.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/calibre/gui2/preferences/search.py b/src/calibre/gui2/preferences/search.py index e0ef773698..2899861a2a 100644 --- a/src/calibre/gui2/preferences/search.py +++ b/src/calibre/gui2/preferences/search.py @@ -7,7 +7,6 @@ __docformat__ = 'restructuredtext en' from qt.core import QApplication, QTimer -from calibre.db.categories import find_categories from calibre.gui2 import config, error_dialog, gprefs from calibre.gui2.preferences import ( AbortCommit, CommaSeparatedList, ConfigWidgetBase, test_widget, From dc9da6b2e8e835cb0cc39e78f637572c441b9390 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 18 Apr 2023 22:31:58 +0530 Subject: [PATCH 0488/2055] Allow adding arbitrary files to a book record stored as auxiliary data by right clicking the add books button and choosing "Add data files" --- manual/gui.rst | 14 +++++++++++++- src/calibre/db/cache.py | 6 ++++++ src/calibre/gui2/actions/add.py | 14 ++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/manual/gui.rst b/manual/gui.rst index 19b57869a1..0d6960e664 100644 --- a/manual/gui.rst +++ b/manual/gui.rst @@ -57,7 +57,9 @@ Add books 6. **Add files to selected book records**: Allows you to add or update the files associated with an existing book in your library. - 7. **Add an empty file to selected book records**: Allows you to add an empty file of the specified format to the selected book records. + 7. **Add data files to selected book records**: Allows you to add any number of extra files that will be stored in a :file:`data` sub-directory in the book directory + + 8. **Add an empty file to selected book records**: Allows you to add an empty file of the specified format to the selected book records. The :guilabel:`Add books` action can read metadata from a wide variety of e-book formats. In addition, it tries to guess metadata from the filename. See the :ref:`config_filename_metadata` section, to learn how to configure this. @@ -70,6 +72,16 @@ To add an additional format for an existing book you can do any of three things: 3. Click the :guilabel:`Add books` button in the top right area of the :guilabel:`Edit metadata` dialog, accessed by the :ref:`edit_meta_information` action. +.. note:: + The extra data files added to a book will be managed by calibre, as part of + the book. However, they cannot be viewed directly or used as conversion + sources. Nor are they indexed by the Full text search engine in calibre. To + view them select the book and press the :kbd:`O` key which will open up the + book folder in your file manager, from where the extra files can be viewed + using any program you like. They are most useful to store any ancilliary + data associated with a book such as supplementary reading material, digital + resources, etc. + .. _edit_meta_information: Edit metadata diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 51ed62aff4..f7776cd830 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -3049,6 +3049,12 @@ class Cache: def reindex_annotations(self): self.backend.reindex_annotations() + @write_api + def add_extra_files(self, book_id, map_of_relpath_to_stream_or_path): + path = self._field_for('path', book_id).replace('/', os.sep) + for relpath, stream_or_path in map_of_relpath_to_stream_or_path.items(): + self.backend.add_extra_file(relpath, stream_or_path, path) + def import_library(library_key, importer, library_path, progress=None, abort=None): from calibre.db.backend import DB diff --git a/src/calibre/gui2/actions/add.py b/src/calibre/gui2/actions/add.py index fdc1e4ce07..f163785382 100644 --- a/src/calibre/gui2/actions/add.py +++ b/src/calibre/gui2/actions/add.py @@ -71,6 +71,8 @@ class AddAction(InterfaceAction): triggered=self.add_formats, shortcut='Shift+A') ma('add-formats-clipboard', _('Add files to selected book records from clipboard'), triggered=self.add_formats_from_clipboard, shortcut='Shift+Alt+A', icon='edit-paste.png') + ma('add-extra-files-to-books', _( + 'Add data files to selected book records')).triggered.connect(self.add_extra_files) ma('add-empty-format-to-books', _( 'Add an empty file to selected book records')).triggered.connect(self.add_empty_format_choose) self.add_menu.addSeparator() @@ -145,6 +147,18 @@ class AddAction(InterfaceAction): if books: self._add_formats(books, ids) + def add_extra_files(self): + ids = self._check_add_formats_ok() + if not ids: + return + books = choose_files_and_remember_all_files(self.gui, 'add extra data files dialog dir', + _('Select extra data files'), filters=get_filters()) + if books: + rmap = {'data/' + os.path.basename(x): x for x in books} + db = self.gui.current_db.new_api + for book_id in ids: + db.add_extra_files(book_id, rmap) + def _add_formats(self, paths, ids): if len(ids) > 1 and not question_dialog( self.gui, From 994a9fa0068cd24f8e6f7086ce985410956c69cb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 18 Apr 2023 22:38:20 +0530 Subject: [PATCH 0489/2055] Check library now ignores the data folder used to store extra data files for books --- src/calibre/library/check_library.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/calibre/library/check_library.py b/src/calibre/library/check_library.py index 09799d3959..eb5778a819 100644 --- a/src/calibre/library/check_library.py +++ b/src/calibre/library/check_library.py @@ -12,13 +12,13 @@ import traceback from calibre import isbytestring from calibre.constants import filesystem_encoding +from calibre.db.backend import COVER_FILE_NAME, METADATA_FILE_NAME, TRASH_DIR_NAME from calibre.ebooks import BOOK_EXTENSIONS from calibre.utils.localization import _ from polyglot.builtins import iteritems -from calibre.db.backend import TRASH_DIR_NAME EBOOK_EXTENSIONS = frozenset(BOOK_EXTENSIONS) -NORMALS = frozenset({'metadata.opf', 'cover.jpg'}) +NORMALS = frozenset({METADATA_FILE_NAME, COVER_FILE_NAME, 'data'}) IGNORE_AT_TOP_LEVEL = frozenset({'metadata.db', 'metadata_db_prefs_backup.json', 'metadata_pre_restore.db', 'full-text-search.db', TRASH_DIR_NAME}) ''' @@ -169,7 +169,7 @@ class CheckLibrary: os.path.join(path, fmt[0]+'.'+fmt[1].lower()), id_)) if self.db.has_cover(id_): self.missing_covers.append((title_dir, - os.path.join(path, 'cover.jpg'), id_)) + os.path.join(path, COVER_FILE_NAME), id_)) def is_ebook_file(self, filename): ext = os.path.splitext(filename)[1] @@ -187,7 +187,7 @@ class CheckLibrary: filenames = frozenset(f for f in os.listdir(os.path.join(lib, db_path)) if not self.ignore_name(f) and ( os.path.splitext(f)[1] not in self.ignore_ext or - f == 'cover.jpg')) + f == COVER_FILE_NAME)) book_id = int(book_id) formats = frozenset(filter(self.is_ebook_file, filenames)) book_formats = frozenset(x[0]+'.'+x[1].lower() for x in @@ -252,10 +252,10 @@ class CheckLibrary: # check cached has_cover if self.db.has_cover(book_id): - if 'cover.jpg' not in filenames: + if COVER_FILE_NAME not in filenames: self.missing_covers.append((title_dir, - os.path.join(db_path, 'cover.jpg'), book_id)) + os.path.join(db_path, COVER_FILE_NAME), book_id)) else: - if 'cover.jpg' in filenames: + if COVER_FILE_NAME in filenames: self.extra_covers.append((title_dir, - os.path.join(db_path, 'cover.jpg'), book_id)) + os.path.join(db_path, COVER_FILE_NAME), book_id)) From e1b00c6af5a22333f0d44f9a52be2cc19a5a4eec Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Tue, 18 Apr 2023 21:00:43 +0100 Subject: [PATCH 0490/2055] Add saved sorts to the "Sort by" action top-level menu --- src/calibre/gui2/actions/sort.py | 12 ++++++++++++ src/calibre/gui2/library/models.py | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/src/calibre/gui2/actions/sort.py b/src/calibre/gui2/actions/sort.py index b587b7cb27..dd5de380cd 100644 --- a/src/calibre/gui2/actions/sort.py +++ b/src/calibre/gui2/actions/sort.py @@ -5,6 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' from contextlib import suppress +from functools import partial from qt.core import ( QAbstractItemView, QAction, QDialog, QDialogButtonBox, QIcon, QListWidget, QListWidgetItem, QSize, Qt, QToolButton, QVBoxLayout, pyqtSignal, @@ -82,6 +83,14 @@ class SortByAction(InterfaceAction): lv = self.gui.library_view m = lv.model() db = m.db + + # Add saved sorts to the menu + saved_sorts = db.new_api.pref('saved_multisort_specs', {}) + if saved_sorts: + for name in sorted(saved_sorts.keys(), key=primary_sort_key): + menu.addAction(name, partial(self.named_sort_selected, saved_sorts[name])) + menu.addSeparator() + try: sort_col, order = m.sorted_on except TypeError: @@ -144,6 +153,9 @@ class SortByAction(InterfaceAction): hidden.append(i.data(Qt.ItemDataRole.UserRole)) db.new_api.set_pref(SORT_HIDDEN_PREF, tuple(hidden)) + def named_sort_selected(self, sort_spec): + self.gui.library_view.multisort(sort_spec) + def choose_multisort(self): from calibre.gui2.dialogs.multisort import ChooseMultiSort d = ChooseMultiSort(self.gui.current_db, parent=self.gui, is_device_connected=self.gui.device_connected) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index a01999dab3..2906a45369 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -583,6 +583,11 @@ class BooksModel(QAbstractTableModel): # {{{ if not self.db: return self.db.multisort(self.sort_history[:tweaks['maximum_resort_levels']]) + if self.sort_history: + # There are ways to get here that don't set sorted_on, e.g., the + # sort_by action. I don't think sort_history can be empty, but it + # doesn't hurt to check + self.sorted_on = (self.sort_history[0]) if reset: self.beginResetModel(), self.endResetModel() From abde717e29d7469f53837bde19d7f82af3e63316 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 Apr 2023 12:09:12 +0530 Subject: [PATCH 0491/2055] Save to disk now exports data files associated with the book --- src/calibre/db/backend.py | 50 +++++++------ src/calibre/db/cache.py | 12 ++++ src/calibre/db/tests/filesystem.py | 2 + src/calibre/gui2/preferences/save_template.py | 4 +- src/calibre/gui2/preferences/saving.py | 2 +- src/calibre/gui2/preferences/saving.ui | 71 ++++++++++--------- src/calibre/gui2/save.py | 37 +++++++--- src/calibre/library/save_to_disk.py | 2 + 8 files changed, 116 insertions(+), 64 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 96557722ef..56ef791c74 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1889,7 +1889,7 @@ class DB: os.makedirs(tpath) update_paths_in_db() - def iter_extra_files(self, book_id, book_path, formats_field, yield_paths=False): + def iter_extra_files(self, book_id, book_path, formats_field, yield_paths=False, pattern=''): known_files = {COVER_FILE_NAME, METADATA_FILE_NAME} for fmt in formats_field.for_book(book_id, default_value=()): fname = formats_field.format_fname(book_id, fmt) @@ -1897,25 +1897,35 @@ class DB: if fpath: known_files.add(os.path.basename(fpath)) full_book_path = os.path.abspath(os.path.join(self.library_path, book_path)) - for dirpath, dirnames, filenames in os.walk(full_book_path): - for fname in filenames: - path = os.path.join(dirpath, fname) - if os.access(path, os.R_OK): - relpath = os.path.relpath(path, full_book_path) - relpath = relpath.replace(os.sep, '/') - if relpath not in known_files: - mtime = os.path.getmtime(path) - if yield_paths: - yield relpath, path, mtime - else: - try: - src = open(path, 'rb') - except OSError: - if iswindows: - time.sleep(1) - src = open(path, 'rb') - with src: - yield relpath, src, mtime + if pattern: + from pathlib import Path + def iterator(): + p = Path(full_book_path) + for x in p.glob(pattern): + yield str(x) + else: + def iterator(): + for dirpath, dirnames, filenames in os.walk(full_book_path): + for fname in filenames: + path = os.path.join(dirpath, fname) + yield path + for path in iterator(): + if os.access(path, os.R_OK): + relpath = os.path.relpath(path, full_book_path) + relpath = relpath.replace(os.sep, '/') + if relpath not in known_files: + mtime = os.path.getmtime(path) + if yield_paths: + yield relpath, path, mtime + else: + try: + src = open(path, 'rb') + except OSError: + if iswindows: + time.sleep(1) + src = open(path, 'rb') + with src: + yield relpath, src, mtime def add_extra_file(self, relpath, stream, book_path): dest = os.path.abspath(os.path.join(self.library_path, book_path, relpath)) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index f7776cd830..02ccb7a61d 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -3051,10 +3051,22 @@ class Cache: @write_api def add_extra_files(self, book_id, map_of_relpath_to_stream_or_path): + ' Add extra data files ' path = self._field_for('path', book_id).replace('/', os.sep) for relpath, stream_or_path in map_of_relpath_to_stream_or_path.items(): self.backend.add_extra_file(relpath, stream_or_path, path) + @read_api + def list_extra_files_matching(self, book_id, pattern=''): + ' List extra data files matching the specified patter. Empty pattern matches all. Recursive globbing with ** is supported. ' + path = self._field_for('path', book_id).replace('/', os.sep) + ans = {} + if path: + for (relpath, path, mtime) in self.backend.iter_extra_files( + book_id, path, self.fields['formats'], yield_paths=True, pattern=pattern): + ans[relpath] = path + return ans + def import_library(library_key, importer, library_path, progress=None, abort=None): from calibre.db.backend import DB diff --git a/src/calibre/db/tests/filesystem.py b/src/calibre/db/tests/filesystem.py index 545c068e26..724994c053 100644 --- a/src/calibre/db/tests/filesystem.py +++ b/src/calibre/db/tests/filesystem.py @@ -221,6 +221,8 @@ class FilesystemTest(BaseTest): os.mkdir(os.path.join(bookdir, 'sub')) with open(os.path.join(bookdir, 'sub', 'recurse'), 'w') as f: f.write('recurse') + self.assertEqual(set(cache.list_extra_files_matching(1, 'sub/**/*')), {'sub/recurse'}) + self.assertEqual(set(cache.list_extra_files_matching(1, '')), {'exf', 'sub/recurse'}) for part_size in (1 << 30, 100, 1): with TemporaryDirectory('export_lib') as tdir, TemporaryDirectory('import_lib') as idir: exporter = Exporter(tdir, part_size=part_size) diff --git a/src/calibre/gui2/preferences/save_template.py b/src/calibre/gui2/preferences/save_template.py index 82cf951261..47d4510d53 100644 --- a/src/calibre/gui2/preferences/save_template.py +++ b/src/calibre/gui2/preferences/save_template.py @@ -18,8 +18,8 @@ class SaveTemplate(QWidget, Ui_Form): changed_signal = pyqtSignal() - def __init__(self, *args): - QWidget.__init__(self, *args) + def __init__(self, parent=None): + QWidget.__init__(self, parent) Ui_Form.__init__(self) self.setupUi(self) self.orig_help_text = self.help_label.text() diff --git a/src/calibre/gui2/preferences/saving.py b/src/calibre/gui2/preferences/saving.py index 647787ddeb..7325dafd68 100644 --- a/src/calibre/gui2/preferences/saving.py +++ b/src/calibre/gui2/preferences/saving.py @@ -23,7 +23,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r = self.register for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf', - 'replace_whitespace', 'to_lowercase', 'formats', 'timefmt'): + 'replace_whitespace', 'to_lowercase', 'formats', 'timefmt', 'save_extra_files'): r(x, self.proxy) r('show_files_after_save', gprefs) diff --git a/src/calibre/gui2/preferences/saving.ui b/src/calibre/gui2/preferences/saving.ui index a1e59629d9..4668072a4a 100644 --- a/src/calibre/gui2/preferences/saving.ui +++ b/src/calibre/gui2/preferences/saving.ui @@ -14,20 +14,40 @@ Form + + + + + + + + + + Save metadata in a separate &OPF file + + + + + + + Save &data files as well + + + - Here you can control how calibre will save your books when you click the "Save to disk" button: + Here you can control how calibre will save your books when you click the "Save to disk" button: true - - + + - Save &cover separately + Change paths to &lowercase @@ -38,19 +58,8 @@ - - - - Update &metadata in saved copies - - - - - - - Change paths to &lowercase - - + + @@ -62,8 +71,12 @@ - - + + + + Update &metadata in saved copies + + @@ -75,11 +88,12 @@ - - - - - + + + + Save &cover separately + + @@ -88,14 +102,7 @@ - - - - Save metadata in a separate &OPF file - - - - + &Show files in the file browser after saving to disk diff --git a/src/calibre/gui2/save.py b/src/calibre/gui2/save.py index 9c49a897df..9976a42a5e 100644 --- a/src/calibre/gui2/save.py +++ b/src/calibre/gui2/save.py @@ -4,23 +4,29 @@ __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' -import traceback, errno, os, time, shutil -from collections import namedtuple, defaultdict - +import errno +import os +import shutil +import time +import traceback +from collections import defaultdict, namedtuple from qt.core import QObject, Qt, pyqtSignal -from calibre import prints, force_unicode +from calibre import force_unicode, prints from calibre.constants import DEBUG from calibre.customize.ui import can_set_metadata from calibre.db.errors import NoSuchFormat from calibre.ebooks.metadata import authors_to_string from calibre.ebooks.metadata.opf2 import metadata_to_opf -from calibre.ptempfile import PersistentTemporaryDirectory, SpooledTemporaryFile -from calibre.gui2 import error_dialog, warning_dialog, gprefs, open_local_file +from calibre.gui2 import error_dialog, gprefs, open_local_file, warning_dialog from calibre.gui2.dialogs.progress import ProgressDialog +from calibre.library.save_to_disk import ( + find_plugboard, get_path_components, plugboard_save_to_disk_value, sanitize_args, +) +from calibre.ptempfile import PersistentTemporaryDirectory, SpooledTemporaryFile +from calibre.utils.filenames import make_long_path_useable from calibre.utils.formatter_functions import load_user_template_functions -from calibre.utils.ipc.pool import Pool, Failure -from calibre.library.save_to_disk import sanitize_args, get_path_components, find_plugboard, plugboard_save_to_disk_value +from calibre.utils.ipc.pool import Failure, Pool from polyglot.builtins import iteritems, itervalues from polyglot.queue import Empty @@ -205,7 +211,10 @@ class Saver(QObject): self.errors[book_id].append(('critical', _('Requested formats not available'))) return - if not fmts and not self.opts.write_opf and not self.opts.save_cover: + extra_files = {} + if self.opts.save_extra_files: + extra_files = self.db.new_api.list_extra_files_matching(int(book_id), 'data/**/*') + if not fmts and not self.opts.write_opf and not self.opts.save_cover and not extra_files: return # On windows python incorrectly raises an access denied exception @@ -252,6 +261,16 @@ class Saver(QObject): mi.cover, mi.cover_data = None, (None, None) if self.opts.update_metadata: d['fmts'] = [] + if extra_files: + for relpath, src_path in extra_files.items(): + src_path = make_long_path_useable(src_path) + if os.access(src_path, os.R_OK): + dest = make_long_path_useable(os.path.abspath(os.path.join(base_dir, relpath))) + try: + shutil.copy2(src_path, dest) + except FileNotFoundError: + os.makedirs(os.path.dirname(dest), exist_ok=True) + shutil.copy2(src_path, dest) for fmt in fmts: try: fmtpath = self.write_fmt(book_id, fmt, base_path) diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py index 28bd91440c..ccf88d5e8b 100644 --- a/src/calibre/library/save_to_disk.py +++ b/src/calibre/library/save_to_disk.py @@ -125,6 +125,8 @@ def config(defaults=None): x('single_dir', default=False, help=_('Save into a single folder, ignoring the template' ' folder structure')) + x('save_extra_files', default=True, help=_( + 'Save any data files associated with the book when saving the book')) return c From 4982620da46c80ddf05610da01e7880accddae68 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 Apr 2023 12:12:43 +0530 Subject: [PATCH 0492/2055] ... --- manual/gui.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/gui.rst b/manual/gui.rst index 0d6960e664..1ef99c8fd9 100644 --- a/manual/gui.rst +++ b/manual/gui.rst @@ -78,7 +78,7 @@ To add an additional format for an existing book you can do any of three things: sources. Nor are they indexed by the Full text search engine in calibre. To view them select the book and press the :kbd:`O` key which will open up the book folder in your file manager, from where the extra files can be viewed - using any program you like. They are most useful to store any ancilliary + using any program you like. They are most useful to store any auxiliary data associated with a book such as supplementary reading material, digital resources, etc. From b45ca52f0f99aaa6da040e594ce7ba3cc7145e02 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 Apr 2023 17:19:33 +0530 Subject: [PATCH 0493/2055] calibredb export: Support exporting extra data files --- src/calibre/db/backend.py | 11 ++++++++++- src/calibre/db/cache.py | 16 +++++++++++----- src/calibre/db/cli/cmd_export.py | 28 +++++++++++++++++++++++++--- src/calibre/library/save_to_disk.py | 22 +++++++++++++--------- 4 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 56ef791c74..535027cc8f 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -42,7 +42,7 @@ from calibre.utils.date import EPOCH, parse_date, utcfromtimestamp, utcnow from calibre.utils.filenames import ( WindowsAtomicFolderMove, ascii_filename, atomic_rename, copyfile_using_links, copytree_using_links, hardlink_file, is_case_sensitive, is_fat_filesystem, - remove_dir_if_empty, samefile, + make_long_path_useable, remove_dir_if_empty, samefile, ) from calibre.utils.formatter_functions import ( compile_user_template_functions, formatter_functions, load_user_template_functions, @@ -1889,6 +1889,15 @@ class DB: os.makedirs(tpath) update_paths_in_db() + def copy_extra_file_to(self, book_id, book_path, relpath, stream_or_path): + full_book_path = os.path.abspath(os.path.join(self.library_path, book_path)) + src_path = make_long_path_useable(os.path.join(full_book_path, relpath)) + if isinstance(stream_or_path, str): + shutil.copy2(src_path, make_long_path_useable(stream_or_path)) + else: + with open(src_path, 'rb') as src: + shutil.copyfileobj(src, stream_or_path) + def iter_extra_files(self, book_id, book_path, formats_field, yield_paths=False, pattern=''): known_files = {COVER_FILE_NAME, METADATA_FILE_NAME} for fmt in formats_field.for_book(book_id, default_value=()): diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 02ccb7a61d..6081fcd5ca 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -3058,15 +3058,21 @@ class Cache: @read_api def list_extra_files_matching(self, book_id, pattern=''): - ' List extra data files matching the specified patter. Empty pattern matches all. Recursive globbing with ** is supported. ' - path = self._field_for('path', book_id).replace('/', os.sep) + ' List extra data files matching the specified pattern. Empty pattern matches all. Recursive globbing with ** is supported. ' + path = self._field_for('path', book_id) ans = {} if path: - for (relpath, path, mtime) in self.backend.iter_extra_files( - book_id, path, self.fields['formats'], yield_paths=True, pattern=pattern): - ans[relpath] = path + book_path = path.replace('/', os.sep) + for (relpath, file_path, mtime) in self.backend.iter_extra_files( + book_id, book_path, self.fields['formats'], yield_paths=True, pattern=pattern): + ans[relpath] = file_path return ans + @read_api + def copy_extra_file_to(self, book_id, relpath, stream_or_path): + path = self._field_for('path', book_id).replace('/', os.sep) + self.backend.copy_extra_file_to(book_id, path, relpath, stream_or_path) + def import_library(library_key, importer, library_path, progress=None, abort=None): from calibre.db.backend import DB diff --git a/src/calibre/db/cli/cmd_export.py b/src/calibre/db/cli/cmd_export.py index 38eeaf8f94..20032dd5e4 100644 --- a/src/calibre/db/cli/cmd_export.py +++ b/src/calibre/db/cli/cmd_export.py @@ -21,9 +21,13 @@ def implementation(db, notify_changes, action, *args): return db.all_book_ids() if action == 'setup': book_id, formats = args + if not db.has_id(book_id): + raise KeyError(f'No book with id {book_id} present') mi = db.get_metadata(book_id) plugboards = db.pref('plugboards', {}) formats = get_formats(db.formats(book_id), formats) + extra_files_for_export = tuple(db.list_extra_files_matching(book_id, 'data/**/*')) + plugboards['extra_files_for_export'] = extra_files_for_export return mi, plugboards, formats, db.library_id, db.pref( 'user_template_functions', [] ) @@ -34,6 +38,14 @@ def implementation(db, notify_changes, action, *args): if is_remote: return db.format(book_id, fmt) db.copy_format_to(book_id, fmt, dest) + if action == 'extra_file': + book_id, relpath, dest = args + if is_remote: + from io import BytesIO + output = BytesIO() + db.copy_extra_file_to(book_id, relpath, output) + return output.getvalue() + db.copy_extra_file_to(book_id, relpath, dest) def option_parser(get_parser, args): @@ -44,7 +56,8 @@ def option_parser(get_parser, args): Export the books specified by ids (a comma separated list) to the filesystem. The export operation saves all formats of the book, its cover and metadata (in -an opf file). You can get id numbers from the search command. +an OPF file). Any extra data files associated with the book are also saved. +You can get id numbers from the search command. ''' ) ) @@ -74,7 +87,7 @@ an opf file). You can get id numbers from the search command. help=_('Report progress') ) c = config() - for pref in ['asciiize', 'update_metadata', 'write_opf', 'save_cover']: + for pref in ['asciiize', 'update_metadata', 'write_opf', 'save_cover', 'save_extra_files']: opt = c.get_option(pref) switch = '--dont-' + pref.replace('_', '-') parser.add_option( @@ -118,15 +131,24 @@ class DBProxy: with open(path, 'wb') as f: f.write(fdata) + def copy_extra_file_to(self, book_id, relpath, path): + fdata = self.dbctx.run('export', 'extra_file', book_id, relpath, path) + if self.dbctx.is_remote: + if fdata is None: + raise FileNotFoundError(relpath) + with open(path, 'wb') as f: + f.write(fdata) + def export(opts, dbctx, book_id, dest, dbproxy, length, first): mi, plugboards, formats, library_id, template_funcs = dbctx.run( 'export', 'setup', book_id, opts.formats ) + extra_files = plugboards.pop('extra_files_for_export', ()) if dbctx.is_remote and first: load_user_template_functions(library_id, template_funcs) return do_save_book_to_disk( - dbproxy, book_id, mi, plugboards, formats, dest, opts, length + dbproxy, book_id, mi, plugboards, formats, dest, opts, length, extra_files ) diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py index ccf88d5e8b..8c6c67cf14 100644 --- a/src/calibre/library/save_to_disk.py +++ b/src/calibre/library/save_to_disk.py @@ -5,7 +5,6 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import errno import os import re import traceback @@ -17,7 +16,9 @@ from calibre.db.lazy import FormatsList from calibre.ebooks.metadata import fmt_sidx, title_sort from calibre.utils.config import Config, StringConfig, tweaks from calibre.utils.date import as_local_time, is_date_undefined -from calibre.utils.filenames import ascii_filename, shorten_components_to +from calibre.utils.filenames import ( + ascii_filename, make_long_path_useable, shorten_components_to, +) from calibre.utils.formatter import TemplateFormatter from calibre.utils.localization import _ @@ -322,7 +323,7 @@ def update_metadata(mi, fmt, stream, plugboards, cdata, error_report=None, plugb def do_save_book_to_disk(db, book_id, mi, plugboards, - formats, root, opts, length): + formats, root, opts, length, extra_files=()): originals = mi.cover, mi.pubdate, mi.timestamp formats_written = False try: @@ -335,12 +336,7 @@ def do_save_book_to_disk(db, book_id, mi, plugboards, base_path = os.path.join(root, *components) base_name = os.path.basename(base_path) dirpath = os.path.dirname(base_path) - try: - os.makedirs(dirpath) - except OSError as err: - if err.errno != errno.EEXIST: - raise - + os.makedirs(dirpath, exist_ok=True) cdata = None if opts.save_cover: cdata = db.cover(book_id) @@ -357,6 +353,14 @@ def do_save_book_to_disk(db, book_id, mi, plugboards, finally: mi.cover, mi.pubdate, mi.timestamp = originals + if extra_files and opts.save_extra_files: + for relpath in extra_files: + data_dest_path = os.path.abspath(os.path.join(dirpath, relpath)) + try: + db.copy_extra_file_to(book_id, relpath, data_dest_path) + except FileNotFoundError: + os.makedirs(make_long_path_useable(os.path.dirname(data_dest_path))) + db.copy_extra_file_to(book_id, relpath, data_dest_path) if not formats: return not formats_written, book_id, mi.title From 2f6cd98741ff97b34513397f909c5b33976c9eeb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 Apr 2023 17:39:47 +0530 Subject: [PATCH 0494/2055] calibredb add_format: Add support for adding extra data files --- src/calibre/db/backend.py | 5 ++++- src/calibre/db/cache.py | 6 ++++-- src/calibre/db/cli/cmd_add_format.py | 24 +++++++++++++++++++++--- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 535027cc8f..59c9b22294 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1936,8 +1936,10 @@ class DB: with src: yield relpath, src, mtime - def add_extra_file(self, relpath, stream, book_path): + def add_extra_file(self, relpath, stream, book_path, replace=True): dest = os.path.abspath(os.path.join(self.library_path, book_path, relpath)) + if not replace and os.path.exists(dest): + return False if isinstance(stream, str): try: shutil.copy2(stream, dest) @@ -1952,6 +1954,7 @@ class DB: d = open(dest, 'wb') with d: shutil.copyfileobj(stream, d) + return True def write_backup(self, path, raw): path = os.path.abspath(os.path.join(self.library_path, path, METADATA_FILE_NAME)) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 6081fcd5ca..3bc6eaa47d 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -3050,11 +3050,13 @@ class Cache: self.backend.reindex_annotations() @write_api - def add_extra_files(self, book_id, map_of_relpath_to_stream_or_path): + def add_extra_files(self, book_id, map_of_relpath_to_stream_or_path, replace=True): ' Add extra data files ' path = self._field_for('path', book_id).replace('/', os.sep) + added = {} for relpath, stream_or_path in map_of_relpath_to_stream_or_path.items(): - self.backend.add_extra_file(relpath, stream_or_path, path) + added[relpath] = self.backend.add_extra_file(relpath, stream_or_path, path, replace) + return added @read_api def list_extra_files_matching(self, book_id, pattern=''): diff --git a/src/calibre/db/cli/cmd_add_format.py b/src/calibre/db/cli/cmd_add_format.py index f180846a0a..ccc1456734 100644 --- a/src/calibre/db/cli/cmd_add_format.py +++ b/src/calibre/db/cli/cmd_add_format.py @@ -15,8 +15,14 @@ def implementation(db, notify_changes, book_id, data, fmt, replace): is_remote = notify_changes is not None if is_remote: data = BytesIO(data[1]) - added = db.add_format(book_id, fmt, data, replace=replace) - if is_remote and added: + relpath = '' + if fmt.startswith('.EXTRA_DATA_FILE:'): + relpath = fmt[len('.EXTRA_DATA_FILE:'):] + if relpath: + added = db.add_extra_files(book_id, {relpath: data}, replace=replace)[relpath] + else: + added = db.add_format(book_id, fmt, data, replace=replace) + if is_remote and added and not relpath: notify_changes(formats_added({book_id: (fmt,)})) return added @@ -40,6 +46,13 @@ it is replaced, unless the do not replace option is specified.\ action='store_false', help=_('Do not replace the format if it already exists') ) + parser.add_option( + '--as-extra-data-file', + default=False, + action='store_true', + help=_('Add the file as an extra data file to the book, not an ebook format') + ) + return parser @@ -48,9 +61,14 @@ def main(opts, args, dbctx): raise SystemExit(_('You must specify an id and an e-book file')) id, path, fmt = int(args[0]), args[1], os.path.splitext(args[1])[-1] + if opts.as_extra_data_file: + fmt = '.EXTRA_DATA_FILE:' + 'data/' + os.path.basename(args[1]) + else: + fmt = fmt[1:].upper() if not fmt: raise SystemExit(_('e-book file must have an extension')) - fmt = fmt[1:].upper() if not dbctx.run('add_format', id, dbctx.path(path), fmt, opts.replace): + if opts.as_extra_data_file: + raise SystemExit(f'An extra data file with the filename {os.path.basename(args[1])} already exists') raise SystemExit(_('A %(fmt)s file already exists for book: %(id)d, not replacing')%dict(fmt=fmt, id=id)) return 0 From 6cf5501674b671f77b165614b53f346aab719cd7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 Apr 2023 17:54:39 +0530 Subject: [PATCH 0495/2055] Fix some warnings from the font subset process not making it into the final report --- src/calibre/ebooks/oeb/polish/subset.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/calibre/ebooks/oeb/polish/subset.py b/src/calibre/ebooks/oeb/polish/subset.py index 1d59cc3ef7..a49bf7f78d 100644 --- a/src/calibre/ebooks/oeb/polish/subset.py +++ b/src/calibre/ebooks/oeb/polish/subset.py @@ -47,7 +47,7 @@ def subset_all_fonts(container, font_stats, report): chars = font_stats.get(name, set()) with container.open(name, 'rb') as f: f.seek(0, os.SEEK_END) - total_old += f.tell() + font_size = f.tell() if not chars: remove.add(name) report(_('Removed unused font: %s')%name) @@ -57,23 +57,24 @@ def subset_all_fonts(container, font_stats, report): try: font_name = get_font_names(raw)[-1] except Exception as e: - container.log.warning( + report( 'Corrupted font: %s, ignoring. Error: %s'%( name, as_unicode(e))) continue warnings = [] - container.log('Subsetting font: %s'%(font_name or name)) + report('Subsetting font: %s'%(font_name or name)) try: nraw, old_sizes, new_sizes = subset(raw, chars, warnings=warnings) except UnsupportedFont as e: - container.log.warning( - 'Unsupported font: %s, ignoring. Error: %s'%( + report( + 'Unsupported font: %s, ignoring. Error: %s'%( name, as_unicode(e))) continue + total_old += font_size for w in warnings: - container.log.warn(w) + report(w) olen = sum(itervalues(old_sizes)) nlen = sum(itervalues(new_sizes)) total_new += len(nraw) From b41e2be8e3f6fd423b908ffeb4e4a60f923fe16e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 Apr 2023 18:02:27 +0530 Subject: [PATCH 0496/2055] Kobo driver: Add support for the new Kobo Elipsa 2E Fixes #2016070 [Support kobo elipsa 2e](https://bugs.launchpad.net/calibre/+bug/2016070) --- src/calibre/devices/kobo/driver.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 0be7234504..1d5f614cf4 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -1451,6 +1451,7 @@ class KOBOTOUCH(KOBO): CLARA_HD_PRODUCT_ID = [0x4228] CLARA_2E_PRODUCT_ID = [0x4235] ELIPSA_PRODUCT_ID = [0x4233] + ELIPSA_2E_PRODUCT_ID = [0x4236] FORMA_PRODUCT_ID = [0x4229] GLO_PRODUCT_ID = [0x4173] GLO_HD_PRODUCT_ID = [0x4223] @@ -1467,7 +1468,7 @@ class KOBOTOUCH(KOBO): MINI_PRODUCT_ID + TOUCH_PRODUCT_ID + TOUCH2_PRODUCT_ID + \ AURA_ONE_PRODUCT_ID + CLARA_HD_PRODUCT_ID + FORMA_PRODUCT_ID + LIBRA_H2O_PRODUCT_ID + \ NIA_PRODUCT_ID + ELIPSA_PRODUCT_ID + \ - SAGE_PRODUCT_ID + LIBRA2_PRODUCT_ID + CLARA_2E_PRODUCT_ID + SAGE_PRODUCT_ID + LIBRA2_PRODUCT_ID + CLARA_2E_PRODUCT_ID + ELIPSA_2E_PRODUCT_ID BCD = [0x0110, 0x0326, 0x401, 0x409] @@ -3561,6 +3562,9 @@ class KOBOTOUCH(KOBO): def isClara2E(self): return self.detected_device.idProduct in self.CLARA_2E_PRODUCT_ID + def isElipsa2E(self): + return self.detected_device.idProduct in self.ELIPSA_2E_PRODUCT_ID + def isElipsa(self): return self.detected_device.idProduct in self.ELIPSA_PRODUCT_ID @@ -3613,6 +3617,8 @@ class KOBOTOUCH(KOBO): _cover_file_endings = self.GLO_HD_COVER_FILE_ENDINGS elif self.isElipsa(): _cover_file_endings = self.AURA_ONE_COVER_FILE_ENDINGS + elif self.isElipsa2E(): + _cover_file_endings = self.GLO_HD_COVER_FILE_ENDINGS elif self.isForma(): _cover_file_endings = self.FORMA_COVER_FILE_ENDINGS elif self.isGlo(): @@ -3661,6 +3667,8 @@ class KOBOTOUCH(KOBO): device_name = 'Kobo Clara 2E' elif self.isElipsa(): device_name = 'Kobo Elipsa' + elif self.isElipsa2E(): + device_name = 'Kobo Elipsa 2E' elif self.isForma(): device_name = 'Kobo Forma' elif self.isGlo(): From cc89ccb265593d234669bab4d94a0efc4224a273 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 Apr 2023 18:17:50 +0530 Subject: [PATCH 0497/2055] E-book viewer: Show an error if the user tries to search for only punctuation or spaces in the search modes that ignore these. Fixes #2015795 [Searching for punctuation freezes the e-book viewer](https://bugs.launchpad.net/calibre/+bug/2015795) --- src/calibre/gui2/viewer/search.py | 8 ++++++++ src/calibre/gui2/viewer/ui.py | 3 +++ 2 files changed, 11 insertions(+) diff --git a/src/calibre/gui2/viewer/search.py b/src/calibre/gui2/viewer/search.py index ad43b3ec8e..30002aaead 100644 --- a/src/calibre/gui2/viewer/search.py +++ b/src/calibre/gui2/viewer/search.py @@ -145,6 +145,14 @@ class Search: self._nsd = word_pats, full_pat return self._nsd + @property + def is_empty(self): + if not self.text: + return True + if self.mode in ('normal', 'word') and not regex.sub(r'[\s\p{P}]+', '', self.text): + return True + return False + def __str__(self): from collections import namedtuple s = ('text', 'mode', 'case_sensitive', 'backwards') diff --git a/src/calibre/gui2/viewer/ui.py b/src/calibre/gui2/viewer/ui.py index b9e6813d04..bb6084b211 100644 --- a/src/calibre/gui2/viewer/ui.py +++ b/src/calibre/gui2/viewer/ui.py @@ -336,6 +336,9 @@ class EbookViewer(MainWindow): def start_search(self, search_query): name = self.web_view.current_content_file if name: + if search_query.is_empty and search_query.text: + return error_dialog(self, _('Empty search expression'), _( + 'Cannot search for {!r} as it contains only punctuation and spaces.').format(search_query.text), show=True) self.web_view.get_current_cfi(self.search_widget.set_anchor_cfi) self.search_widget.start_search(search_query, name) self.web_view.setFocus(Qt.FocusReason.OtherFocusReason) From b63ca391430b85475c680da7d868a57b6feb85f7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 Apr 2023 22:54:34 +0530 Subject: [PATCH 0498/2055] Nicer error when failing to decode CFI --- src/pyj/read_book/cfi.pyj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyj/read_book/cfi.pyj b/src/pyj/read_book/cfi.pyj index 537a20a6a2..41f4ec6ccd 100644 --- a/src/pyj/read_book/cfi.pyj +++ b/src/pyj/read_book/cfi.pyj @@ -322,6 +322,7 @@ def decode(cfi, doc): error = None node = doc + orig_cfi = cfi while cfi.length > 0 and not error: r = cfi.match(simple_node_regex) if r: # Path step @@ -335,7 +336,7 @@ def decode(cfi, doc): cfi = cfi.substr(r[0].length) else: if target: - error = "No matching child found for CFI: " + cfi + error = f"No matching child found for CFI: {orig_cfi} leftover: {cfi}" break else: cfi = cfi.substr(r[0].length) From d153d026e9460d56c1a66ec6532be23414919f88 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 Apr 2023 23:02:17 +0530 Subject: [PATCH 0499/2055] When creating and using id assertions in CFI check that the ID is unique --- src/pyj/read_book/cfi.pyj | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/pyj/read_book/cfi.pyj b/src/pyj/read_book/cfi.pyj index 41f4ec6ccd..2253eca0ee 100644 --- a/src/pyj/read_book/cfi.pyj +++ b/src/pyj/read_book/cfi.pyj @@ -238,7 +238,14 @@ def encode(doc, node, offset, tail): index -= 1 # Add id assertions for robustness where possible id = node.id - idspec = ('[' + escape_for_cfi(id) + ']') if id else '' + idspec = '' + if id: + try: + multiples = document.querySelectorAll('#' + id) + except: + multiples = None + if multiples and multiples.length < 2: + idspec = ('[' + escape_for_cfi(id) + ']') cfi = '/' + index + idspec + cfi node = p @@ -268,7 +275,12 @@ def node_for_path_step(parent, target, assertion): if assertion: q = document.getElementById(assertion) if q: - return q + try: + multiples = document.querySelectorAll('#' + assertion) + except: + multiples = None + if multiples and multiples.length < 2: + return q is_element = target % 2 == 0 target //= 2 if is_element and target > 0: From dbd0d591cf7982baa566a750009c9750f9e317f8 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 19 Apr 2023 21:43:25 +0100 Subject: [PATCH 0500/2055] In the Sort by tool, use a combo box when saving to make it easier to replace an existing sort. --- src/calibre/gui2/dialogs/multisort.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/dialogs/multisort.py b/src/calibre/gui2/dialogs/multisort.py index 0cae89eb75..aa938fbd05 100644 --- a/src/calibre/gui2/dialogs/multisort.py +++ b/src/calibre/gui2/dialogs/multisort.py @@ -143,9 +143,13 @@ class ChooseMultiSort(Dialog): spec = self.current_sort_spec if not spec: return self.no_column_selected_error() - name, ok = QInputDialog.getText(self, _('Choose name'), - _('Choose a name for these settings')) - if ok: + d = QInputDialog(self) + d.setComboBoxEditable(True) + d.setComboBoxItems(self.saved_specs.keys()) + d.setWindowTitle(_('Choose name')) + d.setLabelText(_('Choose a name for these settings')) + if d.exec(): + name = d.textValue() q = self.saved_specs q[name] = spec self.saved_specs = q From 1ef173bca59de0cb6f0baf1a239422d6c5bf57b2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 20 Apr 2023 07:18:48 +0530 Subject: [PATCH 0501/2055] DRYer --- src/calibre/utils/rapydscript.py | 2 +- src/pyj/read_book/cfi.pyj | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/calibre/utils/rapydscript.py b/src/calibre/utils/rapydscript.py index 01fdce65a9..de79f40174 100644 --- a/src/calibre/utils/rapydscript.py +++ b/src/calibre/utils/rapydscript.py @@ -350,8 +350,8 @@ def run_rapydscript_tests(): setup_fake_protocol, setup_profile ) must_use_qt() - setup_default_profile() setup_fake_protocol() + setup_default_profile() base = base_dir() rapydscript_dir = os.path.join(base, 'src', 'pyj') diff --git a/src/pyj/read_book/cfi.pyj b/src/pyj/read_book/cfi.pyj index 2253eca0ee..424cde5567 100644 --- a/src/pyj/read_book/cfi.pyj +++ b/src/pyj/read_book/cfi.pyj @@ -52,6 +52,16 @@ def get_current_time(target): # {{{ return fstr(target.currentTime or 0) # }}} +def id_is_unique(idval): # {{{ + try: + multiples = document.querySelectorAll('[id=' + window.CSS.escape(idval) + ']') + except: + return False + if multiples and multiples.length < 2: + return True + return False +# }}} + # Convert point to character offset {{{ def range_has_point(range_, x, y): rects = range_.getClientRects() @@ -239,13 +249,8 @@ def encode(doc, node, offset, tail): # Add id assertions for robustness where possible id = node.id idspec = '' - if id: - try: - multiples = document.querySelectorAll('#' + id) - except: - multiples = None - if multiples and multiples.length < 2: - idspec = ('[' + escape_for_cfi(id) + ']') + if id and id_is_unique(id): + idspec = ('[' + escape_for_cfi(id) + ']') cfi = '/' + index + idspec + cfi node = p @@ -274,13 +279,8 @@ def node_at_index(nodes, target, index, iter_text_nodes): def node_for_path_step(parent, target, assertion): if assertion: q = document.getElementById(assertion) - if q: - try: - multiples = document.querySelectorAll('#' + assertion) - except: - multiples = None - if multiples and multiples.length < 2: - return q + if q and id_is_unique(assertion): + return q is_element = target % 2 == 0 target //= 2 if is_element and target > 0: From 6d6173f1ffa0167ac95e45a4417f8c04306946a9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 20 Apr 2023 10:13:20 +0530 Subject: [PATCH 0502/2055] Remove unused code dis was not being defined so we were setting max width/height to "undefinedpx" --- src/pyj/read_book/paged_mode.pyj | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/pyj/read_book/paged_mode.pyj b/src/pyj/read_book/paged_mode.pyj index 707355d273..ee8b4dfc45 100644 --- a/src/pyj/read_book/paged_mode.pyj +++ b/src/pyj/read_book/paged_mode.pyj @@ -328,24 +328,14 @@ def layout(is_single_page, on_resize): number_of_cols = 1 document.body.style.columnWidth = f'100vw' - # Some browser engine, WebKit at least, adjust column sizes to please - # themselves, unless the container size is an exact multiple, so we check - # for that and manually set the container sizes. def check_column_sizes(): nonlocal number_of_cols ncols = number_of_cols = (scroll_viewport.paged_content_inline_size() + gap) / col_and_gap if ncols is not Math.floor(ncols): - data = {'col_size':col_size, 'gap':gap, 'scrollWidth':scroll_viewport.paged_content_inline_size(), 'ncols':ncols, 'desired_inline_size':dis} + data = {'col_size':col_size, 'gap':gap, 'scrollWidth':scroll_viewport.paged_content_inline_size(), 'ncols':ncols} return data data = check_column_sizes() - if data: - dis = data.desired_inline_size - for elem in document.documentElement, document.body: - set_css(elem, max_width=dis + 'px', min_width=dis + 'px') - if scroll_viewport.vertical_writing_mode: - set_css(elem, max_height=dis + 'px', min_height=dis + 'px') - data = check_column_sizes() if data: print('WARNING: column layout broken, probably because there is some non-reflowable content in the book whose inline size is greater than the column size', data) From bc7a01934a72f6a31eef200dce93da086af18550 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 20 Apr 2023 13:33:00 +0530 Subject: [PATCH 0503/2055] Content server viewer: Fix end of chapter content being occasionally skipped when scrolling by screen full with multiple pages. Fixes #2015617 [[Content Server] Last page not shown by Firefox if short](https://bugs.launchpad.net/calibre/+bug/2015617) --- src/pyj/dom.pyj | 21 ++++++++++ src/pyj/read_book/paged_mode.pyj | 69 ++++++++++++++++++-------------- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/src/pyj/dom.pyj b/src/pyj/dom.pyj index 13a7aca3a0..536d6532cb 100644 --- a/src/pyj/dom.pyj +++ b/src/pyj/dom.pyj @@ -47,6 +47,27 @@ def set_css(elem, **kw): s.setProperty('-' + prefix + '-' + name, val) return elem +def set_important_css(elem, **kw): + if jstype(elem) is 'string': + elem = document.querySelector(elem) + s = elem.style + if s: + for prop in kw: + name, val = str.replace(str.rstrip(prop, '_'), '_', '-'), kw[prop] + if val is None or val is undefined: + s.removeProperty(name) + else: + s.setProperty(name, val, 'important') + prefixes = simple_vendor_prefixes[name] + if prefixes: + for prefix in prefixes: + if val is None or val is undefined: + s.removeProperty('-' + prefix + '-' + name) + else: + s.setProperty('-' + prefix + '-' + name, val, 'important') + return elem + + def build_rule(selector, **kw): ans = v'[selector + " { "]' for prop in kw: diff --git a/src/pyj/read_book/paged_mode.pyj b/src/pyj/read_book/paged_mode.pyj index ee8b4dfc45..c868191f5a 100644 --- a/src/pyj/read_book/paged_mode.pyj +++ b/src/pyj/read_book/paged_mode.pyj @@ -26,7 +26,7 @@ from __python__ import hash_literals, bound_methods import traceback from elementmaker import E -from dom import set_css +from dom import set_important_css from read_book.cfi import ( at_current as cfi_at_current, at_point as cfi_at_point, scroll_to as cfi_scroll_to @@ -66,22 +66,34 @@ def handle_rtl_body(body_style): def create_page_div(elem): div = E('blank-page-div', ' \n ') document.body.appendChild(div) - set_css(div, break_before='always', display='block', white_space='pre', background_color='transparent', - background_image='none', border_width='0', float='none', position='static') + # the min-height is needed to get firefox to always insert a column break before this div + set_important_css(div, break_before='column', break_inside='avoid', display='block', white_space='pre', background_color='transparent', + background_image='none', border_width='0', float='none', position='static', min_height='100vh') _in_paged_mode = False def in_paged_mode(): return _in_paged_mode -col_size = screen_inline = screen_block = cols_per_screen = gap = col_and_gap = number_of_cols = last_scrolled_to_column = 0 +col_size = screen_inline = screen_block = cols_per_screen = gap = col_and_gap = last_scrolled_to_column = 0 is_full_screen_layout = False +def get_number_of_cols(dont_return_integer): + # we dont store this because of the chrome resize bug where the document width + # sometimes changes a few milliseconds after layout in paged mode + if is_full_screen_layout: + return 1 + ans = (scroll_viewport.paged_content_inline_size() + gap) / col_and_gap + if not dont_return_integer: + ans = Math.floor(ans) + return ans + + def reset_paged_mode_globals(): - nonlocal _in_paged_mode, col_size, col_and_gap, screen_block, gap, screen_inline, is_full_screen_layout, cols_per_screen, number_of_cols, last_scrolled_to_column + nonlocal _in_paged_mode, col_size, col_and_gap, screen_block, gap, screen_inline, is_full_screen_layout, cols_per_screen, last_scrolled_to_column scroll_viewport.reset_globals() - col_size = screen_inline = screen_block = cols_per_screen = gap = col_and_gap = number_of_cols = last_scrolled_to_column = 0 + col_size = screen_inline = screen_block = cols_per_screen = gap = col_and_gap = last_scrolled_to_column = 0 is_full_screen_layout = _in_paged_mode = False resize_manager.reset() @@ -130,8 +142,8 @@ def fit_images(): if previously_limited or image_block_size > maxb or (image_block_size is maxb and image_inline_size > col_size): block_limited_images.push(img) if previously_limited: - set_css(img, break_before='auto', display=data.display) - set_css(img, break_inside='avoid') + set_important_css(img, break_before='auto', display=data.display) + set_important_css(img, break_inside='avoid') for img_tag, max_inline_size in inline_limited_images: if scroll_viewport.vertical_writing_mode: @@ -142,9 +154,9 @@ def fit_images(): for img_tag in block_limited_images: if scroll_viewport.vertical_writing_mode: - set_css(img_tag, break_before='always', max_width='100vw') + set_important_css(img_tag, break_before='always', max_width='100vw') else: - set_css(img_tag, break_before='always', max_height='100vh') + set_important_css(img_tag, break_before='always', max_height='100vh') set_elem_data(img_tag, 'block-limited', True) @@ -234,7 +246,7 @@ class ScrollResizeBugWatcher: scroll_resize_bug_watcher = ScrollResizeBugWatcher() def layout(is_single_page, on_resize): - nonlocal _in_paged_mode, col_size, col_and_gap, screen_block, gap, screen_inline, is_full_screen_layout, cols_per_screen, number_of_cols + nonlocal _in_paged_mode, col_size, col_and_gap, screen_block, gap, screen_inline, is_full_screen_layout, cols_per_screen line_height(True) rem_size(True) body_style = window.getComputedStyle(document.body) @@ -285,7 +297,7 @@ def layout(is_single_page, on_resize): screen_block = scroll_viewport.block_size() col_and_gap = col_size + gap - set_css(document.body, column_gap=gap + 'px', column_width=col_size + 'px', column_rule='0px inset blue', + set_important_css(document.body, column_gap=gap + 'px', column_width=col_size + 'px', column_rule='0px inset blue', min_width='0', max_width='none', min_height='0', max_height='100vh', column_fill='auto', margin='0', border_width='0', padding='0', box_sizing='content-box', width=scroll_viewport.width() + 'px', height=scroll_viewport.height() + 'px', overflow_wrap='break-word' @@ -298,7 +310,7 @@ def layout(is_single_page, on_resize): if c: # Remove page breaks on the first few elements to prevent blank pages # at the start of a chapter - set_css(c, break_before='avoid') + set_important_css(c, break_before='avoid') if c.tagName.toLowerCase() is 'div': c2 = first_child(c) if c2 and not has_start_text(c): @@ -306,7 +318,7 @@ def layout(is_single_page, on_resize): #

, see for example: https://bugs.launchpad.net/bugs/1366074 # In this case, we also modify the first child of the div # as long as there was no text before it. - set_css(c2, break_before='avoid') + set_important_css(c2, break_before='avoid') if first_layout: # Because of a bug in webkit column mode, svg elements defined with @@ -325,20 +337,15 @@ def layout(is_single_page, on_resize): cols_per_screen = 1 col_size = screen_inline col_and_gap = col_size + gap - number_of_cols = 1 document.body.style.columnWidth = f'100vw' def check_column_sizes(): - nonlocal number_of_cols - ncols = number_of_cols = (scroll_viewport.paged_content_inline_size() + gap) / col_and_gap - if ncols is not Math.floor(ncols): - data = {'col_size':col_size, 'gap':gap, 'scrollWidth':scroll_viewport.paged_content_inline_size(), 'ncols':ncols} - return data - - data = check_column_sizes() - if data: - print('WARNING: column layout broken, probably because there is some non-reflowable content in the book whose inline size is greater than the column size', data) + nc = get_number_of_cols(True) + if Math.floor(nc) is not nc: + data = {'col_size':col_size, 'gap':gap, 'scrollWidth':scroll_viewport.paged_content_inline_size(), 'ncols':nc} + print('WARNING: column layout broken, probably because there is some non-reflowable content in the book whose inline size is greater than the column size', data) + check_column_sizes() _in_paged_mode = True fit_images() scroll_resize_bug_watcher.layout_done() @@ -378,7 +385,7 @@ def scroll_to_pos(pos, notify=False, duration=1000): def scroll_to_previous_position(fsd): fsd = fsd or next_spine_item.forward_scroll_data next_spine_item.forward_scroll_data = None - if 0 < fsd.cols_left < cols_per_screen and cols_per_screen < number_of_cols: + if 0 < fsd.cols_left < cols_per_screen and cols_per_screen < get_number_of_cols(): scroll_resize_bug_watcher.last_command = scroll_to_previous_position.bind(None, fsd) scroll_to_column(fsd.current_col) return True @@ -414,7 +421,7 @@ def current_column_location(): def number_of_cols_left(): current_col = column_at(current_scroll_offset() + 10) - cols_left = number_of_cols - (current_col + cols_per_screen) + cols_left = get_number_of_cols() - (current_col + cols_per_screen) return Math.max(0, cols_left) @@ -431,7 +438,10 @@ def next_screen_location(): if limit < col_and_gap: return -1 if ans > limit: - ans = limit if Math.ceil(current_scroll_offset()) < limit else -1 + current_pos = Math.ceil(current_scroll_offset()) + ans = limit if current_pos < limit else -1 + if cols_per_screen is 1 and ans is not -1 and ans - current_pos < col_size: + ans = -1 # cant scroll partial columns return ans @@ -642,7 +652,7 @@ def progress_frac(frac): def page_counts(): if in_paged_mode(): - return {'current': column_at_current_scroll_offset(), 'total': number_of_cols, 'pages_per_screen': cols_per_screen} + return {'current': column_at_current_scroll_offset(), 'total': get_number_of_cols(), 'pages_per_screen': cols_per_screen} doc_size = scroll_viewport.document_block_size() screen_size = scroll_viewport.block_size() pos = scroll_viewport.block_pos() @@ -732,7 +742,8 @@ def scroll_by_page(backward, by_screen, flip_if_rtl_page_progression): next_spine_item(backward) else: if not backward: - scrolled_frac = (pages / number_of_cols) if number_of_cols > 0 else 0 + nc = get_number_of_cols() + scrolled_frac = (pages / nc) if nc > 0 else 0 get_boss().report_human_scroll(scrolled_frac) else: get_boss().report_human_scroll() From 532d17c070e635d483341f073e6044870d8ccc11 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 20 Apr 2023 13:35:52 +0530 Subject: [PATCH 0504/2055] Bump max supported Kobo firmware version --- src/calibre/devices/kobo/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 1d5f614cf4..01afb2211c 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -1399,7 +1399,7 @@ class KOBOTOUCH(KOBO): # Starting with firmware version 3.19.x, the last number appears to be is a # build number. A number will be recorded here but it can be safely ignored # when testing the firmware version. - max_supported_fwversion = (4, 35, 20400) + max_supported_fwversion = (4, 36, 21095) # The following document firmware versions where new function or devices were added. # Not all are used, but this feels a good place to record it. min_fwversion_shelves = (2, 0, 0) From 7e469587dc87116fbf10659644e170de8a440942 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Thu, 20 Apr 2023 10:51:40 +0100 Subject: [PATCH 0505/2055] I forgot to sort the names. --- src/calibre/gui2/dialogs/multisort.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/multisort.py b/src/calibre/gui2/dialogs/multisort.py index aa938fbd05..7a8a7479a7 100644 --- a/src/calibre/gui2/dialogs/multisort.py +++ b/src/calibre/gui2/dialogs/multisort.py @@ -145,7 +145,7 @@ class ChooseMultiSort(Dialog): return self.no_column_selected_error() d = QInputDialog(self) d.setComboBoxEditable(True) - d.setComboBoxItems(self.saved_specs.keys()) + d.setComboBoxItems(sorted(self.saved_specs.keys(), key=primary_sort_key)) d.setWindowTitle(_('Choose name')) d.setLabelText(_('Choose a name for these settings')) if d.exec(): From 7af89102815a76c38ffde26b0dcd419c78ed4d7c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 20 Apr 2023 17:58:29 +0530 Subject: [PATCH 0506/2055] E-book viewer: Fix a regression that caused notes from a different highlights to be shown in some situations. Fixes #2017130 [Private bug](https://bugs.launchpad.net/calibre/+bug/2017130) --- src/pyj/range_utils.pyj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyj/range_utils.pyj b/src/pyj/range_utils.pyj index fb2c712037..7a070a35dd 100644 --- a/src/pyj/range_utils.pyj +++ b/src/pyj/range_utils.pyj @@ -32,7 +32,7 @@ def first_annot_in_range(r, annot_id_uuid_map): parent = r.commonAncestorContainer doc = parent.ownerDocument or document is_full_tree = parent is doc.documentElement - in_range = not is_full_tree + in_range = is_full_tree iterator = doc.createNodeIterator(parent) while True: node = iterator.nextNode() From b92d673ad4620eca931ccc2f001c8d06180b171b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 20 Apr 2023 18:09:57 +0530 Subject: [PATCH 0507/2055] DRYer --- src/pyj/range_utils.pyj | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/src/pyj/range_utils.pyj b/src/pyj/range_utils.pyj index 7a070a35dd..7a5200e575 100644 --- a/src/pyj/range_utils.pyj +++ b/src/pyj/range_utils.pyj @@ -28,27 +28,6 @@ def text_nodes_in_range(r): return ans -def first_annot_in_range(r, annot_id_uuid_map): - parent = r.commonAncestorContainer - doc = parent.ownerDocument or document - is_full_tree = parent is doc.documentElement - in_range = is_full_tree - iterator = doc.createNodeIterator(parent) - while True: - node = iterator.nextNode() - if not node: - break - if not in_range and node.isSameNode(r.startContainer): - in_range = True - if in_range: - if node.dataset and node.dataset.calibreRangeWrapper: - annot_id = annot_id_uuid_map[node.dataset.calibreRangeWrapper] - if annot_id: - return annot_id - if not is_full_tree and node.isSameNode(r.endContainer): - break - - def all_annots_in_range(r, annot_id_uuid_map, ans): parent = r.commonAncestorContainer doc = parent.ownerDocument or document @@ -65,12 +44,18 @@ def all_annots_in_range(r, annot_id_uuid_map, ans): if node.dataset and node.dataset.calibreRangeWrapper: annot_id = annot_id_uuid_map[node.dataset.calibreRangeWrapper] if annot_id: + if not ans: + return annot_id ans[annot_id] = True if not is_full_tree and node.isSameNode(r.endContainer): break return ans +def first_annot_in_range(r, annot_id_uuid_map): + return all_annots_in_range(r, annot_id_uuid_map) + + def all_annots_in_selection(sel, annot_id_uuid_map): ans = v'{}' for i in range(sel.rangeCount): From 5ba24a04ebed8f6f09e8ec6a95c04adf6be7a128 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 21 Apr 2023 08:20:25 +0530 Subject: [PATCH 0508/2055] version 6.16.0 --- Changelog.txt | 39 +++++++++++++++++++++++++++++++++++++++ src/calibre/constants.py | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Changelog.txt b/Changelog.txt index 313ade7cb3..b5f753347f 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -23,6 +23,45 @@ # - title by author # }}} +{{{ 6.16.0 2023-04-20 + +:: new features + +- [major] Allow storing extra data files with a book + + Right click the Add books button to add arbitrary files as "data files" to a book record. + These are managed by calibre along with the book files, but cannot be used for conversion/viewing. + Select a book and press the "O" key to view the data files in your file explorer. + +- Allow undoing the deletion of books from the calibre library + + Now deleted books are stored in a calibre "Trash bin" from which they can be restored with + a single click. To view the trash bin, right click the "Remove books" button. + +- [2016070] Kobo driver: Add support for the new Kobo Elipsa 2E + +- Book details: if an item has an associated link then offer that link in the item's context menu + + +:: bug fixes + +- [2015617] Content server viewer: Fix end of chapter content being occasionally skipped when scrolling by screen full with multiple pages + +- [2017130] E-book viewer: Fix a regression that caused notes from a different highlights to be shown in some situations + +- [2015795] E-book viewer: Show an error if the user tries to search for only punctuation or spaces in the search modes that ignore these + +- Fix custom columns not showing in Book details links from other libraries + +:: improved recipes +- Frontline +- Outlook Magazine + +:: new recipes +- Bar and Bench by unkn0wn +- The Washington Post Print Edition by unkn0wn +}}} + {{{ 6.15.1 2023-04-07 :: new features diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 54aa9fe02f..c124f2fbd6 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -5,7 +5,7 @@ from functools import lru_cache import sys, locale, codecs, os, collections, collections.abc __appname__ = 'calibre' -numeric_version = (6, 15, 1) +numeric_version = (6, 16, 0) __version__ = '.'.join(map(str, numeric_version)) git_version = None __author__ = "Kovid Goyal " From 9837ad804177702e9da6550375bf2ec08d1b3611 Mon Sep 17 00:00:00 2001 From: ping Date: Fri, 21 Apr 2023 12:51:51 +0800 Subject: [PATCH 0509/2055] Fix Scientific American recipe --- recipes/scientific_american.recipe | 153 ++++++++++++++--------------- 1 file changed, 73 insertions(+), 80 deletions(-) diff --git a/recipes/scientific_american.recipe b/recipes/scientific_american.recipe index d6ab1cfc59..76dfd77c8e 100644 --- a/recipes/scientific_american.recipe +++ b/recipes/scientific_american.recipe @@ -1,30 +1,30 @@ #!/usr/bin/env python -__license__ = 'GPL v3' +__license__ = "GPL v3" + +import json +from datetime import datetime +from os.path import splitext +from urllib.parse import urljoin from calibre.web.feeds.news import BasicNewsRecipe, classes -from css_selectors import Select - - -def absurl(url): - if url.startswith('/'): - url = 'https://www.scientificamerican.com' + url - return url class ScientificAmerican(BasicNewsRecipe): - title = u'Scientific American' - description = u'Popular Science. Monthly magazine. Should be downloaded around the middle of each month.' - category = 'science' - __author__ = 'Kovid Goyal' + title = "Scientific American" + description = "Popular Science. Monthly magazine. Should be downloaded around the middle of each month." + category = "science" + __author__ = "Kovid Goyal" no_stylesheets = True - language = 'en' - publisher = 'Nature Publishing Group' + language = "en" + publisher = "Nature Publishing Group" remove_empty_feeds = True remove_javascript = True - timefmt = ' [%B %Y]' - remove_attributes = ['height','width'] - masthead_url = 'https://static.scientificamerican.com/sciam/assets/Image/newsletter/salogo.png' - extra_css = ''' + timefmt = " [%B %Y]" + remove_attributes = ["height", "width"] + masthead_url = ( + "https://static.scientificamerican.com/sciam/assets/Image/newsletter/salogo.png" + ) + extra_css = """ .image-captioned{font-size:small;} .feature-article__byline-authors{font-size:small;} .article-header__inner__category{font-size:small; color:gray;} @@ -33,85 +33,78 @@ class ScientificAmerican(BasicNewsRecipe): .opinion-article__byline-authors{font-size:small;} .article-author{font-size:small;} [role="presentation"]{font-size:small;} - ''' + """ - needs_subscription = 'optional' + needs_subscription = "optional" keep_only_tags = [ classes( - 'article-header article-content article-media article-author article-text feature-article--header' - ' feature-article--header-title opinion-article__header-title author-bio'), + "article-header article-content article-media article-author article-text feature-article--header" + " feature-article--header-title opinion-article__header-title author-bio" + ), ] remove_tags = [ - classes('aside-banner moreToExplore article-footer flex-column--25 article-author__suggested medium-up-hide'), - dict(id=['seeAlsoLinks']), + classes( + "aside-banner moreToExplore article-footer flex-column--25 article-author__suggested medium-up-hide" + ), + dict(id=["seeAlsoLinks"]), ] def get_browser(self, *args): br = BasicNewsRecipe.get_browser(self) if self.username and self.password: - br.open('https://www.scientificamerican.com/my-account/login/') - br.select_form(predicate=lambda f: f.attrs.get('id') == 'login') - br['emailAddress'] = self.username - br['password'] = self.password + br.open("https://www.scientificamerican.com/my-account/login/") + br.select_form(predicate=lambda f: f.attrs.get("id") == "login") + br["emailAddress"] = self.username + br["password"] = self.password br.submit() return br def parse_index(self): # Get the cover, date and issue URL - root = self.index_to_soup( - 'https://www.scientificamerican.com/sciammag/', as_tree=True) - select = Select(root) - self.cover_url = [x.get('src', '') for x in select('main .store-listing__img img')][0] - url = [x.get('href', '') for x in select('main .store-listing__img a')][0] - url = absurl(url) + fp_soup = self.index_to_soup("https://www.scientificamerican.com") + curr_issue_link = fp_soup.select(".tout_current-issue__cover a") + if not curr_issue_link: + self.abort_recipe_processing("Unable to find issue link") + issue_url = curr_issue_link[0]["href"] + soup = self.index_to_soup(issue_url) + script = soup.find("script", id="__NEXT_DATA__") + if not script: + self.abort_recipe_processing("Unable to find script") - # Now parse the actual issue to get the list of articles - select = Select(self.index_to_soup(url, as_tree=True)) - self.cover_url = [x.get('src', '') for x in select('main .product-detail__image img')][0].split('?')[0] - self.cover_url += '?w=800' - feeds = [] - for i, section in enumerate(select('#sa_body .toc-articles')): - if i == 0: - feeds.append( - ('Features', list(self.parse_sciam_features(select, section)))) - else: - feeds.extend(self.parse_sciam_departments(select, section)) + issue_info = ( + json.loads(script.contents[0]) + .get("props", {}) + .get("pageProps", {}) + .get("issue", {}) + ) + if not issue_info: + self.abort_recipe_processing("Unable to find issue info") - return feeds + image_id, _ = splitext(issue_info["image"]) + self.cover_url = f"https://static.scientificamerican.com/sciam/cache/file/{image_id}_source.jpg?w=800" - def parse_sciam_features(self, select, section): - for article in select('article[data-article-title]', section): - title = article.get('data-article-title') - url = 'https://www.scientificamerican.com/{}/'.format(article.get('id').replace('-', '/', 1)) - desc = '' - for p in select('p.t_body', article): - desc += self.tag_to_string(p) - break - for p in select('.t_meta', article): - desc += ' ' + self.tag_to_string(p) - break - self.log('Found feature article: %s at %s' % (title, url)) - self.log('\t' + desc) - yield {'title': title, 'url': url, 'description': desc} + edition_date = datetime.strptime(issue_info["issue_date"], "%Y-%m-%d") + self.timefmt = f" [{edition_date:%B %Y}]" - def parse_sciam_departments(self, select, section): - section_title, articles = 'Unknown', [] - for li in select('li[data-article-title]', section): - for span in select('span.department-title', li): - if articles: - yield section_title, articles - section_title, articles = self.tag_to_string(span), [] - self.log('\nFound section: %s' % section_title) - break - url = 'https://www.scientificamerican.com/{}/'.format(li.get('id').replace('-', '/', 1)) - for h2 in select('h2.t_listing-title', li): - title = self.tag_to_string(h2) - break - else: - continue - articles.append( - {'title': title, 'url': url, 'description': ''}) - self.log('\tFound article: %s at %s' % (title, url)) - if articles: - yield section_title, articles + feeds = {} + for section in ("featured", "departments"): + for article in issue_info.get("article_previews", {}).get(section, []): + if section == "featured": + feed_name = "Features" + else: + feed_name = article["category"] + if feed_name not in feeds: + feeds[feed_name] = [] + feeds[feed_name].append( + { + "title": article["title"], + "url": urljoin( + "https://www.scientificamerican.com/article/", + article["slug"], + ), + "description": article["summary"], + } + ) + + return feeds.items() From e4eae39a5f151797c9554780a6ebe2525d03c512 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 21 Apr 2023 13:41:40 +0530 Subject: [PATCH 0510/2055] Add tests for various external DLL based modules in pillow --- src/calibre/test_build.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/calibre/test_build.py b/src/calibre/test_build.py index d504d748d6..adaca52206 100644 --- a/src/calibre/test_build.py +++ b/src/calibre/test_build.py @@ -397,6 +397,22 @@ class BuildTest(unittest.TestCase): except ImportError: from PIL import _imaging, _imagingft, _imagingmath _imaging, _imagingmath, _imagingft + from PIL import features + from io import StringIO + out = StringIO() + features.pilinfo(out=out, supported_formats=False) + out = out.getvalue() + for line in '''\ + --- PIL CORE support ok + --- FREETYPE2 support ok + --- WEBP support ok + --- WEBP Transparency support ok + --- WEBPMUX support ok + --- WEBP Animation support ok + --- JPEG support ok + --- ZLIB (PNG/ZIP) support ok + '''.splitlines(): + self.assertIn(line.strip(), out) i = Image.open(I('lt.png', allow_user_override=False)) self.assertGreaterEqual(i.size, (20, 20)) i = Image.open(P('catalog/DefaultCover.jpg', allow_user_override=False)) From 7e014c68510220781cbd699d261d27a4fb7d82dc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 21 Apr 2023 13:43:05 +0530 Subject: [PATCH 0511/2055] ... --- src/calibre/test_build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/test_build.py b/src/calibre/test_build.py index adaca52206..d6a71cc361 100644 --- a/src/calibre/test_build.py +++ b/src/calibre/test_build.py @@ -418,7 +418,7 @@ class BuildTest(unittest.TestCase): i = Image.open(P('catalog/DefaultCover.jpg', allow_user_override=False)) self.assertGreaterEqual(i.size, (20, 20)) - @unittest.skipUnless(iswindows and not is_ci, 'File dialog helper only used on windows (non-continuous-itegration)') + @unittest.skipUnless(iswindows and not is_ci, 'File dialog helper only used on windows (non-continuous-integration)') def test_file_dialog_helper(self): from calibre.gui2.win_file_dialogs import test test() From 73d1406e295fa4de0c6a5ac3fdcc75bf24c8fb48 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 21 Apr 2023 14:14:42 +0530 Subject: [PATCH 0512/2055] Missed a few places to make long paths useable in copy_tree --- src/calibre/utils/copy_files.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index 6715c7d32a..d5b00a8a95 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -71,7 +71,7 @@ class WindowsFileCopier: def _open_file(self, path: str, retry_on_sharing_violation: bool = True) -> 'winutil.Handle': try: - return winutil.create_file(path, winutil.GENERIC_READ, + return winutil.create_file(make_long_path_useable(path), winutil.GENERIC_READ, winutil.FILE_SHARE_DELETE, winutil.OPEN_EXISTING, winutil.FILE_FLAG_SEQUENTIAL_SCAN) except OSError as e: @@ -104,23 +104,23 @@ class WindowsFileCopier: def copy_all(self) -> None: for src_path, dest_path in self.copy_map.items(): with suppress(Exception): - windows_hardlink(src_path, dest_path) - shutil.copystat(src_path, dest_path, follow_symlinks=False) + windows_hardlink(make_long_path_useable(src_path), make_long_path_useable(dest_path)) + shutil.copystat(make_long_path_useable(src_path), make_long_path_useable(dest_path), follow_symlinks=False) continue handle = self.path_to_handle_map[src_path] winutil.set_file_pointer(handle, 0, winutil.FILE_BEGIN) - with open(dest_path, 'wb') as f: + with open(make_long_path_useable(dest_path), 'wb') as f: sz = 1024 * 1024 while True: raw = winutil.read_file(handle, sz) if not raw: break f.write(raw) - shutil.copystat(src_path, dest_path, follow_symlinks=False) + shutil.copystat(make_long_path_useable(src_path), make_long_path_useable(dest_path), follow_symlinks=False) def rename_all(self) -> None: for src_path, dest_path in self.copy_map.items(): - winutil.move_file(src_path, dest_path) + winutil.move_file(make_long_path_useable(src_path), make_long_path_useable(dest_path)) def delete_all_source_files(self) -> None: for src_path in self.copy_map: From d7632c256cbd6cdf96b321e80ebfa47e4d296968 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 21 Apr 2023 14:35:51 +0530 Subject: [PATCH 0513/2055] Book details: Show a clickable link to open the data files folder when extra data files are present for the book --- src/calibre/ebooks/metadata/book/render.py | 12 ++++++++++-- src/calibre/gui2/actions/view.py | 4 ++++ src/calibre/gui2/book_details.py | 3 +++ src/calibre/gui2/init.py | 1 + 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index 398f0cabc7..7c16fdab53 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -6,6 +6,7 @@ __copyright__ = '2014, Kovid Goyal ' import os from functools import partial +from contextlib import suppress from calibre import prepare_string_for_xml, force_unicode from calibre.ebooks.metadata import fmt_sidx, rating_to_stars @@ -201,7 +202,6 @@ def mi_to_html( path = force_unicode(mi.path, filesystem_encoding) scheme = 'devpath' if isdevice else 'path' loc = path if isdevice else book_id - pathstr = _('Click to open') extra = '' if isdevice: durl = path @@ -211,7 +211,15 @@ def mi_to_html( prepare_string_for_xml(durl)) if show_links: link = '{}{}'.format(action(scheme, book_id=book_id, loc=loc), - prepare_string_for_xml(path, True), pathstr, extra) + prepare_string_for_xml(path, True), _('Click to open'), extra) + if not isdevice: + data_path = os.path.join(path, 'data') + with suppress(OSError): + if os.listdir(data_path): + link += ' \xa0 {}'.format( + action('data-path', book_id=book_id, loc=book_id), + prepare_string_for_xml(data_path, True), _('Data files')) + else: link = prepare_string_for_xml(path, True) ans.append((field, row % (name, link))) diff --git a/src/calibre/gui2/actions/view.py b/src/calibre/gui2/actions/view.py index fbf6d00226..f949547dd0 100644 --- a/src/calibre/gui2/actions/view.py +++ b/src/calibre/gui2/actions/view.py @@ -281,6 +281,10 @@ class ViewAction(InterfaceAction): path = self.gui.library_view.model().db.abspath(id_, index_is_id=True) open_local_file(path) + def view_data_folder_for_id(self, id_): + path = self.gui.library_view.model().db.abspath(id_, index_is_id=True) + open_local_file(os.path.join(path, 'data')) + def view_book(self, triggered): rows = self.gui.current_view().selectionModel().selectedRows() self._view_books(rows) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 0f3443e702..883180bfbb 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -1088,6 +1088,7 @@ class BookDetails(DetailsLayout): # {{{ show_book_info = pyqtSignal() open_containing_folder = pyqtSignal(int) + open_data_folder = pyqtSignal(int) view_specific_format = pyqtSignal(int, object) search_requested = pyqtSignal(object, object) remove_specific_format = pyqtSignal(int, object) @@ -1237,6 +1238,8 @@ class BookDetails(DetailsLayout): # {{{ browse(data['url']) elif dt == 'path': self.open_containing_folder.emit(int(data['loc'])) + elif dt == 'data-path': + self.open_data_folder.emit(int(data['loc'])) elif dt == 'devpath': self.view_device_book.emit(data['loc']) else: diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index 5b31623aea..92ce19fe31 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -669,6 +669,7 @@ class LayoutMixin: # {{{ self.iactions['Add Books'].remote_file_dropped_on_book, type=Qt.ConnectionType.QueuedConnection) self.book_details.open_containing_folder.connect(self.iactions['View'].view_folder_for_id) + self.book_details.open_data_folder.connect(self.iactions['View'].view_data_folder_for_id) self.book_details.view_specific_format.connect(self.iactions['View'].view_format_by_id) self.book_details.search_requested.connect(self.set_search_string_with_append) self.book_details.remove_specific_format.connect( From 67ad60d5360384936e77e798dbb793f1abad3ce6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 21 Apr 2023 14:44:31 +0530 Subject: [PATCH 0514/2055] When listing the trash dir ignore entries without a useable metadata.opf --- src/calibre/db/backend.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 59c9b22294..7049a12320 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -2086,9 +2086,12 @@ class DB: try: book_id = int(x.name) mtime = x.stat(follow_symlinks=False).st_mtime + with open(make_long_path_useable(os.path.join(x.path, METADATA_FILE_NAME)), 'rb') as opf_stream: + opf = OPF(opf_stream, basedir=x.path) except Exception: + import traceback + traceback.print_exc() continue - opf = OPF(os.path.join(x.path, METADATA_FILE_NAME), basedir=x.path) books.append(TrashEntry(book_id, opf.title or unknown, (opf.authors or au)[0], os.path.join(x.path, COVER_FILE_NAME), mtime)) base = os.path.join(self.trash_dir, 'f') um = {'title': unknown, 'authors': au} From 46406ca9a7c8024c9455ddb0e7446f3a13c1e20c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 21 Apr 2023 14:47:40 +0530 Subject: [PATCH 0515/2055] Also ignore unparseable metadata.json --- src/calibre/db/backend.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 7049a12320..8d4fd14fa6 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -2107,8 +2107,13 @@ class DB: for f in os.scandir(x.path): if f.is_file(follow_symlinks=False): if f.name == 'metadata.json': - with open(f.path, 'rb') as mf: - metadata = json.loads(mf.read()) + try: + with open(f.path, 'rb') as mf: + metadata = json.loads(mf.read()) + except Exception: + import traceback + traceback.print_exc() + continue else: formats.add(f.name.upper()) if formats: From 125c7bbf26d634da6c063468c1000586e6d8e906 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Fri, 21 Apr 2023 11:55:59 +0100 Subject: [PATCH 0516/2055] Use tabs instead of spaces in the template tester's sample python text. This makes indenting and outdenting in the tester consistent. --- src/calibre/gui2/dialogs/template_dialog.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/calibre/gui2/dialogs/template_dialog.py b/src/calibre/gui2/dialogs/template_dialog.py index e8bd1851c7..d5eeee402e 100644 --- a/src/calibre/gui2/dialogs/template_dialog.py +++ b/src/calibre/gui2/dialogs/template_dialog.py @@ -618,17 +618,17 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): def add_python_template_header_text(self): self.textbox.setPlainText('''python: def evaluate(book, context): - # book is a calibre metadata object - # context is an instance of calibre.utils.formatter.PythonTemplateContext, - # which currently contains the following attributes: - # db: a calibre legacy database object. - # globals: the template global variable dictionary. - # arguments: is a list of arguments if the template is called by a GPM template, otherwise None. - # funcs: used to call Built-in/User functions and Stored GPM/Python templates. - # Example: context.funcs.list_re_group() +\t# book is a calibre metadata object +\t# context is an instance of calibre.utils.formatter.PythonTemplateContext, +\t# which currently contains the following attributes: +\t# db: a calibre legacy database object. +\t# globals: the template global variable dictionary. +\t# arguments: is a list of arguments if the template is called by a GPM template, otherwise None. +\t# funcs: used to call Built-in/User functions and Stored GPM/Python templates. +\t# Example: context.funcs.list_re_group() - # your Python code goes here - return 'a string' +\t# your Python code goes here +\treturn 'a string' ''') def set_word_wrap(self, to_what): From f7fb20e431100f0a9d4bc90ed6f008394a623d6b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 21 Apr 2023 17:13:38 +0530 Subject: [PATCH 0517/2055] Ensure metadata.opf is always written when deleting book even if it is not sequenced for backup. Fixes #2017217 [Trash Bin not working](https://bugs.launchpad.net/calibre/+bug/2017217) --- src/calibre/db/cache.py | 28 +++++++++++++++++++--------- src/calibre/db/tests/add_remove.py | 4 ++++ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 3bc6eaa47d..5e64164b8b 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -1515,6 +1515,15 @@ class Cache: return random.choice(tuple(self.dirtied_cache)) return None + def _metadata_as_object_for_dump(self, book_id): + mi = self._get_metadata(book_id) + # Always set cover to cover.jpg. Even if cover doesn't exist, + # no harm done. This way no need to call dirtied when + # cover is set/removed + mi.cover = 'cover.jpg' + mi.all_annotations = self._all_annotations_for_book(book_id) + return mi + @read_api def get_metadata_for_dump(self, book_id): mi = None @@ -1527,12 +1536,7 @@ class Cache: # While a book is being created, the path is empty. Don't bother to # try to write the opf, because it will go to the wrong folder. if self._field_for('path', book_id): - mi = self._get_metadata(book_id) - # Always set cover to cover.jpg. Even if cover doesn't exist, - # no harm done. This way no need to call dirtied when - # cover is set/removed - mi.cover = 'cover.jpg' - mi.all_annotations = self._all_annotations_for_book(book_id) + mi = self._metadata_as_object_for_dump(book_id) except: # This almost certainly means that the book has been deleted while # the backup operation sat in the queue. @@ -2052,9 +2056,15 @@ class Cache: except Exception: path = None path_map[book_id] = path - # ensure metadata.opf is written so we can restore the book - if not permanent: - self._dump_metadata(book_ids=tuple(bid for bid, path in path_map.items() if path)) + if not permanent and path: + # ensure metadata.opf is written and up-to-date so we can restore the book + try: + mi = self._metadata_as_object_for_dump(book_id) + raw = metadata_to_opf(mi) + self.backend.write_backup(path, raw) + except Exception: + import traceback + traceback.print_exc() self.backend.remove_books(path_map, permanent=permanent) for field in itervalues(self.fields): try: diff --git a/src/calibre/db/tests/add_remove.py b/src/calibre/db/tests/add_remove.py index 8e08f58146..2afbb97a5b 100644 --- a/src/calibre/db/tests/add_remove.py +++ b/src/calibre/db/tests/add_remove.py @@ -7,10 +7,12 @@ __docformat__ = 'restructuredtext en' import glob import os +from contextlib import suppress from datetime import timedelta from io import BytesIO from tempfile import NamedTemporaryFile +from calibre.db.backend import METADATA_FILE_NAME from calibre.db.tests.base import IMG, BaseTest from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.date import UNDEFINED_DATE, now, utcnow @@ -298,6 +300,8 @@ class AddRemoveTest(BaseTest): fm_before = cache.format_metadata(1, 'FMT1', allow_cache=False), cache.format_metadata(1, 'FMT2', allow_cache=False) os.mkdir(os.path.join(bookpath, 'xyz')) open(os.path.join(bookpath, 'xyz', 'abc'), 'w').close() + with suppress(FileNotFoundError): + os.remove(os.path.join(bookpath, METADATA_FILE_NAME)) cache.remove_books((1,)) cache.move_book_from_trash(1) b, f = cache.list_trash_entries() From a8229ffc9d6f894e50fe3bfc16e9b2a447a58510 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 21 Apr 2023 17:28:41 +0530 Subject: [PATCH 0518/2055] Trash bin: Add a button to clear the bin. Fixes #2017232 [Enhancement Request: One-click 'empty recycle bin'](https://bugs.launchpad.net/calibre/+bug/2017232) --- src/calibre/db/backend.py | 6 ++++++ src/calibre/db/cache.py | 4 ++++ src/calibre/gui2/trash.py | 15 ++++++++++++++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 8d4fd14fa6..cc90ceb1f2 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1984,6 +1984,12 @@ class DB: def trash_dir(self): return os.path.abspath(os.path.join(self.library_path, TRASH_DIR_NAME)) + def clear_trash_dir(self): + tdir = self.trash_dir + if os.path.exists(tdir): + self.rmtree(tdir) + self.ensure_trash_dir() + def ensure_trash_dir(self): tdir = self.trash_dir os.makedirs(os.path.join(tdir, 'b'), exist_ok=True) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 5e64164b8b..0afb6cf519 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2676,6 +2676,10 @@ class Cache: def is_closed(self): return self.backend.is_closed + @write_api + def clear_trash_bin(self): + self.backend.clear_trash_dir() + @read_api def list_trash_entries(self): books, formats = self.backend.list_trash_entries() diff --git a/src/calibre/gui2/trash.py b/src/calibre/gui2/trash.py index e666c2ff07..60ed7d80b3 100644 --- a/src/calibre/gui2/trash.py +++ b/src/calibre/gui2/trash.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2023, Kovid Goyal - import time import traceback from operator import attrgetter @@ -15,6 +14,7 @@ from typing import Iterator, List from calibre import fit_image from calibre.db.backend import DEFAULT_TRASH_EXPIRY_TIME_SECONDS, TrashEntry from calibre.gui2 import error_dialog +from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.widgets import BusyCursor from calibre.gui2.widgets2 import Dialog @@ -145,6 +145,7 @@ class TrashView(Dialog): la.setBuddy(ad) l.addLayout(h) + h = QHBoxLayout() l.addWidget(self.bb) self.restore_button = b = self.bb.addButton(_('&Restore selected'), QDialogButtonBox.ButtonRole.ActionRole) b.clicked.connect(self.restore_selected) @@ -153,6 +154,18 @@ class TrashView(Dialog): b.setToolTip(_('Remove the selected entries from the trash bin, thereby deleting them permanently')) b.setIcon(QIcon.ic('edit-clear.png')) b.clicked.connect(self.delete_selected) + self.clear_button = b = self.bb.addButton(_('&Clear'), QDialogButtonBox.ButtonRole.ResetRole) + b.clicked.connect(self.clear_all) + b.setIcon(QIcon.ic('dialog_warning.png')) + self.update_titles() + self.bb.button(QDialogButtonBox.StandardButton.Close).setFocus(Qt.FocusReason.OtherFocusReason) + + def clear_all(self): + if not confirm('

'+_('All books and formats will be permanently deleted! Are you sure?'), 'clear_trash_bin', self): + return + self.db.clear_trash_bin() + self.books.clear() + self.formats.clear() self.update_titles() def update_titles(self): From a17b860df58765628c70dbe038c4f2e391089ae2 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Fri, 21 Apr 2023 13:06:32 +0100 Subject: [PATCH 0519/2055] Add a menu action in book details to copy the link to a book's data folder if one exists. --- src/calibre/gui2/book_details.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 883180bfbb..fc369d437c 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -5,6 +5,7 @@ import os import re from collections import namedtuple +from contextlib import suppress from functools import lru_cache, partial from qt.core import ( QAction, QApplication, QClipboard, QColor, QDialog, QEasingCurve, QIcon, @@ -474,6 +475,13 @@ def create_copy_links(menu, data=None): menu.addSeparator() link(_('Link to show book in calibre'), f'calibre://show-book/{library_id}/{book_id}') link(_('Link to show book details in a popup window'), f'calibre://book-details/{library_id}/{book_id}') + mi = db.new_api.get_proxy_metadata(book_id) + data_path = os.path.join(db.backend.library_path, mi.path, 'data') + with suppress(OSError): + if os.listdir(data_path): + if iswindows: + data_path = '/' + data_path.replace('\\', '/') + link(_("Link to open book's data files folder"), 'file://' + data_path) if data: field = data.get('field') if data['type'] == 'author': @@ -490,7 +498,6 @@ def create_copy_links(menu, data=None): if all_links: menu.addSeparator() - mi = db.new_api.get_proxy_metadata(book_id) all_links.insert(0, '') all_links.insert(0, mi.get('title') + ' - ' + ' & '.join(mi.get('authors'))) link(_('Copy all the above links'), '\n'.join(all_links)) From 1a5bf9ffa8bdd0f67de3be6aa26bb0d09ff2d66f Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Fri, 21 Apr 2023 14:34:14 +0100 Subject: [PATCH 0520/2055] Refresh book details after adding extra files in case the link isn't alreadyy showing. --- src/calibre/gui2/actions/add.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/gui2/actions/add.py b/src/calibre/gui2/actions/add.py index f163785382..31aa002e90 100644 --- a/src/calibre/gui2/actions/add.py +++ b/src/calibre/gui2/actions/add.py @@ -158,6 +158,8 @@ class AddAction(InterfaceAction): db = self.gui.current_db.new_api for book_id in ids: db.add_extra_files(book_id, rmap) + self.gui.library_view.model().refresh_ids(ids, + current_row=self.gui.library_view.currentIndex().row()) def _add_formats(self, paths, ids): if len(ids) > 1 and not question_dialog( From e227e0f1232b667d8e6b3771a08df3182fbb6267 Mon Sep 17 00:00:00 2001 From: Guido Falsi Date: Fri, 21 Apr 2023 15:44:22 +0200 Subject: [PATCH 0521/2055] Adapt union syntax to work on python 3.9 --- src/calibre/utils/copy_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index d5b00a8a95..262f096678 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -127,7 +127,7 @@ class WindowsFileCopier: winutil.delete_file(make_long_path_useable(src_path)) -def get_copier() -> Union[UnixFileCopier | WindowsFileCopier]: +def get_copier() -> Union[UnixFileCopier, WindowsFileCopier]: return WindowsFileCopier() if iswindows else UnixFileCopier() From 464030a796410d67683da63152520a8e220e9e84 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 21 Apr 2023 20:09:18 +0530 Subject: [PATCH 0522/2055] Fix copy to library not copying extra files There was a bug in the test as well that allowed this bug through. --- src/calibre/db/copy_to_library.py | 2 +- src/calibre/db/tests/add_remove.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calibre/db/copy_to_library.py b/src/calibre/db/copy_to_library.py index 6488ec636b..51466ef470 100644 --- a/src/calibre/db/copy_to_library.py +++ b/src/calibre/db/copy_to_library.py @@ -105,7 +105,7 @@ def copy_one_book( bp = db.field_for('path', book_id) if bp: for (relpath, src_path, mtime) in db.backend.iter_extra_files(book_id, bp, db.fields['formats'], yield_paths=True): - nbp = newdb.field_for('path', book_id) + nbp = newdb.field_for('path', new_book_id) if nbp: newdb.backend.add_extra_file(relpath, src_path, nbp) postprocess_copy(book_id, new_book_id, new_authors, db, newdb, identical_books_data, duplicate_action) diff --git a/src/calibre/db/tests/add_remove.py b/src/calibre/db/tests/add_remove.py index 2afbb97a5b..5a7c514be6 100644 --- a/src/calibre/db/tests/add_remove.py +++ b/src/calibre/db/tests/add_remove.py @@ -405,6 +405,7 @@ class AddRemoveTest(BaseTest): compare_field('uuid', self.assertNotEqual) self.assertEqual(src_db.all_annotations_for_book(1), dest_db.all_annotations_for_book(max(dest_db.all_book_ids()))) rdata = copy_one_book(1, src_db, dest_db, preserve_date=False, preserve_uuid=True) + data_file_new_book_id = rdata['new_book_id'] self.assertEqual(rdata, make_rdata(new_book_id=max(dest_db.all_book_ids()))) compare_field('timestamp', self.assertNotEqual) compare_field('uuid') @@ -426,7 +427,7 @@ class AddRemoveTest(BaseTest): for new_book_id in (1, 4, 5): self.assertEqual(dest_db.format(new_book_id, 'FMT1'), b'replaced') self.assertEqual(dest_db.format(rdata['new_book_id'], 'FMT1'), b'second-round') - bookdir = os.path.dirname(dest_db.format_abspath(1, '__COVER_INTERNAL__')) + bookdir = os.path.dirname(dest_db.format_abspath(data_file_new_book_id, '__COVER_INTERNAL__')) self.assertEqual('exf', open(os.path.join(bookdir, 'exf')).read()) self.assertEqual('recurse', open(os.path.join(bookdir, 'sub', 'recurse')).read()) From 9ccabd206f2b9de96a86d4b2223700a68da083f9 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Fri, 21 Apr 2023 16:31:23 +0100 Subject: [PATCH 0523/2055] Improvement in custom comments header position documentation --- src/calibre/gui2/dialogs/book_info.py | 7 +++++-- src/calibre/gui2/preferences/create_custom_column.py | 10 +++++++--- src/calibre/gui2/preferences/look_feel.ui | 6 ++++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index e9cebb86fd..ea6f905983 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -88,8 +88,11 @@ class Configure(Dialog): h.addLayout(v) self.l.addLayout(h) - self.l.addWidget(QLabel('

' + _( - 'Note that comments will always be displayed at the end, regardless of the order you assign here'))) + txt = QLabel('

' + _( + 'Note: comments-like columns will always be displayed at ' + 'the end unless their "Heading position" is "Show heading to the side"')+'

') + txt.setWordWrap(True) + self.l.addWidget(txt) b = self.bb.addButton(_('Restore &defaults'), QDialogButtonBox.ButtonRole.ActionRole) b.clicked.connect(self.restore_defaults) diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index 627f02319b..ef9e327a9a 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -396,9 +396,13 @@ class CreateCustomColumn(QDialog): ('side', _('Show heading to the side of the text')) ): ct.addItem(text, k) - ct.setToolTip(_('Choose whether or not the column heading is shown in the Book\n' - 'details panel and, if shown, where')) - self.comments_heading_position_label = add_row(_('Column heading:'), ct) + ct.setToolTip('

' + + _('Choose whether or not the column heading is shown in the Book ' + 'details panel and, if shown, where. Setting this to ' + "'Show heading to the side of the text' moves the information " + "from dislayed with other comments to displayed with the " + "non-comments columns.") + '

') + self.comments_heading_position_label = add_row(_('Heading position:'), ct) self.comments_type = ct = QComboBox(self) for k, text in ( diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index a010116a77..fa0609bb6f 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -817,10 +817,12 @@ A value of zero means calculate automatically. - + - Note that <b>comments</b> will always be displayed at the end, regardless of the position you assign here. + <p>Note: <b>comments</b>-like columns will always + be displayed at the end unless their "Heading position" is + "Show heading to the side".</p> true From 3ac4dc6e435f728eb76533e70ae10293c6b786a9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 22 Apr 2023 07:16:51 +0530 Subject: [PATCH 0524/2055] Avoid div-by-zero in fit_image --- src/calibre/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index 638eac538b..4f0337573e 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -345,6 +345,8 @@ def fit_image(width, height, pwidth, pheight): @param pheight: Height of box @return: scaled, new_width, new_height. scaled is True iff new_width and/or new_height is different from width or height. ''' + if height < 1 or width < 1: + return False, int(width), int(height) scaled = height > pheight or width > pwidth if height > pheight: corrf = pheight / float(height) From f9bc895f0a8e8ee7034c34d8e0644539654a0923 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 22 Apr 2023 07:17:48 +0530 Subject: [PATCH 0525/2055] Fix exception when opening book info popup window with no cover and fit cover checkbox checked --- src/calibre/gui2/dialogs/book_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index ea6f905983..eea83a7d26 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -341,7 +341,7 @@ class BookInfo(QDialog): self.cover.set_marked(self.marked) return pixmap = self.cover_pixmap - if self.fit_cover.isChecked(): + if self.fit_cover.isChecked() and not pixmap.isNull(): scaled, new_width, new_height = fit_image(pixmap.width(), pixmap.height(), self.cover.size().width()-10, self.cover.size().height()-10) From 4deb2384c29cbbbf91f133110ea0ab74b64492be Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 22 Apr 2023 07:18:55 +0530 Subject: [PATCH 0526/2055] Bump supported dbversion in kobo driver At least one user reports that the driver seems to work OK with the latest dbversion. https://www.mobileread.com/forums/showthread.php?t=353443 --- src/calibre/devices/kobo/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 01afb2211c..1cbb49e2b5 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -1385,7 +1385,7 @@ class KOBOTOUCH(KOBO): ' Based on the existing Kobo driver by %s.') % KOBO.author # icon = 'devices/kobotouch.jpg' - supported_dbversion = 171 + supported_dbversion = 174 min_supported_dbversion = 53 min_dbversion_series = 65 min_dbversion_externalid = 65 From d5dbb3c63a1fe05f85a31cf1e4cd021ec76e750a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 22 Apr 2023 07:29:21 +0530 Subject: [PATCH 0527/2055] Fix #2017316 [Error when renaming tag in Tags Manager](https://bugs.launchpad.net/calibre/+bug/2017316) --- src/calibre/gui2/dialogs/tag_list_editor.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index 0a6e5cc494..aaff6a7cff 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -107,6 +107,12 @@ class CountTableWidgetItem(QTableWidgetItem): return self._count < other._count +def was_item(tag=''): + item = QTableWidgetItem() + item.setFlags(item.flags() & ~(Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable)) + item.setData(Qt.ItemDataRole.DisplayRole, tag) + return item + class EditColumnDelegate(QItemDelegate): editing_finished = pyqtSignal(int) editing_started = pyqtSignal(int) @@ -466,11 +472,8 @@ class TagListEditor(QDialog, Ui_TagListEditor): item.setFlags(item.flags() & ~(Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable)) self.table.setItem(row, 1, item) - item = QTableWidgetItem() - item.setFlags(item.flags() & ~(Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable)) - if _id in self.to_rename or _id in self.to_delete: - item.setData(Qt.ItemDataRole.DisplayRole, tag) - self.table.setItem(row, 2, item) + was = tag if _id in self.to_rename or _id in self.to_delete else '' + self.table.setItem(row, 2, was_item(was)) item = QTableWidgetItem() if self.link_map is None: @@ -591,6 +594,9 @@ class TagListEditor(QDialog, Ui_TagListEditor): id_ = int(item.data(Qt.ItemDataRole.UserRole)) self.to_rename[id_] = new_text orig = self.table.item(item.row(), 2) + if orig is None: + orig = was_item() + self.table.setItem(item.row(), 2, orig) item.setText(new_text) orig.setData(Qt.ItemDataRole.DisplayRole, item.initial_text()) self.table.blockSignals(False) From cb97b2b1fa8d140529e813dbcebbb2a97d0ec973 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 22 Apr 2023 10:14:26 +0530 Subject: [PATCH 0528/2055] Allow not saving initial dir in choose_files as well --- src/calibre/gui2/linux_file_dialogs.py | 18 ++++++++++++------ src/calibre/gui2/qt_file_dialogs.py | 4 ++-- src/calibre/gui2/win_file_dialogs.py | 6 +++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/calibre/gui2/linux_file_dialogs.py b/src/calibre/gui2/linux_file_dialogs.py index cd394dd61e..f671913441 100644 --- a/src/calibre/gui2/linux_file_dialogs.py +++ b/src/calibre/gui2/linux_file_dialogs.py @@ -178,14 +178,17 @@ def kdialog_choose_files( filters=[], all_files=True, select_only_single_file=False, - default_dir='~'): - initial_dir = get_initial_dir(name, title, default_dir, False) + default_dir='~', + no_save_dir=False, +): + initial_dir = get_initial_dir(name, title, default_dir, no_save_dir) args = [] if not select_only_single_file: args += '--multiple --separate-output'.split() args += ['--getopenfilename', initial_dir, kdialog_filters(filters, all_files)] ans = run_kde(kde_cmd(window, title, *args)) - save_initial_dir(name, title, ans[0] if ans else None, False, is_file=True) + if not no_save_dir: + save_initial_dir(name, title, ans[0] if ans else None, False, is_file=True) return ans @@ -258,14 +261,17 @@ def zenity_choose_files( filters=[], all_files=True, select_only_single_file=False, - default_dir='~'): - initial_dir = get_initial_dir(name, title, default_dir, False) + default_dir='~', + no_save_dir=False, +): + initial_dir = get_initial_dir(name, title, default_dir, no_save_dir) args = ['--filename=' + os.path.join(initial_dir, '.fgdfg.gdfhjdhf*&^839')] args += zenity_filters(filters, all_files) if not select_only_single_file: args.append('--multiple') ans = run_zenity(zenity_cmd(window, title, *args)) - save_initial_dir(name, title, ans[0] if ans else None, False, is_file=True) + if not no_save_dir: + save_initial_dir(name, title, ans[0] if ans else None, False, is_file=True) return ans diff --git a/src/calibre/gui2/qt_file_dialogs.py b/src/calibre/gui2/qt_file_dialogs.py index 77f94d3e82..b7c387c1b3 100644 --- a/src/calibre/gui2/qt_file_dialogs.py +++ b/src/calibre/gui2/qt_file_dialogs.py @@ -193,7 +193,7 @@ def choose_dir(window, name, title, default_dir='~', no_save_dir=False): def choose_files(window, name, title, - filters=[], all_files=True, select_only_single_file=False, default_dir='~'): + filters=[], all_files=True, select_only_single_file=False, default_dir='~', no_save_dir=False): ''' Ask user to choose a bunch of files. :param name: Unique dialog name used to store the opened directory @@ -207,7 +207,7 @@ def choose_files(window, name, title, ''' mode = QFileDialog.FileMode.ExistingFile if select_only_single_file else QFileDialog.FileMode.ExistingFiles fd = FileDialog(title=title, name=name, filters=filters, default_dir=default_dir, - parent=window, add_all_files_filter=all_files, mode=mode, + parent=window, add_all_files_filter=all_files, mode=mode, no_save_dir=no_save_dir, ) fd.setParent(None) if fd.accepted: diff --git a/src/calibre/gui2/win_file_dialogs.py b/src/calibre/gui2/win_file_dialogs.py index 52e0587781..b8b9007fd5 100644 --- a/src/calibre/gui2/win_file_dialogs.py +++ b/src/calibre/gui2/win_file_dialogs.py @@ -260,13 +260,13 @@ def choose_dir(window, name, title, default_dir='~', no_save_dir=False): def choose_files(window, name, title, - filters=(), all_files=True, select_only_single_file=False, default_dir='~'): - name, initial_folder = get_initial_folder(name, title, default_dir) + filters=(), all_files=True, select_only_single_file=False, default_dir='~', no_save_dir=False): + name, initial_folder = get_initial_folder(name, title, default_dir, no_save_dir) file_types = list(filters) if all_files: file_types.append((_('All files'), ['*'])) ans = run_file_dialog(window, title, allow_multiple=not select_only_single_file, initial_folder=initial_folder, file_types=file_types) - if ans: + if ans and not no_save_dir: dynamic.set(name, os.path.dirname(ans[0])) return ans return None From 679877607d05518779611d3436d950290e74e10d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 22 Apr 2023 10:56:12 +0530 Subject: [PATCH 0529/2055] Comments editor: Add buttons to create links to data files and also to folders --- src/calibre/gui2/comments_editor.py | 64 ++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index 71599805cf..5fbd0dd042 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -11,17 +11,17 @@ from html5_parser import parse from lxml import html from qt.core import ( QAction, QApplication, QBrush, QByteArray, QCheckBox, QColor, QColorDialog, QDialog, - QDialogButtonBox, QFont, QFontInfo, QFontMetrics, QFormLayout, QIcon, QKeySequence, - QLabel, QLineEdit, QMenu, QPalette, QPlainTextEdit, QPushButton, QSize, - QSyntaxHighlighter, Qt, QTabWidget, QTextBlockFormat, QTextCharFormat, QTextCursor, - QTextEdit, QTextFormat, QTextListFormat, QTimer, QToolButton, QUrl, QVBoxLayout, - QWidget, pyqtSignal, pyqtSlot, + QDialogButtonBox, QFont, QFontInfo, QFontMetrics, QFormLayout, QHBoxLayout, QIcon, + QKeySequence, QLabel, QLineEdit, QMenu, QPalette, QPlainTextEdit, QPushButton, + QSize, QSyntaxHighlighter, Qt, QTabWidget, QTextBlockFormat, QTextCharFormat, + QTextCursor, QTextEdit, QTextFormat, QTextListFormat, QTimer, QToolButton, QUrl, + QVBoxLayout, QWidget, pyqtSignal, pyqtSlot, ) from calibre import xml_replace_entities from calibre.ebooks.chardet import xml_to_unicode from calibre.gui2 import ( - NO_URL_FORMATTING, choose_files, error_dialog, gprefs, is_dark_theme, + NO_URL_FORMATTING, choose_dir, choose_files, error_dialog, gprefs, is_dark_theme, ) from calibre.gui2.book_details import css from calibre.gui2.flow_toolbar import create_flow_toolbar @@ -650,7 +650,10 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{ return url = self.parse_link(link) if url.isValid(): - url = str(url.toString(NO_URL_FORMATTING)) + if url.isLocalFile() and not os.path.isabs(url.toLocalFile()): + url = url.toLocalFile() + else: + url = url.toString(NO_URL_FORMATTING) self.focus_self() with self.editing_cursor() as c: if is_image: @@ -702,24 +705,46 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{ d.treat_as_image = QCheckBox(d) d.setMinimumWidth(600) d.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok|QDialogButtonBox.StandardButton.Cancel) - d.br = b = QPushButton(_('&Browse')) - b.setIcon(QIcon.ic('document_open.png')) + d.br = b = QPushButton(_('&File')) + base = os.path.dirname(self.base_url.toLocalFile()) if self.base_url else os.getcwd() + data_path = os.path.join(base, 'data') + if self.base_url: + os.makedirs(data_path, exist_ok=True) - def cf(): + def cf(data_dir=False): filetypes = [] if d.treat_as_image.isChecked(): filetypes = [(_('Images'), 'png jpeg jpg gif'.split())] - files = choose_files(d, 'select link file', _('Choose file'), filetypes, select_only_single_file=True) + if data_dir: + files = choose_files( + d, 'select link file', _('Choose file'), filetypes, select_only_single_file=True, no_save_dir=True, + default_dir=data_path) + else: + files = choose_files(d, 'select link file', _('Choose file'), filetypes, select_only_single_file=True) if files: path = files[0] d.url.setText(path) if path and os.path.exists(path): with open(path, 'rb') as f: q = what(f) - is_image = q in {'jpeg', 'png', 'gif'} + is_image = q in {'jpeg', 'png', 'gif', 'webp'} d.treat_as_image.setChecked(is_image) + if data_dir: + path = os.path.relpath(path, base) + d.url.setText(path) + b.clicked.connect(lambda: cf()) + d.brdf = b = QPushButton(_('&Data file')) + b.clicked.connect(lambda: cf(True)) + b.setToolTip(_('A relative link to a data file associated with this book')) + if not os.path.exists(data_path): + b.setVisible(False) + d.brd = b = QPushButton(_('F&older')) + def cd(): + path = choose_dir(d, 'select link folder', _('Choose folder')) + if path: + d.url.setText(path) + b.clicked.connect(cd) - b.clicked.connect(cf) d.la = la = QLabel(_( 'Enter a URL. If you check the "Treat the URL as an image" box ' 'then the URL will be added as an image reference instead of as ' @@ -733,7 +758,9 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{ l.addRow(_('Enter &URL:'), d.url) l.addRow(_('Treat the URL as an &image'), d.treat_as_image) l.addRow(_('Enter &name (optional):'), d.name) - l.addRow(_('Choose a file on your computer:'), d.br) + h = QHBoxLayout() + h.addWidget(d.br), h.addWidget(d.brdf), h.addWidget(d.brd) + l.addRow(_('Choose a file on your computer:'), h) l.addRow(d.bb) d.bb.accepted.connect(d.accept) d.bb.rejected.connect(d.reject) @@ -753,6 +780,14 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{ url = QUrl(link, QUrl.ParsingMode.TolerantMode) if url.isValid(): return url + else: + if self.base_url: + base = os.path.dirname(self.base_url.toLocalFile()) + else: + base = os.getcwd() + candidate = os.path.join(base, link) + if os.path.exists(candidate): + return QUrl.fromLocalFile(link) if os.path.exists(link): return QUrl.fromLocalFile(link) @@ -1260,6 +1295,7 @@ if __name__ == '__main__': from calibre.gui2 import Application app = Application([]) w = Editor(one_line_toolbar=False) + w.set_base_url(QUrl.fromLocalFile(os.getcwd())) w.resize(800, 600) w.setWindowFlag(Qt.WindowType.Dialog) w.show() From dba94e1b5bf0f4e269833e0c97a89a6ca12ebe7c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 22 Apr 2023 11:01:56 +0530 Subject: [PATCH 0530/2055] DRYer --- src/calibre/db/backend.py | 1 + src/calibre/ebooks/metadata/book/render.py | 19 +++++++++++-------- src/calibre/gui2/actions/view.py | 7 ++++--- src/calibre/gui2/book_details.py | 3 ++- src/calibre/gui2/comments_editor.py | 3 ++- src/calibre/library/check_library.py | 6 ++++-- 6 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index cc90ceb1f2..75e381f89d 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -60,6 +60,7 @@ COVER_FILE_NAME = 'cover.jpg' METADATA_FILE_NAME = 'metadata.opf' DEFAULT_TRASH_EXPIRY_TIME_SECONDS = 14 * 86400 TRASH_DIR_NAME = '.caltrash' +DATA_DIR_NAME = 'data' BOOK_ID_PATH_TEMPLATE = ' ({})' CUSTOM_DATA_TYPES = frozenset(('rating', 'text', 'comments', 'datetime', 'int', 'float', 'bool', 'series', 'composite', 'enumeration')) diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index 7c16fdab53..f5400ce73f 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -5,18 +5,21 @@ __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' import os -from functools import partial from contextlib import suppress +from functools import partial -from calibre import prepare_string_for_xml, force_unicode -from calibre.ebooks.metadata import fmt_sidx, rating_to_stars -from calibre.ebooks.metadata.search_internet import name_for, url_for_author_search, url_for_book_search, qquote, DEFAULT_AUTHOR_SOURCE -from calibre.ebooks.metadata.sources.identify import urls_from_identifiers +from calibre import force_unicode, prepare_string_for_xml from calibre.constants import filesystem_encoding +from calibre.db.backend import DATA_DIR_NAME +from calibre.ebooks.metadata import fmt_sidx, rating_to_stars +from calibre.ebooks.metadata.search_internet import ( + DEFAULT_AUTHOR_SOURCE, name_for, qquote, url_for_author_search, url_for_book_search, +) +from calibre.ebooks.metadata.sources.identify import urls_from_identifiers from calibre.library.comments import comments_to_html, markdown -from calibre.utils.icu import sort_key +from calibre.utils.date import format_date, is_date_undefined from calibre.utils.formatter import EvalFormatter -from calibre.utils.date import is_date_undefined, format_date +from calibre.utils.icu import sort_key from calibre.utils.localization import calibre_langcode_to_name from calibre.utils.serialize import json_dumps from polyglot.binary import as_hex_unicode @@ -213,7 +216,7 @@ def mi_to_html( link = '{}{}'.format(action(scheme, book_id=book_id, loc=loc), prepare_string_for_xml(path, True), _('Click to open'), extra) if not isdevice: - data_path = os.path.join(path, 'data') + data_path = os.path.join(path, DATA_DIR_NAME) with suppress(OSError): if os.listdir(data_path): link += ' \xa0 {}'.format( diff --git a/src/calibre/gui2/actions/view.py b/src/calibre/gui2/actions/view.py index f949547dd0..738e6770e5 100644 --- a/src/calibre/gui2/actions/view.py +++ b/src/calibre/gui2/actions/view.py @@ -9,12 +9,13 @@ import json import os import time from functools import partial -from qt.core import QAction, QIcon, pyqtSignal, QDialog +from qt.core import QAction, QDialog, QIcon, pyqtSignal from calibre.constants import ismacos, iswindows +from calibre.db.backend import DATA_DIR_NAME from calibre.gui2 import ( Dispatcher, config, elided_text, error_dialog, info_dialog, open_local_file, - question_dialog + question_dialog, ) from calibre.gui2.actions import InterfaceAction from calibre.gui2.dialogs.choose_format import ChooseFormatDialog @@ -283,7 +284,7 @@ class ViewAction(InterfaceAction): def view_data_folder_for_id(self, id_): path = self.gui.library_view.model().db.abspath(id_, index_is_id=True) - open_local_file(os.path.join(path, 'data')) + open_local_file(os.path.join(path, DATA_DIR_NAME)) def view_book(self, triggered): rows = self.gui.current_view().selectionModel().selectedRows() diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index fc369d437c..0eade46865 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -15,6 +15,7 @@ from qt.core import ( from calibre import fit_image, sanitize_file_name from calibre.constants import config_dir, iswindows +from calibre.db.backend import DATA_DIR_NAME from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks.metadata.book.base import Metadata, field_metadata from calibre.ebooks.metadata.book.render import mi_to_html @@ -476,7 +477,7 @@ def create_copy_links(menu, data=None): link(_('Link to show book in calibre'), f'calibre://show-book/{library_id}/{book_id}') link(_('Link to show book details in a popup window'), f'calibre://book-details/{library_id}/{book_id}') mi = db.new_api.get_proxy_metadata(book_id) - data_path = os.path.join(db.backend.library_path, mi.path, 'data') + data_path = os.path.join(db.backend.library_path, mi.path, DATA_DIR_NAME) with suppress(OSError): if os.listdir(data_path): if iswindows: diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index 5fbd0dd042..2761e89f30 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -19,6 +19,7 @@ from qt.core import ( ) from calibre import xml_replace_entities +from calibre.db.backend import DATA_DIR_NAME from calibre.ebooks.chardet import xml_to_unicode from calibre.gui2 import ( NO_URL_FORMATTING, choose_dir, choose_files, error_dialog, gprefs, is_dark_theme, @@ -707,7 +708,7 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{ d.bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok|QDialogButtonBox.StandardButton.Cancel) d.br = b = QPushButton(_('&File')) base = os.path.dirname(self.base_url.toLocalFile()) if self.base_url else os.getcwd() - data_path = os.path.join(base, 'data') + data_path = os.path.join(base, DATA_DIR_NAME) if self.base_url: os.makedirs(data_path, exist_ok=True) diff --git a/src/calibre/library/check_library.py b/src/calibre/library/check_library.py index eb5778a819..655e5599e8 100644 --- a/src/calibre/library/check_library.py +++ b/src/calibre/library/check_library.py @@ -12,13 +12,15 @@ import traceback from calibre import isbytestring from calibre.constants import filesystem_encoding -from calibre.db.backend import COVER_FILE_NAME, METADATA_FILE_NAME, TRASH_DIR_NAME +from calibre.db.backend import ( + COVER_FILE_NAME, DATA_DIR_NAME, METADATA_FILE_NAME, TRASH_DIR_NAME, +) from calibre.ebooks import BOOK_EXTENSIONS from calibre.utils.localization import _ from polyglot.builtins import iteritems EBOOK_EXTENSIONS = frozenset(BOOK_EXTENSIONS) -NORMALS = frozenset({METADATA_FILE_NAME, COVER_FILE_NAME, 'data'}) +NORMALS = frozenset({METADATA_FILE_NAME, COVER_FILE_NAME, DATA_DIR_NAME}) IGNORE_AT_TOP_LEVEL = frozenset({'metadata.db', 'metadata_db_prefs_backup.json', 'metadata_pre_restore.db', 'full-text-search.db', TRASH_DIR_NAME}) ''' From a91889f3901c5599267d125afd6c49414aeeb085 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 22 Apr 2023 11:07:52 +0530 Subject: [PATCH 0531/2055] Move constants used by db backend into a separate module so we dont need to import the full backend everywhere else --- src/calibre/db/backend.py | 22 ++++------------------ src/calibre/db/constants.py | 22 ++++++++++++++++++++++ src/calibre/db/restore.py | 3 ++- src/calibre/db/tests/add_remove.py | 2 +- src/calibre/ebooks/metadata/book/render.py | 2 +- src/calibre/gui2/actions/view.py | 2 +- src/calibre/gui2/book_details.py | 2 +- src/calibre/gui2/comments_editor.py | 2 +- src/calibre/gui2/trash.py | 2 +- src/calibre/library/check_library.py | 2 +- 10 files changed, 35 insertions(+), 26 deletions(-) create mode 100644 src/calibre/db/constants.py diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 75e381f89d..111d80c647 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -16,9 +16,7 @@ import sys import time import uuid from contextlib import closing, suppress -from dataclasses import dataclass from functools import partial -from typing import Sequence from calibre import as_unicode, force_unicode, isbytestring, prints from calibre.constants import ( @@ -26,6 +24,10 @@ from calibre.constants import ( ) from calibre.db import SPOOL_SIZE, FTSQueryError from calibre.db.annotations import annot_db_data, unicode_normalize +from calibre.db.constants import ( + BOOK_ID_PATH_TEMPLATE, COVER_FILE_NAME, DEFAULT_TRASH_EXPIRY_TIME_SECONDS, + METADATA_FILE_NAME, TRASH_DIR_NAME, TrashEntry, +) from calibre.db.errors import NoSuchFormat from calibre.db.schema_upgrades import SchemaUpgrade from calibre.db.tables import ( @@ -56,27 +58,11 @@ from polyglot.builtins import ( # }}} -COVER_FILE_NAME = 'cover.jpg' -METADATA_FILE_NAME = 'metadata.opf' -DEFAULT_TRASH_EXPIRY_TIME_SECONDS = 14 * 86400 -TRASH_DIR_NAME = '.caltrash' -DATA_DIR_NAME = 'data' -BOOK_ID_PATH_TEMPLATE = ' ({})' CUSTOM_DATA_TYPES = frozenset(('rating', 'text', 'comments', 'datetime', 'int', 'float', 'bool', 'series', 'composite', 'enumeration')) WINDOWS_RESERVED_NAMES = frozenset('CON PRN AUX NUL COM1 COM2 COM3 COM4 COM5 COM6 COM7 COM8 COM9 LPT1 LPT2 LPT3 LPT4 LPT5 LPT6 LPT7 LPT8 LPT9'.split()) -@dataclass -class TrashEntry: - book_id: int - title: str - author: str - cover_path: str - mtime: float - formats: Sequence[str] = () - - class DynamicFilter: # {{{ 'No longer used, present for legacy compatibility' diff --git a/src/calibre/db/constants.py b/src/calibre/db/constants.py new file mode 100644 index 0000000000..821027b61b --- /dev/null +++ b/src/calibre/db/constants.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# License: GPLv3 Copyright: 2023, Kovid Goyal + +from dataclasses import dataclass +from typing import Sequence + +COVER_FILE_NAME = 'cover.jpg' +METADATA_FILE_NAME = 'metadata.opf' +DEFAULT_TRASH_EXPIRY_TIME_SECONDS = 14 * 86400 +TRASH_DIR_NAME = '.caltrash' +DATA_DIR_NAME = 'data' +BOOK_ID_PATH_TEMPLATE = ' ({})' + + +@dataclass +class TrashEntry: + book_id: int + title: str + author: str + cover_path: str + mtime: float + formats: Sequence[str] = () diff --git a/src/calibre/db/restore.py b/src/calibre/db/restore.py index 24c0f1db37..ebe284036c 100644 --- a/src/calibre/db/restore.py +++ b/src/calibre/db/restore.py @@ -17,7 +17,8 @@ from threading import Thread from calibre import force_unicode, isbytestring from calibre.constants import filesystem_encoding -from calibre.db.backend import DB, METADATA_FILE_NAME, TRASH_DIR_NAME, DBPrefs +from calibre.db.backend import DB, DBPrefs +from calibre.db.constants import METADATA_FILE_NAME, TRASH_DIR_NAME from calibre.db.cache import Cache from calibre.ebooks.metadata.opf2 import OPF from calibre.ptempfile import TemporaryDirectory diff --git a/src/calibre/db/tests/add_remove.py b/src/calibre/db/tests/add_remove.py index 5a7c514be6..6e2d84e09f 100644 --- a/src/calibre/db/tests/add_remove.py +++ b/src/calibre/db/tests/add_remove.py @@ -12,7 +12,7 @@ from datetime import timedelta from io import BytesIO from tempfile import NamedTemporaryFile -from calibre.db.backend import METADATA_FILE_NAME +from calibre.db.constants import METADATA_FILE_NAME from calibre.db.tests.base import IMG, BaseTest from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.date import UNDEFINED_DATE, now, utcnow diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index f5400ce73f..b4a8fde5b0 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -10,7 +10,7 @@ from functools import partial from calibre import force_unicode, prepare_string_for_xml from calibre.constants import filesystem_encoding -from calibre.db.backend import DATA_DIR_NAME +from calibre.db.constants import DATA_DIR_NAME from calibre.ebooks.metadata import fmt_sidx, rating_to_stars from calibre.ebooks.metadata.search_internet import ( DEFAULT_AUTHOR_SOURCE, name_for, qquote, url_for_author_search, url_for_book_search, diff --git a/src/calibre/gui2/actions/view.py b/src/calibre/gui2/actions/view.py index 738e6770e5..2c74390189 100644 --- a/src/calibre/gui2/actions/view.py +++ b/src/calibre/gui2/actions/view.py @@ -12,7 +12,7 @@ from functools import partial from qt.core import QAction, QDialog, QIcon, pyqtSignal from calibre.constants import ismacos, iswindows -from calibre.db.backend import DATA_DIR_NAME +from calibre.db.constants import DATA_DIR_NAME from calibre.gui2 import ( Dispatcher, config, elided_text, error_dialog, info_dialog, open_local_file, question_dialog, diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 0eade46865..225db98b5d 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -15,7 +15,7 @@ from qt.core import ( from calibre import fit_image, sanitize_file_name from calibre.constants import config_dir, iswindows -from calibre.db.backend import DATA_DIR_NAME +from calibre.db.constants import DATA_DIR_NAME from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks.metadata.book.base import Metadata, field_metadata from calibre.ebooks.metadata.book.render import mi_to_html diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index 2761e89f30..0aae628075 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -19,7 +19,7 @@ from qt.core import ( ) from calibre import xml_replace_entities -from calibre.db.backend import DATA_DIR_NAME +from calibre.db.constants import DATA_DIR_NAME from calibre.ebooks.chardet import xml_to_unicode from calibre.gui2 import ( NO_URL_FORMATTING, choose_dir, choose_files, error_dialog, gprefs, is_dark_theme, diff --git a/src/calibre/gui2/trash.py b/src/calibre/gui2/trash.py index 60ed7d80b3..1633c0ddcc 100644 --- a/src/calibre/gui2/trash.py +++ b/src/calibre/gui2/trash.py @@ -12,7 +12,7 @@ from qt.core import ( from typing import Iterator, List from calibre import fit_image -from calibre.db.backend import DEFAULT_TRASH_EXPIRY_TIME_SECONDS, TrashEntry +from calibre.db.constants import DEFAULT_TRASH_EXPIRY_TIME_SECONDS, TrashEntry from calibre.gui2 import error_dialog from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.widgets import BusyCursor diff --git a/src/calibre/library/check_library.py b/src/calibre/library/check_library.py index 655e5599e8..a7262d3ae6 100644 --- a/src/calibre/library/check_library.py +++ b/src/calibre/library/check_library.py @@ -12,7 +12,7 @@ import traceback from calibre import isbytestring from calibre.constants import filesystem_encoding -from calibre.db.backend import ( +from calibre.db.constants import ( COVER_FILE_NAME, DATA_DIR_NAME, METADATA_FILE_NAME, TRASH_DIR_NAME, ) from calibre.ebooks import BOOK_EXTENSIONS From 89391da7b7074b42aad7c440e97ac76cfd18175a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 22 Apr 2023 11:20:05 +0530 Subject: [PATCH 0532/2055] Fix another error in the manage category editor when setting was --- src/calibre/gui2/dialogs/tag_list_editor.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index aaff6a7cff..21e17ec5e8 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -593,12 +593,8 @@ class TagListEditor(QDialog, Ui_TagListEditor): for item in items: id_ = int(item.data(Qt.ItemDataRole.UserRole)) self.to_rename[id_] = new_text - orig = self.table.item(item.row(), 2) - if orig is None: - orig = was_item() - self.table.setItem(item.row(), 2, orig) + self.set_was_from_item(item) item.setText(new_text) - orig.setData(Qt.ItemDataRole.DisplayRole, item.initial_text()) self.table.blockSignals(False) def undo_edit(self): @@ -717,8 +713,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.to_delete.add(id_) item.set_is_deleted(True) row = item.row() - orig = self.table.item(row, 2) - orig.setData(Qt.ItemDataRole.DisplayRole, item.initial_text()) + self.set_was_from_item(item) link = self.table.item(row, 3) link.setFlags(link.flags() & ~(Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable)) link.setIcon(QIcon.ic('trash.png')) @@ -728,6 +723,13 @@ class TagListEditor(QDialog, Ui_TagListEditor): if row >= 0: self.table.scrollToItem(self.table.item(row, 0)) + def set_was_from_item(self, item): + orig = self.table.item(item.row(), 2) + if orig is None: + orig = was_item() + self.table.setItem(item.row(), 2, orig) + orig.setData(Qt.ItemDataRole.DisplayRole, item.initial_text()) + def record_sort(self, section): # Note what sort was done so we can redo it when the table is rebuilt sort_name = self.sort_names[section] From 36203a7497c4f2be480d0cea79cafd7b1051668f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 22 Apr 2023 13:10:34 +0530 Subject: [PATCH 0533/2055] Functions to optimize and encode webp images --- bypy/linux/__main__.py | 2 +- bypy/macos/__main__.py | 2 +- bypy/windows/__main__.py | 2 +- src/calibre/utils/img.py | 34 +++++++++++++++++++++++++++++++--- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/bypy/linux/__main__.py b/bypy/linux/__main__.py index 1c13f5b53c..a8660ac424 100644 --- a/bypy/linux/__main__.py +++ b/bypy/linux/__main__.py @@ -38,7 +38,7 @@ qt_get_dll_path = partial(get_dll_path, loc=os.path.join(QT_PREFIX, 'lib')) def binary_includes(): return [ - j(PREFIX, 'bin', x) for x in ('pdftohtml', 'pdfinfo', 'pdftoppm', 'pdftotext', 'optipng', 'JxrDecApp')] + [ + j(PREFIX, 'bin', x) for x in ('pdftohtml', 'pdfinfo', 'pdftoppm', 'pdftotext', 'optipng', 'cwebp', 'JxrDecApp')] + [ j(PREFIX, 'private', 'mozjpeg', 'bin', x) for x in ('jpegtran', 'cjpeg')] + [ ] + list(map( diff --git a/bypy/macos/__main__.py b/bypy/macos/__main__.py index d0cef9a15e..c3c2643486 100644 --- a/bypy/macos/__main__.py +++ b/bypy/macos/__main__.py @@ -493,7 +493,7 @@ class Freeze: print('\nAdding libjpeg, libpng, libwebp, optipng and mozjpeg') for x in ('jpeg.8', 'png16.16', 'webp.7', 'webpmux.3', 'webpdemux.2'): self.install_dylib(join(PREFIX, 'lib', 'lib%s.dylib' % x)) - for x in 'optipng', 'JxrDecApp': + for x in 'optipng', 'JxrDecApp', 'cwebp': self.install_dylib(join(PREFIX, 'bin', x), set_id=False, dest=self.helpers_dir) for x in ('jpegtran', 'cjpeg'): self.install_dylib( diff --git a/bypy/windows/__main__.py b/bypy/windows/__main__.py index c9bd1c7d83..ecf642975b 100644 --- a/bypy/windows/__main__.py +++ b/bypy/windows/__main__.py @@ -134,7 +134,7 @@ def freeze(env, ext_dir, incdir): shutil.copy2(x + '.manifest', env.dll_dir) bindir = os.path.join(PREFIX, 'bin') - for x in ('pdftohtml', 'pdfinfo', 'pdftoppm', 'pdftotext', 'jpegtran-calibre', 'cjpeg-calibre', 'optipng-calibre', 'JXRDecApp-calibre'): + for x in ('pdftohtml', 'pdfinfo', 'pdftoppm', 'pdftotext', 'jpegtran-calibre', 'cjpeg-calibre', 'optipng-calibre', 'cwebp-calibre', 'JXRDecApp-calibre'): copybin(os.path.join(bindir, x + '.exe')) for f in glob.glob(os.path.join(bindir, '*.dll')): if re.search(r'(easylzma|icutest)', f.lower()) is None: diff --git a/src/calibre/utils/img.py b/src/calibre/utils/img.py index e07cdac79e..690a4f81d5 100644 --- a/src/calibre/utils/img.py +++ b/src/calibre/utils/img.py @@ -173,7 +173,8 @@ def image_to_data(img, compression_quality=95, fmt='JPEG', png_compression_level ''' Serialize image to bytestring in the specified format. - :param compression_quality: is for JPEG and goes from 0 to 100. 100 being lowest compression, highest image quality + :param compression_quality: is for JPEG and WEBP and goes from 0 to 100. + 100 being lowest compression, highest image quality. For WEBP 100 means lossless with effort of 70. :param png_compression_level: is for PNG and goes from 0-9. 9 being highest compression. :param jpeg_optimized: Turns on the 'optimize' option for libjpeg which losslessly reduce file size :param jpeg_progressive: Turns on the 'progressive scan' option for libjpeg which allows JPEG images to be downloaded in streaming fashion @@ -202,6 +203,8 @@ def image_to_data(img, compression_quality=95, fmt='JPEG', png_compression_level elif fmt == 'PNG': cl = min(9, max(0, png_compression_level)) w.setQuality(10 * (9-cl)) + elif fmt == 'WEBP': + w.setQuality(compression_quality) if not w.write(img): raise ValueError('Failed to export image as ' + fmt + ' with error: ' + w.errorString()) return ba.data() @@ -548,6 +551,7 @@ def run_optimizer(file_path, cmd, as_filter=False, input_data=None): else: os.close(fd) iname, oname = os.path.basename(file_path), os.path.basename(outfile) + input_size = os.path.getsize(file_path) def repl(q, r): cmd[cmd.index(q)] = r @@ -584,8 +588,9 @@ def run_optimizer(file_path, cmd, as_filter=False, input_data=None): sz = 0 if sz < 1: return '%s returned a zero size image' % cmd[0] - shutil.copystat(file_path, outfile) - atomic_rename(outfile, file_path) + if sz < input_size: + shutil.copystat(file_path, outfile) + atomic_rename(outfile, file_path) finally: try: os.remove(outfile) @@ -612,6 +617,21 @@ def optimize_png(file_path, level=7): return run_optimizer(file_path, cmd) +def run_cwebp(file_path, lossless, q, m, metadata): + exe = get_exe_path('cwebp') + q = max(0, min(q, 100)) + m = max(0, min(m, 6)) + cmd = [exe] + f'-mt -metadata {metadata} -q {q} -m {m} -o'.split() + [False, True] + if lossless: + cmd.insert(1, '-lossless') + return run_optimizer(file_path, cmd) + + +def optimize_webp(file_path, q=100, m=6, metadata='all'): + ' metadata can be a comma seaprated list of all, none, exif, icc, xmp ' + return run_cwebp(file_path, True, q, m, metadata) + + def encode_jpeg(file_path, quality=80): from calibre.utils.speedups import ReadOnlyFileBuffer quality = max(0, min(100, int(quality))) @@ -626,6 +646,10 @@ def encode_jpeg(file_path, quality=80): if not img.save(buf, 'PPM'): raise ValueError('Failed to export image to PPM') return run_optimizer(file_path, cmd, as_filter=True, input_data=ReadOnlyFileBuffer(ba.data())) + + +def encode_webp(file_path, quality=70, m=6, metadata='all'): + return run_cwebp(file_path, False, quality, m, metadata) # }}} @@ -649,6 +673,10 @@ def test(): # {{{ raise SystemExit('optimize_png failed: %s' % ret) if glob('*.bak'): raise SystemExit('Spurious .bak files left behind') + save_image(img, 'test.webp', compression_quality=100) + ret = optimize_webp('test.webp') + if ret is not None: + raise SystemExit('optimize_webp failed: %s' % ret) quantize_image(img) oil_paint_image(img) gaussian_sharpen_image(img) From c6c8fbeb64cbd4d51a18a78543d6c5749771027a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 22 Apr 2023 13:32:19 +0530 Subject: [PATCH 0534/2055] webp default for lossy encoding is q=75 --- src/calibre/utils/img.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/img.py b/src/calibre/utils/img.py index 690a4f81d5..c3f8d86ccd 100644 --- a/src/calibre/utils/img.py +++ b/src/calibre/utils/img.py @@ -648,7 +648,7 @@ def encode_jpeg(file_path, quality=80): return run_optimizer(file_path, cmd, as_filter=True, input_data=ReadOnlyFileBuffer(ba.data())) -def encode_webp(file_path, quality=70, m=6, metadata='all'): +def encode_webp(file_path, quality=75, m=6, metadata='all'): return run_cwebp(file_path, False, quality, m, metadata) # }}} From d3dda1af39916abfa260b458abb1c400cf7ee8ab Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 22 Apr 2023 14:08:52 +0530 Subject: [PATCH 0535/2055] Edit book: Compress images: Support compression of images in the WEBP format as well. Fixes #2017195 [[feature] support webp in epub](https://bugs.launchpad.net/calibre/+bug/2017195) --- src/calibre/ebooks/oeb/polish/images.py | 24 ++++++--- src/calibre/gui2/tweak_book/boss.py | 2 +- src/calibre/gui2/tweak_book/polish.py | 72 ++++++++++++++++--------- 3 files changed, 64 insertions(+), 34 deletions(-) diff --git a/src/calibre/ebooks/oeb/polish/images.py b/src/calibre/ebooks/oeb/polish/images.py index 93b76ec80d..f927b9819c 100644 --- a/src/calibre/ebooks/oeb/polish/images.py +++ b/src/calibre/ebooks/oeb/polish/images.py @@ -4,22 +4,23 @@ import os from functools import partial -from threading import Thread, Event +from threading import Event, Thread -from calibre import detect_ncpus, human_readable, force_unicode, filesystem_encoding +from calibre import detect_ncpus, filesystem_encoding, force_unicode, human_readable from polyglot.builtins import iteritems -from polyglot.queue import Queue, Empty +from polyglot.queue import Empty, Queue class Worker(Thread): daemon = True - def __init__(self, abort, name, queue, results, jpeg_quality, progress_callback): + def __init__(self, abort, name, queue, results, jpeg_quality, webp_quality, progress_callback): Thread.__init__(self, name=name) self.queue, self.results = queue, results self.progress_callback = progress_callback self.jpeg_quality = jpeg_quality + self.webp_quality = webp_quality self.abort = abort self.start() @@ -43,9 +44,16 @@ class Worker(Thread): self.queue.task_done() def compress(self, name, path, mime_type): - from calibre.utils.img import optimize_png, optimize_jpeg, encode_jpeg + from calibre.utils.img import ( + encode_jpeg, encode_webp, optimize_jpeg, optimize_png, optimize_webp, + ) if 'png' in mime_type: func = optimize_png + elif 'webp' in mime_type: + if self.webp_quality is None: + func = optimize_webp + else: + func = partial(encode_webp, quality=self.jpeg_quality) elif self.jpeg_quality is None: func = optimize_jpeg else: @@ -65,12 +73,12 @@ class Worker(Thread): def get_compressible_images(container): mt_map = container.manifest_type_map images = set() - for mt in 'png jpg jpeg'.split(): + for mt in 'png jpg jpeg webp'.split(): images |= set(mt_map.get('image/' + mt, ())) return images -def compress_images(container, report=None, names=None, jpeg_quality=None, progress_callback=lambda n, t, name:True): +def compress_images(container, report=None, names=None, jpeg_quality=None, webp_quality=None, progress_callback=lambda n, t, name:True): images = get_compressible_images(container) if names is not None: images &= set(names) @@ -92,7 +100,7 @@ def compress_images(container, report=None, names=None, jpeg_quality=None, progr if not keep_going: abort.set() progress_callback(0, num_to_process, '') - [Worker(abort, 'CompressImage%d' % i, queue, results, jpeg_quality, pc) for i in range(min(detect_ncpus(), num_to_process))] + [Worker(abort, 'CompressImage%d' % i, queue, results, jpeg_quality, webp_quality, pc) for i in range(min(detect_ncpus(), num_to_process))] queue.join() before_total = after_total = 0 processed_num = 0 diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index dbadd4fde1..bef6d0b2d8 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -1620,7 +1620,7 @@ class Boss(QObject): if d.exec() == QDialog.DialogCode.Accepted: with BusyCursor(): self.add_savepoint(_('Before: compress images')) - d = CompressImagesProgress(names=d.names, jpeg_quality=d.jpeg_quality, parent=self.gui) + d = CompressImagesProgress(names=d.names, jpeg_quality=d.jpeg_quality, webp_quality=d.webp_quality, parent=self.gui) if d.exec() != QDialog.DialogCode.Accepted: self.rewind_savepoint() return diff --git a/src/calibre/gui2/tweak_book/polish.py b/src/calibre/gui2/tweak_book/polish.py index 4ddfa0b2f2..c4f8e0f200 100644 --- a/src/calibre/gui2/tweak_book/polish.py +++ b/src/calibre/gui2/tweak_book/polish.py @@ -9,7 +9,7 @@ from qt.core import ( QAbstractItemView, QApplication, QCheckBox, QDialog, QDialogButtonBox, QHBoxLayout, QIcon, QLabel, QListWidget, QListWidgetItem, QPalette, QPen, QPixmap, QProgressBar, QSize, QSpinBox, QStyle, QStyledItemDelegate, Qt, QTextBrowser, QVBoxLayout, - pyqtSignal, + QWidget, pyqtSignal, ) from threading import Thread @@ -176,6 +176,35 @@ class ImageItemDelegate(QStyledItemDelegate): painter.restore() +class LossyCompression(QWidget): + + def __init__(self, image_type, default_compression=80, parent=None): + super().__init__(parent) + l = QVBoxLayout(self) + image_type = image_type.upper() + self.enable_lossy = el = QCheckBox(_('Enable &lossy compression of {} images').format(image_type)) + el.setToolTip(_('This allows you to change the quality factor used for {} images.\nBy lowering' + ' the quality you can greatly reduce file size, at the expense of the image looking blurred.'.format(image_type))) + l.addWidget(el) + self.h2 = h = QHBoxLayout() + l.addLayout(h) + self.jq = jq = QSpinBox(self) + image_type = image_type.lower() + self.image_type = image_type + self.quality_pref_name = f'{image_type}_compression_quality_for_lossless_compression' + jq.setMinimum(1), jq.setMaximum(100), jq.setValue(tprefs.get(self.quality_pref_name, default_compression)) + jq.setEnabled(False) + jq.setToolTip(_('The image quality, 1 is high compression with low image quality, 100 is low compression with high image quality')) + jq.valueChanged.connect(self.save_compression_quality) + el.toggled.connect(jq.setEnabled) + self.jql = la = QLabel(_('Image &quality:')) + la.setBuddy(jq) + h.addWidget(la), h.addWidget(jq) + + def save_compression_quality(self): + tprefs.set(self.quality_pref_name, self.jq.value()) + + class CompressImages(Dialog): def __init__(self, parent=None): @@ -203,38 +232,30 @@ class CompressImages(Dialog): ' without affecting image quality. Typically image size is reduced by 5 - 15%.')) la.setWordWrap(True) la.setMinimumWidth(250) - l.addWidget(la), l.addSpacing(30) - - self.enable_lossy = el = QCheckBox(_('Enable &lossy compression of JPEG images')) - el.setToolTip(_('This allows you to change the quality factor used for JPEG images.\nBy lowering' - ' the quality you can greatly reduce file size, at the expense of the image looking blurred.')) - l.addWidget(el) - self.h2 = h = QHBoxLayout() - l.addLayout(h) - self.jq = jq = QSpinBox(self) - jq.setMinimum(0), jq.setMaximum(100), jq.setValue(tprefs.get('jpeg_compression_quality_for_lossless_compression', 80)), jq.setEnabled(False) - jq.setToolTip(_('The compression quality, 1 is high compression, 100 is low compression.\nImage' - ' quality is inversely correlated with compression quality.')) - jq.valueChanged.connect(self.save_compression_quality) - el.toggled.connect(jq.setEnabled) - self.jql = la = QLabel(_('Compression &quality:')) - la.setBuddy(jq) - h.addWidget(la), h.addWidget(jq) + l.addWidget(la) + self.jpeg = LossyCompression('jpeg', parent=self) + l.addSpacing(30), l.addWidget(self.jpeg) + self.webp = LossyCompression('webp', default_compression=75, parent=self) + l.addSpacing(30), l.addWidget(self.webp) l.addStretch(10) l.addWidget(self.bb) - def save_compression_quality(self): - tprefs.set('jpeg_compression_quality_for_lossless_compression', self.jq.value()) - @property def names(self): return {item.text() for item in self.images.selectedItems()} @property def jpeg_quality(self): - if not self.enable_lossy.isChecked(): + if not self.jpeg.enable_lossy.isChecked(): return None - return self.jq.value() + return self.jpeg.jq.value() + + @property + def webp_quality(self): + if not self.webp.enable_lossy.isChecked(): + return None + return self.webp.jq.value() + class CompressImagesProgress(Dialog): @@ -242,8 +263,9 @@ class CompressImagesProgress(Dialog): gui_loop = pyqtSignal(object, object, object) cidone = pyqtSignal() - def __init__(self, names=None, jpeg_quality=None, parent=None): + def __init__(self, names=None, jpeg_quality=None, webp_quality=None, parent=None): self.names, self.jpeg_quality = names, jpeg_quality + self.webp_quality = webp_quality self.keep_going = True self.result = (None, '') Dialog.__init__(self, _('Compressing images...'), 'compress-images-progress', parent=parent) @@ -259,7 +281,7 @@ class CompressImagesProgress(Dialog): report = [] try: self.result = (compress_images( - current_container(), report=report.append, names=self.names, jpeg_quality=self.jpeg_quality, + current_container(), report=report.append, names=self.names, jpeg_quality=self.jpeg_quality, webp_quality=self.webp_quality, progress_callback=self.progress_callback )[0], report) except Exception: From cf520a088427461a32e86a5eb6600e22a03c6015 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 22 Apr 2023 14:33:20 +0530 Subject: [PATCH 0536/2055] Dont use email to close launchpad bugs Their email service appears to be broken. So use the fast responder script instead. --- setup/git_pre_commit_hook.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/setup/git_pre_commit_hook.py b/setup/git_pre_commit_hook.py index 742b88229b..4ecb73cd4e 100755 --- a/setup/git_pre_commit_hook.py +++ b/setup/git_pre_commit_hook.py @@ -2,15 +2,15 @@ # License: GPLv3 Copyright: 2008, Kovid Goyal -import importlib import json +import os import re import socket +import subprocess import sys import urllib.error import urllib.parse import urllib.request - from lxml import html ''' @@ -21,7 +21,7 @@ message with the summary of the closed bug. ''' -SENDMAIL = ('/home/kovid/work/env', 'pgp_mail') +LAUNCHPAD = os.path.expanduser('~/work/env/launchpad.py') LAUNCHPAD_BUG = 'https://bugs.launchpad.net/calibre/+bug/%s' GITHUB_BUG = 'https://api.github.com/repos/kovidgoyal/calibre/issues/%s' BUG_PAT = r'(Fix|Implement|Fixes|Fixed|Implemented|See)\s+#(\d+)' @@ -67,14 +67,9 @@ class Bug: action += 'ed' msg = '{} in branch {}. {}'.format(action, 'master', suffix) msg = msg.replace('Fixesed', 'Fixed') - msg += '\n\n status fixreleased' - - sys.path.insert(0, SENDMAIL[0]) - - sendmail = importlib.import_module(SENDMAIL[1]) - - to = bug + '@bugs.launchpad.net' - sendmail.sendmail(msg, to, 'Fixed in master') + env = dict(os.environ) + env['LAUNCHPAD_FIX_BUG'] = msg + subprocess.run([sys.executable, LAUNCHPAD], env=env, input=f'Subject: [Bug ({bug})]', text=True, check=True) def main(): From 2aef0e623203ade9f393d2173da5257d8073598c Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 22 Apr 2023 12:58:30 +0100 Subject: [PATCH 0537/2055] Revert "Fix another error in the manage category editor when setting was" This reverts commit 89391da7b7074b42aad7c440e97ac76cfd18175a. --- src/calibre/gui2/dialogs/tag_list_editor.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index 21e17ec5e8..aaff6a7cff 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -593,8 +593,12 @@ class TagListEditor(QDialog, Ui_TagListEditor): for item in items: id_ = int(item.data(Qt.ItemDataRole.UserRole)) self.to_rename[id_] = new_text - self.set_was_from_item(item) + orig = self.table.item(item.row(), 2) + if orig is None: + orig = was_item() + self.table.setItem(item.row(), 2, orig) item.setText(new_text) + orig.setData(Qt.ItemDataRole.DisplayRole, item.initial_text()) self.table.blockSignals(False) def undo_edit(self): @@ -713,7 +717,8 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.to_delete.add(id_) item.set_is_deleted(True) row = item.row() - self.set_was_from_item(item) + orig = self.table.item(row, 2) + orig.setData(Qt.ItemDataRole.DisplayRole, item.initial_text()) link = self.table.item(row, 3) link.setFlags(link.flags() & ~(Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable)) link.setIcon(QIcon.ic('trash.png')) @@ -723,13 +728,6 @@ class TagListEditor(QDialog, Ui_TagListEditor): if row >= 0: self.table.scrollToItem(self.table.item(row, 0)) - def set_was_from_item(self, item): - orig = self.table.item(item.row(), 2) - if orig is None: - orig = was_item() - self.table.setItem(item.row(), 2, orig) - orig.setData(Qt.ItemDataRole.DisplayRole, item.initial_text()) - def record_sort(self, section): # Note what sort was done so we can redo it when the table is rebuilt sort_name = self.sort_names[section] From 914ea7d54c32b681edff97c33cc896c9862e36e9 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 22 Apr 2023 12:58:49 +0100 Subject: [PATCH 0538/2055] Revert "Fix #2017316 [Error when renaming tag in Tags Manager](https://bugs.launchpad.net/calibre/+bug/2017316)" This reverts commit d5dbb3c63a1fe05f85a31cf1e4cd021ec76e750a. --- src/calibre/gui2/dialogs/tag_list_editor.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index aaff6a7cff..0a6e5cc494 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -107,12 +107,6 @@ class CountTableWidgetItem(QTableWidgetItem): return self._count < other._count -def was_item(tag=''): - item = QTableWidgetItem() - item.setFlags(item.flags() & ~(Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable)) - item.setData(Qt.ItemDataRole.DisplayRole, tag) - return item - class EditColumnDelegate(QItemDelegate): editing_finished = pyqtSignal(int) editing_started = pyqtSignal(int) @@ -472,8 +466,11 @@ class TagListEditor(QDialog, Ui_TagListEditor): item.setFlags(item.flags() & ~(Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable)) self.table.setItem(row, 1, item) - was = tag if _id in self.to_rename or _id in self.to_delete else '' - self.table.setItem(row, 2, was_item(was)) + item = QTableWidgetItem() + item.setFlags(item.flags() & ~(Qt.ItemFlag.ItemIsSelectable|Qt.ItemFlag.ItemIsEditable)) + if _id in self.to_rename or _id in self.to_delete: + item.setData(Qt.ItemDataRole.DisplayRole, tag) + self.table.setItem(row, 2, item) item = QTableWidgetItem() if self.link_map is None: @@ -594,9 +591,6 @@ class TagListEditor(QDialog, Ui_TagListEditor): id_ = int(item.data(Qt.ItemDataRole.UserRole)) self.to_rename[id_] = new_text orig = self.table.item(item.row(), 2) - if orig is None: - orig = was_item() - self.table.setItem(item.row(), 2, orig) item.setText(new_text) orig.setData(Qt.ItemDataRole.DisplayRole, item.initial_text()) self.table.blockSignals(False) From 58f67dd7816a5bb2f1ee3f32c016c0c6a3306c2f Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 22 Apr 2023 13:23:56 +0100 Subject: [PATCH 0539/2055] Bug #2017316: Error when renaming tag in Tags Manager. Note: I rolled back commits d5dbb3c63a1fe05f85a31cf1e4cd021ec76e750a and 89391da7b7074b42aad7c440e97ac76cfd18175a. --- src/calibre/gui2/dialogs/tag_list_editor.py | 113 ++++++++++++-------- src/calibre/gui2/dialogs/tag_list_editor.ui | 19 ---- 2 files changed, 66 insertions(+), 66 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index 0a6e5cc494..98faaa910a 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -6,7 +6,7 @@ from functools import partial from qt.core import ( QAbstractItemView, QAction, QApplication, QColor, QDialog, QDialogButtonBox, QFrame, QIcon, QItemDelegate, QLabel, QMenu, QSize, Qt, QTableWidgetItem, QTimer, - pyqtSignal, + pyqtSignal, sip, ) from calibre.gui2 import error_dialog, gprefs, question_dialog @@ -14,6 +14,7 @@ from calibre.gui2.actions.show_quickview import get_quickview_action_plugin from calibre.gui2.complete2 import EditWithComplete from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.tag_list_editor_ui import Ui_TagListEditor +from calibre.gui2.dialogs.tag_list_editor_table_widget import TleTableWidget from calibre.gui2.widgets import EnLineEdit from calibre.utils.config import prefs from calibre.utils.icu import ( @@ -166,8 +167,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): # Get saved geometry info try: - self.table_column_widths = \ - gprefs.get('tag_list_editor_table_widths', None) + self.table_column_widths = gprefs.get('tag_list_editor_table_widths', None) except: pass @@ -183,23 +183,9 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.get_book_ids = get_book_ids self.text_before_editing = '' - # Capture clicks on the horizontal header to sort the table columns - hh = self.table.horizontalHeader() - hh.sectionResized.connect(self.table_column_resized) - hh.setSectionsClickable(True) - self.table.setSortingEnabled(True) - hh.sectionClicked.connect(self.record_sort) - hh.setSortIndicatorShown(True) - self.sort_names = ('name', 'count', 'was', 'link') self.last_sorted_by = 'name' - self.name_order = self.count_order = self.was_order = self.link_order = 1 - - self.edit_delegate = EditColumnDelegate(self.table, self.check_for_deleted_items) - self.edit_delegate.editing_finished.connect(self.stop_editing) - self.edit_delegate.editing_started.connect(self.start_editing) - self.table.setItemDelegateForColumn(0, self.edit_delegate) - self.table.setItemDelegateForColumn(3, self.edit_delegate) + self.name_order = self.count_order = self.was_order = self.link_order = 0 if prefs['case_sensitive']: self.string_contains = contains @@ -207,21 +193,14 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.string_contains = self.case_insensitive_compare self.delete_button.clicked.connect(self.delete_tags) - self.table.delete_pressed.connect(self.delete_pressed) self.rename_button.clicked.connect(self.rename_tag) self.undo_button.clicked.connect(self.undo_edit) - self.table.itemDoubleClicked.connect(self._rename_tag) - self.table.itemChanged.connect(self.finish_editing) - self.table.itemSelectionChanged.connect(self.selection_changed) self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText(_('&OK')) self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText(_('&Cancel')) self.buttonBox.accepted.connect(self.accepted) self.buttonBox.rejected.connect(self.rejected) - # Ensure that the selection moves with the item focus - self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectItems) - self.search_box.initialize('tag_list_search_box_' + cat_name) le = self.search_box.lineEdit() ac = le.findChild(QAction, QT_HIDDEN_CLEAR_ACTION) @@ -230,19 +209,6 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.search_box.textChanged.connect(self.search_text_changed) self.search_button.clicked.connect(self.do_search) self.search_button.setDefault(True) - l = QLabel(self.table) - self.not_found_label = l - l.setFrameStyle(QFrame.Shape.StyledPanel) - l.setAutoFillBackground(True) - l.setText(_('No matches found')) - l.setAlignment(Qt.AlignmentFlag.AlignVCenter) - l.resize(l.sizeHint()) - l.move(10, 0) - l.setVisible(False) - self.not_found_label_timer = QTimer() - self.not_found_label_timer.setSingleShot(True) - self.not_found_label_timer.timeout.connect( - self.not_found_label_timer_event, type=Qt.ConnectionType.QueuedConnection) self.filter_box.initialize('tag_list_filter_box_' + cat_name) le = self.filter_box.lineEdit() @@ -251,12 +217,8 @@ class TagListEditor(QDialog, Ui_TagListEditor): ac.triggered.connect(self.clear_filter) le.returnPressed.connect(self.do_filter) self.filter_button.clicked.connect(self.do_filter) - self.apply_vl_checkbox.clicked.connect(self.vl_box_changed) - self.table.setEditTriggers(QAbstractItemView.EditTrigger.EditKeyPressed) - - self.restore_geometry(gprefs, 'tag_list_editor_dialog_geometry') self.is_enumerated = False if fm: if fm['datatype'] == 'enumeration': @@ -264,11 +226,9 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.enum_permitted_values = fm.get('display', {}).get('enum_values', None) # Add the data self.search_item_row = -1 + self.table = None self.fill_in_table(None, tag_to_match, ttm_is_first_letter) - self.table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) - self.table.customContextMenuRequested.connect(self.show_context_menu) - def sizeHint(self): return super().sizeHint() + QSize(150, 100) @@ -404,7 +364,66 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.search_box.setText(txt) self.do_search() + def create_table(self): + # For some reason we must recreate the table if we change the row count. + # If we don't then the old items remain even if replaced by setItem(). + # I'm not sure if this is standard Qt behavior or behavior triggered by + # something in this class, but replacing the table fixes it. + if self.table is not None: + self.gridlayout.removeWidget(self.table) + sip.delete(self.table) + self.table = TleTableWidget(self) + self.gridlayout.addWidget(self.table, 2, 1, 1, 4) + + self.table.setAlternatingRowColors(True) + self.table.setSelectionMode(QAbstractItemView.ExtendedSelection) + self.table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectItems) + + hh = self.table.horizontalHeader() + hh.sectionResized.connect(self.table_column_resized) + hh.setSectionsClickable(True) + self.table.setSortingEnabled(True) + hh.sectionClicked.connect(self.record_sort) + hh.setSortIndicatorShown(True) + + self.table.setColumnCount(4) + for col,width in enumerate(self.table_column_widths): + self.table.setColumnWidth(col, width) + + self.edit_delegate = EditColumnDelegate(self.table, self.check_for_deleted_items) + self.edit_delegate.editing_finished.connect(self.stop_editing) + self.edit_delegate.editing_started.connect(self.start_editing) + self.table.setItemDelegateForColumn(0, self.edit_delegate) + self.table.setItemDelegateForColumn(3, self.edit_delegate) + + self.table.delete_pressed.connect(self.delete_pressed) + self.table.itemDoubleClicked.connect(self._rename_tag) + self.table.itemChanged.connect(self.finish_editing) + self.table.itemSelectionChanged.connect(self.selection_changed) + + l = QLabel(self.table) + self.not_found_label = l + l.setFrameStyle(QFrame.Shape.StyledPanel) + l.setAutoFillBackground(True) + l.setText(_('No matches found')) + l.setAlignment(Qt.AlignmentFlag.AlignVCenter) + l.resize(l.sizeHint()) + l.move(10, 0) + l.setVisible(False) + self.not_found_label_timer = QTimer() + self.not_found_label_timer.setSingleShot(True) + self.not_found_label_timer.timeout.connect( + self.not_found_label_timer_event, type=Qt.ConnectionType.QueuedConnection) + + self.table.setEditTriggers(QAbstractItemView.EditTrigger.EditKeyPressed) + self.restore_geometry(gprefs, 'tag_list_editor_dialog_geometry') + + self.table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) + self.table.customContextMenuRequested.connect(self.show_context_menu) + def fill_in_table(self, tags, tag_to_match, ttm_is_first_letter): + self.create_table() + data = self.get_book_ids(self.apply_vl_checkbox.isChecked()) self.all_tags = {} filter_text = icu_lower(str(self.filter_box.text())) @@ -424,8 +443,6 @@ class TagListEditor(QDialog, Ui_TagListEditor): select_item = None self.table.blockSignals(True) - self.table.clear() - self.table.setColumnCount(4) self.name_col = QTableWidgetItem(self.category_name) self.table.setHorizontalHeaderItem(0, self.name_col) self.count_col = QTableWidgetItem(_('Count')) @@ -546,6 +563,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.table.setCurrentItem(self.table.item(on_row, current_column)) items = self.table.selectedItems() self.table.blockSignals(True) + self.table.setSortingEnabled(False) for item in items: if item.row() != on_row: item.set_placeholder(_('Editing...')) @@ -561,6 +579,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): for item in items: if item.row() != on_row and item.is_placeholder: item.reset_placeholder() + self.table.setSortingEnabled(True) self.table.blockSignals(False) def finish_editing(self, edited_item): diff --git a/src/calibre/gui2/dialogs/tag_list_editor.ui b/src/calibre/gui2/dialogs/tag_list_editor.ui index bc22367884..789940df40 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.ui +++ b/src/calibre/gui2/dialogs/tag_list_editor.ui @@ -252,19 +252,6 @@ - - - - true - - - QAbstractItemView::ExtendedSelection - - - QAbstractItemView::SelectRows - - - @@ -280,12 +267,6 @@ QLineEdit
calibre/gui2/widgets.h
- - TleTableWidget - QTableWidget -
calibre/gui2/dialogs/tag_list_editor_table_widget.h
- 1 -
From 124854254ec5654eb0f94797eb3921dbcf4ed92a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 22 Apr 2023 18:13:14 +0530 Subject: [PATCH 0540/2055] ... --- src/calibre/gui2/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index e3242feec5..476494742b 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -722,8 +722,8 @@ class HistoryLineEdit(QComboBox): # {{{ lost_focus = pyqtSignal() - def __init__(self, *args): - QComboBox.__init__(self, *args) + def __init__(self, parent=None): + QComboBox.__init__(self, parent) self.setEditable(True) self.setInsertPolicy(QComboBox.InsertPolicy.NoInsert) self.setMaxCount(10) From f48c2ca8f26b6b6fd7174fedfd91ab9de678063e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 22 Apr 2023 18:35:17 +0530 Subject: [PATCH 0541/2055] ... --- src/calibre/ebooks/metadata/sources/search_engines.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/metadata/sources/search_engines.py b/src/calibre/ebooks/metadata/sources/search_engines.py index fb12e68614..f1567d4b15 100644 --- a/src/calibre/ebooks/metadata/sources/search_engines.py +++ b/src/calibre/ebooks/metadata/sources/search_engines.py @@ -4,12 +4,14 @@ from __future__ import absolute_import, division, print_function, unicode_literals import json +import sys import os import re import time from collections import namedtuple from contextlib import contextmanager from threading import Lock +from functools import partial try: from urllib.parse import parse_qs, quote_plus, unquote, urlencode, quote, urlparse @@ -19,7 +21,7 @@ except ImportError: from lxml import etree -from calibre import browser as _browser, prints, random_user_agent +from calibre import browser as _browser, prints as safe_print, random_user_agent from calibre.constants import cache_dir from calibre.ebooks.chardet import xml_to_unicode from calibre.utils.lock import ExclusiveFile @@ -29,6 +31,7 @@ current_version = (1, 2, 2) minimum_calibre_version = (2, 80, 0) webcache = {} webcache_lock = Lock() +prints = partial(safe_print, file=sys.stderr) Result = namedtuple('Result', 'url title cached_url') From 82ecffb07b620369bcb3a79cd4df119bdb4d050d Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sat, 22 Apr 2023 15:12:51 +0100 Subject: [PATCH 0542/2055] Enhancement #2017318: Open book details window from Quickview. Opens a locked window. --- src/calibre/gui2/dialogs/book_info.py | 20 ++++++++++++++++---- src/calibre/gui2/dialogs/quickview.py | 9 +++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index eea83a7d26..b8401535ae 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -211,10 +211,11 @@ class BookInfo(QDialog): if library_path is not None: self.view = None db = get_gui().library_broker.get_library(library_path) - if not db.new_api.has_id(book_id): + dbn = db.new_api + if not dbn.has_id(book_id): raise ValueError(_("Book {} doesn't exist").format(book_id)) - mi = db.new_api.get_metadata(book_id, get_cover=False) - mi.cover_data = [None, db.new_api.cover(book_id, as_image=True)] + mi = dbn.get_metadata(book_id, get_cover=False) + mi.cover_data = [None, dbn.cover(book_id, as_image=True)] mi.path = None mi.format_files = dict() mi.formats = list() @@ -227,7 +228,18 @@ class BookInfo(QDialog): if dialog_number == DialogNumbers.Slaved: self.slave_connected = True self.view.model().new_bookdisplay_data.connect(self.slave) - self.refresh(row) + if book_id: + db = get_gui().current_db + dbn = db.new_api + mi = dbn.get_metadata(book_id, get_cover=False) + mi.cover_data = [None, dbn.cover(book_id, as_image=True)] + mi.path = dbn._field_for('path', book_id) + mi.format_files = dbn.format_files(book_id) + mi.marked = db.data.get_marked(book_id) + mi.field_metadata = db.field_metadata + self.refresh(row, mi) + else: + self.refresh(row) ema = get_gui().iactions['Edit Metadata'].menuless_qaction a = self.ema = QAction('edit metadata', self) diff --git a/src/calibre/gui2/dialogs/quickview.py b/src/calibre/gui2/dialogs/quickview.py index 207fae23a2..419d9755a2 100644 --- a/src/calibre/gui2/dialogs/quickview.py +++ b/src/calibre/gui2/dialogs/quickview.py @@ -286,6 +286,7 @@ class Quickview(QDialog, Ui_Quickview): self.view_icon = QIcon.ic('view.png') self.view_plugin = self.gui.iactions['View'] + self.show_details_plugin = self.gui.iactions['Show Book Details'] self.edit_metadata_icon = QIcon.ic('edit_input.png') self.quickview_icon = QIcon.ic('quickview.png') self.select_book_icon = QIcon.ic('library.png') @@ -341,6 +342,8 @@ class Quickview(QDialog, Ui_Quickview): a = m.addAction(self.select_book_icon, _('Select this book in the library'), partial(self.select_book, book_id)) a.setEnabled(book_displayed) + m.addAction(_('Open a locked book details window for this book'), + partial(self.show_book_details, book_id)) m.addAction(self.search_icon, _('Find item in the library'), partial(self.do_search, follow_library_view=False)) a = m.addAction(self.edit_metadata_icon, _('Edit metadata'), @@ -761,6 +764,12 @@ class Quickview(QDialog, Ui_Quickview): finally: self.follow_library_view = True + def show_book_details(self, book_id): + try: + self.show_details_plugin.show_book_info(book_id=book_id, locked=True) + finally: + pass + def select_book(self, book_id): ''' Select a book in the library view without changing the QV lists From 29c9f22feeee7fb712aba38638b2cb32182963f8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 09:05:16 +0530 Subject: [PATCH 0543/2055] Fix #2017375 [Data files context menu glitch](https://bugs.launchpad.net/calibre/+bug/2017375) --- src/calibre/gui2/book_details.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 225db98b5d..50f0c73112 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -407,6 +407,15 @@ def add_item_specific_entries(menu, data, book_info, copy_menu, search_menu): ac.current_url = path ac.setText(_('The location of the book')) copy_menu.addAction(ac) + elif dt == 'data-path': + path = data['loc'] + ac = book_info.copy_link_action + path = get_gui().library_view.model().db.abspath(data['loc'], index_is_id=True) + if path: + path = os.path.join(path, DATA_DIR_NAME) + ac.current_url = path + ac.setText(_('The location of the book\'s data files')) + copy_menu.addAction(ac) else: field = data.get('field') if field is not None: @@ -451,8 +460,9 @@ def add_item_specific_entries(menu, data, book_info, copy_menu, search_menu): lambda : book_info.link_clicked.emit(link)) else: v = data.get('original_value') or data.get('value') - copy_menu.addAction(QIcon.ic('edit-copy.png'), _('The text: {}').format(v), - lambda: QApplication.instance().clipboard().setText(v)) + if v: + copy_menu.addAction(QIcon.ic('edit-copy.png'), _('The text: {}').format(v), + lambda: QApplication.instance().clipboard().setText(v)) return search_internet_added From c352e3e9ede001c5fe20aca8bb66a64618b44835 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 09:05:58 +0530 Subject: [PATCH 0544/2055] ... --- setup/git_pre_commit_hook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/git_pre_commit_hook.py b/setup/git_pre_commit_hook.py index 4ecb73cd4e..21d3d0996d 100755 --- a/setup/git_pre_commit_hook.py +++ b/setup/git_pre_commit_hook.py @@ -69,7 +69,7 @@ class Bug: msg = msg.replace('Fixesed', 'Fixed') env = dict(os.environ) env['LAUNCHPAD_FIX_BUG'] = msg - subprocess.run([sys.executable, LAUNCHPAD], env=env, input=f'Subject: [Bug ({bug})]', text=True, check=True) + subprocess.run([sys.executable, LAUNCHPAD], env=env, input=f'Subject: [Bug {bug}]', text=True, check=True) def main(): From 22fc372bbe82fd99bdd96ed79b0930de1e5134ff Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 09:09:02 +0530 Subject: [PATCH 0545/2055] ... --- src/calibre/ebooks/metadata/book/render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index b4a8fde5b0..0c50e6235b 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -214,7 +214,7 @@ def mi_to_html( prepare_string_for_xml(durl)) if show_links: link = '{}{}'.format(action(scheme, book_id=book_id, loc=loc), - prepare_string_for_xml(path, True), _('Click to open'), extra) + prepare_string_for_xml(path, True), _('Book files'), extra) if not isdevice: data_path = os.path.join(path, DATA_DIR_NAME) with suppress(OSError): From fd8717ae41879ab7f3f6b2a379a5249a9b52d31b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 09:39:28 +0530 Subject: [PATCH 0546/2055] ... --- src/calibre/ebooks/metadata/book/render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index 0c50e6235b..5a576ed744 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -219,7 +219,7 @@ def mi_to_html( data_path = os.path.join(path, DATA_DIR_NAME) with suppress(OSError): if os.listdir(data_path): - link += ' \xa0 {}'.format( + link += ', {}'.format( action('data-path', book_id=book_id, loc=book_id), prepare_string_for_xml(data_path, True), _('Data files')) From b81d8f46bcd89cc5d71bb534f789a77f5e58f32e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 12:44:52 +0530 Subject: [PATCH 0547/2055] Nicer presentation of folder links in book details --- src/calibre/ebooks/metadata/book/render.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index 5a576ed744..f50d3da0b6 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -20,7 +20,7 @@ from calibre.library.comments import comments_to_html, markdown from calibre.utils.date import format_date, is_date_undefined from calibre.utils.formatter import EvalFormatter from calibre.utils.icu import sort_key -from calibre.utils.localization import calibre_langcode_to_name +from calibre.utils.localization import calibre_langcode_to_name, ngettext from calibre.utils.serialize import json_dumps from polyglot.binary import as_hex_unicode @@ -213,15 +213,22 @@ def mi_to_html( extra = '
%s'%( prepare_string_for_xml(durl)) if show_links: - link = '{}{}'.format(action(scheme, book_id=book_id, loc=loc), - prepare_string_for_xml(path, True), _('Book files'), extra) - if not isdevice: + has_data_files = False + if isdevice: + text = _('Click to open') + else: data_path = os.path.join(path, DATA_DIR_NAME) with suppress(OSError): if os.listdir(data_path): - link += ', {}'.format( - action('data-path', book_id=book_id, loc=book_id), - prepare_string_for_xml(data_path, True), _('Data files')) + has_data_files = True + text = _('Book files') + name = ngettext('Folder:', 'Folders:', 2 if has_data_files else 1) + link = '{}{}'.format(action(scheme, book_id=book_id, loc=loc), + prepare_string_for_xml(path, True), text, extra) + if has_data_files: + link += ', {}'.format( + action('data-path', book_id=book_id, loc=book_id), + prepare_string_for_xml(data_path, True), _('Data files')) else: link = prepare_string_for_xml(path, True) From feac41c8f7c01c5215b14dd632da343133ac37a3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 12:46:36 +0530 Subject: [PATCH 0548/2055] DRYer --- src/calibre/ebooks/metadata/book/render.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index f50d3da0b6..7d7a259968 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -213,19 +213,19 @@ def mi_to_html( extra = '
%s'%( prepare_string_for_xml(durl)) if show_links: - has_data_files = False + num_of_folders = 1 if isdevice: text = _('Click to open') else: data_path = os.path.join(path, DATA_DIR_NAME) with suppress(OSError): if os.listdir(data_path): - has_data_files = True + num_of_folders = 2 text = _('Book files') - name = ngettext('Folder:', 'Folders:', 2 if has_data_files else 1) + name = ngettext('Folder:', 'Folders:', num_of_folders) link = '{}{}'.format(action(scheme, book_id=book_id, loc=loc), prepare_string_for_xml(path, True), text, extra) - if has_data_files: + if num_of_folders > 1: link += ', {}'.format( action('data-path', book_id=book_id, loc=book_id), prepare_string_for_xml(data_path, True), _('Data files')) From edbf95a9025beb6881c9578d4f775790487f9ff7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 13:04:04 +0530 Subject: [PATCH 0549/2055] Fix data files not being merged when merging books Fixes #2017373 [Possible bug? Extra data files disappear upon merging](https://bugs.launchpad.net/calibre/+bug/2017373) --- src/calibre/db/backend.py | 35 +++++++++---- src/calibre/db/cache.py | 20 ++++++- src/calibre/db/copy_to_library.py | 18 +++++-- src/calibre/db/tests/add_remove.py | 63 +++++++++++++++++++++-- src/calibre/gui2/actions/edit_metadata.py | 4 ++ 5 files changed, 118 insertions(+), 22 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 111d80c647..d9e92bde5a 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1923,25 +1923,38 @@ class DB: with src: yield relpath, src, mtime - def add_extra_file(self, relpath, stream, book_path, replace=True): - dest = os.path.abspath(os.path.join(self.library_path, book_path, relpath)) - if not replace and os.path.exists(dest): - return False + def add_extra_file(self, relpath, stream, book_path, replace=True, auto_rename=False): + bookdir = os.path.join(self.library_path, book_path) + dest = os.path.abspath(os.path.join(bookdir, relpath)) + if not replace and os.path.exists(make_long_path_useable(dest)): + if not auto_rename: + return None + dirname, basename = os.path.split(dest) + num = 0 + while True: + mdir = 'merge conflict' + if num: + mdir += f' {num}' + candidate = os.path.join(dirname, mdir, basename) + if not os.path.exists(make_long_path_useable(candidate)): + dest = candidate + break + num += 1 if isinstance(stream, str): try: - shutil.copy2(stream, dest) + shutil.copy2(make_long_path_useable(stream), make_long_path_useable(dest)) except FileNotFoundError: - os.makedirs(os.path.dirname(dest), exist_ok=True) - shutil.copy2(stream, dest) + os.makedirs(make_long_path_useable(os.path.dirname(dest)), exist_ok=True) + shutil.copy2(make_long_path_useable(stream), make_long_path_useable(dest)) else: try: - d = open(dest, 'wb') + d = open(make_long_path_useable(dest), 'wb') except FileNotFoundError: - os.makedirs(os.path.dirname(dest), exist_ok=True) - d = open(dest, 'wb') + os.makedirs(make_long_path_useable(os.path.dirname(dest)), exist_ok=True) + d = open(make_long_path_useable(dest), 'wb') with d: shutil.copyfileobj(stream, d) - return True + return os.path.relpath(dest, bookdir).replace(os.sep, '/') def write_backup(self, path, raw): path = os.path.abspath(os.path.join(self.library_path, path, METADATA_FILE_NAME)) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 0afb6cf519..37802a3387 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -3064,12 +3064,28 @@ class Cache: self.backend.reindex_annotations() @write_api - def add_extra_files(self, book_id, map_of_relpath_to_stream_or_path, replace=True): + def add_extra_files(self, book_id, map_of_relpath_to_stream_or_path, replace=True, auto_rename=False): ' Add extra data files ' path = self._field_for('path', book_id).replace('/', os.sep) added = {} for relpath, stream_or_path in map_of_relpath_to_stream_or_path.items(): - added[relpath] = self.backend.add_extra_file(relpath, stream_or_path, path, replace) + added[relpath] = bool(self.backend.add_extra_file(relpath, stream_or_path, path, replace, auto_rename)) + return added + + @write_api + def merge_extra_files(self, dest_id, src_ids, replace=False): + ' Merge the extra files from src_ids into dest_id. Conflicting files are auto-renamed unless replace=True in which case they are replaced. ' + added = set() + path = self._field_for('path', dest_id) + if path: + path = path.replace('/', os.sep) + for src_id in src_ids: + book_path = self._field_for('path', src_id) + if book_path: + book_path = book_path.replace('/', os.sep) + for (relpath, file_path, mtime) in self.backend.iter_extra_files( + src_id, book_path, self.fields['formats'], yield_paths=True): + added.add(self.backend.add_extra_file(relpath, file_path, path, replace=replace, auto_rename=True)) return added @read_api diff --git a/src/calibre/db/copy_to_library.py b/src/calibre/db/copy_to_library.py index 51466ef470..e0f7395185 100644 --- a/src/calibre/db/copy_to_library.py +++ b/src/calibre/db/copy_to_library.py @@ -8,15 +8,19 @@ from calibre.utils.date import now from polyglot.builtins import iteritems -def automerge_book(automerge_action, book_id, mi, identical_book_list, newdb, format_map): +def automerge_book(automerge_action, book_id, mi, identical_book_list, newdb, format_map, extra_file_map): seen_fmts = set() replace = automerge_action == 'overwrite' for identical_book in identical_book_list: ib_fmts = newdb.formats(identical_book) if ib_fmts: seen_fmts |= {fmt.upper() for fmt in ib_fmts} + at_least_one_format_added = False for fmt, path in iteritems(format_map): - newdb.add_format(identical_book, fmt, path, replace=replace, run_hooks=False) + if newdb.add_format(identical_book, fmt, path, replace=replace, run_hooks=False): + at_least_one_format_added = True + if at_least_one_format_added and extra_file_map: + newdb.add_extra_files(identical_book, extra_file_map, replace=False, auto_rename=True) if automerge_action == 'new record': incoming_fmts = {fmt.upper() for fmt in format_map} @@ -28,9 +32,12 @@ def automerge_book(automerge_action, book_id, mi, identical_book_list, newdb, fo # We should arguably put only the duplicate # formats, but no real harm is done by having # all formats - return newdb.add_books( + new_book_id = newdb.add_books( [(mi, format_map)], add_duplicates=True, apply_import_tags=tweaks['add_new_book_tags_when_importing_books'], - preserve_uuid=False, run_hooks=False)[0][0] + preserve_uuid=False, run_hooks=False)[0][0] + if extra_file_map: + newdb.add_extra_files(new_book_id, extra_file_map) + return new_book_id def postprocess_copy(book_id, new_book_id, new_authors, db, newdb, identical_books_data, duplicate_action): @@ -72,6 +79,7 @@ def copy_one_book( mi.timestamp = now() format_map = {} fmts = list(db.formats(book_id, verify_formats=False)) + extra_file_map = db.list_extra_files_matching(book_id) for fmt in fmts: path = db.format_abspath(book_id, fmt) if path: @@ -91,7 +99,7 @@ def copy_one_book( identical_book_list = find_identical_books(mi, identical_books_data) if identical_book_list: # books with same author and nearly same title exist in newdb if duplicate_action == 'add_formats_to_existing': - new_book_id = automerge_book(automerge_action, book_id, mi, identical_book_list, newdb, format_map) + new_book_id = automerge_book(automerge_action, book_id, mi, identical_book_list, newdb, format_map, extra_file_map) return_data['action'] = 'automerge' return_data['new_book_id'] = new_book_id postprocess_copy(book_id, new_book_id, new_authors, db, newdb, identical_books_data, duplicate_action) diff --git a/src/calibre/db/tests/add_remove.py b/src/calibre/db/tests/add_remove.py index 6e2d84e09f..ed0325e6f0 100644 --- a/src/calibre/db/tests/add_remove.py +++ b/src/calibre/db/tests/add_remove.py @@ -399,36 +399,91 @@ class AddRemoveTest(BaseTest): def compare_field(field, func=self.assertEqual): func(src_db.field_for(field, rdata['book_id']), dest_db.field_for(field, rdata['new_book_id'])) + def assert_has_extra_files(book_id): + bookdir = os.path.dirname(dest_db.format_abspath(book_id, '__COVER_INTERNAL__')) + self.assertEqual('exf', open(os.path.join(bookdir, 'exf')).read()) + self.assertEqual('recurse', open(os.path.join(bookdir, 'sub', 'recurse')).read()) + + def assert_does_not_have_extra_files(book_id): + bookdir = os.path.dirname(dest_db.format_abspath(book_id, '__COVER_INTERNAL__')) + self.assertFalse(os.path.exists(os.path.join(bookdir, 'exf'))) + self.assertFalse(os.path.exists(os.path.join(bookdir, 'sub', 'recurse'))) + + def clear_extra_files(book_id): + for file_path in dest_db.list_extra_files_matching(book_id).values(): + os.remove(file_path) + + assert_does_not_have_extra_files(1) + rdata = copy_one_book(1, src_db, dest_db) self.assertEqual(rdata, make_rdata(new_book_id=max(dest_db.all_book_ids()))) compare_field('timestamp') compare_field('uuid', self.assertNotEqual) self.assertEqual(src_db.all_annotations_for_book(1), dest_db.all_annotations_for_book(max(dest_db.all_book_ids()))) + assert_has_extra_files(rdata['new_book_id']) + clear_extra_files(rdata['new_book_id']) + rdata = copy_one_book(1, src_db, dest_db, preserve_date=False, preserve_uuid=True) - data_file_new_book_id = rdata['new_book_id'] self.assertEqual(rdata, make_rdata(new_book_id=max(dest_db.all_book_ids()))) compare_field('timestamp', self.assertNotEqual) compare_field('uuid') + assert_has_extra_files(rdata['new_book_id']) + clear_extra_files(rdata['new_book_id']) + rdata = copy_one_book(1, src_db, dest_db, duplicate_action='ignore') self.assertIsNone(rdata['new_book_id']) self.assertEqual(rdata['action'], 'duplicate') src_db.add_format(1, 'FMT1', BytesIO(b'replaced'), run_hooks=False) + assert_does_not_have_extra_files(1) + rdata = copy_one_book(1, src_db, dest_db, duplicate_action='add_formats_to_existing') self.assertEqual(rdata['action'], 'automerge') for new_book_id in (1, 4, 5): self.assertEqual(dest_db.format(new_book_id, 'FMT1'), b'replaced') + assert_has_extra_files(new_book_id) + clear_extra_files(new_book_id) + src_db.add_format(1, 'FMT1', BytesIO(b'second-round'), run_hooks=False) rdata = copy_one_book(1, src_db, dest_db, duplicate_action='add_formats_to_existing', automerge_action='ignore') self.assertEqual(rdata['action'], 'automerge') for new_book_id in (1, 4, 5): self.assertEqual(dest_db.format(new_book_id, 'FMT1'), b'replaced') + assert_does_not_have_extra_files(new_book_id) + rdata = copy_one_book(1, src_db, dest_db, duplicate_action='add_formats_to_existing', automerge_action='new record') self.assertEqual(rdata['action'], 'automerge') for new_book_id in (1, 4, 5): self.assertEqual(dest_db.format(new_book_id, 'FMT1'), b'replaced') + assert_does_not_have_extra_files(new_book_id) self.assertEqual(dest_db.format(rdata['new_book_id'], 'FMT1'), b'second-round') - bookdir = os.path.dirname(dest_db.format_abspath(data_file_new_book_id, '__COVER_INTERNAL__')) - self.assertEqual('exf', open(os.path.join(bookdir, 'exf')).read()) - self.assertEqual('recurse', open(os.path.join(bookdir, 'sub', 'recurse')).read()) + assert_has_extra_files(rdata['new_book_id']) # }}} + + def test_merging_extra_files(self): # {{{ + db = self.init_cache() + + def add_extra(book_id, relpath): + db.add_extra_files(book_id, {relpath: BytesIO(f'{book_id}:{relpath}'.encode())}) + + def extra_files_for(book_id): + ans = {} + for relpath, file_path in db.list_extra_files_matching(book_id).items(): + with open(file_path) as f: + ans[relpath] = f.read() + return ans + + add_extra(1, 'one'), add_extra(1, 'sub/one') + add_extra(2, 'one'), add_extra(2, 'sub/one'), add_extra(2, 'two/two') + add_extra(3, 'one'), add_extra(3, 'sub/one'), add_extra(3, 'three') + + self.assertEqual(extra_files_for(1), { + 'one': '1:one', 'sub/one': '1:sub/one', + }) + db.merge_extra_files(1, (2, 3)) + self.assertEqual(extra_files_for(1), { + 'one': '1:one', 'sub/one': '1:sub/one', + 'merge conflict/one': '2:one', 'sub/merge conflict/one': '2:sub/one', 'two/two': '2:two/two', + 'three': '3:three', 'merge conflict 1/one': '3:one', 'sub/merge conflict 1/one': '3:sub/one', + }) + # }}} diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index 64885a1dc9..bdd42c29a8 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -656,6 +656,7 @@ class EditMetadataAction(InterfaceAction): return self.add_formats(dest_id, self.formats_for_books(rows)) self.merge_metadata(dest_id, src_ids) + self.merge_data_files(dest_id, src_ids) self.delete_books_after_merge(src_ids) # leave the selection highlight on first selected book dest_row = rows[0].row() @@ -667,6 +668,9 @@ class EditMetadataAction(InterfaceAction): self.gui.library_view.model().refresh_ids((dest_id,), cr) self.gui.library_view.horizontalScrollBar().setValue(hpos) + def merge_data_files(self, dest_id, src_ids): + self.gui.current_db.new_api.merge_extra_files(dest_id, src_ids) + def add_formats(self, dest_id, src_books, replace=False): for src_book in src_books: if src_book: From 5468a465eea00edae508950a147566f631258013 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 23 Apr 2023 11:05:49 +0100 Subject: [PATCH 0550/2055] Commit 1 of API for the extra files cache. Commit 2 will resolve conflicts with commit edbf95a --- src/calibre/db/backend.py | 19 +++--- src/calibre/db/backup.py | 3 + src/calibre/db/cache.py | 71 +++++++++++++++++++--- src/calibre/db/copy_to_library.py | 2 +- src/calibre/db/tests/writing.py | 2 +- src/calibre/gui2/actions/choose_library.py | 8 +++ src/calibre/gui2/actions/view.py | 8 ++- 7 files changed, 93 insertions(+), 20 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index d9e92bde5a..9ff0044afb 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1887,11 +1887,12 @@ class DB: def iter_extra_files(self, book_id, book_path, formats_field, yield_paths=False, pattern=''): known_files = {COVER_FILE_NAME, METADATA_FILE_NAME} - for fmt in formats_field.for_book(book_id, default_value=()): - fname = formats_field.format_fname(book_id, fmt) - fpath = self.format_abspath(book_id, fmt, fname, book_path, do_file_rename=False) - if fpath: - known_files.add(os.path.basename(fpath)) + if formats_field is not None: + for fmt in formats_field.for_book(book_id, default_value=()): + fname = formats_field.format_fname(book_id, fmt) + fpath = self.format_abspath(book_id, fmt, fname, book_path, do_file_rename=False) + if fpath: + known_files.add(os.path.basename(fpath)) full_book_path = os.path.abspath(os.path.join(self.library_path, book_path)) if pattern: from pathlib import Path @@ -1910,9 +1911,11 @@ class DB: relpath = os.path.relpath(path, full_book_path) relpath = relpath.replace(os.sep, '/') if relpath not in known_files: - mtime = os.path.getmtime(path) + stat = os.stat(path) + mtime = stat.st_mtime + fsize = stat.st_size if yield_paths: - yield relpath, path, mtime + yield relpath, path, mtime, fsize else: try: src = open(path, 'rb') @@ -1921,7 +1924,7 @@ class DB: time.sleep(1) src = open(path, 'rb') with src: - yield relpath, src, mtime + yield relpath, src, mtime, fsize def add_extra_file(self, relpath, stream, book_path, replace=True, auto_rename=False): bookdir = os.path.join(self.library_path, book_path) diff --git a/src/calibre/db/backup.py b/src/calibre/db/backup.py index d6747e74b0..f42856003d 100644 --- a/src/calibre/db/backup.py +++ b/src/calibre/db/backup.py @@ -68,6 +68,9 @@ class MetadataBackup(Thread): if self.stop_running.is_set() or self.db.is_closed: return traceback.print_exc() + + self.db.check_save_extra_files_cache_needed() + try: book_id = self.db.get_a_dirtied_book() if book_id is None: diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 37802a3387..d142fa05b5 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -154,6 +154,7 @@ class Cache: self.formatter_template_cache = {} self.dirtied_cache = {} self.link_maps_cache = {} + self.extra_files_cache = {} self.vls_for_books_cache = None self.vls_for_books_lib_in_process = None self.vls_cache_lock = Lock() @@ -252,6 +253,8 @@ class Cache: if self.dirtied_cache: self.dirtied_sequence = max(itervalues(self.dirtied_cache))+1 self._initialize_dynamic_categories() + self.extra_files_cache = self.backend.prefs.get('extra_files_cache', {}) + self.extra_files_cache_dirty = False @write_api def initialize_template_cache(self): @@ -273,6 +276,33 @@ class Cache: self.vls_for_books_cache = None self.vls_for_books_lib_in_process = None + @write_api + def clear_extra_files_cache(self, book_id=None): + if book_id is None: + pref_changed = bool(self.extra_files_cache) + self.extra_files_cache = {} + else: + pref_changed = self.extra_files_cache.pop(str(book_id), False) + if pref_changed: + # self.backend.prefs.set('extra_files_cache', self.extra_files_cache) + self.extra_files_cache_dirty = True + + @write_api + def add_to_extra_files_cache(self, book_id, data): + self.extra_files_cache[str(book_id)] = data + # self.backend.prefs.set('extra_files_cache', self.extra_files_cache) + self.extra_files_cache_dirty = True + + @write_api + def save_extra_files_cache_if_needed(self): + if self.extra_files_cache_dirty: + self.backend.prefs.set('extra_files_cache', self.extra_files_cache) + self.extra_files_cache_dirty = False + + @read_api + def get_extra_files_from_cache(self, book_id): + return self.extra_files_cache.get(str(book_id), {}) + @read_api def last_modified(self): return self.backend.last_modified() @@ -293,6 +323,7 @@ class Cache: self._clear_search_caches(book_ids) self.clear_link_map_cache(book_ids) + @write_api def clear_link_map_cache(self, book_ids=None): if book_ids is None: self.link_maps_cache = {} @@ -560,7 +591,6 @@ class Cache: has_more = do_one() except Exception: if self.backend.fts_enabled: - import traceback traceback.print_exc() sleep(self.fts_indexing_sleep_time) @@ -1540,7 +1570,6 @@ class Cache: except: # This almost certainly means that the book has been deleted while # the backup operation sat in the queue. - import traceback traceback.print_exc() return mi, sequence @@ -2063,7 +2092,6 @@ class Cache: raw = metadata_to_opf(mi) self.backend.write_backup(path, raw) except Exception: - import traceback traceback.print_exc() self.backend.remove_books(path_map, permanent=permanent) for field in itervalues(self.fields): @@ -2577,7 +2605,6 @@ class Cache: if progress is not None: progress(item_name, item_count, total) except Exception: - import traceback traceback.print_exc() all_paths = {self._field_for('path', book_id).partition('/')[0] for book_id in self._all_book_ids()} @@ -2666,8 +2693,9 @@ class Cache: try: plugin.run(self) except Exception: - import traceback traceback.print_exc() + # do this last in case a plugin changes the extra files + self.check_save_extra_files_cache_needed() self._shutdown_fts(stage=2) with self.write_lock: self.backend.close() @@ -2966,7 +2994,7 @@ class Cache: bp = self.field_for('path', book_id) extra_files[book_id] = ef = {} if bp: - for (relpath, fobj, mtime) in self.backend.iter_extra_files(book_id, bp, self.fields['formats']): + for (relpath, fobj, mtime, fsize) in self.backend.iter_extra_files(book_id, bp, self.fields['formats']): key = f'{key_prefix}:{book_id}:.|{relpath}' with exporter.start_file(key, mtime=mtime) as dest: shutil.copyfileobj(fobj, dest) @@ -3070,6 +3098,7 @@ class Cache: added = {} for relpath, stream_or_path in map_of_relpath_to_stream_or_path.items(): added[relpath] = bool(self.backend.add_extra_file(relpath, stream_or_path, path, replace, auto_rename)) + self.clear_extra_files_cache(book_id) return added @write_api @@ -3083,11 +3112,37 @@ class Cache: book_path = self._field_for('path', src_id) if book_path: book_path = book_path.replace('/', os.sep) - for (relpath, file_path, mtime) in self.backend.iter_extra_files( + for (relpath, file_path, mtime, fsize) in self.backend.iter_extra_files( src_id, book_path, self.fields['formats'], yield_paths=True): added.add(self.backend.add_extra_file(relpath, file_path, path, replace=replace, auto_rename=True)) + self.clear_extra_files_cache(dest_id) return added + @write_api + def list_extra_files(self, book_id): + ''' + For book_id, returns the dict { + 'relpath': file's relative path from the book's 'data' directory, + 'file_path': full path to the file, + 'mtime': the file's modification time as a floating point number, + 'fsize': the file's size in bytes + } + ''' + ans = self.get_extra_files_from_cache(book_id) + if not ans: + print('not cached', book_id) + path = self._field_for('path', book_id) + if path: + book_path = (path + '/data').replace('/', os.sep) + for (relpath, file_path, mtime, fsize) in self.backend.iter_extra_files( + book_id, book_path, None, yield_paths=True): + ans = dict(zip(('relpath', 'file_path', 'mtime', 'fsize'), + (relpath, file_path, mtime, fsize))) + self.add_to_extra_files_cache(book_id, ans) + else: + print('cached', book_id) + return ans + @read_api def list_extra_files_matching(self, book_id, pattern=''): ' List extra data files matching the specified pattern. Empty pattern matches all. Recursive globbing with ** is supported. ' @@ -3095,7 +3150,7 @@ class Cache: ans = {} if path: book_path = path.replace('/', os.sep) - for (relpath, file_path, mtime) in self.backend.iter_extra_files( + for (relpath, file_path, mtime, fsize) in self.backend.iter_extra_files( book_id, book_path, self.fields['formats'], yield_paths=True, pattern=pattern): ans[relpath] = file_path return ans diff --git a/src/calibre/db/copy_to_library.py b/src/calibre/db/copy_to_library.py index e0f7395185..1a1ed68dda 100644 --- a/src/calibre/db/copy_to_library.py +++ b/src/calibre/db/copy_to_library.py @@ -112,7 +112,7 @@ def copy_one_book( preserve_uuid=preserve_uuid, run_hooks=False)[0][0] bp = db.field_for('path', book_id) if bp: - for (relpath, src_path, mtime) in db.backend.iter_extra_files(book_id, bp, db.fields['formats'], yield_paths=True): + for (relpath, src_path, mtime, fsize) in db.backend.iter_extra_files(book_id, bp, db.fields['formats'], yield_paths=True): nbp = newdb.field_for('path', new_book_id) if nbp: newdb.backend.add_extra_file(relpath, src_path, nbp) diff --git a/src/calibre/db/tests/writing.py b/src/calibre/db/tests/writing.py index ccffb79227..82bc7fb403 100644 --- a/src/calibre/db/tests/writing.py +++ b/src/calibre/db/tests/writing.py @@ -381,7 +381,7 @@ class WritingTest(BaseTest): def read_all_extra_files(book_id=1): ans = {} bp = cache.field_for('path', book_id) - for (relpath, fobj, mtime) in cache.backend.iter_extra_files(book_id, bp, cache.fields['formats']): + for (relpath, fobj, mtime, fsize) in cache.backend.iter_extra_files(book_id, bp, cache.fields['formats']): ans[relpath] = fobj.read() return ans diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index 46f5025eb3..9d9cc6ed5a 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -329,6 +329,10 @@ class ChooseLibraryAction(InterfaceAction): None, None), attr='action_restore_database') ac.triggered.connect(self.restore_database, type=Qt.ConnectionType.QueuedConnection) + ac = self.create_action(spec=(_('Clear extra files cache'), 'lt.png', + None, None), + attr='action_clear_extra_files_cache') + ac.triggered.connect(self.clear_extra_files_cache, type=Qt.ConnectionType.QueuedConnection) self.maintenance_menu.addAction(ac) self.choose_menu.addMenu(self.maintenance_menu) @@ -649,6 +653,10 @@ class ChooseLibraryAction(InterfaceAction): if restore_database(db, self.gui): self.gui.library_moved(db.library_path) + def clear_extra_files_cache(self): + db = self.gui.library_view.model().db + db.new_api.clear_extra_files_cache() + def check_library(self): from calibre.gui2.dialogs.check_library import CheckLibraryDialog, DBCheck self.gui.library_view.save_state() diff --git a/src/calibre/gui2/actions/view.py b/src/calibre/gui2/actions/view.py index 2c74390189..b1986854b9 100644 --- a/src/calibre/gui2/actions/view.py +++ b/src/calibre/gui2/actions/view.py @@ -273,7 +273,9 @@ class ViewAction(InterfaceAction): if not self._view_check(len(rows), max_=10, skip_dialog_name='open-folder-many-check'): return for i, row in enumerate(rows): - path = self.gui.library_view.model().db.abspath(row.row()) + db = self.gui.library_view.model().db + db.new_api.clear_extra_files_cache(self.gui.library_view.model().id(row)) + path = db.abspath(row.row()) open_local_file(path) if ismacos and i < len(rows) - 1: time.sleep(0.1) # Finder cannot handle multiple folder opens @@ -283,7 +285,9 @@ class ViewAction(InterfaceAction): open_local_file(path) def view_data_folder_for_id(self, id_): - path = self.gui.library_view.model().db.abspath(id_, index_is_id=True) + db = self.gui.library_view.model().db + db.new_api.clear_extra_files_cache(id_) + path = db.abspath(id_, index_is_id=True) open_local_file(os.path.join(path, DATA_DIR_NAME)) def view_book(self, triggered): From 310ccfd83285f02279fbb011cad06b8a64d86a4f Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 23 Apr 2023 11:06:41 +0100 Subject: [PATCH 0551/2055] API for extra files cache: resolve conflicts with edbf95a plus a few changes after more testing --- src/calibre/db/backup.py | 2 +- src/calibre/db/cache.py | 31 +++++++++++++++++-------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/calibre/db/backup.py b/src/calibre/db/backup.py index f42856003d..246cfb431a 100644 --- a/src/calibre/db/backup.py +++ b/src/calibre/db/backup.py @@ -69,7 +69,7 @@ class MetadataBackup(Thread): return traceback.print_exc() - self.db.check_save_extra_files_cache_needed() + self.db.save_extra_files_cache() try: book_id = self.db.get_a_dirtied_book() diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index d142fa05b5..d32be529f1 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -294,7 +294,7 @@ class Cache: self.extra_files_cache_dirty = True @write_api - def save_extra_files_cache_if_needed(self): + def save_extra_files_cache(self): if self.extra_files_cache_dirty: self.backend.prefs.set('extra_files_cache', self.extra_files_cache) self.extra_files_cache_dirty = False @@ -2695,7 +2695,7 @@ class Cache: except Exception: traceback.print_exc() # do this last in case a plugin changes the extra files - self.check_save_extra_files_cache_needed() + self.save_extra_files_cache() self._shutdown_fts(stage=2) with self.write_lock: self.backend.close() @@ -3121,26 +3121,29 @@ class Cache: @write_api def list_extra_files(self, book_id): ''' - For book_id, returns the dict { - 'relpath': file's relative path from the book's 'data' directory, - 'file_path': full path to the file, - 'mtime': the file's modification time as a floating point number, - 'fsize': the file's size in bytes - } + Returns information for files in the book's data directory. + + :param book_id: the database book id for the book + + :return: {rel_path: {'file_path', mtime, fsize} ... } + where: + rel_path is the relative path to the file from the data/ directory + file_path is the full path to the file + mtime is the file's modification time. The epoch is OS dependent + fsize is the file's size in bytes + ''' ans = self.get_extra_files_from_cache(book_id) if not ans: - print('not cached', book_id) path = self._field_for('path', book_id) if path: book_path = (path + '/data').replace('/', os.sep) for (relpath, file_path, mtime, fsize) in self.backend.iter_extra_files( book_id, book_path, None, yield_paths=True): - ans = dict(zip(('relpath', 'file_path', 'mtime', 'fsize'), - (relpath, file_path, mtime, fsize))) - self.add_to_extra_files_cache(book_id, ans) - else: - print('cached', book_id) + ans[file_path] = dict(zip(('relpath', 'mtime', 'fsize'), + (relpath, mtime, fsize))) + if ans: + self.add_to_extra_files_cache(book_id, ans) return ans @read_api From b94fcefeba6e24df3ceefe45308f8ae683bac724 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 23 Apr 2023 15:22:28 +0100 Subject: [PATCH 0552/2055] Requested changes to extra_files API --- src/calibre/db/backup.py | 2 -- src/calibre/db/cache.py | 31 +++---------------------------- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/src/calibre/db/backup.py b/src/calibre/db/backup.py index 246cfb431a..2437d7169d 100644 --- a/src/calibre/db/backup.py +++ b/src/calibre/db/backup.py @@ -69,8 +69,6 @@ class MetadataBackup(Thread): return traceback.print_exc() - self.db.save_extra_files_cache() - try: book_id = self.db.get_a_dirtied_book() if book_id is None: diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index d32be529f1..f90b653e2b 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -253,8 +253,6 @@ class Cache: if self.dirtied_cache: self.dirtied_sequence = max(itervalues(self.dirtied_cache))+1 self._initialize_dynamic_categories() - self.extra_files_cache = self.backend.prefs.get('extra_files_cache', {}) - self.extra_files_cache_dirty = False @write_api def initialize_template_cache(self): @@ -279,29 +277,9 @@ class Cache: @write_api def clear_extra_files_cache(self, book_id=None): if book_id is None: - pref_changed = bool(self.extra_files_cache) self.extra_files_cache = {} else: - pref_changed = self.extra_files_cache.pop(str(book_id), False) - if pref_changed: - # self.backend.prefs.set('extra_files_cache', self.extra_files_cache) - self.extra_files_cache_dirty = True - - @write_api - def add_to_extra_files_cache(self, book_id, data): - self.extra_files_cache[str(book_id)] = data - # self.backend.prefs.set('extra_files_cache', self.extra_files_cache) - self.extra_files_cache_dirty = True - - @write_api - def save_extra_files_cache(self): - if self.extra_files_cache_dirty: - self.backend.prefs.set('extra_files_cache', self.extra_files_cache) - self.extra_files_cache_dirty = False - - @read_api - def get_extra_files_from_cache(self, book_id): - return self.extra_files_cache.get(str(book_id), {}) + self.extra_files_cache.pop(book_id, None) @read_api def last_modified(self): @@ -2694,8 +2672,6 @@ class Cache: plugin.run(self) except Exception: traceback.print_exc() - # do this last in case a plugin changes the extra files - self.save_extra_files_cache() self._shutdown_fts(stage=2) with self.write_lock: self.backend.close() @@ -3133,7 +3109,7 @@ class Cache: fsize is the file's size in bytes ''' - ans = self.get_extra_files_from_cache(book_id) + ans = self.extra_files_cache.get(book_id, {}) if not ans: path = self._field_for('path', book_id) if path: @@ -3142,8 +3118,7 @@ class Cache: book_id, book_path, None, yield_paths=True): ans[file_path] = dict(zip(('relpath', 'mtime', 'fsize'), (relpath, mtime, fsize))) - if ans: - self.add_to_extra_files_cache(book_id, ans) + self.extra_files_cache[book_id] = ans return ans @read_api From 25b642acba67533dd51561b9d2884516977f2df0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 19:54:21 +0530 Subject: [PATCH 0553/2055] Include fonttools will eventually be used for subsetting or conversion to/from WOFF --- bypy/sources.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bypy/sources.json b/bypy/sources.json index a8cc0dbb9d..36395f7d0e 100644 --- a/bypy/sources.json +++ b/bypy/sources.json @@ -897,6 +897,15 @@ } }, + { + "name": "fonttools", + "unix": { + "filename": "fonttools-4.39.3.zip", + "hash": "sha256:9234b9f57b74e31b192c3fc32ef1a40750a8fbc1cd9837a7b7bfc4ca4a5c51d7", + "urls": ["pypi"] + } + }, + { "name": "toml", "comment": "Needed for sip (build time dependency)", From 525851464a1f5152a029c1c9aa69f3aabbf692de Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 20:03:52 +0530 Subject: [PATCH 0554/2055] Wrapper code to use fonttools for subsetting --- src/calibre/utils/fonts/subset.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/calibre/utils/fonts/subset.py diff --git a/src/calibre/utils/fonts/subset.py b/src/calibre/utils/fonts/subset.py new file mode 100644 index 0000000000..3aac7aae79 --- /dev/null +++ b/src/calibre/utils/fonts/subset.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# License: GPLv3 Copyright: 2023, Kovid Goyal + +import sys, os +from fontTools.subset import Subsetter, load_font, save_font + + +def subset(input_file_object_or_path, output_file_object_or_path, container_type, chars_or_text=''): + s = Subsetter() + s.options.recommended_glyphs = True + container_type = container_type.lower() + if 'woff' in container_type: + s.options.flavor = 'woff2' + font = load_font(input_file_object_or_path, s.options, dontLoadGlyphNames=False) + unicodes = {ord(x) for x in chars_or_text} + unicodes.add(ord(' ')) + s.populate(unicodes=unicodes) + s.subset(font) + save_font(font, output_file_object_or_path, s.options) + + +if __name__ == '__main__': + import tempfile + src = sys.argv[-1] + with open(os.path.join(tempfile.gettempdir(), os.path.basename(src)), 'wb') as output: + subset(src, output, os.path.splitext(sys.argv[-1])[1][1:], 'abcdefghijk') + a, b = os.path.getsize(src), os.path.getsize(output.name) + print(f'Input: {a} Output: {b}') + print('Written to:', output.name) From ee553442b762d0db65fd551546b92f9f72e36d3c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 20:34:23 +0530 Subject: [PATCH 0555/2055] Return log messages when running subsetting via fonttools --- src/calibre/utils/fonts/subset.py | 45 +++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/calibre/utils/fonts/subset.py b/src/calibre/utils/fonts/subset.py index 3aac7aae79..e41570e664 100644 --- a/src/calibre/utils/fonts/subset.py +++ b/src/calibre/utils/fonts/subset.py @@ -1,29 +1,46 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2023, Kovid Goyal -import sys, os -from fontTools.subset import Subsetter, load_font, save_font +import os +import sys +from logging.handlers import QueueHandler +from queue import Empty, SimpleQueue + +from fontTools.subset import Subsetter, load_font, log, save_font def subset(input_file_object_or_path, output_file_object_or_path, container_type, chars_or_text=''): - s = Subsetter() - s.options.recommended_glyphs = True - container_type = container_type.lower() - if 'woff' in container_type: - s.options.flavor = 'woff2' - font = load_font(input_file_object_or_path, s.options, dontLoadGlyphNames=False) - unicodes = {ord(x) for x in chars_or_text} - unicodes.add(ord(' ')) - s.populate(unicodes=unicodes) - s.subset(font) - save_font(font, output_file_object_or_path, s.options) + log_messages = SimpleQueue() + log_handler = QueueHandler(log_messages) + log.addHandler(log_handler) + try: + s = Subsetter() + s.options.recommended_glyphs = True + container_type = container_type.lower() + if 'woff' in container_type: + s.options.flavor = 'woff2' + font = load_font(input_file_object_or_path, s.options, dontLoadGlyphNames=False) + unicodes = {ord(x) for x in chars_or_text} + unicodes.add(ord(' ')) + s.populate(unicodes=unicodes) + s.subset(font) + save_font(font, output_file_object_or_path, s.options) + finally: + log.removeHandler(log_handler) + msgs = [] + while True: + try: + msgs.append(log_messages.get_nowait().getMessage()) + except Empty: + break + return msgs if __name__ == '__main__': import tempfile src = sys.argv[-1] with open(os.path.join(tempfile.gettempdir(), os.path.basename(src)), 'wb') as output: - subset(src, output, os.path.splitext(sys.argv[-1])[1][1:], 'abcdefghijk') + print('\n'.join(subset(src, output, os.path.splitext(sys.argv[-1])[1][1:], 'abcdefghijk'))) a, b = os.path.getsize(src), os.path.getsize(output.name) print(f'Input: {a} Output: {b}') print('Written to:', output.name) From 81c9c9c1121dfd6cfea01ff61d9dc3068ff6bc2d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 20:35:33 +0530 Subject: [PATCH 0556/2055] Font subsetting: Add support for WOFF format fonts and CID keyed fonts. Also further reduce font file sizes when subsetting. Courtesy of switching to fonttools as the subsetting engine. They now support adding glyphs from GSUB/GPOS tables so should be much more robust than the last time I looked into fonttools and decided to write my own subsetting code instead. --- src/calibre/ebooks/oeb/polish/subset.py | 30 +++++++++++---------- src/calibre/ebooks/oeb/transforms/subset.py | 30 ++++++++++----------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/calibre/ebooks/oeb/polish/subset.py b/src/calibre/ebooks/oeb/polish/subset.py index a49bf7f78d..8dd90f9dd8 100644 --- a/src/calibre/ebooks/oeb/polish/subset.py +++ b/src/calibre/ebooks/oeb/polish/subset.py @@ -5,15 +5,16 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, sys +import os +import sys +from io import BytesIO -from calibre import prints, as_unicode -from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS, XPath, css_text -from calibre.ebooks.oeb.polish.utils import guess_type, OEB_FONTS -from calibre.utils.fonts.sfnt.subset import subset -from calibre.utils.fonts.sfnt.errors import UnsupportedFont +from calibre import as_unicode, prints +from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES, XPath, css_text +from calibre.ebooks.oeb.polish.utils import OEB_FONTS +from calibre.utils.fonts.subset import subset from calibre.utils.fonts.utils import get_font_names -from polyglot.builtins import iteritems, itervalues +from polyglot.builtins import iteritems def remove_font_face_rules(container, sheet, remove_names, base): @@ -33,9 +34,8 @@ def remove_font_face_rules(container, sheet, remove_names, base): def iter_subsettable_fonts(container): - woff_font_types = guess_type('a.woff'), guess_type('a.woff2') for name, mt in iteritems(container.mime_map): - if (mt in OEB_FONTS or name.rpartition('.')[-1].lower() in {'otf', 'ttf'}) and mt not in woff_font_types: + if (mt in OEB_FONTS or name.rpartition('.')[-1].lower() in {'otf', 'ttf'}): yield name, mt @@ -63,20 +63,22 @@ def subset_all_fonts(container, font_stats, report): continue warnings = [] report('Subsetting font: %s'%(font_name or name)) + font_type = os.path.splitext(name)[1][1:].lower() + output = BytesIO() try: - nraw, old_sizes, new_sizes = subset(raw, chars, - warnings=warnings) - except UnsupportedFont as e: + warnings = subset(BytesIO(raw), output, font_type, chars) + except Exception as e: report( 'Unsupported font: %s, ignoring. Error: %s'%( name, as_unicode(e))) continue + nraw = output.getvalue() total_old += font_size for w in warnings: report(w) - olen = sum(itervalues(old_sizes)) - nlen = sum(itervalues(new_sizes)) + olen = len(raw) + nlen = len(nraw) total_new += len(nraw) if nlen == olen: report(_('The font %s was already subset')%font_name) diff --git a/src/calibre/ebooks/oeb/transforms/subset.py b/src/calibre/ebooks/oeb/transforms/subset.py index a11f295a7f..604d8083f7 100644 --- a/src/calibre/ebooks/oeb/transforms/subset.py +++ b/src/calibre/ebooks/oeb/transforms/subset.py @@ -5,11 +5,13 @@ __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' +import os from collections import defaultdict +from io import BytesIO -from calibre.ebooks.oeb.base import urlnormalize, css_text -from calibre.utils.fonts.sfnt.subset import subset, NoGlyphs, UnsupportedFont -from polyglot.builtins import iteritems, itervalues +from calibre.ebooks.oeb.base import css_text, urlnormalize +from calibre.utils.fonts.subset import subset +from polyglot.builtins import iteritems from tinycss.fonts3 import parse_font_family @@ -150,27 +152,25 @@ class SubsetFonts: else: fonts[item.href] = font - for font in itervalues(fonts): + for font in fonts.values(): if not font['chars']: self.log('The font %s is unused. Removing it.'%font['src']) remove(font) continue + old_raw = font['item'].data + output = BytesIO() + font_type = os.path.splitext(font['item'].href)[1][1:].lower() try: - raw, old_stats, new_stats = subset(font['item'].data, font['chars']) - except NoGlyphs: - self.log('The font %s has no used glyphs. Removing it.'%font['src']) - remove(font) - continue - except UnsupportedFont as e: - self.log.warn('The font %s is unsupported for subsetting. %s'%( - font['src'], e)) + subset(BytesIO(old_raw), output, font_type, font['chars']) + except Exception as e: + self.log.warn('The font %s is unsupported for subsetting. %s'%(font['src'], e)) sz = len(font['item'].data) totals[0] += sz totals[1] += sz else: - font['item'].data = raw - nlen = sum(itervalues(new_stats)) - olen = sum(itervalues(old_stats)) + font['item'].data = output.getvalue() + nlen = len(font['item'].data) + olen = len(old_raw) self.log('Decreased the font %s to %.1f%% of its original size'% (font['src'], nlen/olen *100)) totals[0] += nlen From f00c826f88f1beab0a4bf0431a52ae34ddaf6c84 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 20:43:47 +0530 Subject: [PATCH 0557/2055] Build test to check fonttools is present and importable --- src/calibre/test_build.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/calibre/test_build.py b/src/calibre/test_build.py index d6a71cc361..2c32c24a6c 100644 --- a/src/calibre/test_build.py +++ b/src/calibre/test_build.py @@ -158,6 +158,10 @@ class BuildTest(unittest.TestCase): from calibre.utils.certgen import create_key_pair create_key_pair() + def test_fonttools(self): + from fontTools.subset import main + main + def test_msgpack(self): from calibre.utils.date import utcnow from calibre.utils.serialize import msgpack_dumps, msgpack_loads From 4bb9c9fba89bfd430e1028917d925a8bcf293d43 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 20:52:33 +0530 Subject: [PATCH 0558/2055] Install fonttools on Arch CI --- setup/arch-ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/arch-ci.sh b/setup/arch-ci.sh index c390e6fbbb..c97c71f6d0 100755 --- a/setup/arch-ci.sh +++ b/setup/arch-ci.sh @@ -5,7 +5,7 @@ set -xe -pacman -S --noconfirm --needed base-devel sudo git sip pyqt-builder cmake chmlib icu jxrlib hunspell libmtp libusb libwmf optipng podofo python-apsw python-beautifulsoup4 python-cssselect python-css-parser python-dateutil python-jeepney python-dnspython python-feedparser python-html2text python-html5-parser python-lxml python-markdown python-mechanize python-msgpack python-netifaces python-unrardll python-pillow python-psutil python-pygments python-pyqt6 python-regex python-zeroconf python-pyqt6-webengine qt6-svg qt6-imageformats udisks2 hyphen python-pychm python-pycryptodome speech-dispatcher python-sphinx python-urllib3 python-py7zr python-pip uchardet libstemmer poppler +pacman -S --noconfirm --needed base-devel sudo git sip pyqt-builder cmake chmlib icu jxrlib hunspell libmtp libusb libwmf optipng podofo python-apsw python-beautifulsoup4 python-cssselect python-css-parser python-dateutil python-jeepney python-dnspython python-feedparser python-html2text python-html5-parser python-lxml python-markdown python-mechanize python-msgpack python-netifaces python-unrardll python-pillow python-psutil python-pygments python-pyqt6 python-regex python-zeroconf python-pyqt6-webengine qt6-svg qt6-imageformats udisks2 hyphen python-pychm python-pycryptodome speech-dispatcher python-sphinx python-urllib3 python-py7zr python-pip python-fonttools uchardet libstemmer poppler useradd -m ci chown -R ci:users $GITHUB_WORKSPACE From 970ca5293e19fef2e0a9d75e047e201aed5e2346 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 20:59:31 +0530 Subject: [PATCH 0559/2055] Fix a regression in the previous release that could result in empty author folders remaining in the library when the author of a book is changed --- src/calibre/db/backend.py | 3 +++ src/calibre/db/tests/filesystem.py | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index d9e92bde5a..a636d9d503 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1872,6 +1872,9 @@ class DB: if os.path.exists(spath): copy_tree(os.path.abspath(spath), tpath, delete_source=True, transform_destination_filename=transform_format_filenames) + parent = os.path.dirname(spath) + with suppress(OSError): + os.rmdir(parent) # remove empty parent directory else: os.makedirs(tpath) update_paths_in_db() diff --git a/src/calibre/db/tests/filesystem.py b/src/calibre/db/tests/filesystem.py index 724994c053..db55ccac8d 100644 --- a/src/calibre/db/tests/filesystem.py +++ b/src/calibre/db/tests/filesystem.py @@ -127,7 +127,10 @@ class FilesystemTest(BaseTest): from calibre.ebooks.metadata.book.base import Metadata cache.set_metadata(1, Metadata('t1', ('a1', 'a2'))) check_that_filesystem_and_db_entries_match(1) - + # check that empty author folders are removed + for x in os.scandir(cache.backend.library_path): + if x.is_dir(): + self.assertTrue(os.listdir(x.path)) @unittest.skipUnless(iswindows, 'Windows only') def test_windows_atomic_move(self): From a8daf6c065308d3b265381e5e379c3a09cdeef09 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 21:59:06 +0530 Subject: [PATCH 0560/2055] Remove the action to clear extra files cache the extra files cache is an internal implementation detail. I dont want to expose that so prominently to end users. If in practice it turns out that the cache becoming stale is a big issue, we can revisit. --- src/calibre/gui2/actions/choose_library.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index 9d9cc6ed5a..46f5025eb3 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -329,10 +329,6 @@ class ChooseLibraryAction(InterfaceAction): None, None), attr='action_restore_database') ac.triggered.connect(self.restore_database, type=Qt.ConnectionType.QueuedConnection) - ac = self.create_action(spec=(_('Clear extra files cache'), 'lt.png', - None, None), - attr='action_clear_extra_files_cache') - ac.triggered.connect(self.clear_extra_files_cache, type=Qt.ConnectionType.QueuedConnection) self.maintenance_menu.addAction(ac) self.choose_menu.addMenu(self.maintenance_menu) @@ -653,10 +649,6 @@ class ChooseLibraryAction(InterfaceAction): if restore_database(db, self.gui): self.gui.library_moved(db.library_path) - def clear_extra_files_cache(self): - db = self.gui.library_view.model().db - db.new_api.clear_extra_files_cache() - def check_library(self): from calibre.gui2.dialogs.check_library import CheckLibraryDialog, DBCheck self.gui.library_view.save_state() From 9e5bb7a5eabcb355361b1f4afe7799fa97f6d285 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 22:02:26 +0530 Subject: [PATCH 0561/2055] Also clear extra files cache when viewing folder by id --- src/calibre/gui2/actions/view.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/actions/view.py b/src/calibre/gui2/actions/view.py index b1986854b9..7b85dda42f 100644 --- a/src/calibre/gui2/actions/view.py +++ b/src/calibre/gui2/actions/view.py @@ -273,7 +273,7 @@ class ViewAction(InterfaceAction): if not self._view_check(len(rows), max_=10, skip_dialog_name='open-folder-many-check'): return for i, row in enumerate(rows): - db = self.gui.library_view.model().db + db = self.gui.current_db db.new_api.clear_extra_files_cache(self.gui.library_view.model().id(row)) path = db.abspath(row.row()) open_local_file(path) @@ -281,11 +281,13 @@ class ViewAction(InterfaceAction): time.sleep(0.1) # Finder cannot handle multiple folder opens def view_folder_for_id(self, id_): - path = self.gui.library_view.model().db.abspath(id_, index_is_id=True) + db = self.gui.current_db + db.new_api.clear_extra_files_cache(id_) + path = db.abspath(id_, index_is_id=True) open_local_file(path) def view_data_folder_for_id(self, id_): - db = self.gui.library_view.model().db + db = self.gui.current_db db.new_api.clear_extra_files_cache(id_) path = db.abspath(id_, index_is_id=True) open_local_file(os.path.join(path, DATA_DIR_NAME)) From bd483ab92c7a0245782541ff71095525b6e24e92 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 23 Apr 2023 22:05:33 +0530 Subject: [PATCH 0562/2055] Forgot to change the structure of extra_files_cache to allow easy eviction by book_id --- src/calibre/db/cache.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 30a4faaaee..08bce569f0 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -3108,8 +3108,7 @@ class Cache: where relpath is the relative path of the file to the book directory using / as a separator. stat_result is the result of calling os.stat() on the file. ''' - key = book_id, pattern - ans = self.extra_files_cache.get(key) + ans = self.extra_files_cache.setdefault(book_id, {}).get(pattern) if ans is None or not use_cache: ans = [] path = self._field_for('path', book_id) @@ -3118,7 +3117,7 @@ class Cache: book_id, path, self.fields['formats'], yield_paths=True, pattern=pattern ): ans.append((relpath, file_path, stat_result)) - self.extra_files_cache[key] = ans = tuple(ans) + self.extra_files_cache[book_id][pattern] = ans = tuple(ans) return ans @read_api From 7b5024b97a849a4e16ebe16efcd33145be654928 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 23 Apr 2023 20:47:43 +0100 Subject: [PATCH 0563/2055] Two semi-dependent changes: 1) Add a tweak to change the heading for standard columns 2) Add "id", "formats", and "path" as standard columns. They are by default hidden, like "languages" --- resources/default_tweaks.py | 14 +++++++++++++ src/calibre/gui2/library/models.py | 28 +++++++++++-------------- src/calibre/gui2/library/views.py | 30 ++++++++++++--------------- src/calibre/library/field_metadata.py | 29 +++++++++++++++++++++++++- 4 files changed, 67 insertions(+), 34 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 7be3dc9a66..385d444032 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -574,3 +574,17 @@ allow_template_database_functions_in_composites = False # for https://whatever URLs. %u is replaced by the URL to be opened. The scheme # takes a glob pattern allowing a single entry to match multiple URL types. openers_by_scheme = {} + +#: Change standard column heading text to some value +# Use the dictionary below to change a column heading. The format of the +# dictionary is +# {lookup_name: new_heading, ...} +# The new_heading must be unique: no two columns can have the same heading. +# This tweak works only with standard columns: it cannot be used to change +# the heading for a custom column. If a custom column has the same heading as +# one provided here then a number will appended to the custom column's heading +# to make it unique. +# +# Example: +# alternate_column_headings = {'authors':'Writers', 'size':'MBytes'} +alternate_column_headings = {} diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 597c29f29e..99c195c603 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -38,7 +38,7 @@ from calibre.utils.date import ( UNDEFINED_DATE, as_local_time, dt_factory, is_date_undefined, qt_to_dt, ) from calibre.utils.icu import sort_key -from calibre.utils.localization import calibre_langcode_to_name, ngettext +from calibre.utils.localization import calibre_langcode_to_name from calibre.utils.resources import get_path as P from calibre.utils.search_query_parser import ParseException, SearchQueryParser from polyglot.builtins import iteritems, itervalues, string_or_bytes @@ -192,20 +192,12 @@ class BooksModel(QAbstractTableModel): # {{{ self.bi_font = QFont(self.bold_font) self.bi_font.setItalic(True) self.styled_columns = {} - self.orig_headers = { - 'title' : _("Title"), - 'ondevice' : _("On Device"), - 'authors' : _("Author(s)"), - 'size' : _("Size (MB)"), - 'timestamp' : _("Date"), - 'pubdate' : _('Published'), - 'rating' : _('Rating'), - 'publisher' : _("Publisher"), - 'tags' : _("Tags"), - 'series' : ngettext("Series", 'Series', 1), - 'last_modified' : _('Modified'), - 'languages' : _('Languages'), - } + possible_columns = ('title', 'ondevice', 'authors', 'size', 'timestamp', + 'pubdate', 'rating', 'publisher', 'tags', 'series', + 'last_modified', 'languages', 'formats', 'id', 'path') + from calibre.library.field_metadata import FieldMetadata + fm = FieldMetadata() + self.orig_headers = {k: fm[k]['name'] for k in possible_columns} self.db = None @@ -829,6 +821,10 @@ class BooksModel(QAbstractTableModel): # {{{ def renderer(field, decorator=False): idfunc = self.db.id + if field == 'id': + def func(idx): + return idfunc(idx) + return func fffunc = self.db.new_api.fast_field_for field_obj = self.db.new_api.fields[field] m = field_obj.metadata.copy() @@ -953,7 +949,7 @@ class BooksModel(QAbstractTableModel): # {{{ return func - self.dc = {f:renderer(f) for f in 'title authors size timestamp pubdate last_modified rating publisher tags series ondevice languages'.split()} + self.dc = {f:renderer(f) for f in self.orig_headers.keys()} self.dc_decorator = {f:renderer(f, True) for f in ('ondevice',)} for col in self.custom_columns: diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index e3b4b5dad6..3cf9f90ac9 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -808,8 +808,8 @@ class BooksView(QTableView): # {{{ state = {} state['hidden_columns'] = [cm[i] for i in range(h.count()) if h.isSectionHidden(i) and cm[i] != 'ondevice'] - state['last_modified_injected'] = True - state['languages_injected'] = True + for f in ('last_modified', 'languages', 'formats', 'id', 'path'): + state[f+'_injected'] = True state['sort_history'] = \ self.cleanup_sort_history(self.model().sort_history, ignore_column_map=self.is_library_view) state['column_positions'] = {} @@ -923,7 +923,7 @@ class BooksView(QTableView): # {{{ def get_default_state(self): old_state = { - 'hidden_columns': ['last_modified', 'languages'], + 'hidden_columns': ['last_modified', 'languages', 'formats', 'id', 'path'], 'sort_history':[DEFAULT_SORT], 'column_positions': {}, 'column_sizes': {}, @@ -931,9 +931,9 @@ class BooksView(QTableView): # {{{ 'size':'center', 'timestamp':'center', 'pubdate':'center'}, - 'last_modified_injected': True, - 'languages_injected': True, } + for f in ('last_modified', 'languages', 'formats', 'id', 'path'): + old_state[f+'_injected'] = True h = self.column_header cm = self.column_map for i in range(h.count()): @@ -965,18 +965,14 @@ class BooksView(QTableView): # {{{ db.new_api.set_pref(name, ans) else: injected = False - if not ans.get('last_modified_injected', False): - injected = True - ans['last_modified_injected'] = True - hc = ans.get('hidden_columns', []) - if 'last_modified' not in hc: - hc.append('last_modified') - if not ans.get('languages_injected', False): - injected = True - ans['languages_injected'] = True - hc = ans.get('hidden_columns', []) - if 'languages' not in hc: - hc.append('languages') + for f in ('last_modified', 'languages', 'formats', 'id', 'path'): + if not ans.get(f+'_injected', False): + print('injecting', f) + injected = True + ans[f+'_injected'] = True + hc = ans.get('hidden_columns', []) + if f not in hc: + hc.append(f) if injected: db.new_api.set_pref(name, ans) return ans diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index 20dfd10bd5..ee6eef051c 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -5,6 +5,7 @@ Created on 25 May 2010 ''' import traceback +import sys from collections import OrderedDict from calibre.utils.config_base import tweaks @@ -197,7 +198,7 @@ def _builtin_field_metadata(): 'datatype':'int', 'is_multiple':{}, 'kind':'field', - 'name':None, + 'name': _('Id'), 'search_terms':['id'], 'is_custom':False, 'is_category':False, @@ -411,6 +412,15 @@ class FieldMetadata: self._tb_cats[k]['display'] = {} self._tb_cats[k]['is_editable'] = True self._add_search_terms_to_map(k, v['search_terms']) + alternate_headings = tweaks.get('alternate_column_headings', {}) + if alternate_headings: + existing_headings = {k['name'] for k in self._tb_cats.values() if k['name']} + for k,v in alternate_headings.items(): + if k in self._tb_cats.keys(): + v = self.get_unique_field_heading(v) + existing_headings.discard(self._tb_cats[k]['name']) + existing_headings.add(v) + self._tb_cats[k]['name'] = v self._tb_cats['timestamp']['display'] = { 'date_format': tweaks['gui_timestamp_display_format']} self._tb_cats['pubdate']['display'] = { @@ -454,6 +464,10 @@ class FieldMetadata: def __ne__(self, other): return not self.__eq__(other) + def set_field_heading(self, key, heading): + if key in self._tb_cats.keys(): + self._tb_cats[key]['name'] = heading + def sortable_field_keys(self): return [k for k in self._tb_cats.keys() if self._tb_cats[k]['kind']=='field' and @@ -560,6 +574,18 @@ class FieldMetadata: l[k] = self._tb_cats[k] return l + def get_unique_field_heading(self, name): + # Verify column heading is unique. Can only happen if the tweak is set + if tweaks.get('alternate_column_headings', {}): + existing_names = {icu_lower(c['name']) for c in self._tb_cats.values() if c['name'] is not None} + t = icu_lower(name) + if t in existing_names: + for i in range(1, sys.maxsize): + if (t + '_' + str(i)) not in existing_names: + name = name + '_' + str(i) + break + return name + def add_custom_field(self, label, table, column, datatype, colnum, name, display, is_editable, is_multiple, is_category, is_csp=False): @@ -568,6 +594,7 @@ class FieldMetadata: raise ValueError('Duplicate custom field [%s]'%(label)) if datatype not in self.VALID_DATA_TYPES: raise ValueError('Unknown datatype %s for field %s'%(datatype, key)) + name = self.get_unique_field_heading(name) self._tb_cats[key] = {'table':table, 'column':column, 'datatype':datatype, 'is_multiple':is_multiple, 'kind':'field', 'name':name, From f4878d0509e1d8fcc5c0097e1539134be74ae6cf Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 24 Apr 2023 10:52:22 +0530 Subject: [PATCH 0564/2055] Cleanup previous PR I dont think we need to enforce unique column titles. If a user wants to have columns with the same titles, that's up to them. Also avoids the performance penalty. --- resources/default_tweaks.py | 18 +++++---------- src/calibre/gui2/dialogs/quickview.py | 2 +- src/calibre/gui2/library/views.py | 1 - src/calibre/library/field_metadata.py | 33 +++++---------------------- 4 files changed, 13 insertions(+), 41 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 385d444032..c46acd6abf 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -575,16 +575,10 @@ allow_template_database_functions_in_composites = False # takes a glob pattern allowing a single entry to match multiple URL types. openers_by_scheme = {} -#: Change standard column heading text to some value -# Use the dictionary below to change a column heading. The format of the -# dictionary is -# {lookup_name: new_heading, ...} -# The new_heading must be unique: no two columns can have the same heading. -# This tweak works only with standard columns: it cannot be used to change -# the heading for a custom column. If a custom column has the same heading as -# one provided here then a number will appended to the custom column's heading -# to make it unique. -# +#: Change standard column names +# Use the dictionary below to change a column name. +# This tweak works only with standard columns, it cannot be used to change +# the heading for a custom column. # Example: -# alternate_column_headings = {'authors':'Writers', 'size':'MBytes'} -alternate_column_headings = {} +# alternate_column_names = {'authors':'Writers', 'size':'MBytes'} +alternate_column_names = {} diff --git a/src/calibre/gui2/dialogs/quickview.py b/src/calibre/gui2/dialogs/quickview.py index 419d9755a2..f5c40fe437 100644 --- a/src/calibre/gui2/dialogs/quickview.py +++ b/src/calibre/gui2/dialogs/quickview.py @@ -342,7 +342,7 @@ class Quickview(QDialog, Ui_Quickview): a = m.addAction(self.select_book_icon, _('Select this book in the library'), partial(self.select_book, book_id)) a.setEnabled(book_displayed) - m.addAction(_('Open a locked book details window for this book'), + m.addAction(_('Open a locked Book details window for this book'), partial(self.show_book_details, book_id)) m.addAction(self.search_icon, _('Find item in the library'), partial(self.do_search, follow_library_view=False)) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 3cf9f90ac9..ae2a4ecf5b 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -967,7 +967,6 @@ class BooksView(QTableView): # {{{ injected = False for f in ('last_modified', 'languages', 'formats', 'id', 'path'): if not ans.get(f+'_injected', False): - print('injecting', f) injected = True ans[f+'_injected'] = True hc = ans.get('hidden_columns', []) diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index ee6eef051c..ebb01107bd 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -5,7 +5,6 @@ Created on 25 May 2010 ''' import traceback -import sys from collections import OrderedDict from calibre.utils.config_base import tweaks @@ -396,6 +395,7 @@ class FieldMetadata: # search labels that are not db columns search_items = ['all', 'search', 'vl', 'template'] + custom_field_prefix = '#' __calibre_serializable__ = True def __init__(self): @@ -412,22 +412,18 @@ class FieldMetadata: self._tb_cats[k]['display'] = {} self._tb_cats[k]['is_editable'] = True self._add_search_terms_to_map(k, v['search_terms']) - alternate_headings = tweaks.get('alternate_column_headings', {}) - if alternate_headings: - existing_headings = {k['name'] for k in self._tb_cats.values() if k['name']} - for k,v in alternate_headings.items(): - if k in self._tb_cats.keys(): - v = self.get_unique_field_heading(v) - existing_headings.discard(self._tb_cats[k]['name']) - existing_headings.add(v) + try: + for k, v in tweaks['alternate_column_names'].items(): + if k in self._tb_cats and not self.is_custom_field(k): self._tb_cats[k]['name'] = v + except Exception: + traceback.print_exc() self._tb_cats['timestamp']['display'] = { 'date_format': tweaks['gui_timestamp_display_format']} self._tb_cats['pubdate']['display'] = { 'date_format': tweaks['gui_pubdate_display_format']} self._tb_cats['last_modified']['display'] = { 'date_format': tweaks['gui_last_modified_display_format']} - self.custom_field_prefix = '#' self.get = self._tb_cats.get def __getitem__(self, key): @@ -464,10 +460,6 @@ class FieldMetadata: def __ne__(self, other): return not self.__eq__(other) - def set_field_heading(self, key, heading): - if key in self._tb_cats.keys(): - self._tb_cats[key]['name'] = heading - def sortable_field_keys(self): return [k for k in self._tb_cats.keys() if self._tb_cats[k]['kind']=='field' and @@ -574,18 +566,6 @@ class FieldMetadata: l[k] = self._tb_cats[k] return l - def get_unique_field_heading(self, name): - # Verify column heading is unique. Can only happen if the tweak is set - if tweaks.get('alternate_column_headings', {}): - existing_names = {icu_lower(c['name']) for c in self._tb_cats.values() if c['name'] is not None} - t = icu_lower(name) - if t in existing_names: - for i in range(1, sys.maxsize): - if (t + '_' + str(i)) not in existing_names: - name = name + '_' + str(i) - break - return name - def add_custom_field(self, label, table, column, datatype, colnum, name, display, is_editable, is_multiple, is_category, is_csp=False): @@ -594,7 +574,6 @@ class FieldMetadata: raise ValueError('Duplicate custom field [%s]'%(label)) if datatype not in self.VALID_DATA_TYPES: raise ValueError('Unknown datatype %s for field %s'%(datatype, key)) - name = self.get_unique_field_heading(name) self._tb_cats[key] = {'table':table, 'column':column, 'datatype':datatype, 'is_multiple':is_multiple, 'kind':'field', 'name':name, From 8a0088f50db862aefbaa4e236d4b6aaa9ed4c44a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 24 Apr 2023 11:01:45 +0530 Subject: [PATCH 0565/2055] ... --- src/calibre/gui2/trash.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/trash.py b/src/calibre/gui2/trash.py index 1633c0ddcc..7ca44dacc3 100644 --- a/src/calibre/gui2/trash.py +++ b/src/calibre/gui2/trash.py @@ -181,7 +181,7 @@ class TrashView(Dialog): self.db.expire_old_trash() def sizeHint(self): - return QSize(500, 650) + return QSize(530, 650) def do_operation_on_selected(self, func): ok_items, failed_items = [], [] From 14e7acc6f4f68b45482c213b8f7142a5974a25b8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 24 Apr 2023 11:48:15 +0530 Subject: [PATCH 0566/2055] Import fonttools only on demand Fixes #2017476 [Error when using 'Edit metadata' hotkey from locked details window](https://bugs.launchpad.net/calibre/+bug/2017476) --- src/calibre/utils/fonts/subset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/fonts/subset.py b/src/calibre/utils/fonts/subset.py index e41570e664..84d3860b26 100644 --- a/src/calibre/utils/fonts/subset.py +++ b/src/calibre/utils/fonts/subset.py @@ -6,10 +6,10 @@ import sys from logging.handlers import QueueHandler from queue import Empty, SimpleQueue -from fontTools.subset import Subsetter, load_font, log, save_font def subset(input_file_object_or_path, output_file_object_or_path, container_type, chars_or_text=''): + from fontTools.subset import Subsetter, load_font, log, save_font log_messages = SimpleQueue() log_handler = QueueHandler(log_messages) log.addHandler(log_handler) From 259a8555db59a6246ea331614bc67b7ec6eab7b3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 24 Apr 2023 11:50:22 +0530 Subject: [PATCH 0567/2055] ... --- src/calibre/gui2/metadata/basic_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 77bfd4dc6f..43c06eca8a 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -28,7 +28,6 @@ from calibre.ebooks.metadata import ( authors_to_sort_string, check_isbn, string_to_authors, title_sort, ) from calibre.ebooks.metadata.meta import get_metadata -from calibre.ebooks.oeb.polish.main import SUPPORTED as EDIT_SUPPORTED from calibre.gui2 import ( choose_files_and_remember_all_files, choose_images, error_dialog, file_icon_provider, gprefs, @@ -851,6 +850,7 @@ class FormatList(_FormatList): return QSize(sz.width() * 7, sz.height() * 3) def contextMenuEvent(self, event): + from calibre.ebooks.oeb.polish.main import SUPPORTED as EDIT_SUPPORTED item = self.itemFromIndex(self.currentIndex()) originals = [self.item(x).ext.upper() for x in range(self.count())] originals = [x for x in originals if x.startswith('ORIGINAL_')] From cdc4a0a4d323eea3588d56b23b97480c38d27825 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 24 Apr 2023 11:31:40 +0100 Subject: [PATCH 0568/2055] The formatter functions to support extra book files. --- manual/template_lang.rst | 6 +- src/calibre/utils/formatter_functions.py | 102 +++++++++++++++++++++-- 2 files changed, 101 insertions(+), 7 deletions(-) diff --git a/manual/template_lang.rst b/manual/template_lang.rst index 1a467f0e6d..1c6b356e04 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -469,7 +469,7 @@ In `GPM` the functions described in `Single Function Mode` all require an additi * ``booksize()`` -- returns the value of the calibre 'size' field. Returns '' if there are no formats. * ``check_yes_no(field_name, is_undefined, is_false, is_true)`` -- checks if the value of the yes/no field named by the lookup name ``field_name`` is one of the values specified by the parameters, returning ``'yes'`` if a match is found otherwise returning the empty string. Set the parameter ``is_undefined``, ``is_false``, or ``is_true`` to 1 (the number) to check that condition, otherwise set it to 0. Example: - ``check_yes_no("#bool", 1, 0, 1)`` returns ``'yes'`` if the yes/no field ``#bool`` is either True or undefined (neither True nor False). + ``check_yes_no("#bool", 1, 0, 1)`` returns ``'Yes'`` if the yes/no field ``#bool`` is either True or undefined (neither True nor False). More than one of ``is_undefined``, ``is_false``, or ``is_true`` can be set to 1. * ``ceiling(x)`` -- returns the smallest integer greater than or equal to ``x``. Throws an exception if ``x`` is not a number. @@ -493,6 +493,9 @@ In `GPM` the functions described in `Single Function Mode` all require an additi * ``days_between(date1, date2)`` -- return the number of days between ``date1`` and ``date2``. The number is positive if ``date1`` is greater than ``date2``, otherwise negative. If either ``date1`` or ``date2`` are not dates, the function returns the empty string. * ``divide(x, y)`` -- returns ``x / y``. Throws an exception if either ``x`` or ``y`` are not numbers. This function can usually be replaced by the ``/`` operator. * ``eval(string)`` -- evaluates the string as a program, passing the local variables. This permits using the template processor to construct complex results from local variables. In :ref:`Template Program Mode `, because the `{` and `}` characters are interpreted before the template is evaluated you must use `[[` for the `{` character and `]]` for the ``}`` character. They are converted automatically. Note also that prefixes and suffixes (the `|prefix|suffix` syntax) cannot be used in the argument to this function when using :ref:`Template Program Mode `. +* ``extra_file_size(file_name)`` -- returns the size in bytes of the extra file ``file_name`` in the book's ``data/`` folder if it exists, otherwise ``-1``. See also the functions ``has_extra_files()``, ``extra_file_names()`` and ``extra_file_modtime()``. This function can be used only in the GUI. +* ``extra_file_modtime(file_name, format_spec)`` -- returns the modification time of the extra file ``file_name`` in the book's ``data/`` folder if it exists, otherwise ``-1``. The modtime is formatted according to ``format_string`` (see ``format_date()`` for details). If ``format_string`` is the empty string, returns the modtime as the floating point number of seconds since the epoch. See also the functions ``has_extra_files()``, ``extra_file_names()`` and ``extra_file_size()``. The epoch is OS dependent. This function can be used only in the GUI. +* ``extra_file_names(sep)`` -- returns a ``sep``-separated list of extra files in the book's ``data/`` folder. See also the functions ``has_extra_files()``, ``extra_file_size()`` and ``extra_file_modtime()``. This function can be used only in the GUI. * ``field(lookup_name)`` -- returns the value of the metadata field with lookup name ``lookup_name``. * ``field_exists(field_name)`` -- checks if a field (column) with the lookup name ``field_name`` exists, returning ``'1'`` if so and the empty string if not. * ``finish_formatting(val, fmt, prefix, suffix)`` -- apply the format, prefix, and suffix to a value in the same way as done in a template like ``{series_index:05.2f| - |- }``. This function is provided to ease conversion of complex single-function- or template-program-mode templates to `GPM` Templates. For example, the following program produces the same output as the above template:: @@ -554,6 +557,7 @@ In `GPM` the functions described in `Single Function Mode` all require an additi * ``formats_sizes()`` -- return a comma-separated list of colon-separated ``FMT:SIZE`` items giving the sizes in bytes of the formats of a book. You can use the select function to get the size for a specific format. Note that format names are always uppercase, as in EPUB. * ``fractional_part(x)`` -- returns the value after the decimal point. For example, ``fractional_part(3.14)`` returns ``0.14``. Throws an exception if ``x`` is not a number. * ``has_cover()`` -- return ``'Yes'`` if the book has a cover, otherwise the empty string. +* ``has_extra_files()`` -- returns ``'Yes'`` if there are any extra files for the book (files in the folder ``data/`` in the book's folder), otherwise ``''`` (the empty string). See also the functions ``extra_file_names()``, ``extra_file_size()`` and ``extra_file_modtime()`` This function can be used only in the GUI. * ``is_marked()`` -- check whether the book is `marked` in calibre. If it is then return the value of the mark, either ``'true'`` (lower case) or a comma-separated list of named marks. Returns ``''`` (the empty string) if the book is not marked. This function works only in the GUI. * ``language_codes(lang_strings)`` -- return the `language codes `_ for the language names passed in `lang_strings`. The strings must be in the language of the current locale. ``Lang_strings`` is a comma-separated list. * ``list_contains(value, separator, [ pattern, found_val, ]* not_found_val)`` -- (Alias of ``in_list``) Interpreting the value as a list of items separated by ``separator``, evaluate the ``pattern`` against each value in the list. If the ``pattern`` matches any value then return ``found_val``, otherwise return ``not_found_val``. The ``pattern`` and ``found_value`` can be repeated as many times as desired, permitting returning different values depending on the search. The patterns are checked in order. The first match is returned. Aliases: ``in_list()``, ``list_contains()`` diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index e3b6bf8346..1cc73c4a2a 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -2144,14 +2144,14 @@ class BuiltinCheckYesNo(BuiltinFormatterFunction): res = getattr(mi, field, None) if res is None: if is_undefined == '1': - return 'yes' + return 'Yes' return "" if not isinstance(res, bool): raise ValueError(_('check_yes_no requires the field be a Yes/No custom column')) if is_false == '1' and not res: - return 'yes' + return 'Yes' if is_true == '1' and res: - return 'yes' + return 'Yes' return "" @@ -2387,6 +2387,95 @@ class BuiltinBookValues(BuiltinFormatterFunction): raise ValueError(e) +class BuiltinHasExtraFiles(BuiltinFormatterFunction): + name = 'has_extra_files' + arg_count = 0 + category = 'Template database functions' + __doc__ = doc = _("has_extra_files() -- returns 'Yes' if there are any extra " + "files, otherwise '' (the empty string). " + 'This function can be used only in the GUI.') + + def evaluate(self, formatter, kwargs, mi, locals): + db = self.get_database(mi).new_api + try: + files = db.list_extra_files(mi.id, use_cache=True, pattern='data/**/*') + return 'Yes' if files else '' + except Exception as e: + traceback.print_exc() + raise ValueError(e) + + +class BuiltinExtraFileNames(BuiltinFormatterFunction): + name = 'extra_file_names' + arg_count = 1 + category = 'Template database functions' + __doc__ = doc = _("extra_file_names(sep) -- returns a sep-separated list of " + "extra files in the book's 'data/' folder. " + 'This function can be used only in the GUI.') + + def evaluate(self, formatter, kwargs, mi, locals, sep): + db = self.get_database(mi).new_api + try: + files = db.list_extra_files(mi.id, use_cache=True, pattern='data/**/*') + return sep.join([file[0][5:] for file in files]) + except Exception as e: + traceback.print_exc() + raise ValueError(e) + + +class BuiltinExtraFileSize(BuiltinFormatterFunction): + name = 'extra_file_size' + arg_count = 1 + category = 'Template database functions' + __doc__ = doc = _("extra_file_size(file_name) -- returns the size in bytes of " + "the extra file 'file_name' in the book's 'data/' folder if " + "it exists, otherwise -1." + 'This function can be used only in the GUI.') + + def evaluate(self, formatter, kwargs, mi, locals, file_name): + db = self.get_database(mi).new_api + try: + file_name = 'data/' + file_name + files = db.list_extra_files(mi.id, use_cache=True, pattern='data/**/*') + for f in files: + if f[0] == file_name: + return str(f[2].st_size) + return str(-1) + except Exception as e: + traceback.print_exc() + raise ValueError(e) + + +class BuiltinExtraFileModtime(BuiltinFormatterFunction): + name = 'extra_file_modtime' + arg_count = 2 + category = 'Template database functions' + __doc__ = doc = _("extra_file_modtime(file_name, format_spec) -- returns the " + "modification time of the extra file 'file_name' in the " + "book's 'data/' folder if it exists, otherwise -1.0. The " + "modtime is formatted according to 'format_string' " + "(see format_date()). If 'format_string' is empty, returns " + "the modtime as the floating point number of seconds since " + "the epoch. The epoch is OS dependent. " + "This function can be used only in the GUI.") + + def evaluate(self, formatter, kwargs, mi, locals, file_name, format_string): + db = self.get_database(mi).new_api + try: + file_name = 'data/' + file_name + files = db.list_extra_files(mi.id, use_cache=True, pattern='data/**/*') + for f in files: + if f[0] == file_name: + val = f[2].st_mtime + if format_string: + return format_date(datetime.fromtimestamp(val), format_string) + return str(val) + return str(1.0) + except Exception as e: + traceback.print_exc() + raise ValueError(e) + + _formatter_builtins = [ BuiltinAdd(), BuiltinAnd(), BuiltinApproximateFormats(), BuiltinArguments(), BuiltinAssign(), @@ -2396,12 +2485,13 @@ _formatter_builtins = [ BuiltinCmp(), BuiltinConnectedDeviceName(), BuiltinConnectedDeviceUUID(), BuiltinContains(), BuiltinCount(), BuiltinCurrentLibraryName(), BuiltinCurrentLibraryPath(), BuiltinCurrentVirtualLibraryName(), BuiltinDateArithmetic(), - BuiltinDaysBetween(), BuiltinDivide(), BuiltinEval(), BuiltinFirstNonEmpty(), - BuiltinField(), BuiltinFieldExists(), + BuiltinDaysBetween(), BuiltinDivide(), BuiltinEval(), + BuiltinExtraFileNames(), BuiltinExtraFileSize(), BuiltinExtraFileModtime(), + BuiltinFirstNonEmpty(), BuiltinField(), BuiltinFieldExists(), BuiltinFinishFormatting(), BuiltinFirstMatchingCmp(), BuiltinFloor(), BuiltinFormatDate(), BuiltinFormatNumber(), BuiltinFormatsModtimes(), BuiltinFormatsPaths(), BuiltinFormatsSizes(), BuiltinFractionalPart(), - BuiltinGlobals(), + BuiltinGlobals(), BuiltinHasExtraFiles(), BuiltinHasCover(), BuiltinHumanReadable(), BuiltinIdentifierInList(), BuiltinIfempty(), BuiltinLanguageCodes(), BuiltinLanguageStrings(), BuiltinInList(), BuiltinIsMarked(), BuiltinListCountMatching(), From 663ccc2166c378360ef94757ce8ae3c5db20fc91 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 24 Apr 2023 13:19:30 +0100 Subject: [PATCH 0569/2055] Prevent calling edit metadata from locked book details windows. EM edits the currently selected book, which is almost certainly not the book in the locked window. --- src/calibre/gui2/book_details.py | 7 +++++-- src/calibre/gui2/dialogs/book_info.py | 9 ++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 50f0c73112..9fb46360c7 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -561,8 +561,11 @@ def details_context_menu_event(view, ev, book_info, add_popup_action=False, edit if add_popup_action: menu.addMenu(get_gui().iactions['Show Book Details'].qaction.menu()) else: - ema = get_gui().iactions['Edit Metadata'].menuless_qaction - menu.addAction(_('Open the Edit metadata window') + '\t' + ema.shortcut().toString(QKeySequence.SequenceFormat.NativeText), edit_metadata) + # We can't open edit metadata from a locked window because EM expects to + # be editing the current book, which this book probably isn't + if edit_metadata is not None: + ema = get_gui().iactions['Edit Metadata'].menuless_qaction + menu.addAction(_('Open the Edit metadata window') + '\t' + ema.shortcut().toString(QKeySequence.SequenceFormat.NativeText), edit_metadata) if not reindex_fmt_added: menu.addSeparator() menu.addAction(_( diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index b8401535ae..b292742173 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -119,19 +119,21 @@ class Configure(Dialog): class Details(HTMLDisplay): - def __init__(self, book_info, parent=None, allow_context_menu=True): + def __init__(self, book_info, parent=None, allow_context_menu=True, is_locked=False): HTMLDisplay.__init__(self, parent) self.book_info = book_info self.edit_metadata = getattr(parent, 'edit_metadata', None) self.setDefaultStyleSheet(css()) self.allow_context_menu = allow_context_menu + self.is_locked = is_locked def sizeHint(self): return QSize(350, 350) def contextMenuEvent(self, ev): if self.allow_context_menu: - details_context_menu_event(self, ev, self.book_info, edit_metadata=self.edit_metadata) + details_context_menu_event(self, ev, self.book_info, + edit_metadata=None if self.is_locked else self.edit_metadata) class DialogNumbers(IntEnum): @@ -167,7 +169,8 @@ class BookInfo(QDialog): self.splitter.addWidget(self.cover) self.details = Details(parent.book_details.book_info, self, - allow_context_menu=library_path is None) + allow_context_menu=library_path is None, + is_locked = dialog_number == DialogNumbers.Locked) self.details.anchor_clicked.connect(self.on_link_clicked) self.link_delegate = link_delegate self.details.setAttribute(Qt.WidgetAttribute.WA_OpaquePaintEvent, False) From 27bb04c2bd8e9cc83cca9fa023c5239d663fc5f7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 24 Apr 2023 20:13:26 +0530 Subject: [PATCH 0570/2055] DRYer --- src/calibre/utils/formatter_functions.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 1cc73c4a2a..72b91ff9a0 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -23,6 +23,7 @@ from math import ceil, floor, modf, trunc from calibre import human_readable, prepare_string_for_xml, prints from calibre.constants import DEBUG +from calibre.db.constants import DATA_FILE_PATTERN from calibre.ebooks.metadata import title_sort from calibre.utils.config import tweaks from calibre.utils.date import UNDEFINED_DATE, format_date, now, parse_date @@ -2398,7 +2399,7 @@ class BuiltinHasExtraFiles(BuiltinFormatterFunction): def evaluate(self, formatter, kwargs, mi, locals): db = self.get_database(mi).new_api try: - files = db.list_extra_files(mi.id, use_cache=True, pattern='data/**/*') + files = db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) return 'Yes' if files else '' except Exception as e: traceback.print_exc() @@ -2416,7 +2417,7 @@ class BuiltinExtraFileNames(BuiltinFormatterFunction): def evaluate(self, formatter, kwargs, mi, locals, sep): db = self.get_database(mi).new_api try: - files = db.list_extra_files(mi.id, use_cache=True, pattern='data/**/*') + files = db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) return sep.join([file[0][5:] for file in files]) except Exception as e: traceback.print_exc() @@ -2436,7 +2437,7 @@ class BuiltinExtraFileSize(BuiltinFormatterFunction): db = self.get_database(mi).new_api try: file_name = 'data/' + file_name - files = db.list_extra_files(mi.id, use_cache=True, pattern='data/**/*') + files = db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) for f in files: if f[0] == file_name: return str(f[2].st_size) @@ -2463,7 +2464,7 @@ class BuiltinExtraFileModtime(BuiltinFormatterFunction): db = self.get_database(mi).new_api try: file_name = 'data/' + file_name - files = db.list_extra_files(mi.id, use_cache=True, pattern='data/**/*') + files = db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) for f in files: if f[0] == file_name: val = f[2].st_mtime From 93c161e96f812c0ac885de850e0583bd83f6fd13 Mon Sep 17 00:00:00 2001 From: un-pogaz <46523284+un-pogaz@users.noreply.github.com> Date: Mon, 24 Apr 2023 17:55:49 +0200 Subject: [PATCH 0571/2055] fix comments markdown in v4, the switch to QtWebEngine introduced a bug where, in "HTML comments", paragraphs with a single
produced an visual extra gap in the render, problem will have been solved by using non-breaking space

 

(topic https://www.mobileread.com/forums/showthread.php?t=325991) However, the "calibre.library.comments > markdown" still produces HTML with


causing the rendering of "Markdown comments" in Book Details (and Book Info) to still have this extra gap. This commit is to fix this and do a more consistent rendering between "HTML comments" and "Markdown comments" --- src/calibre/library/comments.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/library/comments.py b/src/calibre/library/comments.py index fe3853d656..1e312d5b5b 100644 --- a/src/calibre/library/comments.py +++ b/src/calibre/library/comments.py @@ -134,7 +134,8 @@ def markdown(val): except AttributeError: from calibre.ebooks.markdown import Markdown md = markdown.Markdown = Markdown() - return md.convert(val) + val = md.convert(val) + return re.sub(r']*?)>\s*\s*

','\xa0

', val) def merge_comments(one, two): From 0b9064bcc6d74fee34f2c615b29419c437e428bf Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 24 Apr 2023 22:24:38 +0530 Subject: [PATCH 0572/2055] Dont list directories in list_extra_files() --- src/calibre/db/backend.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 86ba120e94..3f9692c0e8 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -12,6 +12,7 @@ import hashlib import json import os import shutil +import stat import sys import time import uuid @@ -1914,9 +1915,11 @@ class DB: relpath = os.path.relpath(path, full_book_path) relpath = relpath.replace(os.sep, '/') if relpath not in known_files: - stat = os.stat(path) + stat_result = os.stat(path) + if stat.S_ISDIR(stat_result.st_mode): + continue if yield_paths: - yield relpath, path, stat + yield relpath, path, stat_result else: try: src = open(path, 'rb') @@ -1925,7 +1928,7 @@ class DB: time.sleep(1) src = open(path, 'rb') with src: - yield relpath, src, stat + yield relpath, src, stat_result def add_extra_file(self, relpath, stream, book_path, replace=True, auto_rename=False): bookdir = os.path.join(self.library_path, book_path) From 6a0b6f46de76111ab8f58e1a9c80245920db6ac6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 24 Apr 2023 22:25:47 +0530 Subject: [PATCH 0573/2055] Ignore files for which stat() fails when listing extra files --- src/calibre/db/backend.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 3f9692c0e8..c292c0b937 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1915,7 +1915,10 @@ class DB: relpath = os.path.relpath(path, full_book_path) relpath = relpath.replace(os.sep, '/') if relpath not in known_files: - stat_result = os.stat(path) + try: + stat_result = os.stat(path) + except OSError: + continue if stat.S_ISDIR(stat_result.st_mode): continue if yield_paths: From 79cd85af357b76742bcb6d6db3db9fe473d8064d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 24 Apr 2023 22:28:47 +0530 Subject: [PATCH 0574/2055] Only show the Data files link if there is an actual file in the data folder, ignore sub-folders --- src/calibre/ebooks/metadata/book/render.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index 7d7a259968..74f9e4b77e 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -219,8 +219,10 @@ def mi_to_html( else: data_path = os.path.join(path, DATA_DIR_NAME) with suppress(OSError): - if os.listdir(data_path): - num_of_folders = 2 + for de in os.scandir(data_path): + if de.is_file(): + num_of_folders = 2 + break text = _('Book files') name = ngettext('Folder:', 'Folders:', num_of_folders) link = '{}{}'.format(action(scheme, book_id=book_id, loc=loc), From 300c68b1e4cb61c0e7d634100f741f83d801195c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 24 Apr 2023 22:37:54 +0530 Subject: [PATCH 0575/2055] Scan opened book/data folders for changes to extra files for a few minutes after they are opened, updating caches and the GUI as changes are detected --- src/calibre/gui2/actions/view.py | 21 +++--- src/calibre/gui2/extra_files_watcher.py | 87 +++++++++++++++++++++++++ src/calibre/gui2/ui.py | 4 ++ 3 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 src/calibre/gui2/extra_files_watcher.py diff --git a/src/calibre/gui2/actions/view.py b/src/calibre/gui2/actions/view.py index 7b85dda42f..db28358fb3 100644 --- a/src/calibre/gui2/actions/view.py +++ b/src/calibre/gui2/actions/view.py @@ -272,24 +272,23 @@ class ViewAction(InterfaceAction): return if not self._view_check(len(rows), max_=10, skip_dialog_name='open-folder-many-check'): return + db = self.gui.current_db for i, row in enumerate(rows): - db = self.gui.current_db - db.new_api.clear_extra_files_cache(self.gui.library_view.model().id(row)) + self.gui.extra_files_watcher.watch_book(db.id(row.row())) path = db.abspath(row.row()) - open_local_file(path) - if ismacos and i < len(rows) - 1: - time.sleep(0.1) # Finder cannot handle multiple folder opens + if path: + open_local_file(path) + if ismacos and i < len(rows) - 1: + time.sleep(0.1) # Finder cannot handle multiple folder opens def view_folder_for_id(self, id_): - db = self.gui.current_db - db.new_api.clear_extra_files_cache(id_) - path = db.abspath(id_, index_is_id=True) + self.gui.extra_files_watcher.watch_book(id_) + path = self.gui.current_db.abspath(id_, index_is_id=True) open_local_file(path) def view_data_folder_for_id(self, id_): - db = self.gui.current_db - db.new_api.clear_extra_files_cache(id_) - path = db.abspath(id_, index_is_id=True) + self.gui.extra_files_watcher.watch_book(id_) + path = self.gui.current_db.abspath(id_, index_is_id=True) open_local_file(os.path.join(path, DATA_DIR_NAME)) def view_book(self, triggered): diff --git a/src/calibre/gui2/extra_files_watcher.py b/src/calibre/gui2/extra_files_watcher.py new file mode 100644 index 0000000000..f5b94c01f4 --- /dev/null +++ b/src/calibre/gui2/extra_files_watcher.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# License: GPLv3 Copyright: 2023, Kovid Goyal + + +from qt.core import QObject, QTimer +from time import monotonic +from typing import NamedTuple, Tuple + +from calibre.db.constants import DATA_FILE_PATTERN + + +class ExtraFile(NamedTuple): + relpath: str + mtime: float + size: int + + +class ExtraFiles(NamedTuple): + last_changed_at: float + files: Tuple[ExtraFile, ...] + + +class ExtraFilesWatcher(QObject): + + WATCH_FOR = 300 # seconds + TICK_INTERVAL = 1 # seconds + + def __init__(self, parent=None): + super().__init__(parent) + self.watched_book_ids = {} + self.timer = QTimer(self) + self.timer.setInterval(int(self.TICK_INTERVAL * 1000)) + self.timer.timeout.connect(self.check_registered_books) + + def clear(self): + self.watched_book_ids.clear() + self.timer.stop() + + def watch_book(self, book_id): + if book_id not in self.watched_book_ids: + try: + self.watched_book_ids[book_id] = ExtraFiles(monotonic(), self.get_extra_files(book_id)) + except Exception: + import traceback + traceback.print_exc() + return + self.timer.start() + + @property + def gui(self): + ans = self.parent() + if hasattr(ans, 'current_db'): + return ans + from calibre.gui2.ui import get_gui + return get_gui() + + def get_extra_files(self, book_id): + db = self.gui.current_db.new_api + return tuple(ExtraFile(relpath, stat_result.st_mtime, stat_result.st_size) for + relpath, file_path, stat_result in db.list_extra_files(book_id, pattern=DATA_FILE_PATTERN)) + + def check_registered_books(self): + changed = {} + remove = set() + now = monotonic() + for book_id, extra_files in self.watched_book_ids.items(): + try: + ef = self.get_extra_files(book_id) + except Exception: + # book probably deleted + remove.add(book_id) + continue + if ef != extra_files.files: + changed[book_id] = ef + elif now - extra_files.last_changed_at > self.WATCH_FOR: + remove.add(book_id) + if changed: + self.refresh_gui(changed) + for book_id, files in changed.items(): + self.watched_book_ids[book_id] = self.watched_book_ids[book_id]._replace(files=files, last_changed_at=now) + for book_id in remove: + self.watched_book_ids.pop(book_id, None) + if not self.watched_book_ids: + self.timer.stop() + + def refresh_gui(self, book_ids): + self.gui.library_view.model().refresh_ids(frozenset(book_ids), current_row=self.gui.library_view.currentIndex().row()) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index efceb8843f..49f6c3e156 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -30,6 +30,7 @@ from calibre.constants import ( from calibre.customize import PluginInstallationType from calibre.customize.ui import available_store_plugins, interface_actions from calibre.db.legacy import LibraryDatabase +from calibre.gui2.extra_files_watcher import ExtraFilesWatcher from calibre.gui2 import ( Dispatcher, GetMetadata, config, error_dialog, gprefs, info_dialog, max_available_height, open_url, question_dialog, warning_dialog, @@ -118,6 +119,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ def __init__(self, opts, parent=None, gui_debug=None): MainWindow.__init__(self, opts, parent=parent, disable_automatic_gc=True) self.setWindowIcon(QApplication.instance().windowIcon()) + self.extra_files_watcher = ExtraFilesWatcher(self) self.jobs_pointer = Pointer(self) self.proceed_requested.connect(self.do_proceed, type=Qt.ConnectionType.QueuedConnection) @@ -914,6 +916,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ import traceback traceback.print_exc() self.library_path = newloc + self.extra_files_watcher.clear() prefs['library_path'] = self.library_path self.book_on_device(None, reset=True) db.set_book_on_device_func(self.book_on_device) @@ -1148,6 +1151,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.shutdown_started.emit() self.show_shutdown_message() self.server_change_notification_timer.stop() + self.extra_files_watcher.clear() try: self.event_in_db.disconnect() except Exception: From cfe2ff3c4b124817ec1233f1b6a94cb83303677a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 24 Apr 2023 22:42:00 +0530 Subject: [PATCH 0576/2055] Add a comment explaining why we regex the markdown output --- src/calibre/library/comments.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/library/comments.py b/src/calibre/library/comments.py index 1e312d5b5b..21320e28cc 100644 --- a/src/calibre/library/comments.py +++ b/src/calibre/library/comments.py @@ -135,6 +135,8 @@ def markdown(val): from calibre.ebooks.markdown import Markdown md = markdown.Markdown = Markdown() val = md.convert(val) + # The Qt Rich text widgets display


as two blank lines instead + # of one so fix that here. return re.sub(r']*?)>\s*\s*

','\xa0

', val) From 8a450c458a7e3bc5a9dbb9c81d655ae380605fc5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 24 Apr 2023 23:07:32 +0530 Subject: [PATCH 0577/2055] String changes --- manual/template_lang.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/manual/template_lang.rst b/manual/template_lang.rst index 1c6b356e04..570590c5bb 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -493,8 +493,8 @@ In `GPM` the functions described in `Single Function Mode` all require an additi * ``days_between(date1, date2)`` -- return the number of days between ``date1`` and ``date2``. The number is positive if ``date1`` is greater than ``date2``, otherwise negative. If either ``date1`` or ``date2`` are not dates, the function returns the empty string. * ``divide(x, y)`` -- returns ``x / y``. Throws an exception if either ``x`` or ``y`` are not numbers. This function can usually be replaced by the ``/`` operator. * ``eval(string)`` -- evaluates the string as a program, passing the local variables. This permits using the template processor to construct complex results from local variables. In :ref:`Template Program Mode `, because the `{` and `}` characters are interpreted before the template is evaluated you must use `[[` for the `{` character and `]]` for the ``}`` character. They are converted automatically. Note also that prefixes and suffixes (the `|prefix|suffix` syntax) cannot be used in the argument to this function when using :ref:`Template Program Mode `. -* ``extra_file_size(file_name)`` -- returns the size in bytes of the extra file ``file_name`` in the book's ``data/`` folder if it exists, otherwise ``-1``. See also the functions ``has_extra_files()``, ``extra_file_names()`` and ``extra_file_modtime()``. This function can be used only in the GUI. -* ``extra_file_modtime(file_name, format_spec)`` -- returns the modification time of the extra file ``file_name`` in the book's ``data/`` folder if it exists, otherwise ``-1``. The modtime is formatted according to ``format_string`` (see ``format_date()`` for details). If ``format_string`` is the empty string, returns the modtime as the floating point number of seconds since the epoch. See also the functions ``has_extra_files()``, ``extra_file_names()`` and ``extra_file_size()``. The epoch is OS dependent. This function can be used only in the GUI. +* ``extra_file_size(file_name)`` -- returns the size in bytes of the extra file ``file_name`` in the book's ``data/`` folder if it exists, otherwise ``-1``. See also the functions ``has_extra_files()``, ``extra_file_names()`` and ``extra_file_modtime()``. This function can be used only in the GUI. +* ``extra_file_modtime(file_name, format_date)`` -- returns the modification time of the extra file ``file_name`` in the book's ``data/`` folder if it exists, otherwise ``-1``. The modtime is formatted according to ``format_string`` (see ``format_date()`` for details). If ``format_string`` is the empty string, returns the modtime as the floating point number of seconds since the epoch. See also the functions ``has_extra_files()``, ``extra_file_names()`` and ``extra_file_size()``. The epoch is OS dependent. This function can be used only in the GUI. * ``extra_file_names(sep)`` -- returns a ``sep``-separated list of extra files in the book's ``data/`` folder. See also the functions ``has_extra_files()``, ``extra_file_size()`` and ``extra_file_modtime()``. This function can be used only in the GUI. * ``field(lookup_name)`` -- returns the value of the metadata field with lookup name ``lookup_name``. * ``field_exists(field_name)`` -- checks if a field (column) with the lookup name ``field_name`` exists, returning ``'1'`` if so and the empty string if not. @@ -557,7 +557,7 @@ In `GPM` the functions described in `Single Function Mode` all require an additi * ``formats_sizes()`` -- return a comma-separated list of colon-separated ``FMT:SIZE`` items giving the sizes in bytes of the formats of a book. You can use the select function to get the size for a specific format. Note that format names are always uppercase, as in EPUB. * ``fractional_part(x)`` -- returns the value after the decimal point. For example, ``fractional_part(3.14)`` returns ``0.14``. Throws an exception if ``x`` is not a number. * ``has_cover()`` -- return ``'Yes'`` if the book has a cover, otherwise the empty string. -* ``has_extra_files()`` -- returns ``'Yes'`` if there are any extra files for the book (files in the folder ``data/`` in the book's folder), otherwise ``''`` (the empty string). See also the functions ``extra_file_names()``, ``extra_file_size()`` and ``extra_file_modtime()`` This function can be used only in the GUI. +* ``has_extra_files()`` -- returns ``'Yes'`` if there are any extra files for the book (files in the folder ``data/`` in the book's folder), otherwise ``''`` (the empty string). See also the functions ``extra_file_names()``, ``extra_file_size()`` and ``extra_file_modtime()``. This function can be used only in the GUI. * ``is_marked()`` -- check whether the book is `marked` in calibre. If it is then return the value of the mark, either ``'true'`` (lower case) or a comma-separated list of named marks. Returns ``''`` (the empty string) if the book is not marked. This function works only in the GUI. * ``language_codes(lang_strings)`` -- return the `language codes `_ for the language names passed in `lang_strings`. The strings must be in the language of the current locale. ``Lang_strings`` is a comma-separated list. * ``list_contains(value, separator, [ pattern, found_val, ]* not_found_val)`` -- (Alias of ``in_list``) Interpreting the value as a list of items separated by ``separator``, evaluate the ``pattern`` against each value in the list. If the ``pattern`` matches any value then return ``found_val``, otherwise return ``not_found_val``. The ``pattern`` and ``found_value`` can be repeated as many times as desired, permitting returning different values depending on the search. The patterns are checked in order. The first match is returned. Aliases: ``in_list()``, ``list_contains()`` From d0ba1ada1683f30fd40ac6fe2f6834a2ad7e3cc8 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 24 Apr 2023 21:05:01 +0100 Subject: [PATCH 0578/2055] Fix for the string changes fix for this file. Both the original and the changed file were incorrect. --- manual/template_lang.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manual/template_lang.rst b/manual/template_lang.rst index 570590c5bb..5126ce4878 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -494,7 +494,7 @@ In `GPM` the functions described in `Single Function Mode` all require an additi * ``divide(x, y)`` -- returns ``x / y``. Throws an exception if either ``x`` or ``y`` are not numbers. This function can usually be replaced by the ``/`` operator. * ``eval(string)`` -- evaluates the string as a program, passing the local variables. This permits using the template processor to construct complex results from local variables. In :ref:`Template Program Mode `, because the `{` and `}` characters are interpreted before the template is evaluated you must use `[[` for the `{` character and `]]` for the ``}`` character. They are converted automatically. Note also that prefixes and suffixes (the `|prefix|suffix` syntax) cannot be used in the argument to this function when using :ref:`Template Program Mode `. * ``extra_file_size(file_name)`` -- returns the size in bytes of the extra file ``file_name`` in the book's ``data/`` folder if it exists, otherwise ``-1``. See also the functions ``has_extra_files()``, ``extra_file_names()`` and ``extra_file_modtime()``. This function can be used only in the GUI. -* ``extra_file_modtime(file_name, format_date)`` -- returns the modification time of the extra file ``file_name`` in the book's ``data/`` folder if it exists, otherwise ``-1``. The modtime is formatted according to ``format_string`` (see ``format_date()`` for details). If ``format_string`` is the empty string, returns the modtime as the floating point number of seconds since the epoch. See also the functions ``has_extra_files()``, ``extra_file_names()`` and ``extra_file_size()``. The epoch is OS dependent. This function can be used only in the GUI. +* ``extra_file_modtime(file_name, format_spec)`` -- returns the modification time of the extra file ``file_name`` in the book's ``data/`` folder if it exists, otherwise ``-1``. The modtime is formatted according to ``format_spec`` (see ``format_date()`` for details). If ``format_spec`` is the empty string, returns the modtime as the floating point number of seconds since the epoch. See also the functions ``has_extra_files()``, ``extra_file_names()`` and ``extra_file_size()``. The epoch is OS dependent. This function can be used only in the GUI. * ``extra_file_names(sep)`` -- returns a ``sep``-separated list of extra files in the book's ``data/`` folder. See also the functions ``has_extra_files()``, ``extra_file_size()`` and ``extra_file_modtime()``. This function can be used only in the GUI. * ``field(lookup_name)`` -- returns the value of the metadata field with lookup name ``lookup_name``. * ``field_exists(field_name)`` -- checks if a field (column) with the lookup name ``field_name`` exists, returning ``'1'`` if so and the empty string if not. From 6cfbfb0f516dc2a9cf802d15e84728b2132069f6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 25 Apr 2023 10:39:09 +0530 Subject: [PATCH 0579/2055] Implement workaround for incorrect initial horz scroll position of book list --- src/calibre/gui2/library/views.py | 49 +++++++++++++++++++------------ 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index ae2a4ecf5b..b336e1e4a6 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -5,31 +5,35 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import itertools, operator -from functools import partial +import itertools +import operator from collections import OrderedDict - +from functools import partial from qt.core import ( - QTableView, Qt, QAbstractItemView, QMenu, pyqtSignal, QFont, QModelIndex, - QIcon, QItemSelection, QMimeData, QDrag, QStyle, QPoint, QUrl, QHeaderView, QEvent, - QStyleOptionHeader, QItemSelectionModel, QSize, QFontMetrics, - QDialog, QGridLayout, QPushButton, QDialogButtonBox, QLabel, QSpinBox) + QAbstractItemView, QDialog, QDialogButtonBox, QDrag, QEvent, QFont, QFontMetrics, + QGridLayout, QHeaderView, QIcon, QItemSelection, QItemSelectionModel, QLabel, QMenu, + QMimeData, QModelIndex, QPoint, QPushButton, QSize, QSpinBox, QStyle, + QStyleOptionHeader, Qt, QTableView, QTimer, QUrl, pyqtSignal, +) -from calibre.constants import islinux +from calibre import force_unicode +from calibre.constants import filesystem_encoding, islinux +from calibre.gui2 import FunctionDispatcher, error_dialog, gprefs from calibre.gui2.dialogs.enum_values_edit import EnumValuesEdit -from calibre.gui2.library.delegates import (RatingDelegate, PubDateDelegate, - TextDelegate, DateDelegate, CompleteDelegate, CcTextDelegate, CcLongTextDelegate, - CcBoolDelegate, CcCommentsDelegate, CcDateDelegate, CcTemplateDelegate, - CcEnumDelegate, CcNumberDelegate, LanguagesDelegate, SeriesDelegate, CcSeriesDelegate) +from calibre.gui2.gestures import GestureManager +from calibre.gui2.library import DEFAULT_SORT +from calibre.gui2.library.alternate_views import ( + AlternateViews, handle_enter_press, setup_dnd_interface, +) +from calibre.gui2.library.delegates import ( + CcBoolDelegate, CcCommentsDelegate, CcDateDelegate, CcEnumDelegate, + CcLongTextDelegate, CcNumberDelegate, CcSeriesDelegate, CcTemplateDelegate, + CcTextDelegate, CompleteDelegate, DateDelegate, LanguagesDelegate, PubDateDelegate, + RatingDelegate, SeriesDelegate, TextDelegate, +) from calibre.gui2.library.models import BooksModel, DeviceBooksModel from calibre.gui2.pin_columns import PinTableView -from calibre.gui2.library.alternate_views import AlternateViews, setup_dnd_interface, handle_enter_press -from calibre.gui2.gestures import GestureManager -from calibre.utils.config import tweaks, prefs -from calibre.gui2 import error_dialog, gprefs, FunctionDispatcher -from calibre.gui2.library import DEFAULT_SORT -from calibre.constants import filesystem_encoding -from calibre import force_unicode +from calibre.utils.config import prefs, tweaks from calibre.utils.icu import primary_sort_key from polyglot.builtins import iteritems @@ -1055,6 +1059,13 @@ class BooksView(QTableView): # {{{ self.series_delegate.set_auto_complete_function(db.all_series) self.publisher_delegate.set_auto_complete_function(db.all_publishers) self.alternate_views.set_database(db, stage=1) + # need to let a few event loop ticks pass for the bug to manifest + QTimer.singleShot(10, self.workaround_initial_horizontal_scroll_bug) + + def workaround_initial_horizontal_scroll_bug(self): + h = self.horizontalScrollBar() + if h.value() == h.maximum(): + h.setValue(0) def marked_changed(self, old_marked, current_marked): self.alternate_views.marked_changed(old_marked, current_marked) From 0437378bfa63acd965f323bf23eb64139b7118c6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 25 Apr 2023 10:49:56 +0530 Subject: [PATCH 0580/2055] The template functions shouldn't assume the name of the data folder --- src/calibre/utils/formatter_functions.py | 25 ++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 72b91ff9a0..b30fe22693 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -13,6 +13,7 @@ __docformat__ = 'restructuredtext en' import inspect import numbers +import posixpath import re import traceback from contextlib import suppress @@ -23,7 +24,7 @@ from math import ceil, floor, modf, trunc from calibre import human_readable, prepare_string_for_xml, prints from calibre.constants import DEBUG -from calibre.db.constants import DATA_FILE_PATTERN +from calibre.db.constants import DATA_DIR_NAME, DATA_FILE_PATTERN from calibre.ebooks.metadata import title_sort from calibre.utils.config import tweaks from calibre.utils.date import UNDEFINED_DATE, format_date, now, parse_date @@ -2411,14 +2412,14 @@ class BuiltinExtraFileNames(BuiltinFormatterFunction): arg_count = 1 category = 'Template database functions' __doc__ = doc = _("extra_file_names(sep) -- returns a sep-separated list of " - "extra files in the book's 'data/' folder. " - 'This function can be used only in the GUI.') + "extra files in the book's '{}/' folder. " + 'This function can be used only in the GUI.').format(DATA_DIR_NAME) def evaluate(self, formatter, kwargs, mi, locals, sep): db = self.get_database(mi).new_api try: files = db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) - return sep.join([file[0][5:] for file in files]) + return sep.join(file[0].partition('/')[-1] for file in files) except Exception as e: traceback.print_exc() raise ValueError(e) @@ -2429,17 +2430,17 @@ class BuiltinExtraFileSize(BuiltinFormatterFunction): arg_count = 1 category = 'Template database functions' __doc__ = doc = _("extra_file_size(file_name) -- returns the size in bytes of " - "the extra file 'file_name' in the book's 'data/' folder if " + "the extra file 'file_name' in the book's '{}/' folder if " "it exists, otherwise -1." - 'This function can be used only in the GUI.') + 'This function can be used only in the GUI.').format(DATA_DIR_NAME) def evaluate(self, formatter, kwargs, mi, locals, file_name): db = self.get_database(mi).new_api try: - file_name = 'data/' + file_name + q = posixpath.join(DATA_DIR_NAME, file_name) files = db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) for f in files: - if f[0] == file_name: + if f[0] == q: return str(f[2].st_size) return str(-1) except Exception as e: @@ -2453,20 +2454,20 @@ class BuiltinExtraFileModtime(BuiltinFormatterFunction): category = 'Template database functions' __doc__ = doc = _("extra_file_modtime(file_name, format_spec) -- returns the " "modification time of the extra file 'file_name' in the " - "book's 'data/' folder if it exists, otherwise -1.0. The " + "book's '{}/' folder if it exists, otherwise -1.0. The " "modtime is formatted according to 'format_string' " "(see format_date()). If 'format_string' is empty, returns " "the modtime as the floating point number of seconds since " "the epoch. The epoch is OS dependent. " - "This function can be used only in the GUI.") + "This function can be used only in the GUI.").format(DATA_DIR_NAME) def evaluate(self, formatter, kwargs, mi, locals, file_name, format_string): db = self.get_database(mi).new_api try: - file_name = 'data/' + file_name + q = posixpath.join(DATA_DIR_NAME, file_name) files = db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) for f in files: - if f[0] == file_name: + if f[0] == q: val = f[2].st_mtime if format_string: return format_date(datetime.fromtimestamp(val), format_string) From 1457f8ecc9eed28384e45202389d15c036395485 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 25 Apr 2023 11:00:39 +0530 Subject: [PATCH 0581/2055] Use a NamedTuple for the list of extra files --- src/calibre/db/cache.py | 16 ++++++++++++---- src/calibre/db/cli/cmd_export.py | 2 +- src/calibre/db/copy_to_library.py | 4 ++-- src/calibre/db/tests/add_remove.py | 10 +++++----- src/calibre/db/tests/filesystem.py | 10 +++++----- src/calibre/gui2/extra_files_watcher.py | 4 ++-- src/calibre/gui2/save.py | 4 ++-- src/calibre/utils/formatter_functions.py | 19 ++++++++----------- 8 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 08bce569f0..fd996c4b29 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -20,6 +20,7 @@ from io import DEFAULT_BUFFER_SIZE, BytesIO from queue import Queue from threading import Lock from time import monotonic, sleep, time +from typing import NamedTuple, Tuple from calibre import as_unicode, detect_ncpus, isbytestring from calibre.constants import iswindows, preferred_encoding @@ -53,6 +54,12 @@ from calibre.utils.localization import canonicalize_lang from polyglot.builtins import cmp, iteritems, itervalues, string_or_bytes +class ExtraFile(NamedTuple): + relpath: str + file_path: str + stat_result: os.stat_result + + def api(f): f.is_cache_api = True return f @@ -3096,7 +3103,7 @@ class Cache: return added @read_api - def list_extra_files(self, book_id, use_cache=False, pattern=''): + def list_extra_files(self, book_id, use_cache=False, pattern='') -> Tuple[ExtraFile, ...]: ''' Get information about extra files in the book's directory. @@ -3104,8 +3111,9 @@ class Cache: :param pattern: the pattern of filenames to search for. Empty pattern matches all extra files. Patterns must use / as separator. Use the DATA_FILE_PATTERN constant to match files inside the data directory. - :return: A tuple of all extra files matching the specified pattern. Each element of the tuple is (relpath, file_path, stat_result) - where relpath is the relative path of the file to the book directory using / as a separator. + :return: A tuple of all extra files matching the specified pattern. Each element of the tuple is + ExtraFile(relpath, file_path, stat_result). Where relpath is the relative path of the file + to the book directory using / as a separator. stat_result is the result of calling os.stat() on the file. ''' ans = self.extra_files_cache.setdefault(book_id, {}).get(pattern) @@ -3116,7 +3124,7 @@ class Cache: for (relpath, file_path, stat_result) in self.backend.iter_extra_files( book_id, path, self.fields['formats'], yield_paths=True, pattern=pattern ): - ans.append((relpath, file_path, stat_result)) + ans.append(ExtraFile(relpath, file_path, stat_result)) self.extra_files_cache[book_id][pattern] = ans = tuple(ans) return ans diff --git a/src/calibre/db/cli/cmd_export.py b/src/calibre/db/cli/cmd_export.py index 66e463d290..e2ce552daf 100644 --- a/src/calibre/db/cli/cmd_export.py +++ b/src/calibre/db/cli/cmd_export.py @@ -27,7 +27,7 @@ def implementation(db, notify_changes, action, *args): mi = db.get_metadata(book_id) plugboards = db.pref('plugboards', {}) formats = get_formats(db.formats(book_id), formats) - extra_files_for_export = tuple(relpath for (relpath, file_path, stat_result) in db.list_extra_files(book_id, pattern=DATA_FILE_PATTERN)) + extra_files_for_export = tuple(ef.relpath for ef in db.list_extra_files(book_id, pattern=DATA_FILE_PATTERN)) plugboards['extra_files_for_export'] = extra_files_for_export return mi, plugboards, formats, db.library_id, db.pref( 'user_template_functions', [] diff --git a/src/calibre/db/copy_to_library.py b/src/calibre/db/copy_to_library.py index 98dbe81d83..f470a805be 100644 --- a/src/calibre/db/copy_to_library.py +++ b/src/calibre/db/copy_to_library.py @@ -80,8 +80,8 @@ def copy_one_book( format_map = {} fmts = list(db.formats(book_id, verify_formats=False)) extra_file_map = {} - for (relpath, file_path, stat_result) in db.list_extra_files(book_id): - extra_file_map[relpath] = file_path + for ef in db.list_extra_files(book_id): + extra_file_map[ef.relpath] = ef.file_path for fmt in fmts: path = db.format_abspath(book_id, fmt) if path: diff --git a/src/calibre/db/tests/add_remove.py b/src/calibre/db/tests/add_remove.py index 636ad8f330..4c3e8edafe 100644 --- a/src/calibre/db/tests/add_remove.py +++ b/src/calibre/db/tests/add_remove.py @@ -410,8 +410,8 @@ class AddRemoveTest(BaseTest): self.assertFalse(os.path.exists(os.path.join(bookdir, 'sub', 'recurse'))) def clear_extra_files(book_id): - for (relpath, file_path, stat_result) in dest_db.list_extra_files(book_id): - os.remove(file_path) + for ef in dest_db.list_extra_files(book_id): + os.remove(ef.file_path) assert_does_not_have_extra_files(1) @@ -468,9 +468,9 @@ class AddRemoveTest(BaseTest): def extra_files_for(book_id): ans = {} - for relpath, file_path, stat_result in db.list_extra_files(book_id): - with open(file_path) as f: - ans[relpath] = f.read() + for ef in db.list_extra_files(book_id): + with open(ef.file_path) as f: + ans[ef.relpath] = f.read() return ans add_extra(1, 'one'), add_extra(1, 'sub/one') diff --git a/src/calibre/db/tests/filesystem.py b/src/calibre/db/tests/filesystem.py index 83bf8917cc..e7e851da50 100644 --- a/src/calibre/db/tests/filesystem.py +++ b/src/calibre/db/tests/filesystem.py @@ -104,9 +104,9 @@ class FilesystemTest(BaseTest): # test only formats being changed init_cache() ef = set() - for (relpath, file_path, stat_result) in cache.list_extra_files(1): - ef.add(relpath) - self.assertTrue(os.path.exists(file_path)) + for efx in cache.list_extra_files(1): + ef.add(efx.relpath) + self.assertTrue(os.path.exists(efx.file_path)) self.assertEqual(ef, {'a.side', 'subdir/a.fmt1'}) fname = cache.fields['formats'].table.fname_map[1]['FMT1'] cache.fields['formats'].table.fname_map[1]['FMT1'] = 'some thing else' @@ -229,8 +229,8 @@ class FilesystemTest(BaseTest): os.mkdir(os.path.join(bookdir, 'sub')) with open(os.path.join(bookdir, 'sub', 'recurse'), 'w') as f: f.write('recurse') - self.assertEqual({relpath for (relpath, _, _) in cache.list_extra_files(1, pattern='sub/**/*')}, {'sub/recurse'}) - self.assertEqual({relpath for (relpath, _, _) in cache.list_extra_files(1)}, {'exf', 'sub/recurse'}) + self.assertEqual({ef.relpath for ef in cache.list_extra_files(1, pattern='sub/**/*')}, {'sub/recurse'}) + self.assertEqual({ef.relpath for ef in cache.list_extra_files(1)}, {'exf', 'sub/recurse'}) for part_size in (1 << 30, 100, 1): with TemporaryDirectory('export_lib') as tdir, TemporaryDirectory('import_lib') as idir: exporter = Exporter(tdir, part_size=part_size) diff --git a/src/calibre/gui2/extra_files_watcher.py b/src/calibre/gui2/extra_files_watcher.py index f5b94c01f4..2bd39361c1 100644 --- a/src/calibre/gui2/extra_files_watcher.py +++ b/src/calibre/gui2/extra_files_watcher.py @@ -56,8 +56,8 @@ class ExtraFilesWatcher(QObject): def get_extra_files(self, book_id): db = self.gui.current_db.new_api - return tuple(ExtraFile(relpath, stat_result.st_mtime, stat_result.st_size) for - relpath, file_path, stat_result in db.list_extra_files(book_id, pattern=DATA_FILE_PATTERN)) + return tuple(ExtraFile(ef.relpath, ef.stat_result.st_mtime, ef.stat_result.st_size) for + ef in db.list_extra_files(book_id, pattern=DATA_FILE_PATTERN)) def check_registered_books(self): changed = {} diff --git a/src/calibre/gui2/save.py b/src/calibre/gui2/save.py index 83c512f764..584c64d353 100644 --- a/src/calibre/gui2/save.py +++ b/src/calibre/gui2/save.py @@ -215,8 +215,8 @@ class Saver(QObject): extra_files = {} if self.opts.save_extra_files: extra_files = {} - for (relpath, file_path, stat_result) in self.db.new_api.list_extra_files(int(book_id), pattern=DATA_FILE_PATTERN): - extra_files[relpath] = file_path + for efx in self.db.new_api.list_extra_files(int(book_id), pattern=DATA_FILE_PATTERN): + extra_files[efx.relpath] = efx.file_path if not fmts and not self.opts.write_opf and not self.opts.save_cover and not extra_files: return diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index b30fe22693..a9a567a958 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -2400,8 +2400,7 @@ class BuiltinHasExtraFiles(BuiltinFormatterFunction): def evaluate(self, formatter, kwargs, mi, locals): db = self.get_database(mi).new_api try: - files = db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) - return 'Yes' if files else '' + return 'Yes' if db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) else '' except Exception as e: traceback.print_exc() raise ValueError(e) @@ -2419,7 +2418,7 @@ class BuiltinExtraFileNames(BuiltinFormatterFunction): db = self.get_database(mi).new_api try: files = db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) - return sep.join(file[0].partition('/')[-1] for file in files) + return sep.join(file.relpath.partition('/')[-1] for file in files) except Exception as e: traceback.print_exc() raise ValueError(e) @@ -2438,10 +2437,9 @@ class BuiltinExtraFileSize(BuiltinFormatterFunction): db = self.get_database(mi).new_api try: q = posixpath.join(DATA_DIR_NAME, file_name) - files = db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) - for f in files: - if f[0] == q: - return str(f[2].st_size) + for f in db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN): + if f.relpath == q: + return str(f.stat_result.st_size) return str(-1) except Exception as e: traceback.print_exc() @@ -2465,10 +2463,9 @@ class BuiltinExtraFileModtime(BuiltinFormatterFunction): db = self.get_database(mi).new_api try: q = posixpath.join(DATA_DIR_NAME, file_name) - files = db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) - for f in files: - if f[0] == q: - val = f[2].st_mtime + for f in db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN): + if f.relpath == q: + val = f.stat_result.st_mtime if format_string: return format_date(datetime.fromtimestamp(val), format_string) return str(val) From 82392bdd617869a3d470ffa162797345bf9b4264 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 25 Apr 2023 11:03:33 +0530 Subject: [PATCH 0582/2055] ... --- src/calibre/gui2/extra_files_watcher.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/extra_files_watcher.py b/src/calibre/gui2/extra_files_watcher.py index 2bd39361c1..41b34a2462 100644 --- a/src/calibre/gui2/extra_files_watcher.py +++ b/src/calibre/gui2/extra_files_watcher.py @@ -84,4 +84,5 @@ class ExtraFilesWatcher(QObject): self.timer.stop() def refresh_gui(self, book_ids): - self.gui.library_view.model().refresh_ids(frozenset(book_ids), current_row=self.gui.library_view.currentIndex().row()) + lv = self.gui.library_view + lv.model().refresh_ids(frozenset(book_ids), current_row=lv.currentIndex().row()) From 6c9bd54cb6cb3580b3e12be1a45fdc289f37e5d8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 25 Apr 2023 20:38:38 +0530 Subject: [PATCH 0583/2055] Scan sub-folders recursively when showing the data files link --- src/calibre/ebooks/metadata/book/render.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index 74f9e4b77e..7bec32db70 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -219,8 +219,8 @@ def mi_to_html( else: data_path = os.path.join(path, DATA_DIR_NAME) with suppress(OSError): - for de in os.scandir(data_path): - if de.is_file(): + for dirpath, dirnames, filenames in os.walk(data_path): + if filenames: num_of_folders = 2 break text = _('Book files') From b990609ac1f8784895708f1729a84b974e7cc3a8 Mon Sep 17 00:00:00 2001 From: un-pogaz <46523284+un-pogaz@users.noreply.github.com> Date: Tue, 25 Apr 2023 18:15:53 +0200 Subject: [PATCH 0584/2055] Enhancement #2017403: Add a preview tab to Markdown "long text" editor (https://bugs.launchpad.net/calibre/+bug/2017403) --- src/calibre/gui2/custom_column_widgets.py | 40 ++++++- src/calibre/gui2/markdown_editor.py | 122 ++++++++++++++++++++++ 2 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 src/calibre/gui2/markdown_editor.py diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index b328d7b6ee..98f0957bfa 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -18,6 +18,7 @@ from qt.core import ( from calibre.ebooks.metadata import title_sort from calibre.gui2 import UNDEFINED_QDATETIME, elided_text, error_dialog, gprefs from calibre.gui2.comments_editor import Editor as CommentsEditor +from calibre.gui2.markdown_editor import Editor as MarkdownEditor from calibre.gui2.complete2 import EditWithComplete as EWC from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.library.delegates import ClearingDoubleSpinBox, ClearingSpinBox @@ -467,6 +468,41 @@ class Comments(Base): self.signals_to_disconnect.append(self._tb.data_changed) +class Markdown(Base): + + def setup_ui(self, parent): + self._box = QGroupBox(parent) + self._box.setTitle(label_string(self.col_metadata['name'])) + self._layout = QVBoxLayout() + self._tb = MarkdownEditor(self._box) + self._tb.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + # self._tb.setTabChangesFocus(True) + self._layout.addWidget(self._tb) + self._box.setLayout(self._layout) + self.widgets = [self._box] + + def setter(self, val): + self._tb.markdown = str(val or '').strip() + + def getter(self): + val = self._tb.markdown.strip() + if not val: + val = None + return val + + @property + def tab(self): + return self._tb.tab + + @tab.setter + def tab(self, val): + self._tb.tab = val + + def connect_data_changed(self, slot): + self._tb.editor.textChanged.connect(slot) + self.signals_to_disconnect.append(self._tb.editor.textChanged) + + class MultipleWidget(QWidget): def __init__(self, parent, only_manage_items=False, widget=EditWithComplete, name=None): @@ -780,8 +816,10 @@ def comments_factory(db, key, parent): ctype = fm.get('display', {}).get('interpret_as', 'html') if ctype == 'short-text': return SimpleText(db, key, parent) - if ctype in ('long-text', 'markdown'): + if ctype == 'long-text': return LongText(db, key, parent) + if ctype == 'markdown': + return Markdown(db, key, parent) return Comments(db, key, parent) diff --git a/src/calibre/gui2/markdown_editor.py b/src/calibre/gui2/markdown_editor.py new file mode 100644 index 0000000000..f709ad2675 --- /dev/null +++ b/src/calibre/gui2/markdown_editor.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python + + +__license__ = 'GPL v3' +__copyright__ = '2023, un_pogaz ' +__docformat__ = 'restructuredtext en' + +from qt.core import ( + QPlainTextEdit, Qt, QTabWidget, QVBoxLayout, QWidget, +) + +from calibre.gui2.book_details import css +from calibre.gui2.widgets2 import HTMLDisplay +from calibre.library.comments import markdown + + +class Editor(QWidget): # {{{ + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self._layout = QVBoxLayout(self) + self.setLayout(self._layout) + self.tabs = QTabWidget(self) + self.tabs.setTabPosition(QTabWidget.TabPosition.South) + self._layout.addWidget(self.tabs) + + self.editor = QPlainTextEdit(self.tabs) + + self.preview = HTMLDisplay(self.tabs) + self.preview.setDefaultStyleSheet(css()) + self.preview.setTabChangesFocus(True) + + self.tabs.addTab(self.editor, _('&Markdown source')) + self.tabs.addTab(self.preview, _('&Preview')) + + self.tabs.currentChanged[int].connect(self.change_tab) + self.layout().setContentsMargins(0, 0, 0, 0) + + def set_minimum_height_for_editor(self, val): + self.editor.setMinimumHeight(val) + + @property + def markdown(self): + self.tabs.setCurrentIndex(0) + return self.editor.toPlainText().strip() + + @markdown.setter + def markdown(self, v): + self.editor.setPlainText(str(v or '')) + + def change_tab(self, index): + if index == 0: # changing to source + pass + if index == 1: # changing to preview + html = markdown(self.editor.toPlainText().strip()) + val = f'''\ + + + +
{html}
+ + ''' + self.preview.setHtml(val) + + @property + def tab(self): + return 'code' if self.tabs.currentWidget() is self.editor else 'preview' + + @tab.setter + def tab(self, val): + self.tabs.setCurrentWidget(self.preview if val == 'preview' else self.editor) + + def set_readonly(self, val): + self.editor.setReadOnly(bool(val)) + + def hide_tabs(self): + self.tabs.tabBar().setVisible(False) + + def smarten_punctuation(self): + from calibre.ebooks.conversion.preprocess import smarten_punctuation + markdown = self.markdown + newmarkdown = smarten_punctuation(markdown) + if markdown != newmarkdown: + self.markdown = newmarkdown + +# }}} + + +if __name__ == '__main__': + from calibre.gui2 import Application + app = Application([]) + w = Editor() + w.resize(800, 600) + w.setWindowFlag(Qt.WindowType.Dialog) + w.show() + w.markdown = '''\ +test *italic* **bold** ***bold-italic*** `code` [link](https://calibre-ebook.com) span + +> Blockquotes + + if '>'+' '*4 in string: + pre_block() + +1. list 1 + 1. list 1.1 + 2. list 1.2 +2. list 2 + +*** + +* list +- list + +# Headers 1 +## Headers 2 +### Headers 3 +#### Headers 4 +##### Headers 5 +###### Headers 6 +''' + app.exec() + # print(w.markdown) From eff5ec46d0e29bc1c4714cebf5fb70a996aa257c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 25 Apr 2023 22:18:34 +0530 Subject: [PATCH 0585/2055] Proper fix for initial scroll to right when Id column at right bug --- src/calibre/gui2/library/views.py | 34 +++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index b336e1e4a6..7965aded1f 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -13,7 +13,7 @@ from qt.core import ( QAbstractItemView, QDialog, QDialogButtonBox, QDrag, QEvent, QFont, QFontMetrics, QGridLayout, QHeaderView, QIcon, QItemSelection, QItemSelectionModel, QLabel, QMenu, QMimeData, QModelIndex, QPoint, QPushButton, QSize, QSpinBox, QStyle, - QStyleOptionHeader, Qt, QTableView, QTimer, QUrl, pyqtSignal, + QStyleOptionHeader, Qt, QTableView, QUrl, pyqtSignal, ) from calibre import force_unicode @@ -1059,13 +1059,6 @@ class BooksView(QTableView): # {{{ self.series_delegate.set_auto_complete_function(db.all_series) self.publisher_delegate.set_auto_complete_function(db.all_publishers) self.alternate_views.set_database(db, stage=1) - # need to let a few event loop ticks pass for the bug to manifest - QTimer.singleShot(10, self.workaround_initial_horizontal_scroll_bug) - - def workaround_initial_horizontal_scroll_bug(self): - h = self.horizontalScrollBar() - if h.value() == h.maximum(): - h.setValue(0) def marked_changed(self, old_marked, current_marked): self.alternate_views.marked_changed(old_marked, current_marked) @@ -1293,12 +1286,27 @@ class BooksView(QTableView): # {{{ self.column_header.update() def scroll_to_row(self, row): + row = min(row, self.model().rowCount(QModelIndex())-1) if row > -1: - h = self.horizontalHeader() - for i in range(h.count()): - if not h.isSectionHidden(i) and h.sectionViewportPosition(i) >= 0: - self.scrollTo(self.model().index(row, i), QAbstractItemView.ScrollHint.PositionAtCenter) - break + # taken from Qt implementation of scrollTo but this ensured horizontal position is not affected + vh = self.verticalHeader() + viewport_height = self.viewport().height() + vertical_offset = vh.offset() + vertical_position = vh.sectionPosition(row) + cell_height = vh.sectionSize(row) + + pos = 'center' + if vertical_position - vertical_offset < 0 or cell_height > viewport_height: + pos = 'top' + elif vertical_position - vertical_offset + cell_height > viewport_height: + pos = 'bottom' + vsb = self.verticalScrollBar() + if pos == 'top': + vsb.setValue(vertical_position) + elif pos == 'bottom': + vsb.setValue(vertical_position - viewport_height + cell_height) + else: + vsb.setValue(vertical_position - ((viewport_height - cell_height) // 2)) @property def current_book(self): From eede10c998d17663f093ae94593e61a33a8a1326 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 25 Apr 2023 22:21:32 +0530 Subject: [PATCH 0586/2055] set_current_row() should preserve the current column and the horizontal scroll position --- src/calibre/gui2/library/views.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 7965aded1f..144e3108df 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -1337,6 +1337,7 @@ class BooksView(QTableView): # {{{ row = self.model().db.data.id_to_index(book_id) if row > -1 and row < self.model().rowCount(QModelIndex()): h = self.horizontalHeader() + hpos = self.horizontalScrollBar().value() logical_indices = list(range(h.count())) logical_indices = [x for x in logical_indices if not h.isSectionHidden(x)] @@ -1347,6 +1348,9 @@ class BooksView(QTableView): # {{{ pairs.sort(key=lambda x: x[1]) i = pairs[0][0] index = self.model().index(row, i) + ci = self.currentIndex() + if ci.isValid(): + index = self.model().index(row, ci.column()) if for_sync: sm = self.selectionModel() sm.setCurrentIndex(index, QItemSelectionModel.SelectionFlag.NoUpdate) @@ -1355,6 +1359,7 @@ class BooksView(QTableView): # {{{ if select: sm = self.selectionModel() sm.select(index, QItemSelectionModel.SelectionFlag.ClearAndSelect|QItemSelectionModel.SelectionFlag.Rows) + self.horizontalScrollBar().setValue(hpos) def select_cell(self, row_number=0, logical_column=0): if row_number > -1 and row_number < self.model().rowCount(QModelIndex()): From d1d4f06ca28365e73a874c77990fa2a8fed8ae3a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 25 Apr 2023 22:30:38 +0530 Subject: [PATCH 0587/2055] Special case display of path field name in book details preferences to match dual use as "folders/path" --- src/calibre/gui2/preferences/look_feel.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 80099d1134..1601f50299 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -255,6 +255,8 @@ class DisplayedFields(QAbstractListModel): # {{{ name = self.db.field_metadata[field]['name'] except: pass + if field == 'path': + name = _('Folders/path') if not name: return field return f'{name} ({field})' From 646982e3d46acd06475938462b66c5af41321dfb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 26 Apr 2023 08:34:42 +0530 Subject: [PATCH 0588/2055] Fix #2017709 [6.16.1*: alternate_column_names don't show in MDE](https://bugs.launchpad.net/calibre/+bug/2017709) --- src/calibre/gui2/metadata/basic_widgets.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 43c06eca8a..cb4bb599f3 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -771,7 +771,10 @@ class SeriesIndexEdit(make_undoable(QDoubleSpinBox), ToMetadataMixin): class BuddyLabel(QLabel): # {{{ def __init__(self, buddy): - QLabel.__init__(self, buddy.LABEL) + sval = tweaks['alternate_column_names'].get(getattr(buddy, 'FIELD_NAME', None)) + if sval: + sval = '&' + sval + QLabel.__init__(self, sval or buddy.LABEL) self.setBuddy(buddy) self.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignVCenter) # }}} From b319a1ceaa166e2c3881dffe1c0f3de2c9a69405 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 26 Apr 2023 08:38:07 +0530 Subject: [PATCH 0589/2055] ... --- src/calibre/gui2/metadata/basic_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index cb4bb599f3..4be82d1f38 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -773,7 +773,7 @@ class BuddyLabel(QLabel): # {{{ def __init__(self, buddy): sval = tweaks['alternate_column_names'].get(getattr(buddy, 'FIELD_NAME', None)) if sval: - sval = '&' + sval + sval = '&' + sval.replace('&', '&&') QLabel.__init__(self, sval or buddy.LABEL) self.setBuddy(buddy) self.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignVCenter) From 809ce3dc44af99f1ae85ed7e019fe957e8ce0243 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 26 Apr 2023 08:41:44 +0530 Subject: [PATCH 0590/2055] Fix #2017708 [6.16.1*: New columns missing from column icon dropdown](https://bugs.launchpad.net/calibre/+bug/2017708) --- src/calibre/library/coloring.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/calibre/library/coloring.py b/src/calibre/library/coloring.py index 69caf8548e..e6c491295f 100644 --- a/src/calibre/library/coloring.py +++ b/src/calibre/library/coloring.py @@ -263,8 +263,7 @@ def conditionable_columns(fm): def displayable_columns(fm): yield color_row_key for key in fm.displayable_field_keys(): - if key not in ('sort', 'author_sort', 'comments', 'formats', - 'identifiers', 'path'): + if key not in ('sort', 'author_sort', 'comments', 'identifiers',): yield key From f5b35bb97e503c45cbb8057a20ac72f1e37cc7b1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 26 Apr 2023 08:51:57 +0530 Subject: [PATCH 0591/2055] Remove list of chrome/firefox versions from user agent data The upstream data source (Wikipedia) was deleted and these arent currently used anyway --- setup/browser_data.py | 47 ---------------------------------- src/calibre/utils/random_ua.py | 8 ------ 2 files changed, 55 deletions(-) diff --git a/setup/browser_data.py b/setup/browser_data.py index b1196203f1..95352209dd 100644 --- a/setup/browser_data.py +++ b/setup/browser_data.py @@ -8,8 +8,6 @@ import sys from datetime import datetime from urllib.request import urlopen -from setup import download_securely - def download_from_calibre_server(url): ca = os.path.join(sys.resources_location, 'calibre-ebook-root-CA.crt') @@ -34,49 +32,6 @@ def common_user_agents(): return ans, list(sorted(ans, reverse=True, key=ans.__getitem__)) -def firefox_versions(): - print('Getting firefox versions...') - import html5lib - raw = download_securely( - 'https://www.mozilla.org/en-US/firefox/releases/').decode('utf-8') - root = html5lib.parse(raw, treebuilder='lxml', namespaceHTMLElements=False) - ol = root.xpath('//main[@id="main-content"]/ol')[0] - ol.xpath('descendant::li/strong/a[@href]') - ans = filter_ans(ol.xpath('descendant::li/strong/a[@href]/text()')) - if not ans: - raise ValueError('Failed to download list of firefox versions') - return ans - - -def chrome_versions(): - print('Getting chrome versions...') - import html5lib - raw = download_securely( - 'https://en.wikipedia.org/wiki/Google_Chrome_version_history').decode('utf-8') - root = html5lib.parse(raw, treebuilder='lxml', namespaceHTMLElements=False) - table = root.xpath('//*[@id="mw-content-text"]//tbody')[-1] - ans = [] - for tr in table.iterchildren('tr'): - cells = tuple(tr.iterchildren('td')) - if not cells: - continue - if not cells[2].text or not cells[2].text.strip(): - continue - s = cells[0].get('style') - if '#a0e75a' not in s and 'salmon' not in s: - break - chrome_version = cells[0].text.strip() - ts = datetime.strptime(cells[1].text.strip().split()[ - 0], '%Y-%m-%d').date().strftime('%Y-%m-%d') - try: - webkit_version = cells[2].text.strip().split()[1] - except IndexError: - continue - ans.append({'date': ts, 'chrome_version': chrome_version, - 'webkit_version': webkit_version}) - return list(reversed(ans)) - - def all_desktop_platforms(user_agents): ans = set() for ua in user_agents: @@ -92,8 +47,6 @@ def all_desktop_platforms(user_agents): def get_data(): ua_freq_map, common = common_user_agents() ans = { - 'chrome_versions': chrome_versions(), - 'firefox_versions': firefox_versions(), 'common_user_agents': common, 'user_agents_popularity': ua_freq_map, 'timestamp': datetime.utcnow().isoformat() + '+00:00', diff --git a/src/calibre/utils/random_ua.py b/src/calibre/utils/random_ua.py index 4e69bc622b..35f46e639f 100644 --- a/src/calibre/utils/random_ua.py +++ b/src/calibre/utils/random_ua.py @@ -48,18 +48,10 @@ def user_agents_popularity_map(): return user_agent_data().get('user_agents_popularity', {}) -def all_firefox_versions(limit=10): - return user_agent_data()['firefox_versions'][:limit] - - def random_desktop_platform(): return random.choice(user_agent_data()['desktop_platforms']) -def all_chrome_versions(limit=10): - return user_agent_data()['chrome_versions'][:limit] - - def accept_header_for_ua(ua): # See https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values if 'Firefox/' in ua: From 13191a08c787f543d9b2e498da2feb21b29c5b0e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 26 Apr 2023 09:27:22 +0530 Subject: [PATCH 0592/2055] Ensure that all sorting of the books list via calibre APIs preserves selected indices and current index and horizontal position I dont think there any sorts done without going through the view functions. But we will see. --- src/calibre/gui2/library/models.py | 7 +++++ src/calibre/gui2/library/views.py | 42 ++++++++++-------------------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 99c195c603..4b71831f0f 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -1721,6 +1721,13 @@ class DeviceBooksModel(BooksModel): # {{{ def paths(self, rows): return [self.db[self.map[r.row()]].path for r in rows] + def id(self, row): + row = getattr(row, 'row', lambda:row)() + try: + return self.db[self.map[row]].path + except Exception: + return None + def paths_for_db_ids(self, db_ids, as_map=False): res = defaultdict(list) if as_map else [] for r,b in enumerate(self.db): diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 144e3108df..d335ae4eee 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -150,8 +150,7 @@ class PreserveViewState: # {{{ and dont affect the scroll position. ''' - def __init__(self, view, preserve_hpos=True, preserve_vpos=True, - require_selected_ids=True): + def __init__(self, view, preserve_hpos=True, preserve_vpos=True, require_selected_ids=True): self.view = view self.require_selected_ids = require_selected_ids self.preserve_hpos = preserve_hpos @@ -439,10 +438,6 @@ class BooksView(QTableView): # {{{ hv = self.verticalHeader() hv.setSectionsClickable(True) hv.setCursor(Qt.CursorShape.PointingHandCursor) - self.selected_ids = [] - self._model.about_to_be_sorted.connect(self.about_to_be_sorted) - self._model.sorting_done.connect(self.sorting_done, - type=Qt.ConnectionType.QueuedConnection) self.set_row_header_visibility() self.allow_mirroring = True if self.is_library_view: @@ -698,7 +693,8 @@ class BooksView(QTableView): # {{{ self.column_header.blockSignals(True) self.column_header.setSortIndicator(col, order) self.column_header.blockSignals(False) - self.model().sort(col, order) + with self.preserve_state(preserve_vpos=False, require_selected_ids=False): + self.model().sort(col, order) if self.is_library_view: self.set_sort_indicator(col, ascending) @@ -727,17 +723,6 @@ class BooksView(QTableView): # {{{ gprefs[pname] = previous self.sort_by_named_field(field, previous[field]) - def about_to_be_sorted(self, idc): - selected_rows = [r.row() for r in self.selectionModel().selectedRows()] - self.selected_ids = [idc(r) for r in selected_rows] - - def sorting_done(self, indexc): - pos = self.horizontalScrollBar().value() - self.select_rows(self.selected_ids, using_ids=True, change_current=True, - scroll=True) - self.selected_ids = [] - self.horizontalScrollBar().setValue(pos) - def sort_by_named_field(self, field, order, reset=True): if isinstance(order, Qt.SortOrder): order = order == Qt.SortOrder.AscendingOrder @@ -745,7 +730,8 @@ class BooksView(QTableView): # {{{ idx = self.column_map.index(field) self.sort_by_column_and_order(idx, order) else: - self._model.sort_by_named_field(field, order, reset) + with self.preserve_state(preserve_vpos=False, require_selected_ids=False): + self._model.sort_by_named_field(field, order, reset) self.set_sort_indicator(-1, True) def multisort(self, fields, reset=True, only_if_different=False): @@ -767,7 +753,8 @@ class BooksView(QTableView): # {{{ sh.insert(0, (n, d)) sh = self.cleanup_sort_history(sh, ignore_column_map=True) self._model.sort_history = [tuple(x) for x in sh] - self._model.resort(reset=reset) + with self.preserve_state(preserve_vpos=False, require_selected_ids=False): + self._model.resort(reset=reset) col = fields[0][0] ascending = fields[0][1] try: @@ -781,13 +768,12 @@ class BooksView(QTableView): # {{{ self._model.resort(reset=True) def reverse_sort(self): - with self.preserve_state(preserve_vpos=False, require_selected_ids=False): - m = self.model() - try: - sort_col, order = m.sorted_on - except TypeError: - sort_col, order = 'date', True - self.sort_by_named_field(sort_col, not order) + m = self.model() + try: + sort_col, order = m.sorted_on + except TypeError: + sort_col, order = 'date', True + self.sort_by_named_field(sort_col, not order) # }}} # Ondevice column {{{ @@ -1483,7 +1469,7 @@ class BooksView(QTableView): # {{{ for idx in self.selectedIndexes(): r = idx.row() i = m.id(r) - if i not in seen: + if i not in seen and i is not None: ans.append(i) seen.add(i) return seen if as_set else ans From 3fb819da03fbdc0df311099eee0ae013bf9ff57d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 26 Apr 2023 09:39:20 +0530 Subject: [PATCH 0593/2055] Fix styling of tabs at the bottom in dark mode --- src/calibre/gui2/palette.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/palette.py b/src/calibre/gui2/palette.py index d919154eec..9334013e5b 100644 --- a/src/calibre/gui2/palette.py +++ b/src/calibre/gui2/palette.py @@ -380,14 +380,23 @@ class PaletteManager(QObject): QTabBar::tab:selected { background-color: palette(base); border: 1px solid gray; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-bottom-width: 0; padding: 2px 8px; margin-left: -4px; margin-right: -4px; } +QTabBar::tab:top:selected { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-width: 0; +} + +QTabBar::tab:bottom:selected { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-width: 0; +} + QTabBar::tab:first:selected { margin-left: 0; /* the first selected tab has nothing to overlap with on the left */ } From ef13a517743443384232dd04bdd8731d27e1cca7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 26 Apr 2023 12:35:21 +0530 Subject: [PATCH 0594/2055] Cleanup previous PR --- src/calibre/gui2/custom_column_widgets.py | 6 ++ src/calibre/gui2/markdown_editor.py | 79 ++++++++++++++++------- 2 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 98f0957bfa..a5f9d949b9 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -481,6 +481,12 @@ class Markdown(Base): self._box.setLayout(self._layout) self.widgets = [self._box] + def initialize(self, book_id): + path = self.db.abspath(book_id, index_is_id=True) + if path: + self._tb.set_base_url(QUrl.fromLocalFile(os.path.join(path, 'metadata.html'))) + return super().initialize(book_id) + def setter(self, val): self._tb.markdown = str(val or '').strip() diff --git a/src/calibre/gui2/markdown_editor.py b/src/calibre/gui2/markdown_editor.py index f709ad2675..e54e1f1b91 100644 --- a/src/calibre/gui2/markdown_editor.py +++ b/src/calibre/gui2/markdown_editor.py @@ -1,34 +1,58 @@ #!/usr/bin/env python +# License: GPLv3 Copyright: 2023, un_pogaz - -__license__ = 'GPL v3' -__copyright__ = '2023, un_pogaz ' -__docformat__ = 'restructuredtext en' - +import os from qt.core import ( - QPlainTextEdit, Qt, QTabWidget, QVBoxLayout, QWidget, + QPlainTextEdit, Qt, QTabWidget, QUrl, QVBoxLayout, QWidget, pyqtSignal, ) +from calibre.gui2 import safe_open_url from calibre.gui2.book_details import css from calibre.gui2.widgets2 import HTMLDisplay from calibre.library.comments import markdown +class Preview(HTMLDisplay): + + def __init__(self, parent=None): + super().__init__(parent) + self.setDefaultStyleSheet(css()) + self.setTabChangesFocus(True) + self.base_url = None + + def loadResource(self, rtype, qurl): + if self.base_url is not None and qurl.isRelative(): + qurl = self.base_url.resolved(qurl) + return super().loadResource(rtype, qurl) + + +class MarkdownEdit(QPlainTextEdit): + + smarten_punctuation = pyqtSignal() + + def contextMenuEvent(self, ev): + m = self.createStandardContextMenu() + m.addSeparator() + m.addAction(_('Smarten punctuation'), self.smarten_punctuation.emit) + m.exec(ev.globalPos()) + + class Editor(QWidget): # {{{ def __init__(self, parent=None): QWidget.__init__(self, parent) + self.base_url = None self._layout = QVBoxLayout(self) self.setLayout(self._layout) self.tabs = QTabWidget(self) self.tabs.setTabPosition(QTabWidget.TabPosition.South) self._layout.addWidget(self.tabs) - self.editor = QPlainTextEdit(self.tabs) + self.editor = MarkdownEdit(self) + self.editor.smarten_punctuation.connect(self.smarten_punctuation) - self.preview = HTMLDisplay(self.tabs) - self.preview.setDefaultStyleSheet(css()) - self.preview.setTabChangesFocus(True) + self.preview = Preview(self) + self.preview.anchor_clicked.connect(self.link_clicked) self.tabs.addTab(self.editor, _('&Markdown source')) self.tabs.addTab(self.preview, _('&Preview')) @@ -36,31 +60,40 @@ class Editor(QWidget): # {{{ self.tabs.currentChanged[int].connect(self.change_tab) self.layout().setContentsMargins(0, 0, 0, 0) + def link_clicked(self, qurl): + safe_open_url(qurl) + + def set_base_url(self, qurl): + self.base_url = qurl + self.preview.base_url = self.base_url + def set_minimum_height_for_editor(self, val): self.editor.setMinimumHeight(val) @property def markdown(self): - self.tabs.setCurrentIndex(0) return self.editor.toPlainText().strip() @markdown.setter def markdown(self, v): self.editor.setPlainText(str(v or '')) + if self.tab == 'preview': + self.update_preview() def change_tab(self, index): - if index == 0: # changing to source - pass if index == 1: # changing to preview - html = markdown(self.editor.toPlainText().strip()) - val = f'''\ - - - -
{html}
- - ''' - self.preview.setHtml(val) + self.update_preview() + + def update_preview(self): + html = markdown(self.editor.toPlainText().strip()) + val = f'''\ + + + +
{html}
+ + ''' + self.preview.setHtml(val) @property def tab(self): @@ -82,7 +115,6 @@ class Editor(QWidget): # {{{ newmarkdown = smarten_punctuation(markdown) if markdown != newmarkdown: self.markdown = newmarkdown - # }}} @@ -90,6 +122,7 @@ if __name__ == '__main__': from calibre.gui2 import Application app = Application([]) w = Editor() + w.set_base_url(QUrl.fromLocalFile(os.getcwd())) w.resize(800, 600) w.setWindowFlag(Qt.WindowType.Dialog) w.show() From 2a58a4b1a8d826819c87f9a1509578fb9c888109 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 26 Apr 2023 14:09:55 +0530 Subject: [PATCH 0595/2055] Forgot to restore current index before restoring current_id/current_row which was preventing the current column from being preserved --- src/calibre/gui2/library/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index d335ae4eee..044c5fee03 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -162,6 +162,7 @@ class PreserveViewState: # {{{ self.current_id = None self.vscroll = self.hscroll = 0 self.original_view = None + self.row = self.col = -1 def __enter__(self): self.init_vals() @@ -171,11 +172,16 @@ class PreserveViewState: # {{{ self.current_id = self.view.current_id self.vscroll = view.verticalScrollBar().value() self.hscroll = view.horizontalScrollBar().value() + ci = self.view.currentIndex() + self.row, self.col = ci.row(), ci.column() except: import traceback traceback.print_exc() def __exit__(self, *args): + ci = self.view.model().index(self.row, self.col) + if ci.isValid(): + self.view.setCurrentIndex(ci) if self.selected_ids or not self.require_selected_ids: if self.current_id is not None: self.view.current_id = self.current_id From 55b6205d84c00a7111f1013488aab1de21814542 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 26 Apr 2023 14:12:46 +0530 Subject: [PATCH 0596/2055] ... --- src/calibre/gui2/library/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 044c5fee03..476721b411 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -206,7 +206,7 @@ class PreserveViewState: # {{{ def state(self): self.__enter__() return {x:getattr(self, x) for x in ('selected_ids', 'current_id', - 'vscroll', 'hscroll')} + 'vscroll', 'hscroll', 'row', 'col')} @state.setter def state(self, state): From c26bcb26db6f5f0a63ebdb2dc1223d0187bc8e53 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 26 Apr 2023 11:47:23 +0100 Subject: [PATCH 0597/2055] Remove the column heading tweak. Also add not setting a delegate for non-editable standard columns --- resources/default_tweaks.py | 8 ------- src/calibre/gui2/library/models.py | 26 +++++++++++++++------- src/calibre/gui2/library/views.py | 7 +++--- src/calibre/gui2/metadata/basic_widgets.py | 5 +---- src/calibre/library/field_metadata.py | 6 ----- 5 files changed, 23 insertions(+), 29 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index c46acd6abf..7be3dc9a66 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -574,11 +574,3 @@ allow_template_database_functions_in_composites = False # for https://whatever URLs. %u is replaced by the URL to be opened. The scheme # takes a glob pattern allowing a single entry to match multiple URL types. openers_by_scheme = {} - -#: Change standard column names -# Use the dictionary below to change a column name. -# This tweak works only with standard columns, it cannot be used to change -# the heading for a custom column. -# Example: -# alternate_column_names = {'authors':'Writers', 'size':'MBytes'} -alternate_column_names = {} diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 4b71831f0f..9c4a51600a 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -38,7 +38,7 @@ from calibre.utils.date import ( UNDEFINED_DATE, as_local_time, dt_factory, is_date_undefined, qt_to_dt, ) from calibre.utils.icu import sort_key -from calibre.utils.localization import calibre_langcode_to_name +from calibre.utils.localization import calibre_langcode_to_name, ngettext from calibre.utils.resources import get_path as P from calibre.utils.search_query_parser import ParseException, SearchQueryParser from polyglot.builtins import iteritems, itervalues, string_or_bytes @@ -192,13 +192,23 @@ class BooksModel(QAbstractTableModel): # {{{ self.bi_font = QFont(self.bold_font) self.bi_font.setItalic(True) self.styled_columns = {} - possible_columns = ('title', 'ondevice', 'authors', 'size', 'timestamp', - 'pubdate', 'rating', 'publisher', 'tags', 'series', - 'last_modified', 'languages', 'formats', 'id', 'path') - from calibre.library.field_metadata import FieldMetadata - fm = FieldMetadata() - self.orig_headers = {k: fm[k]['name'] for k in possible_columns} - + self.orig_headers = { + 'title' : _("Title"), + 'ondevice' : _("On Device"), + 'authors' : _("Author(s)"), + 'size' : _("Size (MB)"), + 'timestamp' : _("Date"), + 'pubdate' : _('Published'), + 'rating' : _('Rating'), + 'publisher' : _("Publisher"), + 'tags' : _("Tags"), + 'series' : ngettext("Series", 'Series', 1), + 'last_modified' : _('Modified'), + 'languages' : _('Languages'), + 'formats' : _('Formats'), + 'id' : _('Id'), + 'path' : _('Path'), + } self.db = None self.formatter = SafeFormat() diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 476721b411..056865eac4 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -1137,9 +1137,10 @@ class BooksView(QTableView): # {{{ elif cc['datatype'] == 'enumeration': set_item_delegate(colhead, self.cc_enum_delegate) else: - dattr = colhead+'_delegate' - delegate = colhead if hasattr(self, dattr) else 'text' - set_item_delegate(colhead, getattr(self, delegate+'_delegate')) + if colhead in self._model.editable_cols: + dattr = colhead+'_delegate' + delegate = colhead if hasattr(self, dattr) else 'text' + set_item_delegate(colhead, getattr(self, delegate+'_delegate')) self.restore_state() self.set_ondevice_column_visibility() diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 4be82d1f38..43c06eca8a 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -771,10 +771,7 @@ class SeriesIndexEdit(make_undoable(QDoubleSpinBox), ToMetadataMixin): class BuddyLabel(QLabel): # {{{ def __init__(self, buddy): - sval = tweaks['alternate_column_names'].get(getattr(buddy, 'FIELD_NAME', None)) - if sval: - sval = '&' + sval.replace('&', '&&') - QLabel.__init__(self, sval or buddy.LABEL) + QLabel.__init__(self, buddy.LABEL) self.setBuddy(buddy) self.setAlignment(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignVCenter) # }}} diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index ebb01107bd..c3c8a45ec8 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -412,12 +412,6 @@ class FieldMetadata: self._tb_cats[k]['display'] = {} self._tb_cats[k]['is_editable'] = True self._add_search_terms_to_map(k, v['search_terms']) - try: - for k, v in tweaks['alternate_column_names'].items(): - if k in self._tb_cats and not self.is_custom_field(k): - self._tb_cats[k]['name'] = v - except Exception: - traceback.print_exc() self._tb_cats['timestamp']['display'] = { 'date_format': tweaks['gui_timestamp_display_format']} self._tb_cats['pubdate']['display'] = { From 1299110a1190b3b32462113d55ab63622d5b6e0a Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 26 Apr 2023 13:36:56 +0100 Subject: [PATCH 0598/2055] Add lock/unlock mouse column movement to the column heading context menu --- src/calibre/gui2/library/views.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 056865eac4..b7e1127bf9 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -55,10 +55,10 @@ def restrict_column_width(self, col, old_size, new_size): class HeaderView(QHeaderView): # {{{ - def __init__(self, *args): + def __init__(self, *args, **kwargs): QHeaderView.__init__(self, *args) if self.orientation() == Qt.Orientation.Horizontal: - self.setSectionsMovable(True) + self.setSectionsMovable(kwargs.get('allow_mouse_resize', True)) self.setSectionsClickable(True) self.setTextElideMode(Qt.TextElideMode.ElideRight) self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) @@ -419,8 +419,10 @@ class BooksView(QTableView): # {{{ self.can_add_columns = True self.was_restored = False self.allow_save_state = True - self.column_header = HeaderView(Qt.Orientation.Horizontal, self) - self.pin_view.column_header = HeaderView(Qt.Orientation.Horizontal, self.pin_view) + self.column_header = HeaderView(Qt.Orientation.Horizontal, self, + allow_mouse_resize=gprefs.get('allow_column_movement_with_mouse', True)) + self.pin_view.column_header = HeaderView(Qt.Orientation.Horizontal, self.pin_view, + allow_mouse_resize=gprefs.get('allow_column_movement_with_mouse', True)) self.setHorizontalHeader(self.column_header) self.pin_view.setHorizontalHeader(self.pin_view.column_header) self.column_header.sectionMoved.connect(self.save_state) @@ -497,6 +499,12 @@ class BooksView(QTableView): # {{{ gprefs['book_list_split'] = self.pin_view.isVisible() self.save_state() return + if action in ('lock', 'unlock'): + val = action == 'unlock' + self.column_header.setSectionsMovable(val) + self.pin_view.column_header.setSectionsMovable(val) + gprefs.set('allow_column_movement_with_mouse', val) + if not action or not column or not view: return try: @@ -680,6 +688,14 @@ class BooksView(QTableView): # {{{ ac.setText(_('Un-split the book list')) else: ac.setText(_('Split the book list')) + if view.column_header.sectionsMovable(): + view.column_header_context_menu.addAction( + QIcon.ic('drm-locked.png'), _("Don't allow moving columns with the mouse"), + partial(self.column_header_context_handler, action='lock')) + else: + view.column_header_context_menu.addAction( + QIcon.ic('drm-unlocked.png'), _("Allow moving columns with the mouse"), + partial(self.column_header_context_handler, action='unlock')) if has_context_menu: view.column_header_context_menu.popup(view.column_header.mapToGlobal(pos)) # }}} From 1b9759a4ef50200746043ae6cedbeb693f93ac70 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 26 Apr 2023 19:52:54 +0530 Subject: [PATCH 0599/2055] ... --- src/calibre/gui2/library/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index b7e1127bf9..aff08c08bd 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -58,7 +58,7 @@ class HeaderView(QHeaderView): # {{{ def __init__(self, *args, **kwargs): QHeaderView.__init__(self, *args) if self.orientation() == Qt.Orientation.Horizontal: - self.setSectionsMovable(kwargs.get('allow_mouse_resize', True)) + self.setSectionsMovable(kwargs.get('allow_mouse_movement', True)) self.setSectionsClickable(True) self.setTextElideMode(Qt.TextElideMode.ElideRight) self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) @@ -420,9 +420,9 @@ class BooksView(QTableView): # {{{ self.was_restored = False self.allow_save_state = True self.column_header = HeaderView(Qt.Orientation.Horizontal, self, - allow_mouse_resize=gprefs.get('allow_column_movement_with_mouse', True)) + allow_mouse_movement=gprefs.get('allow_column_movement_with_mouse', True)) self.pin_view.column_header = HeaderView(Qt.Orientation.Horizontal, self.pin_view, - allow_mouse_resize=gprefs.get('allow_column_movement_with_mouse', True)) + allow_mouse_movement=gprefs.get('allow_column_movement_with_mouse', True)) self.setHorizontalHeader(self.column_header) self.pin_view.setHorizontalHeader(self.pin_view.column_header) self.column_header.sectionMoved.connect(self.save_state) From 2108ddd6f296f75a3f09352a862eb97b986d8735 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 26 Apr 2023 20:51:13 +0530 Subject: [PATCH 0600/2055] Add a rudimentary syntax highlighter for the markdown editor widget Somebody that cares is welcome to improve it --- src/calibre/gui2/markdown_editor.py | 5 + .../gui2/markdown_syntax_highlighter.py | 345 ++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 src/calibre/gui2/markdown_syntax_highlighter.py diff --git a/src/calibre/gui2/markdown_editor.py b/src/calibre/gui2/markdown_editor.py index e54e1f1b91..50dc91e608 100644 --- a/src/calibre/gui2/markdown_editor.py +++ b/src/calibre/gui2/markdown_editor.py @@ -30,6 +30,11 @@ class MarkdownEdit(QPlainTextEdit): smarten_punctuation = pyqtSignal() + def __init__(self, parent=None): + super().__init__(parent) + from calibre.gui2.markdown_syntax_highlighter import MarkdownHighlighter + self.highlighter = MarkdownHighlighter(self.document()) + def contextMenuEvent(self, ev): m = self.createStandardContextMenu() m.addSeparator() diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py new file mode 100644 index 0000000000..1eae02e57e --- /dev/null +++ b/src/calibre/gui2/markdown_syntax_highlighter.py @@ -0,0 +1,345 @@ +#!/usr/bin/env python +# License: GPLv3 Copyright: 2023, Kovid Goyal + + +import re +from qt.core import ( + QApplication, QBrush, QColor, QFont, QSyntaxHighlighter, QTextCharFormat, + QTextCursor, QTextLayout, +) + +from calibre.gui2.palette import dark_link_color, light_link_color + + +class MarkdownHighlighter(QSyntaxHighlighter): + + MARKDOWN_KEYS_REGEX = { + 'Bold' : re.compile('(?P\*\*)(?P.+)(?P=delim)'), + 'uBold': re.compile('(?P__)(?P[^_]{2,})(?P=delim)'), + 'Italic': re.compile('(?P\*)(?P[^*]{2,})(?P=delim)'), + 'uItalic': re.compile('(?P_)(?P[^_]+)(?P=delim)'), + 'Link': re.compile('(?u)(^|(?P
[^!]))\[.*?\]:?[ \t]*\(?[^)]+\)?'),
+        'Image': re.compile('(?u)!\[.*?\]\(.+?\)'),
+        'HeaderAtx': re.compile('(?u)^\#{1,6}(.*?)\#*(\n|$)'),
+        'Header': re.compile('^(.+)[ \t]*\n(=+|-+)[ \t]*\n+'),
+        'CodeBlock': re.compile('^([ ]{4,}|\t).*'),
+        'UnorderedList': re.compile('(?u)^\s*(\* |\+ |- )+\s*'),
+        'UnorderedListStar': re.compile('^\s*(\* )+\s*'),
+        'OrderedList': re.compile('(?u)^\s*(\d+\. )\s*'),
+        'BlockQuote': re.compile('(?u)^\s*>+\s*'),
+        'BlockQuoteCount': re.compile('^[ \t]*>[ \t]?'),
+        'CodeSpan': re.compile('(?P`+).+?(?P=delim)'),
+        'HR': re.compile('(?u)^(\s*(\*|-)\s*){3,}$'),
+        'eHR': re.compile('(?u)^(\s*(\*|=)\s*){3,}$'),
+        'Html': re.compile('<.+?>')
+    }
+
+    light_theme =  {
+        "bold": {"color":"#859900", "font-weight":"bold", "font-style":"normal"},
+        "emphasis": {"color":"#b58900", "font-weight":"bold", "font-style":"italic"},
+        "link": {"color":light_link_color.name(), "font-weight":"normal", "font-style":"normal"},
+        "image": {"color":"#cb4b16", "font-weight":"normal", "font-style":"normal"},
+        "header": {"color":"#2aa198", "font-weight":"bold", "font-style":"normal"},
+        "unorderedlist": {"color":"red", "font-weight":"normal", "font-style":"normal"},
+        "orderedlist": {"color":"red", "font-weight":"normal", "font-style":"normal"},
+        "blockquote": {"color":"red", "font-weight":"normal", "font-style":"normal"},
+        "codespan": {"color":"#ff5800", "font-weight":"normal", "font-style":"normal"},
+        "codeblock": {"color":"#ff5800", "font-weight":"normal", "font-style":"normal"},
+        "line": {"color":"#2aa198", "font-weight":"normal", "font-style":"normal"},
+        "html": {"color":"#c000c0", "font-weight":"normal", "font-style":"normal"}
+    }
+
+    dark_theme =  {
+        "bold": {"color":"#859900", "font-weight":"bold", "font-style":"normal"},
+        "emphasis": {"color":"#b58900", "font-weight":"bold", "font-style":"italic"},
+        "link": {"color":dark_link_color.name(), "font-weight":"normal", "font-style":"normal"},
+        "image": {"color":"#cb4b16", "font-weight":"normal", "font-style":"normal"},
+        "header": {"color":"#2aa198", "font-weight":"bold", "font-style":"normal"},
+        "unorderedlist": {"color":"yellow", "font-weight":"normal", "font-style":"normal"},
+        "orderedlist": {"color":"yellow", "font-weight":"normal", "font-style":"normal"},
+        "blockquote": {"color":"yellow", "font-weight":"normal", "font-style":"normal"},
+        "codespan": {"color":"#90ee90", "font-weight":"normal", "font-style":"normal"},
+        "codeblock": {"color":"#ff9900", "font-weight":"normal", "font-style":"normal"},
+        "line": {"color":"#2aa198", "font-weight":"normal", "font-style":"normal"},
+        "html": {"color":"#F653A6", "font-weight":"normal", "font-style":"normal"}
+    }
+
+    def __init__(self, parent):
+        super().__init__(parent)
+        theme = self.dark_theme if QApplication.instance().is_dark_theme else self.light_theme
+        self.setTheme(theme)
+
+    def setTheme(self, theme):
+        self.theme = theme
+        self.MARKDOWN_KWS_FORMAT = {}
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['bold']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['bold']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['bold']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['Bold'] = format
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['bold']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['bold']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['bold']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['uBold'] = format
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['emphasis']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['emphasis']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['emphasis']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['Italic'] = format
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['emphasis']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['emphasis']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['emphasis']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['uItalic'] = format
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['link']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['link']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['link']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['Link'] = format
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['image']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['image']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['image']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['Image'] = format
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['header']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['header']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['header']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['Header'] = format
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['header']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['header']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['header']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['HeaderAtx'] = format
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['unorderedlist']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['unorderedlist']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['unorderedlist']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['UnorderedList'] = format
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['orderedlist']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['orderedlist']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['orderedlist']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['OrderedList'] = format
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['blockquote']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['blockquote']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['blockquote']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['BlockQuote'] = format
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['codespan']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['codespan']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['codespan']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['CodeSpan'] = format
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['codeblock']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['codeblock']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['codeblock']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['CodeBlock'] = format
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['line']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['line']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['line']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['HR'] = format
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['line']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['line']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['line']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['eHR'] = format
+
+        format = QTextCharFormat()
+        format.setForeground(QBrush(QColor(theme['html']['color'])))
+        format.setFontWeight(QFont.Weight.Bold if theme['html']['font-weight']=='bold' else QFont.Weight.Normal)
+        format.setFontItalic(True if theme['html']['font-style']=='italic' else False)
+        self.MARKDOWN_KWS_FORMAT['HTML'] = format
+
+        self.rehighlight()
+
+    def highlightBlock(self, text):
+        self.highlightMarkdown(text,0)
+        self.highlightHtml(text)
+
+    def highlightMarkdown(self, text, strt):
+        cursor = QTextCursor(self.document())
+        bf = cursor.blockFormat()
+
+        #Block quotes can contain all elements so process it first
+        self.highlightBlockQuote(text, cursor, bf, strt)
+
+        #If empty line no need to check for below elements just return
+        if self.highlightEmptyLine(text, cursor, bf, strt):
+            return
+
+        #If horizontal line, look at pevious line to see if its a header, process and return
+        if self.highlightHorizontalLine(text, cursor, bf, strt):
+            return
+
+        if self.highlightAtxHeader(text, cursor, bf, strt):
+            return
+
+        self.highlightList(text, cursor, bf, strt)
+
+        self.highlightLink(text, cursor, bf, strt)
+
+        self.highlightImage(text, cursor, bf, strt)
+
+        self.highlightCodeSpan(text, cursor, bf, strt)
+
+        self.highlightEmphasis(text, cursor, bf, strt)
+
+        self.highlightBold(text, cursor, bf, strt)
+
+        self.highlightCodeBlock(text, cursor, bf, strt)
+
+    def highlightBlockQuote(self, text, cursor, bf, strt):
+        found = False
+        mo = re.search(self.MARKDOWN_KEYS_REGEX['BlockQuote'],text)
+        if mo:
+            self.setFormat(mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['BlockQuote'])
+            unquote = re.sub(self.MARKDOWN_KEYS_REGEX['BlockQuoteCount'],'',text)
+            spcs = re.match(self.MARKDOWN_KEYS_REGEX['BlockQuoteCount'],text)
+            spcslen = 0
+            if spcs:
+                spcslen = len(spcs.group(0))
+            self.highlightMarkdown(unquote,spcslen)
+            found = True
+        return found
+
+    def highlightEmptyLine(self, text, cursor, bf, strt):
+        textAscii = str(text.replace('\u2029','\n'))
+        if textAscii.strip():
+            return False
+        else:
+            return True
+
+    def highlightHorizontalLine(self, text, cursor, bf, strt):
+        found = False
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['HR'],text):
+            prevBlock = self.currentBlock().previous()
+            prevCursor = QTextCursor(prevBlock)
+            prev = prevBlock.text()
+            prevAscii = str(prev.replace('\u2029','\n'))
+            if prevAscii.strip():
+                #print "Its a header"
+                prevCursor.select(QTextCursor.LineUnderCursor)
+                #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header'])
+                formatRange = QTextLayout.FormatRange()
+                formatRange.format = self.MARKDOWN_KWS_FORMAT['Header']
+                formatRange.length = prevCursor.block().length()
+                formatRange.start = 0
+                prevCursor.block().layout().setAdditionalFormats([formatRange])
+            self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HR'])
+
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['eHR'],text):
+            prevBlock = self.currentBlock().previous()
+            prevCursor = QTextCursor(prevBlock)
+            prev = prevBlock.text()
+            prevAscii = str(prev.replace('\u2029','\n'))
+            if prevAscii.strip():
+                #print "Its a header"
+                prevCursor.select(QTextCursor.LineUnderCursor)
+                #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header'])
+                formatRange = QTextLayout.FormatRange()
+                formatRange.format = self.MARKDOWN_KWS_FORMAT['Header']
+                formatRange.length = prevCursor.block().length()
+                formatRange.start = 0
+                prevCursor.block().layout().setAdditionalFormats([formatRange])
+            self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HR'])
+        return found
+
+    def highlightAtxHeader(self, text, cursor, bf, strt):
+        found = False
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['HeaderAtx'],text):
+            #bf.setBackground(QBrush(QColor(7,54,65)))
+            #cursor.movePosition(QTextCursor.End)
+            #cursor.mergeBlockFormat(bf)
+            self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HeaderAtx'])
+            found = True
+        return found
+
+    def highlightList(self, text, cursor, bf, strt):
+        found = False
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['UnorderedList'],text):
+            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['UnorderedList'])
+            found = True
+
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['OrderedList'],text):
+            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['OrderedList'])
+            found = True
+        return found
+
+    def highlightLink(self, text, cursor, bf, strt):
+        found = False
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Link'],text):
+            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['Link'])
+            found = True
+        return found
+
+    def highlightImage(self, text, cursor, bf, strt):
+        found = False
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Image'],text):
+            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['Image'])
+            found = True
+        return found
+
+    def highlightCodeSpan(self, text, cursor, bf, strt):
+        found = False
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['CodeSpan'],text):
+            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['CodeSpan'])
+            found = True
+        return found
+
+    def highlightBold(self, text, cursor, bf, strt):
+        found = False
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Bold'],text):
+            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['Bold'])
+            found = True
+
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['uBold'],text):
+            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['uBold'])
+            found = True
+        return found
+
+    def highlightEmphasis(self, text, cursor, bf, strt):
+        found = False
+        unlist = re.sub(self.MARKDOWN_KEYS_REGEX['UnorderedListStar'],'',text)
+        spcs = re.match(self.MARKDOWN_KEYS_REGEX['UnorderedListStar'],text)
+        spcslen = 0
+        if spcs:
+            spcslen = len(spcs.group(0))
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Italic'],unlist):
+            self.setFormat(mo.start()+strt+spcslen, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['Italic'])
+            found = True
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['uItalic'],text):
+            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['uItalic'])
+            found = True
+        return found
+
+    def highlightCodeBlock(self, text, cursor, bf, strt):
+        found = False
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['CodeBlock'],text):
+            stripped = text.lstrip()
+            if stripped[0] not in ('*','-','+','>'):
+                self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['CodeBlock'])
+                found = True
+        return found
+
+    def highlightHtml(self, text):
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Html'], text):
+            self.setFormat(mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HTML'])

From 453fc7baa2391e22be3d041d7148b53f6dc1c54f Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Wed, 26 Apr 2023 21:07:09 +0530
Subject: [PATCH 0601/2055] ...

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index 1eae02e57e..118365550e 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -14,7 +14,7 @@ from calibre.gui2.palette import dark_link_color, light_link_color
 class MarkdownHighlighter(QSyntaxHighlighter):
 
     MARKDOWN_KEYS_REGEX = {
-        'Bold' : re.compile('(?P\*\*)(?P.+)(?P=delim)'),
+        'Bold' : re.compile(r'(?P\*\*)(?P.+)(?P=delim)'),
         'uBold': re.compile('(?P__)(?P[^_]{2,})(?P=delim)'),
         'Italic': re.compile('(?P\*)(?P[^*]{2,})(?P=delim)'),
         'uItalic': re.compile('(?P_)(?P[^_]+)(?P=delim)'),

From 059dd97a8e8dc086ddd66b8b7ae7cbca10018171 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Wed, 26 Apr 2023 21:20:21 +0530
Subject: [PATCH 0602/2055] more regex escape porting

---
 .../gui2/markdown_syntax_highlighter.py        | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index 118365550e..b29311eeb2 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -16,21 +16,21 @@ class MarkdownHighlighter(QSyntaxHighlighter):
     MARKDOWN_KEYS_REGEX = {
         'Bold' : re.compile(r'(?P\*\*)(?P.+)(?P=delim)'),
         'uBold': re.compile('(?P__)(?P[^_]{2,})(?P=delim)'),
-        'Italic': re.compile('(?P\*)(?P[^*]{2,})(?P=delim)'),
+        'Italic': re.compile(r'(?P\*)(?P[^*]{2,})(?P=delim)'),
         'uItalic': re.compile('(?P_)(?P[^_]+)(?P=delim)'),
         'Link': re.compile('(?u)(^|(?P
[^!]))\[.*?\]:?[ \t]*\(?[^)]+\)?'),
-        'Image': re.compile('(?u)!\[.*?\]\(.+?\)'),
-        'HeaderAtx': re.compile('(?u)^\#{1,6}(.*?)\#*(\n|$)'),
+        'Image': re.compile(r'(?u)!\[.*?\]\(.+?\)'),
+        'HeaderAtx': re.compile(r'(?u)^\#{1,6}(.*?)\#*(''\n|$)'),
         'Header': re.compile('^(.+)[ \t]*\n(=+|-+)[ \t]*\n+'),
         'CodeBlock': re.compile('^([ ]{4,}|\t).*'),
-        'UnorderedList': re.compile('(?u)^\s*(\* |\+ |- )+\s*'),
-        'UnorderedListStar': re.compile('^\s*(\* )+\s*'),
-        'OrderedList': re.compile('(?u)^\s*(\d+\. )\s*'),
-        'BlockQuote': re.compile('(?u)^\s*>+\s*'),
+        'UnorderedList': re.compile(r'(?u)^\s*(\* |\+ |- )+\s*'),
+        'UnorderedListStar': re.compile(r'^\s*(\* )+\s*'),
+        'OrderedList': re.compile(r'(?u)^\s*(\d+\. )\s*'),
+        'BlockQuote': re.compile(r'(?u)^\s*>+\s*'),
         'BlockQuoteCount': re.compile('^[ \t]*>[ \t]?'),
         'CodeSpan': re.compile('(?P`+).+?(?P=delim)'),
-        'HR': re.compile('(?u)^(\s*(\*|-)\s*){3,}$'),
-        'eHR': re.compile('(?u)^(\s*(\*|=)\s*){3,}$'),
+        'HR': re.compile(r'(?u)^(\s*(\*|-)\s*){3,}$'),
+        'eHR': re.compile(r'(?u)^(\s*(\*|=)\s*){3,}$'),
         'Html': re.compile('<.+?>')
     }
 

From 8b9f0ad1dbe95d955dd4e75e76015997cfa628d0 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Wed, 26 Apr 2023 21:29:12 +0530
Subject: [PATCH 0603/2055] ...

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index b29311eeb2..821c20ac65 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -18,7 +18,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'uBold': re.compile('(?P__)(?P[^_]{2,})(?P=delim)'),
         'Italic': re.compile(r'(?P\*)(?P[^*]{2,})(?P=delim)'),
         'uItalic': re.compile('(?P_)(?P[^_]+)(?P=delim)'),
-        'Link': re.compile('(?u)(^|(?P
[^!]))\[.*?\]:?[ \t]*\(?[^)]+\)?'),
+        'Link': re.compile(r'(?u)(^|(?P
[^!]))\[.*?\]:?[ ''\t'r']*\(?[^)]+\)?'),
         'Image': re.compile(r'(?u)!\[.*?\]\(.+?\)'),
         'HeaderAtx': re.compile(r'(?u)^\#{1,6}(.*?)\#*(''\n|$)'),
         'Header': re.compile('^(.+)[ \t]*\n(=+|-+)[ \t]*\n+'),

From e75071a63a0f1356d499c7f53a86b9526f7ce780 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Thu, 27 Apr 2023 07:57:38 +0530
Subject: [PATCH 0604/2055] version 6.17.0

---
 Changelog.txt            | 37 +++++++++++++++++++++++++++++++++++++
 src/calibre/constants.py |  2 +-
 2 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/Changelog.txt b/Changelog.txt
index b5f753347f..af5ad9bb99 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -23,6 +23,43 @@
 # - title by author
 # }}}
 
+{{{ 6.17.0 2023-04-26
+
+:: new features
+
+- Font subsetting: Add support for WOFF format fonts and CID keyed fonts. Also further reduce file sizes when subsetting
+
+- Book details: Show a link to open the data files folder when data files are present
+
+- Template language: Add various functions to query the extra files associated with a book
+
+- [2017195] Edit book: Compress images: Support compression of images in the WEBP format as well
+
+- Comments editor: Add buttons to create links to data files and also to folders easily when inserting a link
+
+- Allow displaying the id, formats and path builtin columns via Preferences->Add your own columns
+
+- Add a tweak in Preferences->Tweaks to allow for changing the titles of builtin columns
+
+- [2017232] Trash bin: Add a button to clear the bin
+
+- Metadata editor: Use a dedicated editor with preview for custom columns that store markdown formatted text
+
+:: bug fixes
+
+- Fix a regression in the previous release that could result in empty author folders remaining in the library when the author of a book is changed
+
+- [2017373] Fix the data files associated with a book not being handled when using the Merge books and Copy to library functions
+
+- Fix a regression in the previous release that broke some operations in the Manage tags/authors/etc. dialogs
+
+- [2017217] Ensure metadata.opf is always written when deleting book even if it is not sequenced for backup
+
+:: improved recipes
+- Scientific American
+
+}}}
+
 {{{ 6.16.0 2023-04-20
 
 :: new features
diff --git a/src/calibre/constants.py b/src/calibre/constants.py
index c124f2fbd6..00cefe2d8e 100644
--- a/src/calibre/constants.py
+++ b/src/calibre/constants.py
@@ -5,7 +5,7 @@ from functools import lru_cache
 import sys, locale, codecs, os, collections, collections.abc
 
 __appname__   = 'calibre'
-numeric_version = (6, 16, 0)
+numeric_version = (6, 17, 0)
 __version__   = '.'.join(map(str, numeric_version))
 git_version   = None
 __author__    = "Kovid Goyal "

From c3febd9b29f80da388c8c2624f65701d6f254e79 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Thu, 27 Apr 2023 10:28:54 +0530
Subject: [PATCH 0605/2055] Remove entry about column heading tweak from
 changelog as it was reverted

---
 Changelog.txt | 2 --
 1 file changed, 2 deletions(-)

diff --git a/Changelog.txt b/Changelog.txt
index af5ad9bb99..356d9e51a0 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -39,8 +39,6 @@
 
 - Allow displaying the id, formats and path builtin columns via Preferences->Add your own columns
 
-- Add a tweak in Preferences->Tweaks to allow for changing the titles of builtin columns
-
 - [2017232] Trash bin: Add a button to clear the bin
 
 - Metadata editor: Use a dedicated editor with preview for custom columns that store markdown formatted text

From 831fb0cf4dc2af3f28ecd4e6b8c0d2ef88986e4e Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Thu, 27 Apr 2023 14:58:39 +0530
Subject: [PATCH 0606/2055] Use an encoded URL for the data files link

---
 src/calibre/gui2/book_details.py | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py
index 9fb46360c7..afd60a3080 100644
--- a/src/calibre/gui2/book_details.py
+++ b/src/calibre/gui2/book_details.py
@@ -15,7 +15,7 @@ from qt.core import (
 
 from calibre import fit_image, sanitize_file_name
 from calibre.constants import config_dir, iswindows
-from calibre.db.constants import DATA_DIR_NAME
+from calibre.db.constants import DATA_DIR_NAME, DATA_FILE_PATTERN
 from calibre.ebooks import BOOK_EXTENSIONS
 from calibre.ebooks.metadata.book.base import Metadata, field_metadata
 from calibre.ebooks.metadata.book.render import mi_to_html
@@ -487,12 +487,11 @@ def create_copy_links(menu, data=None):
     link(_('Link to show book in calibre'), f'calibre://show-book/{library_id}/{book_id}')
     link(_('Link to show book details in a popup window'), f'calibre://book-details/{library_id}/{book_id}')
     mi = db.new_api.get_proxy_metadata(book_id)
-    data_path = os.path.join(db.backend.library_path, mi.path, DATA_DIR_NAME)
-    with suppress(OSError):
-        if os.listdir(data_path):
-            if iswindows:
-                data_path = '/' + data_path.replace('\\', '/')
-            link(_("Link to open book's data files folder"), 'file://' + data_path)
+    with suppress(Exception):
+        data_files = db.new_api.list_extra_files(book_id, use_cache=True, pattern=DATA_FILE_PATTERN)
+        if data_files:
+            data_path = os.path.join(db.backend.library_path, mi.path, DATA_DIR_NAME)
+            link(_("Link to open book's data files folder"), bytes(QUrl.fromLocalFile(data_path).toEncoded()).decode('utf-8'))
     if data:
         field = data.get('field')
         if data['type'] == 'author':

From 02e3ba7588a09da436a908322e01d0c6f738523c Mon Sep 17 00:00:00 2001
From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com>
Date: Thu, 27 Apr 2023 15:12:11 +0530
Subject: [PATCH 0607/2055] Update hindu.recipe

---
 recipes/hindu.recipe | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/recipes/hindu.recipe b/recipes/hindu.recipe
index 442d6dba2b..c5e0c07801 100644
--- a/recipes/hindu.recipe
+++ b/recipes/hindu.recipe
@@ -1,7 +1,7 @@
 import json
 import re
 from collections import defaultdict
-from datetime import datetime
+from datetime import date
 from calibre.web.feeds.news import BasicNewsRecipe, classes
 
 
@@ -26,9 +26,10 @@ class TheHindu(BasicNewsRecipe):
 
     extra_css = '''
         .caption {font-size:small; text-align:center;}
-        .author {font-size:small; font-weight:bold;}
+        .author, .dateLine {font-size:small; font-weight:bold;}
         .subhead, .subhead_lead {font-weight:bold;}
         img {display:block; margin:0 auto;}
+        .italic {font-style:italic; color:#202020;}
     '''
 
     ignore_duplicate_articles = {'url'}
@@ -52,20 +53,22 @@ class TheHindu(BasicNewsRecipe):
         BasicNewsRecipe.__init__(self, *args, **kwargs)
         if self.output_profile.short_name.startswith('kindle'):
             if not past_edition:
-                self.title = 'The Hindu ' + datetime.today().strftime('%b %d, %Y')
+                self.title = 'The Hindu ' + date.today().strftime('%b %d, %Y')
 
     def parse_index(self):
+        
         global local_edition
         if local_edition or past_edition:
             if local_edition is None:
                 local_edition = 'th_chennai'
-            today = datetime.today().strftime('%Y-%m-%d')
+            today = date.today().strftime('%Y-%m-%d')
             if past_edition:
                 today = past_edition
                 self.log('Downloading past edition of', local_edition + ' from ' + today)
             url = absurl('/todays-paper/' + today + '/' + local_edition + '/')
         else:
             url = 'https://www.thehindu.com/todays-paper/'
+            
         raw = self.index_to_soup(url, raw=True)
         soup = self.index_to_soup(raw)
         ans = self.hindu_parse_index(soup)
@@ -83,8 +86,8 @@ class TheHindu(BasicNewsRecipe):
             if not self.tag_to_string(script).strip().startswith('let grouped_articles = {}'):
                 continue
             if script is not None:
-                art = re.search(r'grouped_articles = ({\"[^<]+?]})', self.tag_to_string(script))
-                data = json.loads(art.group(1))
+                art = re.search(r'grouped_articles = ({\".*)', self.tag_to_string(script))
+                data = json.JSONDecoder().raw_decode(art.group(1))[0]
 
                 feeds_dict = defaultdict(list)
 

From bdc37ef07830d98216771a056da94dc556667c99 Mon Sep 17 00:00:00 2001
From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com>
Date: Thu, 27 Apr 2023 15:27:30 +0530
Subject: [PATCH 0608/2055] Update psych.recipe

---
 recipes/psych.recipe | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/recipes/psych.recipe b/recipes/psych.recipe
index 12eeacd4a5..235489565c 100644
--- a/recipes/psych.recipe
+++ b/recipes/psych.recipe
@@ -42,7 +42,7 @@ class PsychologyToday(BasicNewsRecipe):
         self.cover_url = absurl(a.img['src'])
         soup = self.index_to_soup(absurl(a['href']))
         articles = []
-        for article in soup.find('div', role='article').findAll('article'):
+        for article in soup.findAll('div', attrs={'class':'article-text'}):
             title = self.tag_to_string(article.find(['h2','h3'])).strip()
             url = absurl(article.find(['h2','h3']).a['href'])
             self.log('\n', title, 'at', url)

From 19a332f74d962bd21baae87a264586e21c5cd878 Mon Sep 17 00:00:00 2001
From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com>
Date: Thu, 27 Apr 2023 15:33:04 +0530
Subject: [PATCH 0609/2055] Update livemint.recipe

---
 recipes/livemint.recipe | 24 ++++++++++++++++--------
 1 file changed, 16 insertions(+), 8 deletions(-)

diff --git a/recipes/livemint.recipe b/recipes/livemint.recipe
index e63471a28f..4b990afd37 100644
--- a/recipes/livemint.recipe
+++ b/recipes/livemint.recipe
@@ -21,16 +21,17 @@ class LiveMint(BasicNewsRecipe):
     remove_empty_feeds =  True
     resolve_internal_links = True
 
-    def get_cover_url(self):
-        soup = self.index_to_soup(
-            'https://www.magzter.com/IN/HT-Digital-Streams-Ltd./Mint-Mumbai/Newspaper/'
-        )
-        for citem in soup.findAll('meta', content=lambda s: s and s.endswith('view/3.jpg')):
-            return citem['content']
-
     if is_saturday:
+        
+        def get_cover_url(self):
+            soup = self.index_to_soup('https://lifestyle.livemint.com/')
+            self.title = 'Mint Lounge'
+            if citem := soup.find('div', attrs={'class':'headLatestIss_cover'}):
+                return citem.img['src'].replace('_tn.jpg', '_mr.jpg')
+        
+        masthead_url = 'https://lifestyle.livemint.com/mintlounge/static-images/lounge-logo.svg'
 
-        oldest_article = 6 # days
+        oldest_article = 6.5 # days
 
         extra_css = '''
             #story-summary-0 {font-style:italic; color:#202020;}
@@ -63,6 +64,13 @@ class LiveMint(BasicNewsRecipe):
                 img['src'] = img['data-img']
             return soup
     else:
+        
+        def get_cover_url(self):
+            soup = self.index_to_soup(
+                'https://www.magzter.com/IN/HT-Digital-Streams-Ltd./Mint-Mumbai/Newspaper/'
+            )
+            for citem in soup.findAll('meta', content=lambda s: s and s.endswith('view/3.jpg')):
+                return citem['content']
 
         extra_css = '''
             img {display:block; margin:0 auto;}

From 2e0a45737a99ae2c2d204c06a84427eb329e8dec Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Thu, 27 Apr 2023 11:20:23 +0100
Subject: [PATCH 0610/2055] Two related changes: 1) Make F2 on a markdown
 column in the booklist use the new markdown dialog. 2) Fix some problems in
 the markdown syntax highlighter that threw exceptions.

---
 src/calibre/gui2/library/delegates.py         | 14 ++++-
 src/calibre/gui2/markdown_editor.py           | 59 +++++++++++++++++--
 .../gui2/markdown_syntax_highlighter.py       |  8 +--
 3 files changed, 70 insertions(+), 11 deletions(-)

diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py
index eb9ac48bf1..eda62deb9e 100644
--- a/src/calibre/gui2/library/delegates.py
+++ b/src/calibre/gui2/library/delegates.py
@@ -5,16 +5,17 @@ __license__   = 'GPL v3'
 __copyright__ = '2010, Kovid Goyal '
 __docformat__ = 'restructuredtext en'
 
-import sys
+import os, sys
 
 from qt.core import (Qt, QApplication, QStyle, QIcon,  QDoubleSpinBox, QStyleOptionViewItem,
-        QSpinBox, QStyledItemDelegate, QComboBox, QTextDocument, QMenu, QKeySequence,
+        QSpinBox, QStyledItemDelegate, QComboBox, QTextDocument, QMenu, QKeySequence, QUrl,
         QAbstractTextDocumentLayout, QFont, QFontInfo, QDate, QDateTimeEdit, QDateTime, QEvent,
         QStyleOptionComboBox, QStyleOptionSpinBox, QLocale, QSize, QLineEdit, QDialog, QPalette)
 
 from calibre.ebooks.metadata import rating_to_stars, title_sort
 from calibre.gui2 import UNDEFINED_QDATETIME, rating_font, gprefs
 from calibre.constants import iswindows
+from calibre.gui2.markdown_editor import MarkdownEditDialog
 from calibre.gui2.widgets import EnLineEdit
 from calibre.gui2.widgets2 import populate_standard_spinbox_context_menu, RatingEditor, DateTimeEdit as DateTimeEditBase
 from calibre.gui2.complete2 import EditWithComplete
@@ -534,7 +535,14 @@ class CcLongTextDelegate(QStyledItemDelegate):  # {{{
             text = ''
         else:
             text = m.db.data[index.row()][m.custom_columns[col]['rec_index']]
-        d = PlainTextDialog(parent, text, column_name=m.custom_columns[col]['name'])
+        column_format = m.custom_columns[m.column_map[index.column()]]['display'].get('interpret_as')
+        if column_format == 'markdown':
+            path = m.db.abspath(index.row(), index_is_id=False)
+            base_url = QUrl.fromLocalFile(os.path.join(path, 'metadata.html')) if path else None
+            d = MarkdownEditDialog(parent, text, column_name=m.custom_columns[col]['name'],
+                                   base_url=base_url)
+        else:
+            d = PlainTextDialog(parent, text, column_name=m.custom_columns[col]['name'])
         if d.exec() == QDialog.DialogCode.Accepted:
             m.setData(index, d.text, Qt.ItemDataRole.EditRole)
         return None
diff --git a/src/calibre/gui2/markdown_editor.py b/src/calibre/gui2/markdown_editor.py
index 50dc91e608..efea26fd75 100644
--- a/src/calibre/gui2/markdown_editor.py
+++ b/src/calibre/gui2/markdown_editor.py
@@ -3,13 +3,14 @@
 
 import os
 from qt.core import (
-    QPlainTextEdit, Qt, QTabWidget, QUrl, QVBoxLayout, QWidget, pyqtSignal,
+    QDialog, QDialogButtonBox, QPlainTextEdit, QSize, Qt, QTabWidget, QUrl,
+    QVBoxLayout, QWidget, pyqtSignal,
 )
 
-from calibre.gui2 import safe_open_url
+from calibre.gui2 import safe_open_url, gprefs
 from calibre.gui2.book_details import css
 from calibre.gui2.widgets2 import HTMLDisplay
-from calibre.library.comments import markdown
+from calibre.library.comments import markdown as get_markdown
 
 
 class Preview(HTMLDisplay):
@@ -42,6 +43,56 @@ class MarkdownEdit(QPlainTextEdit):
         m.exec(ev.globalPos())
 
 
+class MarkdownEditDialog(QDialog):
+
+    def __init__(self, parent, text, column_name=None, base_url=None):
+        QDialog.__init__(self, parent)
+        self.setObjectName("MarkdownEditDialog")
+        self.setWindowTitle(_("Edit markdown"))
+        self.verticalLayout = l = QVBoxLayout(self)
+        self.textbox = editor = Editor(self)
+        editor.set_base_url(base_url)
+        self.buttonBox = bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, self)
+        bb.accepted.connect(self.accept)
+        bb.rejected.connect(self.reject)
+        l.addWidget(editor)
+        l.addWidget(bb)
+        # Remove help icon on title bar
+        icon = self.windowIcon()
+        self.setWindowFlags(self.windowFlags()&(~Qt.WindowType.WindowContextHelpButtonHint))
+        self.setWindowIcon(icon)
+
+        self.textbox.markdown =text
+        # self.textbox.wyswyg_dirtied()
+
+        if column_name:
+            self.setWindowTitle(_('Edit "{0}"').format(column_name))
+        self.restore_geometry(gprefs, 'markdown_edit_dialog_geom')
+
+    def sizeHint(self):
+        return QSize(650, 600)
+
+    def accept(self):
+        self.save_geometry(gprefs, 'markdown_edit_dialog_geom')
+        QDialog.accept(self)
+
+    def reject(self):
+        self.save_geometry(gprefs, 'markdown_edit_dialog_geom')
+        QDialog.reject(self)
+
+    def closeEvent(self, ev):
+        self.save_geometry(gprefs, 'markdown_edit_dialog_geom')
+        return QDialog.closeEvent(self, ev)
+
+    @property
+    def text(self):
+        return self.textbox.markdown
+
+    @text.setter
+    def text(self, val):
+        self.textbox.markdown = val or ''
+
+
 class Editor(QWidget):  # {{{
 
     def __init__(self, parent=None):
@@ -90,7 +141,7 @@ class Editor(QWidget):  # {{{
             self.update_preview()
 
     def update_preview(self):
-        html = markdown(self.editor.toPlainText().strip())
+        html = get_markdown(self.editor.toPlainText().strip())
         val = f'''\
         
             
diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index 821c20ac65..eb5bffdaaa 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -237,13 +237,13 @@ class MarkdownHighlighter(QSyntaxHighlighter):
             prevAscii = str(prev.replace('\u2029','\n'))
             if prevAscii.strip():
                 #print "Its a header"
-                prevCursor.select(QTextCursor.LineUnderCursor)
+                prevCursor.select(QTextCursor.SelectionType.LineUnderCursor)
                 #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header'])
                 formatRange = QTextLayout.FormatRange()
                 formatRange.format = self.MARKDOWN_KWS_FORMAT['Header']
                 formatRange.length = prevCursor.block().length()
                 formatRange.start = 0
-                prevCursor.block().layout().setAdditionalFormats([formatRange])
+                prevCursor.block().layout().setFormats([formatRange])
             self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HR'])
 
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['eHR'],text):
@@ -253,13 +253,13 @@ class MarkdownHighlighter(QSyntaxHighlighter):
             prevAscii = str(prev.replace('\u2029','\n'))
             if prevAscii.strip():
                 #print "Its a header"
-                prevCursor.select(QTextCursor.LineUnderCursor)
+                prevCursor.select(QTextCursor.SelectionType.LineUnderCursor)
                 #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header'])
                 formatRange = QTextLayout.FormatRange()
                 formatRange.format = self.MARKDOWN_KWS_FORMAT['Header']
                 formatRange.length = prevCursor.block().length()
                 formatRange.start = 0
-                prevCursor.block().layout().setAdditionalFormats([formatRange])
+                prevCursor.block().layout().setFormats([formatRange])
             self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HR'])
         return found
 

From c547b188d7c0a146d35a6ef5d38eb4e66df9aa94 Mon Sep 17 00:00:00 2001
From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com>
Date: Thu, 27 Apr 2023 15:50:51 +0530
Subject: [PATCH 0611/2055] Update irish_times.recipe

---
 recipes/irish_times.recipe | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/recipes/irish_times.recipe b/recipes/irish_times.recipe
index 31732eec2e..3c3c97d0c1 100644
--- a/recipes/irish_times.recipe
+++ b/recipes/irish_times.recipe
@@ -33,11 +33,11 @@ class IrishTimes(BasicNewsRecipe):
     temp_files = []
     keep_only_tags = [
         dict(name=['h1', 'h2']),
-        classes('lead-art-wrapper article-body-wrapper'),
+        classes('lead-art-wrapper article-body-wrapper byline-text'),
     ]
     remove_tags = [
         dict(name='button'),
-        classes('sm-promo-headline'),
+        classes('sm-promo-headline top-table-list-container'),
     ]
     remove_attributes = ['width', 'height']
 

From a0739105047c0d5ba819674e3431db6f88c7c8a5 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Thu, 27 Apr 2023 14:58:39 +0530
Subject: [PATCH 0612/2055] Use an encoded URL for the data files link

---
 src/calibre/gui2/book_details.py | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py
index 9fb46360c7..afd60a3080 100644
--- a/src/calibre/gui2/book_details.py
+++ b/src/calibre/gui2/book_details.py
@@ -15,7 +15,7 @@ from qt.core import (
 
 from calibre import fit_image, sanitize_file_name
 from calibre.constants import config_dir, iswindows
-from calibre.db.constants import DATA_DIR_NAME
+from calibre.db.constants import DATA_DIR_NAME, DATA_FILE_PATTERN
 from calibre.ebooks import BOOK_EXTENSIONS
 from calibre.ebooks.metadata.book.base import Metadata, field_metadata
 from calibre.ebooks.metadata.book.render import mi_to_html
@@ -487,12 +487,11 @@ def create_copy_links(menu, data=None):
     link(_('Link to show book in calibre'), f'calibre://show-book/{library_id}/{book_id}')
     link(_('Link to show book details in a popup window'), f'calibre://book-details/{library_id}/{book_id}')
     mi = db.new_api.get_proxy_metadata(book_id)
-    data_path = os.path.join(db.backend.library_path, mi.path, DATA_DIR_NAME)
-    with suppress(OSError):
-        if os.listdir(data_path):
-            if iswindows:
-                data_path = '/' + data_path.replace('\\', '/')
-            link(_("Link to open book's data files folder"), 'file://' + data_path)
+    with suppress(Exception):
+        data_files = db.new_api.list_extra_files(book_id, use_cache=True, pattern=DATA_FILE_PATTERN)
+        if data_files:
+            data_path = os.path.join(db.backend.library_path, mi.path, DATA_DIR_NAME)
+            link(_("Link to open book's data files folder"), bytes(QUrl.fromLocalFile(data_path).toEncoded()).decode('utf-8'))
     if data:
         field = data.get('field')
         if data['type'] == 'author':

From 8d66c6eab3f96ab7acc31e281a1ef052810f8630 Mon Sep 17 00:00:00 2001
From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com>
Date: Thu, 27 Apr 2023 16:57:44 +0530
Subject: [PATCH 0613/2055] Update irish_independent.recipe

---
 recipes/irish_independent.recipe | 33 ++++++++++++--------------------
 1 file changed, 12 insertions(+), 21 deletions(-)

diff --git a/recipes/irish_independent.recipe b/recipes/irish_independent.recipe
index 562016afee..a7425e6a29 100644
--- a/recipes/irish_independent.recipe
+++ b/recipes/irish_independent.recipe
@@ -12,35 +12,26 @@ class IrishIndependent(BasicNewsRecipe):
     description = 'Irish and World news from Irelands Bestselling Daily Broadsheet'
     __author__ = 'Neil Grogan'
     language = 'en_IE'
-    oldest_article = 7
+    oldest_article = 2
     max_articles_per_feed = 100
-    remove_tags_before = dict(id='article')
-    remove_tags_after = [dict(name='div', attrs={'class': 'toolsBottom'})]
     no_stylesheets = True
+
     keep_only_tags = [
-        classes('n-content1 n-content2 n-content3'),
+        dict(name='div', attrs={'class':lambda x: x and '_contentwrapper' in x})
     ]
-    remove_tags_after = classes('quick-subscribe')
+    
     remove_tags = [
-        classes('icon1 icon-close c-lightbox1-side c-socials1 social-embed-consent-wall n-split1-side c-footer1'),
-        dict(attrs={'data-ad-slot': True}),
-        dict(attrs={'data-lightbox': True}),
-        dict(name='form'),
-        dict(attrs={'data-urn': lambda x: x and ':video:' in x}),
+        dict(name='div', attrs={'data-testid':['article-share', 'embed-video']})
     ]
 
     feeds = [
-        (u'Frontpage News', u'http://www.independent.ie/rss'),
-        (u'World News', u'http://www.independent.ie/world-news/rss'),
-        (u'Technology', u'http://www.independent.ie/business/technology/rss'),
-        (u'Sport', u'http://www.independent.ie/sport/rss'),
-        (u'Entertainment', u'http://www.independent.ie/entertainment/rss'),
-        (u'Independent Woman', u'http://www.independent.ie/lifestyle/independent-woman/rss'),
-        (u'Education', u'http://www.independent.ie/education/rss'),
-        (u'Lifestyle', u'http://www.independent.ie/lifestyle/rss'),
-        (u'Travel', u'http://www.independent.ie/travel/rss'),
-        (u'Letters', u'http://www.independent.ie/opinion/letters/rss'),
-        (u'Weather', u'http://www.independent.ie/weather/rss')
+        ('News', 'http://www.independent.ie/rss'),
+        ('Opinion', 'http://www.independent.ie/opinion/rss'),
+        ('Business', 'http://www.independent.ie/business/rss'),
+        ('Sport', 'http://www.independent.ie/sport/rss'),
+        ('Life', 'http://www.independent.ie/life/rss'),
+        ('Style', 'http://www.independent.ie/style/rss'),
+        ('Entertainment', 'http://www.independent.ie/business/rss'),
     ]
 
     def preprocess_html(self, soup):

From 685f8310accbd96415efb32b31d6b009941a1e0b Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Thu, 27 Apr 2023 13:54:23 +0200
Subject: [PATCH 0614/2055] use a map to init the highlighter theme

---
 .../gui2/markdown_syntax_highlighter.py       | 126 +++++-------------
 1 file changed, 31 insertions(+), 95 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index eb5bffdaaa..b6711fe182 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -34,6 +34,27 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'Html': re.compile('<.+?>')
     }
 
+    key_theme_maps = {
+        'Bold': "bold",
+        'uBold': "bold",
+        'Italic': "emphasis",
+        'uItalic': "emphasis",
+        'Link': "link",
+        'Image': "image",
+        'HeaderAtx': "header",
+        'Header': "header",
+        'CodeBlock': "codeblock",
+        'UnorderedList': "unorderedlist",
+        'UnorderedListStar': "unorderedlist",
+        'OrderedList': "orderedlist",
+        'BlockQuote': "blockquote",
+        'BlockQuoteCount': "blockquote",
+        'CodeSpan': "codespan",
+        'HR': "line",
+        'eHR': "line",
+        'Html': "html",
+    }
+
     light_theme =  {
         "bold": {"color":"#859900", "font-weight":"bold", "font-style":"normal"},
         "emphasis": {"color":"#b58900", "font-weight":"bold", "font-style":"italic"},
@@ -73,101 +94,16 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         self.theme = theme
         self.MARKDOWN_KWS_FORMAT = {}
 
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['bold']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['bold']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['bold']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['Bold'] = format
-
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['bold']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['bold']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['bold']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['uBold'] = format
-
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['emphasis']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['emphasis']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['emphasis']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['Italic'] = format
-
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['emphasis']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['emphasis']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['emphasis']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['uItalic'] = format
-
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['link']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['link']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['link']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['Link'] = format
-
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['image']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['image']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['image']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['Image'] = format
-
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['header']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['header']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['header']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['Header'] = format
-
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['header']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['header']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['header']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['HeaderAtx'] = format
-
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['unorderedlist']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['unorderedlist']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['unorderedlist']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['UnorderedList'] = format
-
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['orderedlist']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['orderedlist']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['orderedlist']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['OrderedList'] = format
-
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['blockquote']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['blockquote']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['blockquote']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['BlockQuote'] = format
-
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['codespan']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['codespan']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['codespan']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['CodeSpan'] = format
-
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['codeblock']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['codeblock']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['codeblock']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['CodeBlock'] = format
-
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['line']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['line']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['line']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['HR'] = format
-
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['line']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['line']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['line']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['eHR'] = format
-
-        format = QTextCharFormat()
-        format.setForeground(QBrush(QColor(theme['html']['color'])))
-        format.setFontWeight(QFont.Weight.Bold if theme['html']['font-weight']=='bold' else QFont.Weight.Normal)
-        format.setFontItalic(True if theme['html']['font-style']=='italic' else False)
-        self.MARKDOWN_KWS_FORMAT['HTML'] = format
+        for k,t in self.key_theme_maps.items():
+            subtheme = theme[t]
+            format = QTextCharFormat()
+            if 'color' in subtheme:
+                format.setForeground(QBrush(QColor(subtheme['color'])))
+            if 'font-weight' in subtheme:
+                format.setFontWeight(QFont.Weight.Bold if subtheme['font-weight']=='bold' else QFont.Weight.Normal)
+            if 'font-style' in subtheme:
+                format.setFontItalic(True if subtheme['font-style']=='italic' else False)
+            self.MARKDOWN_KWS_FORMAT[k] = format
 
         self.rehighlight()
 

From 6fa9d1c9e119376523f7a891b90cd9c75284f989 Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Thu, 27 Apr 2023 13:55:15 +0200
Subject: [PATCH 0615/2055] ...

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index b6711fe182..f9f1a56ac5 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -278,4 +278,4 @@ class MarkdownHighlighter(QSyntaxHighlighter):
 
     def highlightHtml(self, text):
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Html'], text):
-            self.setFormat(mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HTML'])
+            self.setFormat(mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['Html'])

From 80dace9d4349cf588660c40599bd70f97ab420fe Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Thu, 27 Apr 2023 13:57:44 +0200
Subject: [PATCH 0616/2055] basic bold and emphasis

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index f9f1a56ac5..613f932fee 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -56,8 +56,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
     }
 
     light_theme =  {
-        "bold": {"color":"#859900", "font-weight":"bold", "font-style":"normal"},
-        "emphasis": {"color":"#b58900", "font-weight":"bold", "font-style":"italic"},
+        "bold": {"font-weight":"bold"},
+        "emphasis": {"font-style":"italic"},
         "link": {"color":light_link_color.name(), "font-weight":"normal", "font-style":"normal"},
         "image": {"color":"#cb4b16", "font-weight":"normal", "font-style":"normal"},
         "header": {"color":"#2aa198", "font-weight":"bold", "font-style":"normal"},
@@ -71,8 +71,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
     }
 
     dark_theme =  {
-        "bold": {"color":"#859900", "font-weight":"bold", "font-style":"normal"},
-        "emphasis": {"color":"#b58900", "font-weight":"bold", "font-style":"italic"},
+        "bold": {"font-weight":"bold"},
+        "emphasis": {"font-style":"italic"},
         "link": {"color":dark_link_color.name(), "font-weight":"normal", "font-style":"normal"},
         "image": {"color":"#cb4b16", "font-weight":"normal", "font-style":"normal"},
         "header": {"color":"#2aa198", "font-weight":"bold", "font-style":"normal"},

From e18950ea04d03e2d415c9ff6cd9252cd505a776b Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Thu, 27 Apr 2023 14:19:48 +0200
Subject: [PATCH 0617/2055] improve HorizontalLine/HeaderLine

---
 .../gui2/markdown_syntax_highlighter.py       | 30 +++++++------------
 1 file changed, 10 insertions(+), 20 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index 613f932fee..755d69772a 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -29,8 +29,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'BlockQuote': re.compile(r'(?u)^\s*>+\s*'),
         'BlockQuoteCount': re.compile('^[ \t]*>[ \t]?'),
         'CodeSpan': re.compile('(?P`+).+?(?P=delim)'),
-        'HR': re.compile(r'(?u)^(\s*(\*|-)\s*){3,}$'),
-        'eHR': re.compile(r'(?u)^(\s*(\*|=)\s*){3,}$'),
+        'HeaderLine': re.compile(r'(?u)^(\s*(-|=)\s*){3,}$'),
+        'HR': re.compile(r'(?u)^(\s*(\*|-|_)\s*){3,}$'),
         'Html': re.compile('<.+?>')
     }
 
@@ -43,6 +43,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'Image': "image",
         'HeaderAtx': "header",
         'Header': "header",
+        'HeaderLine': "header",
         'CodeBlock': "codeblock",
         'UnorderedList': "unorderedlist",
         'UnorderedListStar': "unorderedlist",
@@ -51,7 +52,6 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'BlockQuoteCount': "blockquote",
         'CodeSpan': "codespan",
         'HR': "line",
-        'eHR': "line",
         'Html': "html",
     }
 
@@ -166,23 +166,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
 
     def highlightHorizontalLine(self, text, cursor, bf, strt):
         found = False
-        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['HR'],text):
-            prevBlock = self.currentBlock().previous()
-            prevCursor = QTextCursor(prevBlock)
-            prev = prevBlock.text()
-            prevAscii = str(prev.replace('\u2029','\n'))
-            if prevAscii.strip():
-                #print "Its a header"
-                prevCursor.select(QTextCursor.SelectionType.LineUnderCursor)
-                #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header'])
-                formatRange = QTextLayout.FormatRange()
-                formatRange.format = self.MARKDOWN_KWS_FORMAT['Header']
-                formatRange.length = prevCursor.block().length()
-                formatRange.start = 0
-                prevCursor.block().layout().setFormats([formatRange])
-            self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HR'])
 
-        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['eHR'],text):
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['HeaderLine'],text):
             prevBlock = self.currentBlock().previous()
             prevCursor = QTextCursor(prevBlock)
             prev = prevBlock.text()
@@ -192,11 +177,16 @@ class MarkdownHighlighter(QSyntaxHighlighter):
                 prevCursor.select(QTextCursor.SelectionType.LineUnderCursor)
                 #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header'])
                 formatRange = QTextLayout.FormatRange()
-                formatRange.format = self.MARKDOWN_KWS_FORMAT['Header']
+                formatRange.format = self.MARKDOWN_KWS_FORMAT['HeaderLine']
                 formatRange.length = prevCursor.block().length()
                 formatRange.start = 0
                 prevCursor.block().layout().setFormats([formatRange])
+                self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HeaderLine'])
+                return True
+
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['HR'],text):
             self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HR'])
+            found = True
         return found
 
     def highlightAtxHeader(self, text, cursor, bf, strt):

From 6509d584205399248e276eefc12532f086635926 Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Thu, 27 Apr 2023 14:25:10 +0200
Subject: [PATCH 0618/2055] fix HeaderLine to match with Markdown result

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index 755d69772a..094e09f62e 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -29,7 +29,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'BlockQuote': re.compile(r'(?u)^\s*>+\s*'),
         'BlockQuoteCount': re.compile('^[ \t]*>[ \t]?'),
         'CodeSpan': re.compile('(?P`+).+?(?P=delim)'),
-        'HeaderLine': re.compile(r'(?u)^(\s*(-|=)\s*){3,}$'),
+        'HeaderLine': re.compile(r'(?u)^(-|=)+\s*$'),
         'HR': re.compile(r'(?u)^(\s*(\*|-|_)\s*){3,}$'),
         'Html': re.compile('<.+?>')
     }

From 6c98af26acc26b4ab269036be3d72ff4e17e7919 Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Thu, 27 Apr 2023 14:35:44 +0200
Subject: [PATCH 0619/2055] ...

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index 094e09f62e..f62c714588 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -177,7 +177,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
                 prevCursor.select(QTextCursor.SelectionType.LineUnderCursor)
                 #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header'])
                 formatRange = QTextLayout.FormatRange()
-                formatRange.format = self.MARKDOWN_KWS_FORMAT['HeaderLine']
+                formatRange.format = self.MARKDOWN_KWS_FORMAT['Header']
                 formatRange.length = prevCursor.block().length()
                 formatRange.start = 0
                 prevCursor.block().layout().setFormats([formatRange])

From 400bc9be54eaed1ea3470ed48d8dcc6856e53cac Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Thu, 27 Apr 2023 19:55:49 +0530
Subject: [PATCH 0620/2055] ...

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index f62c714588..7079ac6185 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -99,10 +99,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
             format = QTextCharFormat()
             if 'color' in subtheme:
                 format.setForeground(QBrush(QColor(subtheme['color'])))
-            if 'font-weight' in subtheme:
-                format.setFontWeight(QFont.Weight.Bold if subtheme['font-weight']=='bold' else QFont.Weight.Normal)
-            if 'font-style' in subtheme:
-                format.setFontItalic(True if subtheme['font-style']=='italic' else False)
+            format.setFontWeight(QFont.Weight.Bold if subtheme.get('font-weight') == 'bold' else QFont.Weight.Normal)
+            format.setFontItalic(subtheme.get('font-style') == 'italic')
             self.MARKDOWN_KWS_FORMAT[k] = format
 
         self.rehighlight()

From 6760f583858c1be1469fc3133f4621ae9f1880cf Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Thu, 27 Apr 2023 15:47:40 +0100
Subject: [PATCH 0621/2055] Improvements to new formatter functions
 has_extra_files() and extra_file_names(). Provide the option of using a
 regular expression to filter the files before counting or returning the
 names.

---
 manual/template_lang.rst                 |  4 +--
 src/calibre/utils/formatter_functions.py | 42 ++++++++++++++++++------
 2 files changed, 34 insertions(+), 12 deletions(-)

diff --git a/manual/template_lang.rst b/manual/template_lang.rst
index 5126ce4878..c11df09e88 100644
--- a/manual/template_lang.rst
+++ b/manual/template_lang.rst
@@ -495,7 +495,7 @@ In `GPM` the functions described in `Single Function Mode` all require an additi
 * ``eval(string)`` -- evaluates the string as a program, passing the local variables. This permits using the template processor to construct complex results from local variables. In :ref:`Template Program Mode `, because the `{` and `}` characters are interpreted before the template is evaluated you must use `[[` for the `{` character and `]]` for the ``}`` character. They are converted automatically. Note also that prefixes and suffixes (the `|prefix|suffix` syntax) cannot be used in the argument to this function when using :ref:`Template Program Mode `.
 * ``extra_file_size(file_name)`` -- returns the size in bytes of the extra file ``file_name`` in the book's ``data/`` folder if it exists, otherwise ``-1``. See also the functions ``has_extra_files()``, ``extra_file_names()`` and ``extra_file_modtime()``. This function can be used only in the GUI.
 * ``extra_file_modtime(file_name, format_spec)`` -- returns the modification time of the extra file ``file_name`` in the book's ``data/`` folder if it exists, otherwise ``-1``. The modtime is formatted according to ``format_spec`` (see ``format_date()`` for details). If ``format_spec`` is the empty string, returns the modtime as the floating point number of seconds since the epoch.  See also the functions ``has_extra_files()``, ``extra_file_names()`` and ``extra_file_size()``. The epoch is OS dependent. This function can be used only in the GUI.
-* ``extra_file_names(sep)`` -- returns a ``sep``-separated list of extra files in the book's ``data/`` folder. See also the functions ``has_extra_files()``, ``extra_file_size()`` and ``extra_file_modtime()``. This function can be used only in the GUI.
+* ``extra_file_names(sep [, pattern])`` returns a ``sep``-separated list of extra files in the book's ``data/`` folder. If the optional parameter ``pattern``, a regular expression, is supplied then the list is filtered to files that match ``pattern``. The pattern match is case insensitive. See also the functions ``has_extra_files()``, ``extra_file_modtime()`` and ``extra_file_size()``. This function can be used only in the GUI.
 * ``field(lookup_name)`` -- returns the value of the metadata field with lookup name ``lookup_name``.
 * ``field_exists(field_name)`` -- checks if a field (column) with the lookup name ``field_name`` exists, returning ``'1'`` if so and the empty string if not.
 * ``finish_formatting(val, fmt, prefix, suffix)`` -- apply the format, prefix, and suffix to a value in the same way as done in a template like ``{series_index:05.2f| - |- }``. This function is provided to ease conversion of complex single-function- or template-program-mode templates to `GPM` Templates. For example, the following program produces the same output as the above template::
@@ -557,7 +557,7 @@ In `GPM` the functions described in `Single Function Mode` all require an additi
 * ``formats_sizes()`` -- return a comma-separated list of colon-separated ``FMT:SIZE`` items giving the sizes in bytes of the formats of a book. You can use the select function to get the size for a specific format. Note that format names are always uppercase, as in EPUB.
 * ``fractional_part(x)`` -- returns the value after the decimal point. For example, ``fractional_part(3.14)`` returns ``0.14``. Throws an exception if ``x`` is not a number.
 * ``has_cover()`` -- return ``'Yes'`` if the book has a cover, otherwise the empty string.
-* ``has_extra_files()`` -- returns ``'Yes'`` if there are any extra files for the book (files in the folder ``data/`` in the book's folder), otherwise ``''`` (the empty string). See also the functions ``extra_file_names()``, ``extra_file_size()`` and ``extra_file_modtime()``. This function can be used only in the GUI.
+* ``has_extra_files([pattern])`` -- returns the count of extra files, otherwise '' (the empty string). If the optional parameter ``pattern`` (a regular expression) is supplied then the list is filtered to files that match ``pattern`` before the files are counted. The pattern match is case insensitive. See also the functions ``extra_file_names()``, ``extra_file_size()`` and ``extra_file_modtime()``. This function can be used only in the GUI.
 * ``is_marked()`` -- check whether the book is `marked` in calibre. If it is then return the value of the mark, either ``'true'`` (lower case) or a comma-separated list of named marks. Returns ``''`` (the empty string) if the book is not marked. This function works only in the GUI.
 * ``language_codes(lang_strings)`` -- return the `language codes `_ for the language names passed in `lang_strings`. The strings must be in the language of the current locale. ``Lang_strings`` is a comma-separated list.
 * ``list_contains(value, separator, [ pattern, found_val, ]* not_found_val)`` -- (Alias of ``in_list``) Interpreting the value as a list of items separated by ``separator``, evaluate the ``pattern`` against each value in the list. If the ``pattern`` matches any value then return ``found_val``, otherwise return ``not_found_val``. The ``pattern`` and ``found_value`` can be repeated as many times as desired, permitting returning different values depending on the search. The patterns are checked in order. The first match is returned. Aliases: ``in_list()``, ``list_contains()``
diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py
index a9a567a958..6f82cf7dda 100644
--- a/src/calibre/utils/formatter_functions.py
+++ b/src/calibre/utils/formatter_functions.py
@@ -2391,16 +2391,28 @@ class BuiltinBookValues(BuiltinFormatterFunction):
 
 class BuiltinHasExtraFiles(BuiltinFormatterFunction):
     name = 'has_extra_files'
-    arg_count = 0
+    arg_count = -1
     category = 'Template database functions'
-    __doc__ = doc = _("has_extra_files() -- returns 'Yes' if there are any extra "
+    __doc__ = doc = _("has_extra_files([pattern]) -- returns the count of extra "
                       "files, otherwise '' (the empty string). "
+                      "If the optional parameter 'pattern' (a regular expression) "
+                      "is supplied then the list is filtered to files that match "
+                      "pattern before the files are counted. The pattern match is "
+                      "case insensitive. "
                       'This function can be used only in the GUI.')
 
-    def evaluate(self, formatter, kwargs, mi, locals):
+    def evaluate(self, formatter, kwargs, mi, locals, *args):
+        if len(args) > 1:
+            raise ValueError(_('Incorrect number of arguments for function {0}').format('has_extra_files'))
+        pattern = args[0] if len(args) == 1 else None
         db = self.get_database(mi).new_api
         try:
-            return 'Yes' if db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) else ''
+            files = tuple(f.relpath.partition('/')[-1] for f in
+                          db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN))
+            if pattern:
+                r = re.compile(pattern, re.IGNORECASE)
+                files = tuple(filter(r.search, files))
+            return len(files) if len(files) > 0 else ''
         except Exception as e:
             traceback.print_exc()
             raise ValueError(e)
@@ -2408,17 +2420,27 @@ class BuiltinHasExtraFiles(BuiltinFormatterFunction):
 
 class BuiltinExtraFileNames(BuiltinFormatterFunction):
     name = 'extra_file_names'
-    arg_count = 1
+    arg_count = -1
     category = 'Template database functions'
-    __doc__ = doc = _("extra_file_names(sep) -- returns a sep-separated list of "
-                      "extra files in the book's '{}/' folder. "
+    __doc__ = doc = _("extra_file_names(sep [, pattern]) -- returns a sep-separated "
+                      "list of extra files in the book's '{}/' folder. If the "
+                      "optional parameter 'pattern', a regular expression, is "
+                      "supplied then the list is filtered to files that match pattern. "
+                      "The pattern match is case insensitive. "
                       'This function can be used only in the GUI.').format(DATA_DIR_NAME)
 
-    def evaluate(self, formatter, kwargs, mi, locals, sep):
+    def evaluate(self, formatter, kwargs, mi, locals, sep, *args):
+        if len(args) > 1:
+            raise ValueError(_('Incorrect number of arguments for function {0}').format('has_extra_files'))
+        pattern = args[0] if len(args) == 1 else None
         db = self.get_database(mi).new_api
         try:
-            files = db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN)
-            return sep.join(file.relpath.partition('/')[-1] for file in files)
+            files = tuple(f.relpath.partition('/')[-1] for f in
+                          db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN))
+            if pattern:
+                r = re.compile(pattern, re.IGNORECASE)
+                files = tuple(filter(r.search, files))
+            return sep.join(files)
         except Exception as e:
             traceback.print_exc()
             raise ValueError(e)

From 7b0cecdcbd7ac56ffc177d3e75005509be86d889 Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Thu, 27 Apr 2023 16:03:13 +0100
Subject: [PATCH 0622/2055] Back out the change that removed delegates for
 read-only columns

---
 src/calibre/gui2/library/views.py | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py
index aff08c08bd..c2c3e36a5a 100644
--- a/src/calibre/gui2/library/views.py
+++ b/src/calibre/gui2/library/views.py
@@ -1153,10 +1153,9 @@ class BooksView(QTableView):  # {{{
                 elif cc['datatype'] == 'enumeration':
                     set_item_delegate(colhead, self.cc_enum_delegate)
             else:
-                if colhead in self._model.editable_cols:
-                    dattr = colhead+'_delegate'
-                    delegate = colhead if hasattr(self, dattr) else 'text'
-                    set_item_delegate(colhead, getattr(self, delegate+'_delegate'))
+                dattr = colhead+'_delegate'
+                delegate = colhead if hasattr(self, dattr) else 'text'
+                set_item_delegate(colhead, getattr(self, delegate+'_delegate'))
 
         self.restore_state()
         self.set_ondevice_column_visibility()

From 07f5a9e619fef5c4f481e0dd8c722897e96ee6ce Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Thu, 27 Apr 2023 17:15:00 +0200
Subject: [PATCH 0623/2055] create CcMarkdownDelegate

---
 src/calibre/gui2/library/delegates.py | 65 +++++++++++++++++++++++----
 src/calibre/gui2/library/views.py     |  7 ++-
 2 files changed, 61 insertions(+), 11 deletions(-)

diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py
index eda62deb9e..c88571a574 100644
--- a/src/calibre/gui2/library/delegates.py
+++ b/src/calibre/gui2/library/delegates.py
@@ -26,7 +26,7 @@ from calibre.utils.icu import sort_key
 from calibre.gui2.dialogs.comments_dialog import CommentsDialog, PlainTextDialog
 from calibre.gui2.dialogs.tag_editor import TagEditor
 from calibre.gui2.languages import LanguagesEdit
-
+from calibre.library.comments import markdown
 
 class UpdateEditorGeometry:
 
@@ -535,14 +535,7 @@ class CcLongTextDelegate(QStyledItemDelegate):  # {{{
             text = ''
         else:
             text = m.db.data[index.row()][m.custom_columns[col]['rec_index']]
-        column_format = m.custom_columns[m.column_map[index.column()]]['display'].get('interpret_as')
-        if column_format == 'markdown':
-            path = m.db.abspath(index.row(), index_is_id=False)
-            base_url = QUrl.fromLocalFile(os.path.join(path, 'metadata.html')) if path else None
-            d = MarkdownEditDialog(parent, text, column_name=m.custom_columns[col]['name'],
-                                   base_url=base_url)
-        else:
-            d = PlainTextDialog(parent, text, column_name=m.custom_columns[col]['name'])
+        d = PlainTextDialog(parent, text, column_name=m.custom_columns[col]['name'])
         if d.exec() == QDialog.DialogCode.Accepted:
             m.setData(index, d.text, Qt.ItemDataRole.EditRole)
         return None
@@ -552,6 +545,60 @@ class CcLongTextDelegate(QStyledItemDelegate):  # {{{
 # }}}
 
 
+class CcMarkdownDelegate(QStyledItemDelegate):  # {{{
+
+    '''
+    Delegate for markdown data.
+    '''
+
+    def __init__(self, parent):
+        QStyledItemDelegate.__init__(self, parent)
+        self.document = QTextDocument()
+
+    def paint(self, painter, option, index):
+        self.initStyleOption(option, index)
+        style = QApplication.style() if option.widget is None \
+                                                else option.widget.style()
+        option.text = markdown(option.text)
+        self.document.setHtml(option.text)
+        style.drawPrimitive(QStyle.PrimitiveElement.PE_PanelItemViewItem, option, painter, widget=option.widget)
+        rect = style.subElementRect(QStyle.SubElement.SE_ItemViewItemDecoration, option, self.parent())
+        ic = option.icon
+        if rect.isValid() and not ic.isNull():
+            sz = ic.actualSize(option.decorationSize)
+            painter.drawPixmap(rect.topLeft(), ic.pixmap(sz))
+        ctx = QAbstractTextDocumentLayout.PaintContext()
+        ctx.palette = option.palette
+        if option.state & QStyle.StateFlag.State_Selected:
+            ctx.palette.setColor(QPalette.ColorRole.Text, ctx.palette.color(QPalette.ColorRole.HighlightedText))
+        textRect = style.subElementRect(QStyle.SubElement.SE_ItemViewItemText, option, self.parent())
+        painter.save()
+        painter.translate(textRect.topLeft())
+        painter.setClipRect(textRect.translated(-textRect.topLeft()))
+        self.document.documentLayout().draw(painter, ctx)
+        painter.restore()
+
+    def createEditor(self, parent, option, index):
+        m = index.model()
+        col = m.column_map[index.column()]
+        if check_key_modifier(Qt.KeyboardModifier.ControlModifier):
+            text = ''
+        else:
+            text = m.db.data[index.row()][m.custom_columns[col]['rec_index']]
+        
+        path = m.db.abspath(index.row(), index_is_id=False)
+        base_url = QUrl.fromLocalFile(os.path.join(path, 'metadata.html')) if path else None
+        d = MarkdownEditDialog(parent, text, column_name=m.custom_columns[col]['name'],
+                               base_url=base_url)
+        if d.exec() == QDialog.DialogCode.Accepted:
+            m.setData(index, (d.text), Qt.ItemDataRole.EditRole)
+        return None
+
+    def setModelData(self, editor, model, index):
+        model.setData(index, (editor.textbox.html), Qt.ItemDataRole.EditRole)
+# }}}
+
+
 class CcNumberDelegate(QStyledItemDelegate, UpdateEditorGeometry):  # {{{
 
     '''
diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py
index c2c3e36a5a..31598cd3e5 100644
--- a/src/calibre/gui2/library/views.py
+++ b/src/calibre/gui2/library/views.py
@@ -27,7 +27,7 @@ from calibre.gui2.library.alternate_views import (
 )
 from calibre.gui2.library.delegates import (
     CcBoolDelegate, CcCommentsDelegate, CcDateDelegate, CcEnumDelegate,
-    CcLongTextDelegate, CcNumberDelegate, CcSeriesDelegate, CcTemplateDelegate,
+    CcLongTextDelegate, CcMarkdownDelegate, CcNumberDelegate, CcSeriesDelegate, CcTemplateDelegate,
     CcTextDelegate, CompleteDelegate, DateDelegate, LanguagesDelegate, PubDateDelegate,
     RatingDelegate, SeriesDelegate, TextDelegate,
 )
@@ -396,6 +396,7 @@ class BooksView(QTableView):  # {{{
         self.cc_text_delegate = CcTextDelegate(self)
         self.cc_series_delegate = CcSeriesDelegate(self)
         self.cc_longtext_delegate = CcLongTextDelegate(self)
+        self.cc_markdown_delegate = CcMarkdownDelegate(self)
         self.cc_enum_delegate = CcEnumDelegate(self)
         self.cc_bool_delegate = CcBoolDelegate(self)
         self.cc_comments_delegate = CcCommentsDelegate(self)
@@ -1127,8 +1128,10 @@ class BooksView(QTableView):  # {{{
                     ctype = cc['display'].get('interpret_as', 'html')
                     if ctype == 'short-text':
                         set_item_delegate(colhead, self.cc_text_delegate)
-                    elif ctype in ('long-text', 'markdown'):
+                    elif ctype == 'long-text':
                         set_item_delegate(colhead, self.cc_longtext_delegate)
+                    elif ctype == 'markdown':
+                        set_item_delegate(colhead, self.cc_markdown_delegate)
                     else:
                         set_item_delegate(colhead, self.cc_comments_delegate)
                 elif cc['datatype'] == 'text':

From 8fba5fa3309b07c16db1423ee95bfb8283c2daf8 Mon Sep 17 00:00:00 2001
From: Hugo Meza 
Date: Fri, 28 Apr 2023 00:55:15 +0200
Subject: [PATCH 0624/2055] New recipes: El Confidencial (es) and
 elEconomista.es (es)

---
 recipes/el_confidencial.recipe | 30 +++++++++++++++++++++++++++++
 recipes/el_economista.recipe   | 35 ++++++++++++++++++++++++++++++++++
 2 files changed, 65 insertions(+)
 create mode 100644 recipes/el_confidencial.recipe
 create mode 100644 recipes/el_economista.recipe

diff --git a/recipes/el_confidencial.recipe b/recipes/el_confidencial.recipe
new file mode 100644
index 0000000000..75a1ccb164
--- /dev/null
+++ b/recipes/el_confidencial.recipe
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# vim:fileencoding=utf-8
+__license__ = 'GPL v3'
+__author__ = 'hmeza'
+__description__ = 'El Confidencial - El diario de los lectores influyentes'
+__version__ = 'v1.00.000'
+__date__ = '27, April 2023'
+'''
+https://www.elconfidencial.com/
+'''
+
+
+from calibre.web.feeds.news import BasicNewsRecipe
+
+
+class ElConfidencial(BasicNewsRecipe):
+    title = u'El Confidencial'
+    oldest_article = 7.0
+    __author__ = 'hmeza'
+    description = 'El Confidencial - El diario de los lectores influyentes'
+    timefmt = ' [%d %b, %Y]'
+    publication_type = 'newspaper'
+    language = 'es'
+    encoding = 'utf-8'
+    no_stylesheets = True
+    remove_javascript = True
+
+    feeds = [
+        (u'España', u'https://www.elconfidencial.com/rss/'),
+    ]
diff --git a/recipes/el_economista.recipe b/recipes/el_economista.recipe
new file mode 100644
index 0000000000..1dc0bf97c5
--- /dev/null
+++ b/recipes/el_economista.recipe
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+# vim:fileencoding=utf-8
+__license__ = 'GPL v3'
+__author__ = 'hmeza'
+__description__ = 'El Economista - El diario de los lectores influyentes'
+__version__ = 'v1.00.000'
+__date__ = '27, April 2023'
+'''
+https://www.elconfidencial.com/
+'''
+
+
+from calibre.web.feeds.news import BasicNewsRecipe
+
+
+class ElConfidencial(BasicNewsRecipe):
+    title = u'elEconomista.es'
+    oldest_article = 7.0
+    __author__ = 'hmeza'
+    description = 'elEconomista.es'
+    timefmt = ' [%d %b, %Y]'
+    publication_type = 'newspaper'
+    language = 'es'
+    encoding = 'utf-8'
+    no_stylesheets = True
+    remove_javascript = True
+
+    feeds = [
+        (u'elEconomista economía', u'https://www.eleconomista.es/rss/rss-economia.php'),
+        (u'elEconomista gestion', u'https://www.eleconomista.es/rss/rss-gestion.php'),
+        (u'elEconomista tecnología', u'https://www.eleconomista.es/rss/rss-category.php?category=tecnologia'),
+        (u'Selección elEconomista', u'https://www.eleconomista.es/rss/rss-seleccion-ee.php'),
+        (u'Mercados', u'https://www.eleconomista.es/rss/rss-mercados.php'),
+        (u'Mercados', u'https://www.eleconomista.es/rss/rss-mercados.php'),
+    ]

From c24e9000388bf4b8fdc2ae1ff741e81e533d941d Mon Sep 17 00:00:00 2001
From: Hugo Meza 
Date: Fri, 28 Apr 2023 01:00:49 +0200
Subject: [PATCH 0625/2055] Fixed feeds for El Confidencial

---
 recipes/el_confidencial.recipe | 56 +++++++++++++++++++++++++++++++++-
 1 file changed, 55 insertions(+), 1 deletion(-)

diff --git a/recipes/el_confidencial.recipe b/recipes/el_confidencial.recipe
index 75a1ccb164..3b5b2865cb 100644
--- a/recipes/el_confidencial.recipe
+++ b/recipes/el_confidencial.recipe
@@ -26,5 +26,59 @@ class ElConfidencial(BasicNewsRecipe):
     remove_javascript = True
 
     feeds = [
-        (u'España', u'https://www.elconfidencial.com/rss/'),
+        (u'ACTUALIDAD - España', 'https://rss.elconfidencial.com/espana/'),
+        (u'ACTUALIDAD - Mundo', 'https://rss.elconfidencial.com/mundo/'),
+        (u'ACTUALIDAD - Comunicación', 'https://rss.elconfidencial.com/comunicacion/'),
+        (u'ACTUALIDAD - Sociedad', 'https://rss.elconfidencial.com/sociedad/'),
+        (u'OPINIÓN - A.casado', 'https://rss.blogs.elconfidencial.com/espana/al-grano/'),
+        (u'OPINIÓN - J.a.zarzalejos', 'https://rss.blogs.elconfidencial.com/espana/notebook/'),
+        (u'OPINIÓN - C.sánchez', 'https://rss.blogs.elconfidencial.com/espana/mientras-tanto/'),
+        (u'OPINIÓN - El confidente', 'https://rss.blogs.elconfidencial.com/espana/el-confidente/'),
+        (u'COTIZALIA - Mercados', 'https://rss.elconfidencial.com/mercados/'),
+        (u'COTIZALIA - Economía', 'https://rss.elconfidencial.com/economia/'),
+        (u'COTIZALIA - Empresas', 'https://rss.elconfidencial.com/empresas/'),
+        (u'COTIZALIA - Finanzas personales', 'https://rss.elconfidencial.com/mercados/finanzas-personales/'),
+        (u'COTIZALIA - Vivienda', 'https://rss.elconfidencial.com/vivienda'),
+        (u'COTIZALIA - Fondos de inversión', 'https://rss.elconfidencial.com/mercados/fondos-de-inversion/'),
+        (u'TEKNAUTAS - Aplicaciones', 'https://rss.elconfidencial.com/tags/temas/apps-9337/'),
+        (u'TEKNAUTAS - Emprendedores', 'https://rss.elconfidencial.com/tags/economia/emprendedores-4800/'),
+        (u'TEKNAUTAS - Gadgets', 'https://rss.elconfidencial.com/tags/temas/gadgets-9340/'),
+        (u'TEKNAUTAS - Hardware', 'https://rss.elconfidencial.com/tags/temas/hardware-9341/'),
+        (u'TEKNAUTAS - Internet', 'https://rss.elconfidencial.com/tags/temas/internet-9342/'),
+        (u'TEKNAUTAS - Móviles', 'https://rss.elconfidencial.com/tags/otros/moviles-8601/'),
+        (u'TEKNAUTAS - Redes sociales', 'https://rss.elconfidencial.com/tags/temas/redes-sociales-9344/'),
+        (u'DEPORTES - Fútbol', 'https://rss.elconfidencial.com/deportes/futbol/'),
+        (u'DEPORTES - Baloncesto', 'https://rss.elconfidencial.com/deportes/baloncesto/'),
+        (u'DEPORTES - Fórmula 1', 'https://rss.elconfidencial.com/deportes/formula-1/'),
+        (u'DEPORTES - Motociclismo', 'https://rss.elconfidencial.com/deportes/motociclismo/'),
+        (u'DEPORTES - Tenis', 'https://rss.elconfidencial.com/deportes/tenis/'),
+        (u'DEPORTES - Ciclismo', 'https://rss.elconfidencial.com/deportes/ciclismo/'),
+        (u'DEPORTES - Golf', 'https://rss.elconfidencial.com/deportes/golf/'),
+        (u'DEPORTES - Otros deportes', 'https://rss.elconfidencial.com/deportes/otros-deportes/'),
+        (u'ACV - Alimentación', 'https://rss.elconfidencial.com/tags/otros/alimentacion-5601/'),
+        (u'ACV - Bienestar', 'https://rss.elconfidencial.com/tags/temas/bienestar-9331/'),
+        (u'ACV - Educación', 'https://rss.elconfidencial.com/tags/temas/educacion-9332/'),
+        (u'ACV - Psicología', 'https://rss.elconfidencial.com/tags/temas/psicologia-9333/'),
+        (u'ACV - Salud', 'https://rss.elconfidencial.com/tags/otros/salud-6110/'),
+        (u'ACV - Sexualidad', 'https://rss.elconfidencial.com/tags/temas/sexualidad-6986/'),
+        (u'ACV - Trabajo', 'https://rss.elconfidencial.com/tags/economia/trabajo-5284/'),
+        (u'CULTURA - Libros', 'https://rss.elconfidencial.com/tags/otros/libros-5344/'),
+        (u'CULTURA - Arte', 'https://rss.elconfidencial.com/tags/otros/arte-6092/'),
+        (u'CULTURA - Cine', 'https://rss.elconfidencial.com/tags/otros/cine-7354/'),
+        (u'CULTURA - Música', 'https://rss.elconfidencial.com/tags/otros/musica-5272/'),
+        (u'VANITATIS - Actualidad', 'https://rss.vanitatis.elconfidencial.com/noticias/'),
+        (u'VANITATIS - Tendencias', 'https://rss.vanitatis.elconfidencial.com/estilo/'),
+        (u'VANITATIS - Televisión', 'https://rss.vanitatis.elconfidencial.com/television/'),
+        (u'VANITATIS - Casas reales', 'https://rss.vanitatis.elconfidencial.com/casas-reales/'),
+        (u'VANITATIS - Blogs', 'https://rss.blogs.vanitatis.elconfidencial.com/'),
+        (u'ALIMENTE - Nutrición', 'https://rss.alimente.elconfidencial.com/nutricion/'),
+        (u'ALIMENTE - Consumo', 'https://rss.alimente.elconfidencial.com/consumo/'),
+        (u'ALIMENTE - Gastronomía', 'https://rss.alimente.elconfidencial.com/gastronomia-y-cocina/'),
+        (u'ALIMENTE - Bienestar', 'https://rss.alimente.elconfidencial.com/bienestar/'),
+        (u'ALIMENTE - Recetas', 'https://rss.alimente.elconfidencial.com/recetas/'),
+        (u'GENTLEMAN - Gentlemanía', 'https://rss.gentleman.elconfidencial.com/gentlemania/'),
+        (u'GENTLEMAN - Nombres propios', 'https://rss.gentleman.elconfidencial.com/personajes/'),
+        (u'GENTLEMAN - Style', 'https://rss.gentleman.elconfidencial.com/estilo-hombre/'),
+        (u'GENTLEMAN - Gourmet', 'https://rss.gentleman.elconfidencial.com/gourmet/'),
+        (u'GENTLEMAN - Relojes', 'https://rss.gentleman.elconfidencial.com/relojes/')
     ]

From e5872ca5099afdba54b07c94879087614943207e Mon Sep 17 00:00:00 2001
From: Hugo Meza 
Date: Fri, 28 Apr 2023 01:07:24 +0200
Subject: [PATCH 0626/2055] Removed dup feed for elEconomista.es

---
 recipes/el_economista.recipe | 1 -
 1 file changed, 1 deletion(-)

diff --git a/recipes/el_economista.recipe b/recipes/el_economista.recipe
index 1dc0bf97c5..97d11712cb 100644
--- a/recipes/el_economista.recipe
+++ b/recipes/el_economista.recipe
@@ -31,5 +31,4 @@ class ElConfidencial(BasicNewsRecipe):
         (u'elEconomista tecnología', u'https://www.eleconomista.es/rss/rss-category.php?category=tecnologia'),
         (u'Selección elEconomista', u'https://www.eleconomista.es/rss/rss-seleccion-ee.php'),
         (u'Mercados', u'https://www.eleconomista.es/rss/rss-mercados.php'),
-        (u'Mercados', u'https://www.eleconomista.es/rss/rss-mercados.php'),
     ]

From 6990da058157ca34c9852a67bccd2513c17e3173 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Fri, 28 Apr 2023 08:38:27 +0530
Subject: [PATCH 0627/2055] pep8

---
 recipes/irish_independent.recipe | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/recipes/irish_independent.recipe b/recipes/irish_independent.recipe
index a7425e6a29..681e62646b 100644
--- a/recipes/irish_independent.recipe
+++ b/recipes/irish_independent.recipe
@@ -4,7 +4,7 @@ __copyright__ = '2009 Neil Grogan'
 # Irish Independent Recipe
 #
 
-from calibre.web.feeds.news import BasicNewsRecipe, classes
+from calibre.web.feeds.news import BasicNewsRecipe
 
 
 class IrishIndependent(BasicNewsRecipe):
@@ -19,7 +19,7 @@ class IrishIndependent(BasicNewsRecipe):
     keep_only_tags = [
         dict(name='div', attrs={'class':lambda x: x and '_contentwrapper' in x})
     ]
-    
+
     remove_tags = [
         dict(name='div', attrs={'data-testid':['article-share', 'embed-video']})
     ]

From db3bd2d25c7b39e0144376f0cbf4b48236d1c50f Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Fri, 28 Apr 2023 09:02:46 +0530
Subject: [PATCH 0628/2055] ...

---
 src/calibre/gui2/library/delegates.py | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py
index c88571a574..63a8b954bb 100644
--- a/src/calibre/gui2/library/delegates.py
+++ b/src/calibre/gui2/library/delegates.py
@@ -552,13 +552,12 @@ class CcMarkdownDelegate(QStyledItemDelegate):  # {{{
     '''
 
     def __init__(self, parent):
-        QStyledItemDelegate.__init__(self, parent)
+        super().__init__(parent)
         self.document = QTextDocument()
 
     def paint(self, painter, option, index):
         self.initStyleOption(option, index)
-        style = QApplication.style() if option.widget is None \
-                                                else option.widget.style()
+        style = QApplication.style() if option.widget is None else option.widget.style()
         option.text = markdown(option.text)
         self.document.setHtml(option.text)
         style.drawPrimitive(QStyle.PrimitiveElement.PE_PanelItemViewItem, option, painter, widget=option.widget)
@@ -585,7 +584,7 @@ class CcMarkdownDelegate(QStyledItemDelegate):  # {{{
             text = ''
         else:
             text = m.db.data[index.row()][m.custom_columns[col]['rec_index']]
-        
+
         path = m.db.abspath(index.row(), index_is_id=False)
         base_url = QUrl.fromLocalFile(os.path.join(path, 'metadata.html')) if path else None
         d = MarkdownEditDialog(parent, text, column_name=m.custom_columns[col]['name'],

From a42986964f0f3361a077e4b027a918990ede47ad Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Fri, 28 Apr 2023 12:45:22 +0100
Subject: [PATCH 0629/2055] Make the "Sort by" action play nice with the
 Favourites and Action Chains plugins.

---
 src/calibre/gui2/actions/sort.py | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/src/calibre/gui2/actions/sort.py b/src/calibre/gui2/actions/sort.py
index dd5de380cd..5236301bb4 100644
--- a/src/calibre/gui2/actions/sort.py
+++ b/src/calibre/gui2/actions/sort.py
@@ -71,6 +71,12 @@ class SortByAction(InterfaceAction):
     def about_to_show(self):
         self.update_menu()
 
+    def library_changed(self, db):
+        self.update_menu()
+
+    def initialization_complete(self):
+        self.update_menu()
+
     def update_menu(self, menu=None):
         menu = self.qaction.menu() if menu is None else menu
         for action in menu.actions():
@@ -80,9 +86,8 @@ class SortByAction(InterfaceAction):
                     action.toggled.disconnect()
 
         menu.clear()
-        lv = self.gui.library_view
-        m = lv.model()
-        db = m.db
+        m = self.gui.library_view.model()
+        db = self.gui.current_db
 
         # Add saved sorts to the menu
         saved_sorts = db.new_api.pref('saved_multisort_specs', {})
@@ -161,6 +166,7 @@ class SortByAction(InterfaceAction):
         d = ChooseMultiSort(self.gui.current_db, parent=self.gui, is_device_connected=self.gui.device_connected)
         if d.exec() == QDialog.DialogCode.Accepted:
             self.gui.library_view.multisort(d.current_sort_spec)
+            self.update_menu()
 
     def sort_requested(self, key, ascending):
         if ascending is None:

From 89ccf7bbed0e4e63c911f6f238aa8f9ce75eb973 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Sat, 29 Apr 2023 06:36:02 +0530
Subject: [PATCH 0630/2055] ...

---
 src/calibre/gui2/markdown_editor.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/calibre/gui2/markdown_editor.py b/src/calibre/gui2/markdown_editor.py
index efea26fd75..37bcd43a30 100644
--- a/src/calibre/gui2/markdown_editor.py
+++ b/src/calibre/gui2/markdown_editor.py
@@ -48,7 +48,7 @@ class MarkdownEditDialog(QDialog):
     def __init__(self, parent, text, column_name=None, base_url=None):
         QDialog.__init__(self, parent)
         self.setObjectName("MarkdownEditDialog")
-        self.setWindowTitle(_("Edit markdown"))
+        self.setWindowTitle(_("Edit Markdown"))
         self.verticalLayout = l = QVBoxLayout(self)
         self.textbox = editor = Editor(self)
         editor.set_base_url(base_url)

From ace3150791c5da492a283a7469c8dca36743a0e9 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Sat, 29 Apr 2023 07:07:28 +0530
Subject: [PATCH 0631/2055] =?UTF-8?q?Fix=20a=20regression=20in=206.16=20th?=
 =?UTF-8?q?at=20broke=20restoring=20of=20the=20database.=20Fixes=20#201802?=
 =?UTF-8?q?5=20[ERROR:=20Fall=C3=B3:=20La=20restauraci=C3=B3n=20de=20la=20?=
 =?UTF-8?q?base=20de=20datos=20fall=C3=B3](https://bugs.launchpad.net/cali?=
 =?UTF-8?q?bre/+bug/2018025)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/calibre/db/restore.py | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/calibre/db/restore.py b/src/calibre/db/restore.py
index ebe284036c..48928aa7c5 100644
--- a/src/calibre/db/restore.py
+++ b/src/calibre/db/restore.py
@@ -152,6 +152,13 @@ class Restore(Thread):
                 self.replace_db()
         except:
             self.tb = traceback.format_exc()
+            if self.failed_dirs:
+                for x in self.failed_dirs:
+                    for (dirpath, tb) in self.failed_dirs:
+                        self.tb += f'\n\n-------------\nFailed to restore: {dirpath}\n{tb}'
+            if self.failed_restores:
+                for (book, tb) in self.failed_restores:
+                    self.tb += f'\n\n-------------\nFailed to restore: {book["path"]}\n{tb}'
 
     def load_preferences(self):
         self.progress_callback(None, 1)
@@ -195,6 +202,7 @@ class Restore(Thread):
                 self.process_dir(dirpath, dirnames, filenames, book_id)
             except Exception:
                 self.failed_dirs.append((dirpath, traceback.format_exc()))
+                traceback.print_exc()
             self.progress_callback(_('Processed') + ' ' + dirpath, i+1)
 
     def process_dir(self, dirpath, dirnames, filenames, book_id):
@@ -204,7 +212,7 @@ class Restore(Thread):
                 return os.path.getmtime(path)
             return sys.maxsize
 
-        filenames.sort(key=lambda f: safe_mtime(os.path.join(dirpath, filenames)))
+        filenames.sort(key=lambda f: safe_mtime(os.path.join(dirpath, f)))
         fmt_map = {}
         fmts, formats, sizes, names = [], [], [], []
         for x in filenames:
@@ -285,6 +293,7 @@ class Restore(Thread):
                 self.successes += 1
             except:
                 self.failed_restores.append((book, traceback.format_exc()))
+                traceback.print_exc()
             self.progress_callback(book['mi'].title, i+1)
 
         for field, lmap in self.link_maps.items():

From f2ee5b3da7c4f5e92a2076e44bda28498c3b0b8f Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Sat, 29 Apr 2023 13:42:52 +0530
Subject: [PATCH 0632/2055] string changes

---
 Changelog.txt                                        | 8 ++++----
 src/calibre/ebooks/conversion/plugins/txt_input.py   | 2 +-
 src/calibre/gui2/preferences/create_custom_column.py | 2 +-
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/Changelog.txt b/Changelog.txt
index 356d9e51a0..62590972c2 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -41,7 +41,7 @@
 
 - [2017232] Trash bin: Add a button to clear the bin
 
-- Metadata editor: Use a dedicated editor with preview for custom columns that store markdown formatted text
+- Metadata editor: Use a dedicated editor with preview for custom columns that store Markdown formatted text
 
 :: bug fixes
 
@@ -1526,7 +1526,7 @@ Then, when searching the Tag browser, press Ctrl+Alt+Shift+F to restrict the dis
 
 :: new features
 
-- When adding markdown (.md) or textile (.textile) files that contain references to images, automatically add them as txtz with the images
+- When adding Markdown (.md) or textile (.textile) files that contain references to images, automatically add them as txtz with the images
 
 :: bug fixes
 
@@ -1542,7 +1542,7 @@ Then, when searching the Tag browser, press Ctrl+Alt+Shift+F to restrict the dis
 
 - [1941992] TXT Output: Fix a regression in calibre 5 that caused the max line length option to not work
 
-- When auto converting added TXT files with image references to TXTZ use a full markdown parser to detect markdown images
+- When auto converting added TXT files with image references to TXTZ use a full Markdown parser to detect Markdown images
 
 :: improved recipes
 - BBC News
@@ -1952,7 +1952,7 @@ Useful to have a "slideshow" of book covers. The speed of scrolling can be contr
 
 - [1921689] E-book viewer: Add an option under Preferences->Miscellaneous to not restore open panels such as Search, Table of Contents etc on restart
 
-- When exporting highlights as text or markdown also output top level chapter titles
+- When exporting highlights as text or Markdown also output top level chapter titles
 
 - [1922327] Allow downloading metadata from amazon.se
 
diff --git a/src/calibre/ebooks/conversion/plugins/txt_input.py b/src/calibre/ebooks/conversion/plugins/txt_input.py
index a28f58e405..c1030e3b2c 100644
--- a/src/calibre/ebooks/conversion/plugins/txt_input.py
+++ b/src/calibre/ebooks/conversion/plugins/txt_input.py
@@ -21,7 +21,7 @@ MD_EXTENSIONS = {
     'meta': _('Metadata in the document'),
     'nl2br': _('Treat newlines as hard breaks'),
     'sane_lists': _('Do not allow mixing list types'),
-    'smarty': _('Use markdown\'s internal smartypants parser'),
+    'smarty': _('Use Markdown\'s internal smartypants parser'),
     'tables': _('Support tables'),
     'toc': _('Generate a table of contents'),
     'wikilinks': _('Wiki style links'),
diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py
index ef9e327a9a..cc2cfe82a0 100644
--- a/src/calibre/gui2/preferences/create_custom_column.py
+++ b/src/calibre/gui2/preferences/create_custom_column.py
@@ -409,7 +409,7 @@ class CreateCustomColumn(QDialog):
                 ('html', 'HTML'),
                 ('short-text', _('Short text, like a title')),
                 ('long-text', _('Plain text')),
-                ('markdown', _('Plain text formatted using markdown'))
+                ('markdown', _('Plain text formatted using Markdown'))
         ):
             ct.addItem(text, k)
         ct.setToolTip(_('Choose how the data in this column is interpreted.\n'

From 19f1db9578b3143050efc26b9aaf6603f03ac079 Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Sat, 29 Apr 2023 13:30:46 +0100
Subject: [PATCH 0633/2055] Fix weird problem where a menu can be shown on the
 wrong screen if it is constructed before the first show event. On my test
 system it is 100% repeatable.

FWIW: I spent several hours trying to find a way to work around this issue. The trigger is clear -- populating the menu for the first time outside of the Qt "show()" process. This commit fixes it by not populating the 'real' menu until the show sequence has started. If something wants the menu before the show process has started then a fake action is returned with the menu.
---
 src/calibre/gui2/actions/sort.py | 67 ++++++++++++++++++++++++++++++--
 1 file changed, 64 insertions(+), 3 deletions(-)

diff --git a/src/calibre/gui2/actions/sort.py b/src/calibre/gui2/actions/sort.py
index 5236301bb4..36e1ef6532 100644
--- a/src/calibre/gui2/actions/sort.py
+++ b/src/calibre/gui2/actions/sort.py
@@ -5,10 +5,11 @@ __license__ = 'GPL v3'
 __copyright__ = '2013, Kovid Goyal '
 
 from contextlib import suppress
+from enum import Enum
 from functools import partial
 from qt.core import (
     QAbstractItemView, QAction, QDialog, QDialogButtonBox, QIcon, QListWidget,
-    QListWidgetItem, QSize, Qt, QToolButton, QVBoxLayout, pyqtSignal,
+    QListWidgetItem, QMenu, QSize, Qt, QToolButton, QVBoxLayout, pyqtSignal,
 )
 
 from calibre.gui2.actions import InterfaceAction
@@ -35,6 +36,21 @@ class SortAction(QAction):
         self.sort_requested.emit(self.key, self.ascending)
 
 
+class InitializationEnum(Enum):
+    WaitingInitialization = 0
+    InitializeCalled = 1
+    FullyInitialized = 2
+
+
+class FakeAction(QAction):
+
+    def menu(self):
+        return self.the_menu
+
+    def setMenu(self, menu):
+        self.the_menu = menu
+
+
 class SortByAction(InterfaceAction):
 
     name = 'Sort By'
@@ -43,8 +59,11 @@ class SortByAction(InterfaceAction):
     popup_type = QToolButton.ToolButtonPopupMode.InstantPopup
     action_add_menu = True
     dont_add_to = frozenset(('context-menu-cover-browser', ))
+    init_complete = InitializationEnum.WaitingInitialization
+    menu_shown = False
 
     def genesis(self):
+        self.fake_action = None
         self.sorted_icon = QIcon.ic('ok.png')
         self.qaction.menu().aboutToShow.connect(self.about_to_show)
 
@@ -57,6 +76,45 @@ class SortByAction(InterfaceAction):
         c('reverse_sort_action', _('Reverse current sort'), _('Reverse the current sort order'), self.reverse_sort, 'shift+f5')
         c('reapply_sort_action', _('Re-apply current sort'), _('Re-apply the current sort'), self.reapply_sort, 'f5')
 
+
+    # The property is here because Qt can put the menu on the wrong screen if
+    # the menu is built for the first time outside of the process of showing it.
+    # On Windows this happens if calibre is on screen 2; the menu appears on
+    # screen 1. Why Qt does this I can't say. The problem can be triggered if a
+    # plugin queries the menu during its own initialization. We solve this
+    # problem by giving the caller a fake QAction with the menu, which works for
+    # plugins. We use the "real" action once the user has requested the menu. Of
+    # course, the right fix would be for Qt to show the menu where it should go
+    # even if built previously, but I can't find how to make that happen
+
+    @property
+    def qaction(self):
+        try:
+            # if initialization_complete() hasn't been called or we are fully
+            # initialized then do nothing special
+            if self.init_complete == InitializationEnum.InitializeCalled:
+                if not self.menu_shown:
+                    # This is the problematic case where something wants the
+                    # menu before it's been shown. Construct the fake action
+                    m = QMenu()
+                    self.update_menu(m)
+                    if self.fake_action is None:
+                        self.fake_action = FakeAction()
+                    self.fake_action.setMenu(m)
+                    return self.fake_action
+                else:
+                    # The menu has been shown. No need to construct the fake
+                    # action in the future.
+                    self.init_complete = InitializationEnum.FullyInitialized
+        except Exception:
+            import traceback
+            traceback.print_exc()
+        return self.my_qaction
+
+    @qaction.setter
+    def qaction(self, val):
+        self.my_qaction = val
+
     def reverse_sort(self):
         self.gui.current_view().reverse_sort()
 
@@ -69,13 +127,14 @@ class SortByAction(InterfaceAction):
         self.menuless_qaction.setEnabled(enabled)
 
     def about_to_show(self):
+        self.menu_shown = True
         self.update_menu()
 
     def library_changed(self, db):
         self.update_menu()
 
     def initialization_complete(self):
-        self.update_menu()
+        self.init_complete = InitializationEnum.InitializeCalled
 
     def update_menu(self, menu=None):
         menu = self.qaction.menu() if menu is None else menu
@@ -128,6 +187,8 @@ class SortByAction(InterfaceAction):
                 menu.insertSeparator(before)
             else:
                 menu.addAction(sac)
+        if self.fake_action is not None:
+            self.fake_action.setMenu(menu)
 
     def select_sortable_columns(self):
         db = self.gui.current_db
@@ -166,7 +227,7 @@ class SortByAction(InterfaceAction):
         d = ChooseMultiSort(self.gui.current_db, parent=self.gui, is_device_connected=self.gui.device_connected)
         if d.exec() == QDialog.DialogCode.Accepted:
             self.gui.library_view.multisort(d.current_sort_spec)
-            self.update_menu()
+        self.update_menu()
 
     def sort_requested(self, key, ascending):
         if ascending is None:

From d90d6bb6d414d6b6c5413bcb94e92ac7c4d8e8fe Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Sat, 29 Apr 2023 21:20:42 +0530
Subject: [PATCH 0634/2055] string changes

---
 Changelog.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Changelog.txt b/Changelog.txt
index 62590972c2..23161f494b 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1526,7 +1526,7 @@ Then, when searching the Tag browser, press Ctrl+Alt+Shift+F to restrict the dis
 
 :: new features
 
-- When adding Markdown (.md) or textile (.textile) files that contain references to images, automatically add them as txtz with the images
+- When adding Markdown (.md) or textile (.textile) files that contain references to images, automatically add them as TXTZ with the images
 
 :: bug fixes
 

From e17454d4fe03b13d4d9af4e7f3000e0b28281d46 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Sun, 30 Apr 2023 12:34:43 +0530
Subject: [PATCH 0635/2055] string changes

---
 Changelog.txt                                       | 2 +-
 manual/simple_index.rst                             | 2 +-
 src/calibre/ebooks/conversion/plugins/txt_input.py  | 2 +-
 src/calibre/ebooks/conversion/plugins/txt_output.py | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/Changelog.txt b/Changelog.txt
index 23161f494b..4ba88bfc70 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1526,7 +1526,7 @@ Then, when searching the Tag browser, press Ctrl+Alt+Shift+F to restrict the dis
 
 :: new features
 
-- When adding Markdown (.md) or textile (.textile) files that contain references to images, automatically add them as TXTZ with the images
+- When adding Markdown (.md) or Textile (.textile) files that contain references to images, automatically add them as TXTZ with the images
 
 :: bug fixes
 
diff --git a/manual/simple_index.rst b/manual/simple_index.rst
index a9b9d12dbb..824155b46a 100644
--- a/manual/simple_index.rst
+++ b/manual/simple_index.rst
@@ -21,7 +21,7 @@ available `.
 
 .. only:: online
 
-    **An e-book version of this User Manual is available in** `EPUB format `_,  `AZW3 (Kindle Fire) format `_ and `PDF format `_.
+    **An e-book version of this User Manual is available in** `EPUB format `_,  `AZW3 (Kindle) format `_ and `PDF format `_.
 
 .. rubric:: Sections
 
diff --git a/src/calibre/ebooks/conversion/plugins/txt_input.py b/src/calibre/ebooks/conversion/plugins/txt_input.py
index c1030e3b2c..6aa3c30ed4 100644
--- a/src/calibre/ebooks/conversion/plugins/txt_input.py
+++ b/src/calibre/ebooks/conversion/plugins/txt_input.py
@@ -49,7 +49,7 @@ class TXTInput(InputFormatPlugin):
             'auto': _('Automatically decide which formatting processor to use'),
             'plain': _('No formatting'),
             'heuristic': _('Use heuristics to determine chapter headings, italics, etc.'),
-            'textile': _('Use the TexTile markup language'),
+            'textile': _('Use the Textile markup language'),
             'markdown': _('Use the Markdown markup language')
         },
     }
diff --git a/src/calibre/ebooks/conversion/plugins/txt_output.py b/src/calibre/ebooks/conversion/plugins/txt_output.py
index 65997b7f56..4051d2f30f 100644
--- a/src/calibre/ebooks/conversion/plugins/txt_output.py
+++ b/src/calibre/ebooks/conversion/plugins/txt_output.py
@@ -24,7 +24,7 @@ class TXTOutput(OutputFormatPlugin):
             'formatting_types': {
                 'plain': _('Plain text'),
                 'markdown': _('Markdown formatted text'),
-                'textile': _('TexTile formatted text')
+                'textile': _('Textile formatted text')
             },
     }
 

From 3e539de6572002f08bb7ee5c09fa3cd0ddf7a4c2 Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Sun, 30 Apr 2023 09:31:00 +0200
Subject: [PATCH 0636/2055] fix imbricated bold/italic

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index 7079ac6185..a5df754d4c 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -15,9 +15,9 @@ class MarkdownHighlighter(QSyntaxHighlighter):
 
     MARKDOWN_KEYS_REGEX = {
         'Bold' : re.compile(r'(?P\*\*)(?P.+)(?P=delim)'),
-        'uBold': re.compile('(?P__)(?P[^_]{2,})(?P=delim)'),
+        'uBold': re.compile('(?P__)(?P.+)(?P=delim)'),
         'Italic': re.compile(r'(?P\*)(?P[^*]{2,})(?P=delim)'),
-        'uItalic': re.compile('(?P_)(?P[^_]+)(?P=delim)'),
+        'uItalic': re.compile('(?P_)(?P[^_]{2,})(?P=delim)'),
         'Link': re.compile(r'(?u)(^|(?P
[^!]))\[.*?\]:?[ ''\t'r']*\(?[^)]+\)?'),
         'Image': re.compile(r'(?u)!\[.*?\]\(.+?\)'),
         'HeaderAtx': re.compile(r'(?u)^\#{1,6}(.*?)\#*(''\n|$)'),

From 2f8a32cb56cd679f386b8ff5a4a41f1da0264175 Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Sun, 30 Apr 2023 09:49:52 +0200
Subject: [PATCH 0637/2055] fix single char italic

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index a5df754d4c..fa92f79d74 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -16,8 +16,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
     MARKDOWN_KEYS_REGEX = {
         'Bold' : re.compile(r'(?P\*\*)(?P.+)(?P=delim)'),
         'uBold': re.compile('(?P__)(?P.+)(?P=delim)'),
-        'Italic': re.compile(r'(?P\*)(?P[^*]{2,})(?P=delim)'),
-        'uItalic': re.compile('(?P_)(?P[^_]{2,})(?P=delim)'),
+        'Italic': re.compile(r'(?P\*)(?P([^*]{2,}|[^*]))(?P=delim)'),
+        'uItalic': re.compile('(?P_)(?P([^_]{2,}|[^_]))(?P=delim)'),
         'Link': re.compile(r'(?u)(^|(?P
[^!]))\[.*?\]:?[ ''\t'r']*\(?[^)]+\)?'),
         'Image': re.compile(r'(?u)!\[.*?\]\(.+?\)'),
         'HeaderAtx': re.compile(r'(?u)^\#{1,6}(.*?)\#*(''\n|$)'),

From 5c5f08d709d2409e9b3e3284968209a793d3ad4f Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Sun, 30 Apr 2023 09:56:00 +0200
Subject: [PATCH 0638/2055] reorder highlighting

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index fa92f79d74..a6a28adb9b 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -129,16 +129,16 @@ class MarkdownHighlighter(QSyntaxHighlighter):
 
         self.highlightList(text, cursor, bf, strt)
 
+        self.highlightEmphasis(text, cursor, bf, strt)
+
+        self.highlightBold(text, cursor, bf, strt)
+
         self.highlightLink(text, cursor, bf, strt)
 
         self.highlightImage(text, cursor, bf, strt)
 
         self.highlightCodeSpan(text, cursor, bf, strt)
 
-        self.highlightEmphasis(text, cursor, bf, strt)
-
-        self.highlightBold(text, cursor, bf, strt)
-
         self.highlightCodeBlock(text, cursor, bf, strt)
 
     def highlightBlockQuote(self, text, cursor, bf, strt):

From 6793aaae369751163509f58d8c9075c9348d4073 Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Sun, 30 Apr 2023 10:16:18 +0200
Subject: [PATCH 0639/2055] fix link overflow

The actual regex for Link select the caracter before the bracket if not a start of the line
---
 src/calibre/gui2/markdown_syntax_highlighter.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index a6a28adb9b..5e82914bc1 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -211,7 +211,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
     def highlightLink(self, text, cursor, bf, strt):
         found = False
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Link'],text):
-            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['Link'])
+            start_bracket = mo.group()[0][0] == '['
+            self.setFormat(mo.start()+strt+(0 if start_bracket else 1), mo.end() - mo.start()-strt-(0 if start_bracket else 1), self.MARKDOWN_KWS_FORMAT['Link'])
             found = True
         return found
 

From 6f1ef2ddabba1d05ab86bf882c83397c0e5ac2de Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Sun, 30 Apr 2023 10:18:25 +0100
Subject: [PATCH 0640/2055] Revert "Fix weird problem where a menu can be shown
 on the wrong screen if it is constructed before the first show event. On my
 test system it is 100% repeatable."

This reverts commit 19f1db9578b3143050efc26b9aaf6603f03ac079.
---
 src/calibre/gui2/actions/sort.py | 67 ++------------------------------
 1 file changed, 3 insertions(+), 64 deletions(-)

diff --git a/src/calibre/gui2/actions/sort.py b/src/calibre/gui2/actions/sort.py
index 36e1ef6532..5236301bb4 100644
--- a/src/calibre/gui2/actions/sort.py
+++ b/src/calibre/gui2/actions/sort.py
@@ -5,11 +5,10 @@ __license__ = 'GPL v3'
 __copyright__ = '2013, Kovid Goyal '
 
 from contextlib import suppress
-from enum import Enum
 from functools import partial
 from qt.core import (
     QAbstractItemView, QAction, QDialog, QDialogButtonBox, QIcon, QListWidget,
-    QListWidgetItem, QMenu, QSize, Qt, QToolButton, QVBoxLayout, pyqtSignal,
+    QListWidgetItem, QSize, Qt, QToolButton, QVBoxLayout, pyqtSignal,
 )
 
 from calibre.gui2.actions import InterfaceAction
@@ -36,21 +35,6 @@ class SortAction(QAction):
         self.sort_requested.emit(self.key, self.ascending)
 
 
-class InitializationEnum(Enum):
-    WaitingInitialization = 0
-    InitializeCalled = 1
-    FullyInitialized = 2
-
-
-class FakeAction(QAction):
-
-    def menu(self):
-        return self.the_menu
-
-    def setMenu(self, menu):
-        self.the_menu = menu
-
-
 class SortByAction(InterfaceAction):
 
     name = 'Sort By'
@@ -59,11 +43,8 @@ class SortByAction(InterfaceAction):
     popup_type = QToolButton.ToolButtonPopupMode.InstantPopup
     action_add_menu = True
     dont_add_to = frozenset(('context-menu-cover-browser', ))
-    init_complete = InitializationEnum.WaitingInitialization
-    menu_shown = False
 
     def genesis(self):
-        self.fake_action = None
         self.sorted_icon = QIcon.ic('ok.png')
         self.qaction.menu().aboutToShow.connect(self.about_to_show)
 
@@ -76,45 +57,6 @@ class SortByAction(InterfaceAction):
         c('reverse_sort_action', _('Reverse current sort'), _('Reverse the current sort order'), self.reverse_sort, 'shift+f5')
         c('reapply_sort_action', _('Re-apply current sort'), _('Re-apply the current sort'), self.reapply_sort, 'f5')
 
-
-    # The property is here because Qt can put the menu on the wrong screen if
-    # the menu is built for the first time outside of the process of showing it.
-    # On Windows this happens if calibre is on screen 2; the menu appears on
-    # screen 1. Why Qt does this I can't say. The problem can be triggered if a
-    # plugin queries the menu during its own initialization. We solve this
-    # problem by giving the caller a fake QAction with the menu, which works for
-    # plugins. We use the "real" action once the user has requested the menu. Of
-    # course, the right fix would be for Qt to show the menu where it should go
-    # even if built previously, but I can't find how to make that happen
-
-    @property
-    def qaction(self):
-        try:
-            # if initialization_complete() hasn't been called or we are fully
-            # initialized then do nothing special
-            if self.init_complete == InitializationEnum.InitializeCalled:
-                if not self.menu_shown:
-                    # This is the problematic case where something wants the
-                    # menu before it's been shown. Construct the fake action
-                    m = QMenu()
-                    self.update_menu(m)
-                    if self.fake_action is None:
-                        self.fake_action = FakeAction()
-                    self.fake_action.setMenu(m)
-                    return self.fake_action
-                else:
-                    # The menu has been shown. No need to construct the fake
-                    # action in the future.
-                    self.init_complete = InitializationEnum.FullyInitialized
-        except Exception:
-            import traceback
-            traceback.print_exc()
-        return self.my_qaction
-
-    @qaction.setter
-    def qaction(self, val):
-        self.my_qaction = val
-
     def reverse_sort(self):
         self.gui.current_view().reverse_sort()
 
@@ -127,14 +69,13 @@ class SortByAction(InterfaceAction):
         self.menuless_qaction.setEnabled(enabled)
 
     def about_to_show(self):
-        self.menu_shown = True
         self.update_menu()
 
     def library_changed(self, db):
         self.update_menu()
 
     def initialization_complete(self):
-        self.init_complete = InitializationEnum.InitializeCalled
+        self.update_menu()
 
     def update_menu(self, menu=None):
         menu = self.qaction.menu() if menu is None else menu
@@ -187,8 +128,6 @@ class SortByAction(InterfaceAction):
                 menu.insertSeparator(before)
             else:
                 menu.addAction(sac)
-        if self.fake_action is not None:
-            self.fake_action.setMenu(menu)
 
     def select_sortable_columns(self):
         db = self.gui.current_db
@@ -227,7 +166,7 @@ class SortByAction(InterfaceAction):
         d = ChooseMultiSort(self.gui.current_db, parent=self.gui, is_device_connected=self.gui.device_connected)
         if d.exec() == QDialog.DialogCode.Accepted:
             self.gui.library_view.multisort(d.current_sort_spec)
-        self.update_menu()
+            self.update_menu()
 
     def sort_requested(self, key, ascending):
         if ascending is None:

From 918bd44f5d4e2db9e006d0adf069e747252a5636 Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Sun, 30 Apr 2023 11:22:51 +0200
Subject: [PATCH 0641/2055] primitive support of bold-italic

---
 .../gui2/markdown_syntax_highlighter.py       | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index 5e82914bc1..d9c5c8f2d1 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -18,6 +18,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'uBold': re.compile('(?P__)(?P.+)(?P=delim)'),
         'Italic': re.compile(r'(?P\*)(?P([^*]{2,}|[^*]))(?P=delim)'),
         'uItalic': re.compile('(?P_)(?P([^_]{2,}|[^_]))(?P=delim)'),
+        'BoldItalic': re.compile(r'(?P\*\*\*)(?P([^*]{2,}|[^*]))(?P=delim)'),
+        'uBoldItalic': re.compile(r'(?P___)(?P([^_]{2,}|[^_]))(?P=delim)'),
         'Link': re.compile(r'(?u)(^|(?P
[^!]))\[.*?\]:?[ ''\t'r']*\(?[^)]+\)?'),
         'Image': re.compile(r'(?u)!\[.*?\]\(.+?\)'),
         'HeaderAtx': re.compile(r'(?u)^\#{1,6}(.*?)\#*(''\n|$)'),
@@ -39,6 +41,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'uBold': "bold",
         'Italic': "emphasis",
         'uItalic': "emphasis",
+        'BoldItalic': "boldemphasis",
+        'uBoldItalic': "boldemphasis",
         'Link': "link",
         'Image': "image",
         'HeaderAtx': "header",
@@ -58,6 +62,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
     light_theme =  {
         "bold": {"font-weight":"bold"},
         "emphasis": {"font-style":"italic"},
+        "boldemphasis": {"font-weight":"bold", "font-style":"italic"},
         "link": {"color":light_link_color.name(), "font-weight":"normal", "font-style":"normal"},
         "image": {"color":"#cb4b16", "font-weight":"normal", "font-style":"normal"},
         "header": {"color":"#2aa198", "font-weight":"bold", "font-style":"normal"},
@@ -73,6 +78,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
     dark_theme =  {
         "bold": {"font-weight":"bold"},
         "emphasis": {"font-style":"italic"},
+        "boldemphasis": {"font-weight":"bold", "font-style":"italic"},
         "link": {"color":dark_link_color.name(), "font-weight":"normal", "font-style":"normal"},
         "image": {"color":"#cb4b16", "font-weight":"normal", "font-style":"normal"},
         "header": {"color":"#2aa198", "font-weight":"bold", "font-style":"normal"},
@@ -133,6 +139,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
 
         self.highlightBold(text, cursor, bf, strt)
 
+        self.highlightBoldEmphasis(text, cursor, bf, strt)
+
         self.highlightLink(text, cursor, bf, strt)
 
         self.highlightImage(text, cursor, bf, strt)
@@ -256,6 +264,17 @@ class MarkdownHighlighter(QSyntaxHighlighter):
             found = True
         return found
 
+    def highlightBoldEmphasis(self, text, cursor, bf, strt):
+        found = False
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['BoldItalic'],text):
+            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['BoldItalic'])
+            found = True
+
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['uBoldItalic'],text):
+            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['uBoldItalic'])
+            found = True
+        return found
+
     def highlightCodeBlock(self, text, cursor, bf, strt):
         found = False
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['CodeBlock'],text):

From 5ed24807e73fe3a34124d99f20d27c87078cad55 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Sun, 30 Apr 2023 15:18:57 +0530
Subject: [PATCH 0642/2055] string changes

---
 manual/faq.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/manual/faq.rst b/manual/faq.rst
index 2e95051bc8..380c37a9b2 100644
--- a/manual/faq.rst
+++ b/manual/faq.rst
@@ -252,7 +252,7 @@ In such apps you can go to the online catalog screen and add the IP address of
 the calibre server to browse and download books from your calibre library
 within the app.
 
-How do I use calibre with my Android phone/tablet or Kindle Fire HD?
+How do I use calibre with my Android phone/tablet or Kindle Fire?
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 There are two ways that you can connect your Android device to calibre. Using a USB cable -- or wirelessly, over the air.

From d4d13d0f2a5002a84aacccfd963742087bdb7ea3 Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Sun, 30 Apr 2023 13:08:14 +0100
Subject: [PATCH 0643/2055] Different fix for the wrong screen problem.

---
 src/calibre/gui2/actions/__init__.py          | 58 ++++++++++++++++++-
 src/calibre/gui2/actions/manage_categories.py |  3 +-
 src/calibre/gui2/actions/saved_searches.py    | 50 +---------------
 src/calibre/gui2/actions/sort.py              | 50 ++++++++++++----
 4 files changed, 98 insertions(+), 63 deletions(-)

diff --git a/src/calibre/gui2/actions/__init__.py b/src/calibre/gui2/actions/__init__.py
index 7543e679e0..bc38118a14 100644
--- a/src/calibre/gui2/actions/__init__.py
+++ b/src/calibre/gui2/actions/__init__.py
@@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
 from functools import partial
 from zipfile import ZipFile
 
-from qt.core import (QToolButton, QAction, QIcon, QObject, QMenu,
+from qt.core import (QToolButton, QAction, QIcon, QObject, QMenu, QPoint,
         QKeySequence)
 
 from calibre import prints
@@ -18,6 +18,62 @@ from calibre.gui2.keyboard import NameConflict
 from polyglot.builtins import string_or_bytes
 
 
+def toolbar_widgets_for_action(gui, action):
+    # Search the the toolbars for the widget associated with an action, passing
+    # them to the caller for further processing
+    for x in gui.bars_manager.bars:
+        try:
+            w = x.widgetForAction(action)
+            # It seems that multiple copies of the action can exist, such as
+            # when the device-connected menu is changed while the device is
+            # connected. Use the one that has an actual position.
+            if w is None or w.pos().x() == 0:
+                continue
+            # The button might be hidden
+            if not w.isVisible():
+                continue
+            yield(w)
+        except Exception:
+            continue
+
+
+def show_menu_under_widget(gui, menu, action, name):
+    # First try the tool bar
+    for w in toolbar_widgets_for_action(gui, action):
+        try:
+            # The w.height() assures that the menu opens below the button.
+            menu.exec(w.mapToGlobal(QPoint(0, w.height())))
+            return
+        except Exception:
+            continue
+    # Now try the menu bar
+    for x in gui.bars_manager.menu_bar.added_actions:
+        # This depends on no two menus with the same name.
+        # I don't know if this works on a Mac
+        if x.text() == name:
+            try:
+                # The menu item might be hidden
+                if not x.isVisible():
+                    continue
+                # We can't use x.trigger() because it doesn't put the menu
+                # in the right place. Instead get the position of the menu
+                # widget on the menu bar
+                p = x.parent().menu_bar
+                r = p.actionGeometry(x)
+                # Make sure that the menu item is actually displayed in the menu
+                # and not the overflow
+                if p.geometry().width() < (r.x() + r.width()):
+                    continue
+                # Show the menu under the name in the menu bar
+                menu.exec(p.mapToGlobal(QPoint(r.x()+2, r.height()-2)))
+                return
+            except Exception:
+                continue
+    # No visible button found. Fall back to displaying in upper left corner
+    # of the library view.
+    menu.exec(gui.library_view.mapToGlobal(QPoint(10, 10)))
+
+
 def menu_action_unique_name(plugin, unique_name):
     return '%s : menu action : %s'%(plugin.unique_name, unique_name)
 
diff --git a/src/calibre/gui2/actions/manage_categories.py b/src/calibre/gui2/actions/manage_categories.py
index 9b07cb10db..768da620b2 100644
--- a/src/calibre/gui2/actions/manage_categories.py
+++ b/src/calibre/gui2/actions/manage_categories.py
@@ -4,7 +4,7 @@
 
 from qt.core import QMenu, QToolButton
 
-from calibre.gui2.actions import InterfaceAction
+from calibre.gui2.actions import InterfaceAction, show_menu_under_widget
 
 
 class ManageCategoriesAction(InterfaceAction):
@@ -39,7 +39,6 @@ class ManageCategoriesAction(InterfaceAction):
     # show the menu in the upper left corner of the library view pane. Yes, this
     # is a bit weird but it works as well as a popping up a dialog.
     def show_menu(self):
-        from calibre.gui2.actions.saved_searches import show_menu_under_widget
         show_menu_under_widget(self.gui, self.menu, self.qaction, self.name)
 
     def about_to_show_menu(self):
diff --git a/src/calibre/gui2/actions/saved_searches.py b/src/calibre/gui2/actions/saved_searches.py
index 87bf832cc7..1d85ad274e 100644
--- a/src/calibre/gui2/actions/saved_searches.py
+++ b/src/calibre/gui2/actions/saved_searches.py
@@ -2,55 +2,9 @@
 # License: GPLv3 Copyright: 2022, Charles Haley
 #
 
-from qt.core import QPoint, QMenu, QToolButton
+from qt.core import QMenu, QToolButton
 
-from calibre.gui2.actions import InterfaceAction
-
-
-def show_menu_under_widget(gui, menu, action, name):
-    # First try the tool bar
-    for x in gui.bars_manager.bars:
-        try:
-            w = x.widgetForAction(action)
-            # It seems that multiple copies of the action can exist, such as
-            # when the device-connected menu is changed while the device is
-            # connected. Use the one that has an actual position.
-            if w is None or w.pos().x() == 0:
-                continue
-            # The button might be hidden
-            if not w.isVisible():
-                continue
-            # The w.height() assures that the menu opens below the button.
-            menu.exec(w.mapToGlobal(QPoint(0, w.height())))
-            return
-        except Exception:
-            continue
-    # Now try the menu bar
-    for x in gui.bars_manager.menu_bar.added_actions:
-        # This depends on no two menus with the same name.
-        # I don't know if this works on a Mac
-        if x.text() == name:
-            try:
-                # The menu item might be hidden
-                if not x.isVisible():
-                    continue
-                # We can't use x.trigger() because it doesn't put the menu
-                # in the right place. Instead get the position of the menu
-                # widget on the menu bar
-                p = x.parent().menu_bar
-                r = p.actionGeometry(x)
-                # Make sure that the menu item is actually displayed in the menu
-                # and not the overflow
-                if p.geometry().width() < (r.x() + r.width()):
-                    continue
-                # Show the menu under the name in the menu bar
-                menu.exec(p.mapToGlobal(QPoint(r.x()+2, r.height()-2)))
-                return
-            except Exception:
-                continue
-    # No visible button found. Fall back to displaying in upper left corner
-    # of the library view.
-    menu.exec(gui.library_view.mapToGlobal(QPoint(10, 10)))
+from calibre.gui2.actions import InterfaceAction, show_menu_under_widget
 
 
 class SavedSearchesAction(InterfaceAction):
diff --git a/src/calibre/gui2/actions/sort.py b/src/calibre/gui2/actions/sort.py
index 5236301bb4..fe104ee2d7 100644
--- a/src/calibre/gui2/actions/sort.py
+++ b/src/calibre/gui2/actions/sort.py
@@ -8,10 +8,10 @@ from contextlib import suppress
 from functools import partial
 from qt.core import (
     QAbstractItemView, QAction, QDialog, QDialogButtonBox, QIcon, QListWidget,
-    QListWidgetItem, QSize, Qt, QToolButton, QVBoxLayout, pyqtSignal,
+    QListWidgetItem, QMenu, QSize, Qt, QToolButton, QVBoxLayout, pyqtSignal,
 )
 
-from calibre.gui2.actions import InterfaceAction
+from calibre.gui2.actions import InterfaceAction, show_menu_under_widget, toolbar_widgets_for_action
 from calibre.library.field_metadata import category_icon_map
 from calibre.utils.icu import primary_sort_key
 from polyglot.builtins import iteritems
@@ -40,13 +40,25 @@ class SortByAction(InterfaceAction):
     name = 'Sort By'
     action_spec = (_('Sort by'), 'sort.png', _('Sort the list of books'), None)
     action_type = 'current'
-    popup_type = QToolButton.ToolButtonPopupMode.InstantPopup
+    popup_type = QToolButton.ToolButtonPopupMode.MenuButtonPopup
     action_add_menu = True
     dont_add_to = frozenset(('context-menu-cover-browser', ))
 
     def genesis(self):
         self.sorted_icon = QIcon.ic('ok.png')
-        self.qaction.menu().aboutToShow.connect(self.about_to_show)
+        self.qaction.triggered.connect(self.show_menu)
+
+        # Create a "hidden" menu that can have a shortcut. This also lets us
+        # manually show the menu instead of letting Qt do it to work around a
+        # problem where Qt can show the menu on the wrong screen.
+        self.hidden_menu = QMenu()
+        self.shortcut_action = self.create_menu_action(
+                        menu=self.hidden_menu,
+                        unique_name=_('Sort by'),
+                        text=_('Show the Sort by menu'),
+                        icon=None,
+                        shortcut='Ctrl+F5',
+                        triggered=self.show_menu)
 
         def c(attr, title, tooltip, callback, keys=()):
             ac = self.create_action(spec=(title, None, tooltip, keys), attr=attr)
@@ -57,6 +69,12 @@ class SortByAction(InterfaceAction):
         c('reverse_sort_action', _('Reverse current sort'), _('Reverse the current sort order'), self.reverse_sort, 'shift+f5')
         c('reapply_sort_action', _('Re-apply current sort'), _('Re-apply the current sort'), self.reapply_sort, 'f5')
 
+    def show_menu(self):
+        # We manually show the menu instead of letting Qt do it to work around a
+        # problem where the menu can show on the wrong screen.
+        self.update_menu()
+        show_menu_under_widget(self.gui, self.qaction.menu(), self.qaction, self.name)
+
     def reverse_sort(self):
         self.gui.current_view().reverse_sort()
 
@@ -68,17 +86,20 @@ class SortByAction(InterfaceAction):
         self.qaction.setEnabled(enabled)
         self.menuless_qaction.setEnabled(enabled)
 
-    def about_to_show(self):
-        self.update_menu()
-
     def library_changed(self, db):
         self.update_menu()
 
     def initialization_complete(self):
         self.update_menu()
+        # Remove the down arrow from the buttons as they serve no purpose. They
+        # show exactly what clicking the button shows
+        for w in toolbar_widgets_for_action(self.gui, self.qaction):
+            # Not sure why both styles are necessary, but they are
+            w.setStyleSheet('QToolButton::menu-button {image: none; }'
+                            'QToolButton::menu-arrow {image: none; }')
 
-    def update_menu(self, menu=None):
-        menu = self.qaction.menu() if menu is None else menu
+    def update_menu(self):
+        menu = self.qaction.menu()
         for action in menu.actions():
             if hasattr(action, 'sort_requested'):
                 action.sort_requested.disconnect()
@@ -96,17 +117,22 @@ class SortByAction(InterfaceAction):
                 menu.addAction(name, partial(self.named_sort_selected, saved_sorts[name]))
             menu.addSeparator()
 
+        # Note the current sort column so it can be specially handled below
         try:
             sort_col, order = m.sorted_on
         except TypeError:
             sort_col, order = 'date', True
-        fm = db.field_metadata
-        name_map = {v:k for k, v in iteritems(fm.ui_sortable_field_keys())}
-        hidden = frozenset(db.new_api.pref(SORT_HIDDEN_PREF, default=()) or ())
+
+        # The operations to choose which columns to display and to create saved sorts
         menu.addAction(_('Select sortable columns')).triggered.connect(self.select_sortable_columns)
         menu.addAction(_('Sort on multiple columns'), self.choose_multisort)
         menu.addSeparator()
+
+        # Add the columns to the menu
+        fm = db.field_metadata
+        name_map = {v:k for k, v in iteritems(fm.ui_sortable_field_keys())}
         all_names = sorted(name_map, key=primary_sort_key)
+        hidden = frozenset(db.new_api.pref(SORT_HIDDEN_PREF, default=()) or ())
 
         for name in all_names:
             key = name_map[name]

From 40f1948189c2183517a72a98d8ded6748b71870b Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Sun, 30 Apr 2023 20:30:07 +0530
Subject: [PATCH 0644/2055] Cleanup previous PR

The sort button used by the grid view still needs update_menu to take a
menu parameter
---
 src/calibre/gui2/actions/sort.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/calibre/gui2/actions/sort.py b/src/calibre/gui2/actions/sort.py
index fe104ee2d7..71b285d24a 100644
--- a/src/calibre/gui2/actions/sort.py
+++ b/src/calibre/gui2/actions/sort.py
@@ -98,8 +98,8 @@ class SortByAction(InterfaceAction):
             w.setStyleSheet('QToolButton::menu-button {image: none; }'
                             'QToolButton::menu-arrow {image: none; }')
 
-    def update_menu(self):
-        menu = self.qaction.menu()
+    def update_menu(self, menu=None):
+        menu = menu or self.qaction.menu()
         for action in menu.actions():
             if hasattr(action, 'sort_requested'):
                 action.sort_requested.disconnect()

From 9b7216b4ff7bc6dc96c2eacd93bde92f4aca36bf Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Sun, 30 Apr 2023 14:00:18 +0200
Subject: [PATCH 0645/2055] fix overflow

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index b458619646..a3c29383da 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -14,12 +14,12 @@ from calibre.gui2.palette import dark_link_color, light_link_color
 class MarkdownHighlighter(QSyntaxHighlighter):
 
     MARKDOWN_KEYS_REGEX = {
-        'Bold' : re.compile(r'(?P\*\*)(?P.+)(?P=delim)'),
-        'uBold': re.compile('(?P__)(?P.+)(?P=delim)'),
-        'Italic': re.compile(r'(?P\*)(?P([^*]{2,}|[^*]))(?P=delim)'),
-        'uItalic': re.compile('(?P_)(?P([^_]{2,}|[^_]))(?P=delim)'),
-        'BoldItalic': re.compile(r'(?P\*\*\*)(?P([^*]{2,}|[^*]))(?P=delim)'),
-        'uBoldItalic': re.compile(r'(?P___)(?P([^_]{2,}|[^_]))(?P=delim)'),
+        'Bold' : re.compile(r'(?P\*\*)(?P.+?)(?P=delim)'),
+        'uBold': re.compile('(?P__)(?P.+?)(?P=delim)'),
+        'Italic': re.compile(r'(?P\*)(?P([^*]{2,}?|[^*]))(?P=delim)'),
+        'uItalic': re.compile('(?P_)(?P([^_]{2,}?|[^_]))(?P=delim)'),
+        'BoldItalic': re.compile(r'(?P\*\*\*)(?P([^*]{2,}?|[^*]))(?P=delim)'),
+        'uBoldItalic': re.compile(r'(?P___)(?P([^_]{2,}?|[^_]))(?P=delim)'),
         'Link': re.compile(r'(?u)(^|(?P
[^!]))\[.*?\]:?[ ''\t'r']*\(?[^)]+\)?'),
         'Image': re.compile(r'(?u)!\[.*?\]\(.+?\)'),
         'HeaderAtx': re.compile(r'(?u)^\#{1,6}(.*?)\#*(''\n|$)'),

From c67cdd02bc54d8004c1bc06a8b6968008959aceb Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Sun, 30 Apr 2023 14:28:59 +0200
Subject: [PATCH 0646/2055] improve Link, Image, LinkRef

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index a3c29383da..b131c8ddf4 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -20,8 +20,9 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'uItalic': re.compile('(?P_)(?P([^_]{2,}?|[^_]))(?P=delim)'),
         'BoldItalic': re.compile(r'(?P\*\*\*)(?P([^*]{2,}?|[^*]))(?P=delim)'),
         'uBoldItalic': re.compile(r'(?P___)(?P([^_]{2,}?|[^_]))(?P=delim)'),
-        'Link': re.compile(r'(?u)(^|(?P
[^!]))\[.*?\]:?[ ''\t'r']*\(?[^)]+\)?'),
-        'Image': re.compile(r'(?u)!\[.*?\]\(.+?\)'),
+        'Link': re.compile(r'(?u)(?
Date: Sun, 30 Apr 2023 17:14:15 +0200
Subject: [PATCH 0647/2055] regex tweak

---
 .../gui2/markdown_syntax_highlighter.py       | 24 +++++++++----------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index b131c8ddf4..9e7faa2ca0 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -14,14 +14,14 @@ from calibre.gui2.palette import dark_link_color, light_link_color
 class MarkdownHighlighter(QSyntaxHighlighter):
 
     MARKDOWN_KEYS_REGEX = {
-        'Bold' : re.compile(r'(?P\*\*)(?P.+?)(?P=delim)'),
-        'uBold': re.compile('(?P__)(?P.+?)(?P=delim)'),
-        'Italic': re.compile(r'(?P\*)(?P([^*]{2,}?|[^*]))(?P=delim)'),
-        'uItalic': re.compile('(?P_)(?P([^_]{2,}?|[^_]))(?P=delim)'),
-        'BoldItalic': re.compile(r'(?P\*\*\*)(?P([^*]{2,}?|[^*]))(?P=delim)'),
-        'uBoldItalic': re.compile(r'(?P___)(?P([^_]{2,}?|[^_]))(?P=delim)'),
-        'Link': re.compile(r'(?u)(?\*\*)(?P.+?)(?P=delim)'),
+        'uBold': re.compile(r'(?__)(?P.+?)(?P=delim)'),
+        'Italic': re.compile(r'(?\*)(?P([^*]{2,}?|[^*]))(?P=delim)'),
+        'uItalic': re.compile(r'(?_)(?P([^_]{2,}?|[^_]))(?P=delim)'),
+        'BoldItalic': re.compile(r'(?\*\*\*)(?P([^*]{2,}?|[^*]))(?P=delim)'),
+        'uBoldItalic': re.compile(r'(?___)(?P([^_]{2,}?|[^_]))(?P=delim)'),
+        'Link': re.compile(r'(?u)(?+\s*'),
-        'BlockQuoteCount': re.compile('^[ \t]*>[ \t]?'),
-        'CodeSpan': re.compile('(?P`+).+?(?P=delim)'),
+        'BlockQuote': re.compile(r'(?u)^([ ]{0,3}>)+\s*'),
+        'BlockQuoteCount': re.compile(r'^[ ]{0,3}>[ \t]?'),
+        'CodeSpan': re.compile(r'(?`+).+?(?P=delim)'),
         'HeaderLine': re.compile(r'(?u)^(-|=)+\s*$'),
         'HR': re.compile(r'(?u)^(\s*(\*|-|_)\s*){3,}$'),
-        'Html': re.compile('<.+?>')
+        'Html': re.compile(r'<.+?(?')
     }
 
     key_theme_maps = {

From 5afea801330b326ab01e009e181b597780c92636 Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Sun, 30 Apr 2023 19:20:52 +0200
Subject: [PATCH 0648/2055] improve Header and CodeBlock

---
 .../gui2/markdown_syntax_highlighter.py       | 27 +++++++------------
 1 file changed, 10 insertions(+), 17 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index 9e7faa2ca0..75ec01ae4c 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -23,14 +23,12 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'Link': re.compile(r'(?u)(?)+\s*'),
-        'BlockQuoteCount': re.compile(r'^[ ]{0,3}>[ \t]?'),
+        'BlockQuote': re.compile(r'(?u)^[ ]{0,3}>+[ \t]?'),
         'CodeSpan': re.compile(r'(?`+).+?(?P=delim)'),
         'HeaderLine': re.compile(r'(?u)^(-|=)+\s*$'),
         'HR': re.compile(r'(?u)^(\s*(\*|-|_)\s*){3,}$'),
@@ -47,7 +45,6 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'Link': "link",
         'Image': "image",
         'LinkRef': "link",
-        'HeaderAtx': "header",
         'Header': "header",
         'HeaderLine': "header",
         'CodeBlock': "codeblock",
@@ -55,7 +52,6 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'UnorderedListStar': "unorderedlist",
         'OrderedList': "orderedlist",
         'BlockQuote': "blockquote",
-        'BlockQuoteCount': "blockquote",
         'CodeSpan': "codespan",
         'HR': "line",
         'Html': "html",
@@ -132,7 +128,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         if self.highlightHorizontalLine(text, cursor, bf, strt):
             return
 
-        if self.highlightAtxHeader(text, cursor, bf, strt):
+        if self.highlightHeader(text, cursor, bf, strt):
             return
 
         self.highlightList(text, cursor, bf, strt)
@@ -156,11 +152,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         mo = re.search(self.MARKDOWN_KEYS_REGEX['BlockQuote'],text)
         if mo:
             self.setFormat(mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['BlockQuote'])
-            unquote = re.sub(self.MARKDOWN_KEYS_REGEX['BlockQuoteCount'],'',text)
-            spcs = re.match(self.MARKDOWN_KEYS_REGEX['BlockQuoteCount'],text)
-            spcslen = 0
-            if spcs:
-                spcslen = len(spcs.group(0))
+            spcslen = mo.end()
+            unquote = text[spcslen:]
             self.highlightMarkdown(unquote,spcslen)
             found = True
         return found
@@ -180,7 +173,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
             prevCursor = QTextCursor(prevBlock)
             prev = prevBlock.text()
             prevAscii = str(prev.replace('\u2029','\n'))
-            if prevAscii.strip():
+            if strt == 0 and prevAscii.strip():
                 #print "Its a header"
                 prevCursor.select(QTextCursor.SelectionType.LineUnderCursor)
                 #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header'])
@@ -197,13 +190,13 @@ class MarkdownHighlighter(QSyntaxHighlighter):
             found = True
         return found
 
-    def highlightAtxHeader(self, text, cursor, bf, strt):
+    def highlightHeader(self, text, cursor, bf, strt):
         found = False
-        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['HeaderAtx'],text):
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Header'],text):
             #bf.setBackground(QBrush(QColor(7,54,65)))
             #cursor.movePosition(QTextCursor.End)
             #cursor.mergeBlockFormat(bf)
-            self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HeaderAtx'])
+            self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['Header'])
             found = True
         return found
 
@@ -284,7 +277,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         found = False
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['CodeBlock'],text):
             stripped = text.lstrip()
-            if stripped[0] not in ('*','-','+','>'):
+            if stripped[0] not in ('*','-','+','>') and not re.match('\d+\.', stripped):
                 self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['CodeBlock'])
                 found = True
         return found

From 6287e48ad5eb711f5a9f9e6d9705ef57d4b19dd2 Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Sun, 30 Apr 2023 23:05:22 +0100
Subject: [PATCH 0649/2055] Minor fix to the sort by action: the menu wasn't
 being recomputed after selecting columns to show/not show, causing menu-based
 actions to show the previous values.

---
 src/calibre/gui2/actions/sort.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/calibre/gui2/actions/sort.py b/src/calibre/gui2/actions/sort.py
index 71b285d24a..ff9a68cf18 100644
--- a/src/calibre/gui2/actions/sort.py
+++ b/src/calibre/gui2/actions/sort.py
@@ -183,6 +183,7 @@ class SortByAction(InterfaceAction):
                 if not i.isSelected():
                     hidden.append(i.data(Qt.ItemDataRole.UserRole))
             db.new_api.set_pref(SORT_HIDDEN_PREF, tuple(hidden))
+            self.update_menu()
 
     def named_sort_selected(self, sort_spec):
         self.gui.library_view.multisort(sort_spec)

From 30f65436af7baa90e7c1562b8be38c1140abca03 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Mon, 1 May 2023 06:08:26 +0530
Subject: [PATCH 0650/2055] ...

---
 src/calibre/gui2/book_details.py | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py
index afd60a3080..71f44ffbe1 100644
--- a/src/calibre/gui2/book_details.py
+++ b/src/calibre/gui2/book_details.py
@@ -487,11 +487,12 @@ def create_copy_links(menu, data=None):
     link(_('Link to show book in calibre'), f'calibre://show-book/{library_id}/{book_id}')
     link(_('Link to show book details in a popup window'), f'calibre://book-details/{library_id}/{book_id}')
     mi = db.new_api.get_proxy_metadata(book_id)
-    with suppress(Exception):
-        data_files = db.new_api.list_extra_files(book_id, use_cache=True, pattern=DATA_FILE_PATTERN)
-        if data_files:
-            data_path = os.path.join(db.backend.library_path, mi.path, DATA_DIR_NAME)
-            link(_("Link to open book's data files folder"), bytes(QUrl.fromLocalFile(data_path).toEncoded()).decode('utf-8'))
+    if mi and mi.path:
+        with suppress(Exception):
+            data_files = db.new_api.list_extra_files(book_id, use_cache=True, pattern=DATA_FILE_PATTERN)
+            if data_files:
+                data_path = os.path.join(db.backend.library_path, mi.path, DATA_DIR_NAME)
+                link(_("Link to open book's data files folder"), bytes(QUrl.fromLocalFile(data_path).toEncoded()).decode('utf-8'))
     if data:
         field = data.get('field')
         if data['type'] == 'author':

From 596321564a5b29195181dc8860bb9c8fb8b2487b Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Mon, 1 May 2023 07:04:47 +0200
Subject: [PATCH 0651/2055] recursive BlockQuote

---
 .../gui2/markdown_syntax_highlighter.py       | 98 ++++++++++---------
 1 file changed, 50 insertions(+), 48 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index 75ec01ae4c..73ddf7cea7 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -110,62 +110,64 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         self.rehighlight()
 
     def highlightBlock(self, text):
-        self.highlightMarkdown(text,0)
+        self.offset = 0
+        self.highlightMarkdown(text)
         self.highlightHtml(text)
 
-    def highlightMarkdown(self, text, strt):
+    def highlightMarkdown(self, text):
         cursor = QTextCursor(self.document())
         bf = cursor.blockFormat()
 
-        #Block quotes can contain all elements so process it first
-        self.highlightBlockQuote(text, cursor, bf, strt)
+        #Block quotes can contain all elements so process it first, internaly process recusively and return
+        if self.highlightBlockQuote(text, cursor, bf):
+            return
 
         #If empty line no need to check for below elements just return
-        if self.highlightEmptyLine(text, cursor, bf, strt):
+        if self.highlightEmptyLine(text, cursor, bf):
             return
 
         #If horizontal line, look at pevious line to see if its a header, process and return
-        if self.highlightHorizontalLine(text, cursor, bf, strt):
+        if self.highlightHorizontalLine(text, cursor, bf):
             return
 
-        if self.highlightHeader(text, cursor, bf, strt):
+        if self.highlightHeader(text, cursor, bf):
             return
 
-        self.highlightList(text, cursor, bf, strt)
+        self.highlightList(text, cursor, bf)
 
-        self.highlightEmphasis(text, cursor, bf, strt)
+        self.highlightEmphasis(text, cursor, bf)
 
-        self.highlightBold(text, cursor, bf, strt)
+        self.highlightBold(text, cursor, bf)
 
-        self.highlightBoldEmphasis(text, cursor, bf, strt)
+        self.highlightBoldEmphasis(text, cursor, bf)
 
-        self.highlightLink(text, cursor, bf, strt)
+        self.highlightLink(text, cursor, bf)
 
-        self.highlightImage(text, cursor, bf, strt)
+        self.highlightImage(text, cursor, bf)
 
-        self.highlightCodeSpan(text, cursor, bf, strt)
+        self.highlightCodeSpan(text, cursor, bf)
 
-        self.highlightCodeBlock(text, cursor, bf, strt)
+        self.highlightCodeBlock(text, cursor, bf)
 
-    def highlightBlockQuote(self, text, cursor, bf, strt):
+    def highlightBlockQuote(self, text, cursor, bf):
         found = False
         mo = re.search(self.MARKDOWN_KEYS_REGEX['BlockQuote'],text)
         if mo:
-            self.setFormat(mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['BlockQuote'])
-            spcslen = mo.end()
-            unquote = text[spcslen:]
-            self.highlightMarkdown(unquote,spcslen)
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['BlockQuote'])
+            self.offset += mo.end()
+            unquote = text[mo.end():]
+            self.highlightMarkdown(unquote)
             found = True
         return found
 
-    def highlightEmptyLine(self, text, cursor, bf, strt):
+    def highlightEmptyLine(self, text, cursor, bf):
         textAscii = str(text.replace('\u2029','\n'))
         if textAscii.strip():
             return False
         else:
             return True
 
-    def highlightHorizontalLine(self, text, cursor, bf, strt):
+    def highlightHorizontalLine(self, text, cursor, bf):
         found = False
 
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['HeaderLine'],text):
@@ -173,7 +175,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
             prevCursor = QTextCursor(prevBlock)
             prev = prevBlock.text()
             prevAscii = str(prev.replace('\u2029','\n'))
-            if strt == 0 and prevAscii.strip():
+            if self.offset == 0 and prevAscii.strip():
                 #print "Its a header"
                 prevCursor.select(QTextCursor.SelectionType.LineUnderCursor)
                 #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header'])
@@ -182,72 +184,72 @@ class MarkdownHighlighter(QSyntaxHighlighter):
                 formatRange.length = prevCursor.block().length()
                 formatRange.start = 0
                 prevCursor.block().layout().setFormats([formatRange])
-                self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HeaderLine'])
+                self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HeaderLine'])
                 return True
 
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['HR'],text):
-            self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HR'])
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HR'])
             found = True
         return found
 
-    def highlightHeader(self, text, cursor, bf, strt):
+    def highlightHeader(self, text, cursor, bf):
         found = False
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Header'],text):
             #bf.setBackground(QBrush(QColor(7,54,65)))
             #cursor.movePosition(QTextCursor.End)
             #cursor.mergeBlockFormat(bf)
-            self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['Header'])
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['Header'])
             found = True
         return found
 
-    def highlightList(self, text, cursor, bf, strt):
+    def highlightList(self, text, cursor, bf):
         found = False
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['UnorderedList'],text):
-            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['UnorderedList'])
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['UnorderedList'])
             found = True
 
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['OrderedList'],text):
-            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['OrderedList'])
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['OrderedList'])
             found = True
         return found
 
-    def highlightLink(self, text, cursor, bf, strt):
+    def highlightLink(self, text, cursor, bf):
         found = False
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Link'],text):
-            self.setFormat(mo.start() + strt, mo.end() - mo.start() - strt, self.MARKDOWN_KWS_FORMAT['Link'])
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['Link'])
             found = True
 
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['LinkRef'],text):
-            self.setFormat(mo.start() + strt, mo.end() - mo.start() - strt, self.MARKDOWN_KWS_FORMAT['LinkRef'])
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['LinkRef'])
             found = True
         return found
 
-    def highlightImage(self, text, cursor, bf, strt):
+    def highlightImage(self, text, cursor, bf):
         found = False
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Image'],text):
-            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['Image'])
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['Image'])
             found = True
         return found
 
-    def highlightCodeSpan(self, text, cursor, bf, strt):
+    def highlightCodeSpan(self, text, cursor, bf):
         found = False
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['CodeSpan'],text):
-            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['CodeSpan'])
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['CodeSpan'])
             found = True
         return found
 
-    def highlightBold(self, text, cursor, bf, strt):
+    def highlightBold(self, text, cursor, bf):
         found = False
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Bold'],text):
-            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['Bold'])
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['Bold'])
             found = True
 
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['uBold'],text):
-            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['uBold'])
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['uBold'])
             found = True
         return found
 
-    def highlightEmphasis(self, text, cursor, bf, strt):
+    def highlightEmphasis(self, text, cursor, bf):
         found = False
         unlist = re.sub(self.MARKDOWN_KEYS_REGEX['UnorderedListStar'],'',text)
         spcs = re.match(self.MARKDOWN_KEYS_REGEX['UnorderedListStar'],text)
@@ -255,30 +257,30 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         if spcs:
             spcslen = len(spcs.group(0))
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Italic'],unlist):
-            self.setFormat(mo.start()+strt+spcslen, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['Italic'])
+            self.setFormat(self.offset+ mo.start()+spcslen, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['Italic'])
             found = True
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['uItalic'],text):
-            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['uItalic'])
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['uItalic'])
             found = True
         return found
 
-    def highlightBoldEmphasis(self, text, cursor, bf, strt):
+    def highlightBoldEmphasis(self, text, cursor, bf):
         found = False
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['BoldItalic'],text):
-            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['BoldItalic'])
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['BoldItalic'])
             found = True
 
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['uBoldItalic'],text):
-            self.setFormat(mo.start()+strt, mo.end() - mo.start()-strt, self.MARKDOWN_KWS_FORMAT['uBoldItalic'])
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['uBoldItalic'])
             found = True
         return found
 
-    def highlightCodeBlock(self, text, cursor, bf, strt):
+    def highlightCodeBlock(self, text, cursor, bf):
         found = False
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['CodeBlock'],text):
             stripped = text.lstrip()
             if stripped[0] not in ('*','-','+','>') and not re.match('\d+\.', stripped):
-                self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['CodeBlock'])
+                self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['CodeBlock'])
                 found = True
         return found
 

From 991313a701b3e6242575b9fcf7d338d69c2e601d Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Mon, 1 May 2023 07:37:38 +0200
Subject: [PATCH 0652/2055] fiw overflow italic

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index 73ddf7cea7..a2cecda996 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -16,8 +16,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
     MARKDOWN_KEYS_REGEX = {
         'Bold' : re.compile(r'(?\*\*)(?P.+?)(?P=delim)'),
         'uBold': re.compile(r'(?__)(?P.+?)(?P=delim)'),
-        'Italic': re.compile(r'(?\*)(?P([^*]{2,}?|[^*]))(?P=delim)'),
-        'uItalic': re.compile(r'(?_)(?P([^_]{2,}?|[^_]))(?P=delim)'),
+        'Italic': re.compile(r'(?\*)(?P([^*]{2,}?|[^*]))(?P=delim)'),
+        'uItalic': re.compile(r'(?_)(?P([^_]{2,}?|[^_]))(?P=delim)'),
         'BoldItalic': re.compile(r'(?\*\*\*)(?P([^*]{2,}?|[^*]))(?P=delim)'),
         'uBoldItalic': re.compile(r'(?___)(?P([^_]{2,}?|[^_]))(?P=delim)'),
         'Link': re.compile(r'(?u)(?
Date: Mon, 1 May 2023 11:04:55 +0200
Subject: [PATCH 0653/2055] rework highlightBoldEmphasis

better support for imbricated
---
 .../gui2/markdown_syntax_highlighter.py       | 82 +++++++++++--------
 1 file changed, 46 insertions(+), 36 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index a2cecda996..c4e6a862ca 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -14,12 +14,12 @@ from calibre.gui2.palette import dark_link_color, light_link_color
 class MarkdownHighlighter(QSyntaxHighlighter):
 
     MARKDOWN_KEYS_REGEX = {
-        'Bold' : re.compile(r'(?\*\*)(?P.+?)(?P=delim)'),
+        'Bold': re.compile(r'(?\*\*)(?P.+?)(?P=delim)'),
+        'Italic': re.compile(r'(?\*)(?!\*)(?P([^\*]{2,}?|[^\*]))(?\*\*\*)(?P([^\*]{2,}?|[^\*]))(?__)(?P.+?)(?P=delim)'),
-        'Italic': re.compile(r'(?\*)(?P([^*]{2,}?|[^*]))(?P=delim)'),
-        'uItalic': re.compile(r'(?_)(?P([^_]{2,}?|[^_]))(?P=delim)'),
-        'BoldItalic': re.compile(r'(?\*\*\*)(?P([^*]{2,}?|[^*]))(?P=delim)'),
-        'uBoldItalic': re.compile(r'(?___)(?P([^_]{2,}?|[^_]))(?P=delim)'),
+        'uItalic': re.compile(r'(?_)(?!_)(?P([^_]{2,}?|[^_]))(?___)(?P([^_]{2,}?|[^_]))(? return, do not process extra Bold/Italic
 
-        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Italic'],unlist):
-            self.setFormat(self.offset+ mo.start()+spcslen, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['Italic'])
+            sub_txt = text[match.start()+extra_offset : match.end()-extra_offset]
+            sub_offset = offset + extra_offset + mo.start()
+            self._highlightBoldEmphasis(sub_txt, cursor, bf, sub_offset, bold, emphasis)
+
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Italic'],text):
+            recusive(mo, 1, bold, True)
             found = True
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['uItalic'],text):
-            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['uItalic'])
+            recusive(mo, 1, bold, True)
+            found = True
+
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Bold'],text):
+            recusive(mo, 2, True, emphasis)
+            found = True
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['uBold'],text):
+            recusive(mo, 2, True, emphasis)
             found = True
-        return found
 
-    def highlightBoldEmphasis(self, text, cursor, bf):
-        found = False
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['BoldItalic'],text):
-            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['BoldItalic'])
+            apply(mo, True, True)
+            found = True
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['uBoldItalic'],text):
+            apply(mo, True, True)
             found = True
 
-        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['uBoldItalic'],text):
-            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['uBoldItalic'])
-            found = True
         return found
 
     def highlightCodeBlock(self, text, cursor, bf):

From b35fc9861b51225fd608a75fdbc595f7f6cd087e Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Mon, 1 May 2023 13:02:41 +0200
Subject: [PATCH 0654/2055] ...

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index c4e6a862ca..ba512410b5 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -66,7 +66,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         "header": {"color":"#2aa198", "font-weight":"bold", "font-style":"normal"},
         "unorderedlist": {"color":"red", "font-weight":"normal", "font-style":"normal"},
         "orderedlist": {"color":"red", "font-weight":"normal", "font-style":"normal"},
-        "blockquote": {"color":"red", "font-weight":"normal", "font-style":"normal"},
+        "blockquote": {"color":"red", "font-weight":"bold", "font-style":"normal"},
         "codespan": {"color":"#ff5800", "font-weight":"normal", "font-style":"normal"},
         "codeblock": {"color":"#ff5800", "font-weight":"normal", "font-style":"normal"},
         "line": {"color":"#2aa198", "font-weight":"normal", "font-style":"normal"},
@@ -82,7 +82,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         "header": {"color":"#2aa198", "font-weight":"bold", "font-style":"normal"},
         "unorderedlist": {"color":"yellow", "font-weight":"normal", "font-style":"normal"},
         "orderedlist": {"color":"yellow", "font-weight":"normal", "font-style":"normal"},
-        "blockquote": {"color":"yellow", "font-weight":"normal", "font-style":"normal"},
+        "blockquote": {"color":"yellow", "font-weight":"bold", "font-style":"normal"},
         "codespan": {"color":"#90ee90", "font-weight":"normal", "font-style":"normal"},
         "codeblock": {"color":"#ff9900", "font-weight":"normal", "font-style":"normal"},
         "line": {"color":"#2aa198", "font-weight":"normal", "font-style":"normal"},

From e7f25cf847287fef82eb5cf8a32491b57eaa5c72 Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Mon, 1 May 2023 13:25:05 +0100
Subject: [PATCH 0655/2055] Make model.refresh_rows() also clear the extra
 files cache for those books.

---
 src/calibre/gui2/library/models.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 9c4a51600a..c7beff58b2 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -405,6 +405,8 @@ class BooksModel(QAbstractTableModel):  # {{{
     def refresh_rows(self, rows, current_row=-1):
         self._clear_caches()
         cc = self.columnCount(QModelIndex()) - 1
+        for r in rows:
+            self.db.new_api.clear_extra_files_cache(self.db.id(r))
         for first_row, last_row in group_numbers(rows):
             self.dataChanged.emit(self.index(first_row, 0), self.index(last_row, cc))
             if current_row >= 0 and first_row <= current_row <= last_row:

From 7b1078ec2826bd0b2954fbeb7ae46403d2da83d7 Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Mon, 1 May 2023 13:34:16 +0100
Subject: [PATCH 0656/2055] Add opening the data folder to the Open action.
 Allow it to have a shortcut. Create the data folder if it doesn't exist.

---
 src/calibre/gui2/actions/open.py | 11 +++++++++--
 src/calibre/gui2/actions/view.py | 26 +++++++++++++++++++++++---
 2 files changed, 32 insertions(+), 5 deletions(-)

diff --git a/src/calibre/gui2/actions/open.py b/src/calibre/gui2/actions/open.py
index 31c0b94c00..8998e5f343 100644
--- a/src/calibre/gui2/actions/open.py
+++ b/src/calibre/gui2/actions/open.py
@@ -5,6 +5,7 @@ __license__   = 'GPL v3'
 __copyright__ = '2010, Kovid Goyal '
 __docformat__ = 'restructuredtext en'
 
+from functools import partial
 
 from calibre.gui2.actions import InterfaceAction
 
@@ -12,13 +13,19 @@ from calibre.gui2.actions import InterfaceAction
 class OpenFolderAction(InterfaceAction):
 
     name = 'Open Folder'
-    action_spec = (_('Open containing folder'), 'document_open.png',
+    action_spec = (_('Open book folder'), 'document_open.png',
                    _('Open the folder containing the current book\'s files'), _('O'))
     dont_add_to = frozenset(('context-menu-device',))
     action_type = 'current'
+    action_add_menu = True
+    action_menu_clone_qaction = _('Open book folder')
 
     def genesis(self):
-        self.qaction.triggered.connect(self.gui.iactions['View'].view_folder)
+        va = self.gui.iactions['View'].view_folder
+        self.qaction.triggered.connect(va)
+        a = self.create_menu_action(self.qaction.menu(), 'show-data-folder',
+                                _('Open book data folder'), icon='document_open.png', shortcut=tuple())
+        a.triggered.connect(partial(va, data_folder=True))
 
     def location_selected(self, loc):
         enabled = loc == 'library'
diff --git a/src/calibre/gui2/actions/view.py b/src/calibre/gui2/actions/view.py
index db28358fb3..d7cf1d4b16 100644
--- a/src/calibre/gui2/actions/view.py
+++ b/src/calibre/gui2/actions/view.py
@@ -263,7 +263,7 @@ class ViewAction(InterfaceAction):
                 'cannot be stopped until complete. Do you wish to continue?'
                 ) % num, show_copy_button=False, skip_dialog_name=skip_dialog_name)
 
-    def view_folder(self, *args):
+    def view_folder(self, *args, **kwargs):
         rows = self.gui.current_view().selectionModel().selectedRows()
         if not rows or len(rows) == 0:
             d = error_dialog(self.gui, _('Cannot open folder'),
@@ -272,12 +272,25 @@ class ViewAction(InterfaceAction):
             return
         if not self._view_check(len(rows), max_=10, skip_dialog_name='open-folder-many-check'):
             return
+        data_folder = kwargs.get('data_folder', False)
         db = self.gui.current_db
         for i, row in enumerate(rows):
             self.gui.extra_files_watcher.watch_book(db.id(row.row()))
             path = db.abspath(row.row())
             if path:
-                open_local_file(path)
+                if data_folder:
+                    path = os.path.join(path, DATA_DIR_NAME)
+                    if not os.path.exists(path):
+                        try:
+                            os.mkdir(path)
+                        except Exception as e:
+                            error_dialog(self.gui, _('Failed to create folder'), str(e), show=True)
+                            continue
+                try:
+                    open_local_file(path)
+                except Exception as e:
+                    # We shouldn't get here ...
+                    error_dialog(self.gui, _('Cannot open folder'), str(e), show=True)
                 if ismacos and i < len(rows) - 1:
                     time.sleep(0.1)  # Finder cannot handle multiple folder opens
 
@@ -289,7 +302,14 @@ class ViewAction(InterfaceAction):
     def view_data_folder_for_id(self, id_):
         self.gui.extra_files_watcher.watch_book(id_)
         path = self.gui.current_db.abspath(id_, index_is_id=True)
-        open_local_file(os.path.join(path, DATA_DIR_NAME))
+        path = os.path.join(path, DATA_DIR_NAME)
+        if not os.path.exists(path):
+            try:
+                os.mkdir(path)
+            except Exception as e:
+                error_dialog(self.gui, _('Failed to create folder'), str(e), show=True)
+                return
+        open_local_file(path)
 
     def view_book(self, triggered):
         rows = self.gui.current_view().selectionModel().selectedRows()

From 65bc7643c7e6addcf43b59554593bc43448821f4 Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Mon, 1 May 2023 19:12:47 +0100
Subject: [PATCH 0657/2055] Bug #2018227: User Categories: "Hide empty
 categories" failing for sub-categories

---
 src/calibre/gui2/tag_browser/model.py | 31 ++++++++++++++++++++++++---
 1 file changed, 28 insertions(+), 3 deletions(-)

diff --git a/src/calibre/gui2/tag_browser/model.py b/src/calibre/gui2/tag_browser/model.py
index 732535cf27..de73096f13 100644
--- a/src/calibre/gui2/tag_browser/model.py
+++ b/src/calibre/gui2/tag_browser/model.py
@@ -816,13 +816,38 @@ class TagsModel(QAbstractItemModel):  # {{{
                 process_one_node(category, collapse_model, self.db.new_api.fields['rating'].book_value_map,
                                 state_map.get(category.category_key, {}))
 
-        # Fix up the node tree, reordering as needed and deleting undisplayed nodes
+        # Fix up the node tree, reordering as needed and deleting undisplayed
+        # nodes. First, remove empty user category subnodes if needed. This is a
+        # recursive process because the hierarchical categories were combined
+        # together in process_one_node (above), which also computes the child
+        # count.
+        if self.prefs['tag_browser_hide_empty_categories']:
+            def process_uc_children(parent, depth):
+                new_children = []
+                for node in parent.children:
+                    if node.type == TagTreeItem.CATEGORY:
+                        # I could De Morgan's this but I think it is more
+                        # understandable this way
+                        if node.category_key.startswith('@') and len(node.children) == 0:
+                            pass
+                        else:
+                            new_children.append(node)
+                            process_uc_children(node, depth+1)
+                    else:
+                        new_children.append(node)
+                parent.children = new_children
+            for node in self.root_item.children:
+                if node.category_key.startswith('@'):
+                    process_uc_children(node, 1)
+
+        # Now check the standard categories and root-level user categories,
+        # removing any hidden categories and if needed, empty categories
         new_children = []
         for node in self.root_item.children:
+            if self.prefs['tag_browser_hide_empty_categories'] and len(node.child_tags()) == 0:
+                continue
             key = node.category_key
             if key in self.row_map:
-                if self.prefs['tag_browser_hide_empty_categories'] and len(node.child_tags()) == 0:
-                    continue
                 if self.hidden_categories:
                     if key in self.hidden_categories:
                         continue

From 50ee2874a5c5da4a652e10218489314d2846615b Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Mon, 1 May 2023 20:37:52 +0200
Subject: [PATCH 0658/2055] ...

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index ba512410b5..adb280a7e4 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -290,7 +290,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         found = False
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['CodeBlock'],text):
             stripped = text.lstrip()
-            if stripped[0] not in ('*','-','+','>') and not re.match('\d+\.', stripped):
+            if stripped[0] not in ('*','-','+','>') and not re.match(r'\d+\.', stripped):
                 self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['CodeBlock'])
                 found = True
         return found

From cae787ee820dac94423892b04641ae1d63747479 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Tue, 2 May 2023 07:17:56 +0530
Subject: [PATCH 0659/2055] ...

---
 src/calibre/db/cache.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py
index fd996c4b29..d2b2b1b62a 100644
--- a/src/calibre/db/cache.py
+++ b/src/calibre/db/cache.py
@@ -2402,7 +2402,7 @@ class Cache:
         Example: Assume author A has link X, author B has link Y, tag S has link
         F, and tag T has link G. If book 1 has author A and tag T,
         this method returns {'authors':{'A':'X'}, 'tags':{'T', 'G'}}
-        If book 2's author is neither A nor B and has no tags, this method returns {}
+        If book 2's author is neither A nor B and has no tags, this method returns {}.
 
         :param book_id: the book id in question.
 

From 840529072e4fb7c3ba6e81837a4f5366d72b569b Mon Sep 17 00:00:00 2001
From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com>
Date: Tue, 2 May 2023 13:07:09 +0530
Subject: [PATCH 0660/2055] website got closed

---
 recipes/chowk.recipe | 45 --------------------------------------------
 1 file changed, 45 deletions(-)
 delete mode 100644 recipes/chowk.recipe

diff --git a/recipes/chowk.recipe b/recipes/chowk.recipe
deleted file mode 100644
index eaab142724..0000000000
--- a/recipes/chowk.recipe
+++ /dev/null
@@ -1,45 +0,0 @@
-from calibre.web.feeds.news import BasicNewsRecipe
-
-
-class ChowkRecipe(BasicNewsRecipe):
-    __license__ = 'GPL v3'
-    __author__ = 'kwetal'
-    language = 'en_IN'
-    version = 1
-
-    title = u'Chowk'
-    publisher = u'chowk.com'
-    category = u'Opinion, South Asia'
-    description = u'Ideas & Identities of South Asia'
-
-    use_embedded_content = False
-    remove_empty_feeds = True
-    oldest_article = 30
-    max_articles_per_feed = 100
-
-    remove_javascript = True
-    encoding = 'utf-8'
-
-    feeds = []
-    feeds.append(('Chowk Articles', 'http://www.chowk.com/rss'))
-
-    keep_only_tags = []
-    keep_only_tags.append(dict(name='div', attrs={'id': 'content'}))
-
-    conversion_options = {'comments': description, 'tags': category, 'language': 'en',
-                          'publisher': publisher}
-
-    extra_css = '''
-                body{font-family:verdana,arial,helvetica,geneva,sans-serif;}
-                a {text-decoration: none; color: blue;}
-                div.pgtitle {font-size: x-large; font-weight: bold;}
-                div.wname, div.date {font-size: x-small; color: #696969;}
-                div.wname {margin-top: 1em;}
-                div.date {margin-bottom: 1em;}
-                div.title {font-weight: bold;}
-                '''
-
-    def print_version(self, url):
-        main, sep, id = url.rpartition('/')
-
-        return main + '/print/' + id

From baf7d04dbf28cbd4178f23db1bacb8f8d136a9f3 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Wed, 3 May 2023 21:56:51 +0530
Subject: [PATCH 0661/2055] string changes

---
 src/calibre/db/cache.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py
index d2b2b1b62a..c4fa065c01 100644
--- a/src/calibre/db/cache.py
+++ b/src/calibre/db/cache.py
@@ -2401,7 +2401,7 @@ class Cache:
 
         Example: Assume author A has link X, author B has link Y, tag S has link
         F, and tag T has link G. If book 1 has author A and tag T,
-        this method returns {'authors':{'A':'X'}, 'tags':{'T', 'G'}}
+        this method returns {'authors':{'A':'X'}, 'tags':{'T', 'G'}}.
         If book 2's author is neither A nor B and has no tags, this method returns {}.
 
         :param book_id: the book id in question.

From 4ff5af7576f9d75bbe0270289171c6f3f7224edc Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Wed, 3 May 2023 19:21:29 +0200
Subject: [PATCH 0662/2055] regex tweaks

---
 .../gui2/markdown_syntax_highlighter.py       | 24 +++++++++----------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index adb280a7e4..5bb0c5d10a 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -20,19 +20,19 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'uBold': re.compile(r'(?__)(?P.+?)(?P=delim)'),
         'uItalic': re.compile(r'(?_)(?!_)(?P([^_]{2,}?|[^_]))(?___)(?P([^_]{2,}?|[^_]))(?+[ \t]?'),
+        'Link': re.compile(r'(?+[ \t]?'),
+        'CodeBlock': re.compile('^([ ]{4,}|[ ]*\t).*'),
         'CodeSpan': re.compile(r'(?`+).+?(?P=delim)'),
         'HeaderLine': re.compile(r'(?u)^(-|=)+\s*$'),
         'HR': re.compile(r'(?u)^(\s*(\*|-|_)\s*){3,}$'),
-        'Html': re.compile(r'<.+?(?')
+        'Html': re.compile(r'(?u)')
     }
 
     key_theme_maps = {
@@ -147,7 +147,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
 
     def highlightBlockQuote(self, text, cursor, bf):
         found = False
-        mo = re.search(self.MARKDOWN_KEYS_REGEX['BlockQuote'],text)
+        mo = re.match(self.MARKDOWN_KEYS_REGEX['BlockQuote'],text)
         if mo:
             self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['BlockQuote'])
             self.offset += mo.end()
@@ -290,7 +290,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         found = False
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['CodeBlock'],text):
             stripped = text.lstrip()
-            if stripped[0] not in ('*','-','+','>') and not re.match(r'\d+\.', stripped):
+            if stripped[0] not in ('*','-','+') and not re.match(self.MARKDOWN_KEYS_REGEX['OrderedList'], stripped):
                 self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['CodeBlock'])
                 found = True
         return found

From 4c16604a9dfc429beaa50a122d71f8fab4c28652 Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Wed, 3 May 2023 19:34:55 +0200
Subject: [PATCH 0663/2055] ...

---
 src/calibre/gui2/markdown_syntax_highlighter.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index 5bb0c5d10a..c481773647 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -22,13 +22,13 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'uBoldItalic': re.compile(r'(?___)(?P([^_]{2,}?|[^_]))(?+[ \t]?'),
-        'CodeBlock': re.compile('^([ ]{4,}|[ ]*\t).*'),
+        'CodeBlock': re.compile(r'^([ ]{4,}|[ ]*\t).*'),
         'CodeSpan': re.compile(r'(?`+).+?(?P=delim)'),
         'HeaderLine': re.compile(r'(?u)^(-|=)+\s*$'),
         'HR': re.compile(r'(?u)^(\s*(\*|-|_)\s*){3,}$'),

From 074bdd33302b11c491e96c25c5e133916ffc8639 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Thu, 4 May 2023 08:22:05 +0530
Subject: [PATCH 0664/2055] Replace use of deprecated API

---
 src/calibre/utils/run_tests.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/calibre/utils/run_tests.py b/src/calibre/utils/run_tests.py
index a38815d5b2..65c432a518 100644
--- a/src/calibre/utils/run_tests.py
+++ b/src/calibre/utils/run_tests.py
@@ -53,7 +53,7 @@ class TestResult(unittest.TextTestResult):
 
 
 def find_tests_in_package(package, excludes=('main.py',)):
-    items = list(importlib.resources.contents(package))
+    items = [path.name for path in importlib.resources.files(package).iterdir()]
     suits = []
     excludes = set(excludes) | {x + 'c' for x in excludes}
     seen = set()

From cd888703b868e62b5528f5a7e37ab659164e1809 Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Thu, 4 May 2023 10:06:13 +0200
Subject: [PATCH 0665/2055] add HTML entity highlighter

---
 src/calibre/gui2/markdown_editor.py           |  2 +-
 .../gui2/markdown_syntax_highlighter.py       | 19 ++++++++++++++++---
 2 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/src/calibre/gui2/markdown_editor.py b/src/calibre/gui2/markdown_editor.py
index 37bcd43a30..6d946c746c 100644
--- a/src/calibre/gui2/markdown_editor.py
+++ b/src/calibre/gui2/markdown_editor.py
@@ -183,7 +183,7 @@ if __name__ == '__main__':
     w.setWindowFlag(Qt.WindowType.Dialog)
     w.show()
     w.markdown = '''\
-test *italic* **bold** ***bold-italic*** `code` [link](https://calibre-ebook.com) span
+normal& *italic&* **bold&** ***bold-italic*** `code` [link](https://calibre-ebook.com) span
 
 > Blockquotes
 
diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index c481773647..2220c840d0 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -32,7 +32,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'CodeSpan': re.compile(r'(?`+).+?(?P=delim)'),
         'HeaderLine': re.compile(r'(?u)^(-|=)+\s*$'),
         'HR': re.compile(r'(?u)^(\s*(\*|-|_)\s*){3,}$'),
-        'Html': re.compile(r'(?u)')
+        'Html': re.compile(r'(?u)'),
+        'Entity': re.compile(r'&([A-z]{2,7}|#\d{1,7}|#x[\dA-Fa-f]{1,6});'),
     }
 
     key_theme_maps = {
@@ -55,6 +56,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         'CodeSpan': "codespan",
         'HR': "line",
         'Html': "html",
+        'Entity': "entity",
     }
 
     light_theme =  {
@@ -70,7 +72,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         "codespan": {"color":"#ff5800", "font-weight":"normal", "font-style":"normal"},
         "codeblock": {"color":"#ff5800", "font-weight":"normal", "font-style":"normal"},
         "line": {"color":"#2aa198", "font-weight":"normal", "font-style":"normal"},
-        "html": {"color":"#c000c0", "font-weight":"normal", "font-style":"normal"}
+        "html": {"color":"#c000c0", "font-weight":"normal", "font-style":"normal"},
+        "entity": {"color":"#006496", "font-weight":"normal", "font-style":"normal"},
     }
 
     dark_theme =  {
@@ -86,7 +89,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         "codespan": {"color":"#90ee90", "font-weight":"normal", "font-style":"normal"},
         "codeblock": {"color":"#ff9900", "font-weight":"normal", "font-style":"normal"},
         "line": {"color":"#2aa198", "font-weight":"normal", "font-style":"normal"},
-        "html": {"color":"#F653A6", "font-weight":"normal", "font-style":"normal"}
+        "html": {"color":"#f653a6", "font-weight":"normal", "font-style":"normal"},
+        "entity": {"color":"#ff82ac", "font-weight":"normal", "font-style":"normal"},
     }
 
     def __init__(self, parent):
@@ -141,6 +145,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
 
         self.highlightImage(text, cursor, bf)
 
+        self.highlightEntity(text, cursor, bf)
+
         self.highlightCodeSpan(text, cursor, bf)
 
         self.highlightCodeBlock(text, cursor, bf)
@@ -295,6 +301,13 @@ class MarkdownHighlighter(QSyntaxHighlighter):
                 found = True
         return found
 
+    def highlightEntity(self, text, cursor, bf):
+        found = False
+        for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Entity'],text):
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['Entity'])
+            found = True
+        return found
+
     def highlightHtml(self, text):
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Html'], text):
             self.setFormat(mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['Html'])

From f57cd0d7a7e1dcf0f5b5db282fcd000330406af8 Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Thu, 4 May 2023 09:10:30 +0100
Subject: [PATCH 0666/2055] Enhancement #2018423: Manage Authors: Resize rows
 and add alternating row colouring.

The two dialogs share the row height data. Changing it in one changes the other.
---
 src/calibre/gui2/dialogs/edit_authors_dialog.py | 16 +++++++++++++++-
 src/calibre/gui2/dialogs/tag_list_editor.py     | 10 ++++++++++
 2 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.py b/src/calibre/gui2/dialogs/edit_authors_dialog.py
index 7f3dfb657b..0688161703 100644
--- a/src/calibre/gui2/dialogs/edit_authors_dialog.py
+++ b/src/calibre/gui2/dialogs/edit_authors_dialog.py
@@ -88,9 +88,10 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
         self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText(_('&OK'))
         self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText(_('&Cancel'))
         self.buttonBox.accepted.connect(self.accepted)
+        self.buttonBox.rejected.connect(self.rejected)
         self.apply_vl_checkbox.stateChanged.connect(self.use_vl_changed)
 
-        # Set up the heading for sorting
+        self.table.setAlternatingRowColors(True)
         self.table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
 
         self.find_aut_func = find_aut_func
@@ -112,6 +113,10 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
         hh.sectionClicked.connect(self.do_sort)
         hh.setSortIndicatorShown(True)
 
+        vh = self.table.verticalHeader()
+        vh.setDefaultSectionSize(gprefs.get('general_category_editor_row_height', vh.defaultSectionSize()))
+        vh.sectionResized.connect(self.row_height_changed)
+
         # set up the search & filter boxes
         self.find_box.initialize('manage_authors_search')
         le = self.find_box.lineEdit()
@@ -271,10 +276,16 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
             self.start_find_pos = -1
         self.table.blockSignals(False)
 
+    def row_height_changed(self, row, old, new):
+        self.table.verticalHeader().blockSignals(True)
+        self.table.verticalHeader().setDefaultSectionSize(new)
+        self.table.verticalHeader().blockSignals(False)
+
     def save_state(self):
         self.table_column_widths = []
         for c in range(0, self.table.columnCount()):
             self.table_column_widths.append(self.table.columnWidth(c))
+        gprefs['general_category_editor_row_height'] = self.table.verticalHeader().sectionSize(0)
         gprefs['manage_authors_table_widths'] = self.table_column_widths
         self.save_geometry(gprefs, 'manage_authors_dialog_geometry')
 
@@ -451,6 +462,9 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
             if orig != v:
                 self.result.append((id_, orig['name'], v['name'], v['sort'], v['link']))
 
+    def rejected(self):
+        self.save_state()
+
     def do_recalc_author_sort(self):
         with self.no_cell_changed():
             for row in range(0,self.table.rowCount()):
diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py
index 98faaa910a..74d5bc274d 100644
--- a/src/calibre/gui2/dialogs/tag_list_editor.py
+++ b/src/calibre/gui2/dialogs/tag_list_editor.py
@@ -386,6 +386,10 @@ class TagListEditor(QDialog, Ui_TagListEditor):
         hh.sectionClicked.connect(self.record_sort)
         hh.setSortIndicatorShown(True)
 
+        vh = self.table.verticalHeader()
+        vh.setDefaultSectionSize(gprefs.get('general_category_editor_row_height', vh.defaultSectionSize()))
+        vh.sectionResized.connect(self.row_height_changed)
+
         self.table.setColumnCount(4)
         for col,width in enumerate(self.table_column_widths):
             self.table.setColumnWidth(col, width)
@@ -421,6 +425,11 @@ class TagListEditor(QDialog, Ui_TagListEditor):
         self.table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
         self.table.customContextMenuRequested.connect(self.show_context_menu)
 
+    def row_height_changed(self, row, old, new):
+        self.table.verticalHeader().blockSignals(True)
+        self.table.verticalHeader().setDefaultSectionSize(new)
+        self.table.verticalHeader().blockSignals(False)
+
     def fill_in_table(self, tags, tag_to_match, ttm_is_first_letter):
         self.create_table()
 
@@ -552,6 +561,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
                 self.table.setColumnWidth(c, w)
 
     def save_geometry(self):
+        gprefs['general_category_editor_row_height'] = self.table.verticalHeader().sectionSize(0)
         gprefs['tag_list_editor_table_widths'] = self.table_column_widths
         super().save_geometry(gprefs, 'tag_list_editor_dialog_geometry')
 

From c451efbd4257103417ebf5c6f4aa71f65e45009e Mon Sep 17 00:00:00 2001
From: un-pogaz <46523284+un-pogaz@users.noreply.github.com>
Date: Thu, 4 May 2023 10:58:39 +0200
Subject: [PATCH 0667/2055] apply local bold/italic to Entity

---
 .../gui2/markdown_syntax_highlighter.py       | 27 ++++++++++++++++---
 1 file changed, 24 insertions(+), 3 deletions(-)

diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index 2220c840d0..693f43d6b9 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -73,7 +73,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         "codeblock": {"color":"#ff5800", "font-weight":"normal", "font-style":"normal"},
         "line": {"color":"#2aa198", "font-weight":"normal", "font-style":"normal"},
         "html": {"color":"#c000c0", "font-weight":"normal", "font-style":"normal"},
-        "entity": {"color":"#006496", "font-weight":"normal", "font-style":"normal"},
+        "entity": {"color":"#006496"},
     }
 
     dark_theme =  {
@@ -90,7 +90,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         "codeblock": {"color":"#ff9900", "font-weight":"normal", "font-style":"normal"},
         "line": {"color":"#2aa198", "font-weight":"normal", "font-style":"normal"},
         "html": {"color":"#f653a6", "font-weight":"normal", "font-style":"normal"},
-        "entity": {"color":"#ff82ac", "font-weight":"normal", "font-style":"normal"},
+        "entity": {"color":"#ff82ac"},
     }
 
     def __init__(self, parent):
@@ -102,6 +102,16 @@ class MarkdownHighlighter(QSyntaxHighlighter):
         self.theme = theme
         self.MARKDOWN_KWS_FORMAT = {}
 
+        for k in ['Bold', 'Italic','BoldItalic']:
+            # generate dynamically keys and theme for EntityBold, EntityItalic, EntityBoldItalic
+            t = self.key_theme_maps[k]
+            newtheme = theme['entity'].copy()
+            newtheme.update(theme[t])
+            newthemekey = 'entity'+t
+            newmapkey = 'Entity'+k
+            theme[newthemekey] = newtheme
+            self.key_theme_maps[newmapkey] = newthemekey
+
         for k,t in self.key_theme_maps.items():
             subtheme = theme[t]
             format = QTextCharFormat()
@@ -304,7 +314,18 @@ class MarkdownHighlighter(QSyntaxHighlighter):
     def highlightEntity(self, text, cursor, bf):
         found = False
         for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Entity'],text):
-            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['Entity'])
+            charformat = self.format(self.offset+ mo.start())
+            charbold = charformat.fontWeight() == QFont.Weight.Bold
+            charitalic = charformat.fontItalic()
+            if charbold and charitalic:
+                format = self.MARKDOWN_KWS_FORMAT['EntityBoldItalic']
+            elif charbold and not charitalic:
+                format = self.MARKDOWN_KWS_FORMAT['EntityBold']
+            elif not charbold and charitalic:
+                format = self.MARKDOWN_KWS_FORMAT['EntityItalic']
+            else:
+                format = self.MARKDOWN_KWS_FORMAT['Entity']
+            self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), format)
             found = True
         return found
 

From 0126be0ca4944ce8036b913e7d821e58875b6d60 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Thu, 4 May 2023 17:04:35 +0530
Subject: [PATCH 0668/2055] ...

---
 src/calibre/gui2/toc/main.py       | 2 +-
 src/calibre/gui2/tweak_book/toc.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py
index 9f422c9365..476ea6512c 100644
--- a/src/calibre/gui2/toc/main.py
+++ b/src/calibre/gui2/toc/main.py
@@ -1070,7 +1070,7 @@ class TOCEditor(QDialog):  # {{{
     def workaround_macos_mouse_with_webview_bug(self):
         # macOS is weird: https://bugs.launchpad.net/calibre/+bug/2004639
         # needed as of Qt 6.4.2
-        d = info_dialog(self, _('Loading...'), _('Loading view, please wait...'), show_copy_button=False)
+        d = info_dialog(self, _('Loading...'), _('Loading table of contents view, please wait...'), show_copy_button=False)
         QTimer.singleShot(0, d.reject)
         d.exec()
 
diff --git a/src/calibre/gui2/tweak_book/toc.py b/src/calibre/gui2/tweak_book/toc.py
index 5f085f15dd..19097a8e29 100644
--- a/src/calibre/gui2/tweak_book/toc.py
+++ b/src/calibre/gui2/tweak_book/toc.py
@@ -68,7 +68,7 @@ class TOCEditor(QDialog):
     def workaround_macos_mouse_with_webview_bug(self):
         # macOS is weird: https://bugs.launchpad.net/calibre/+bug/2004639
         # needed as of Qt 6.4.2
-        d = info_dialog(self, _('Loading...'), _('Loading view, please wait...'), show_copy_button=False)
+        d = info_dialog(self, _('Loading...'), _('Loading table of contents view, please wait...'), show_copy_button=False)
         QTimer.singleShot(0, d.reject)
         d.exec()
 

From 1cc6f9eb7ba4b686960279f0eaa19080e4833f3b Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Thu, 4 May 2023 17:05:32 +0530
Subject: [PATCH 0669/2055] string changes

---
 src/calibre/gui2/trash.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/calibre/gui2/trash.py b/src/calibre/gui2/trash.py
index 7ca44dacc3..aa210d3b6b 100644
--- a/src/calibre/gui2/trash.py
+++ b/src/calibre/gui2/trash.py
@@ -250,7 +250,7 @@ class TrashView(Dialog):
             return
         det_msg = []
         for (entry, exc, tb) in failures:
-            det_msg.append(_('Failed for {} with error:').format(entry.title))
+            det_msg.append(_('Failed for the book {} with error:').format(entry.title))
             det_msg.append(tb)
             det_msg.append('-' * 40)
             det_msg.append('')

From 4f364dc351d1cabf61d3383d10a355cd639e4166 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Thu, 4 May 2023 22:15:17 +0530
Subject: [PATCH 0670/2055] Better error when serializing catalog to XML fails

---
 src/calibre/library/catalogs/csv_xml.py | 111 ++++++++++++------------
 1 file changed, 57 insertions(+), 54 deletions(-)

diff --git a/src/calibre/library/catalogs/csv_xml.py b/src/calibre/library/catalogs/csv_xml.py
index 9e192620de..dae26bf0cd 100644
--- a/src/calibre/library/catalogs/csv_xml.py
+++ b/src/calibre/library/catalogs/csv_xml.py
@@ -177,72 +177,75 @@ class CSV_XML(CatalogPlugin):
             else:
                 root = E.calibredb()
             for r in data:
-                record = E.record()
-                root.append(record)
+                try:
+                    record = E.record()
+                    root.append(record)
 
-                for field in fields:
-                    if field.startswith('#'):
-                        val = db.get_field(r['id'], field, index_is_id=True)
-                        if not isinstance(val, str):
-                            val = str(val)
-                        item = getattr(E, field.replace('#', '_'))(val)
-                        record.append(item)
+                    for field in fields:
+                        if field.startswith('#'):
+                            val = db.get_field(r['id'], field, index_is_id=True)
+                            if not isinstance(val, str):
+                                val = str(val)
+                            item = getattr(E, field.replace('#', '_'))(val)
+                            record.append(item)
 
-                for field in ('id', 'uuid', 'publisher', 'rating', 'size',
-                              'isbn', 'ondevice', 'identifiers'):
-                    if field in fields:
-                        val = r[field]
-                        if not val:
-                            continue
-                        if not isinstance(val, (bytes, str)):
-                            if (fm.get(field, {}).get('datatype', None) ==
-                                    'rating' and val):
-                                val = '%.2g' % (val / 2)
-                            val = str(val)
-                        item = getattr(E, field)(val)
-                        record.append(item)
+                    for field in ('id', 'uuid', 'publisher', 'rating', 'size',
+                                'isbn', 'ondevice', 'identifiers'):
+                        if field in fields:
+                            val = r[field]
+                            if not val:
+                                continue
+                            if not isinstance(val, (bytes, str)):
+                                if (fm.get(field, {}).get('datatype', None) ==
+                                        'rating' and val):
+                                    val = '%.2g' % (val / 2)
+                                val = str(val)
+                            item = getattr(E, field)(val)
+                            record.append(item)
 
-                if 'title' in fields:
-                    title = E.title(r['title'], sort=r['sort'])
-                    record.append(title)
+                    if 'title' in fields:
+                        title = E.title(r['title'], sort=r['sort'])
+                        record.append(title)
 
-                if 'authors' in fields:
-                    aus = E.authors(sort=r['author_sort'])
-                    for au in r['authors']:
-                        aus.append(E.author(au))
-                    record.append(aus)
+                    if 'authors' in fields:
+                        aus = E.authors(sort=r['author_sort'])
+                        for au in r['authors']:
+                            aus.append(E.author(au))
+                        record.append(aus)
 
-                for field in ('timestamp', 'pubdate'):
-                    if field in fields:
-                        record.append(getattr(E, field)(isoformat(r[field], as_utc=False)))
+                    for field in ('timestamp', 'pubdate'):
+                        if field in fields:
+                            record.append(getattr(E, field)(isoformat(r[field], as_utc=False)))
 
-                if 'tags' in fields and r['tags']:
-                    tags = E.tags()
-                    for tag in r['tags']:
-                        tags.append(E.tag(tag))
-                    record.append(tags)
+                    if 'tags' in fields and r['tags']:
+                        tags = E.tags()
+                        for tag in r['tags']:
+                            tags.append(E.tag(tag))
+                        record.append(tags)
 
-                if 'comments' in fields and r['comments']:
-                    record.append(E.comments(r['comments']))
+                    if 'comments' in fields and r['comments']:
+                        record.append(E.comments(r['comments']))
 
-                if 'series' in fields and r['series']:
-                    record.append(E.series(r['series'],
-                        index=str(r['series_index'])))
+                    if 'series' in fields and r['series']:
+                        record.append(E.series(r['series'],
+                            index=str(r['series_index'])))
 
-                if 'languages' in fields and r['languages']:
-                    record.append(E.languages(r['languages']))
+                    if 'languages' in fields and r['languages']:
+                        record.append(E.languages(r['languages']))
 
-                if 'cover' in fields and r['cover']:
-                    record.append(E.cover(r['cover'].replace(os.sep, '/')))
+                    if 'cover' in fields and r['cover']:
+                        record.append(E.cover(r['cover'].replace(os.sep, '/')))
 
-                if 'formats' in fields and r['formats']:
-                    fmt = E.formats()
-                    for f in r['formats']:
-                        fmt.append(E.format(f.replace(os.sep, '/')))
-                    record.append(fmt)
+                    if 'formats' in fields and r['formats']:
+                        fmt = E.formats()
+                        for f in r['formats']:
+                            fmt.append(E.format(f.replace(os.sep, '/')))
+                        record.append(fmt)
 
-                if 'library_name' in fields:
-                    record.append(E.library_name(current_library))
+                    if 'library_name' in fields:
+                        record.append(E.library_name(current_library))
+                except Exception as e:
+                    raise Exception('Failed to convert {} to XML with error: {}'.format(r['title'], e)) from e
 
             with open(path_to_output, 'wb') as f:
                 f.write(etree.tostring(root, encoding='utf-8',

From 14a331526a17feb443b64633eae0720a39d6a728 Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Fri, 5 May 2023 10:26:51 +0100
Subject: [PATCH 0671/2055] Bug #2018548: NoneType error when trying to open
 category manager

---
 src/calibre/gui2/dialogs/tag_list_editor.py | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py
index 74d5bc274d..0c78e25cab 100644
--- a/src/calibre/gui2/dialogs/tag_list_editor.py
+++ b/src/calibre/gui2/dialogs/tag_list_editor.py
@@ -165,12 +165,6 @@ class TagListEditor(QDialog, Ui_TagListEditor):
         self.setWindowFlags(self.windowFlags()&(~Qt.WindowType.WindowContextHelpButtonHint))
         self.setWindowIcon(icon)
 
-        # Get saved geometry info
-        try:
-            self.table_column_widths = gprefs.get('tag_list_editor_table_widths', None)
-        except:
-            pass
-
         # initialization
         self.to_rename = {}
         self.to_delete = set()
@@ -370,6 +364,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
         # I'm not sure if this is standard Qt behavior or behavior triggered by
         # something in this class, but replacing the table fixes it.
         if self.table is not None:
+            self.save_geometry()
             self.gridlayout.removeWidget(self.table)
             sip.delete(self.table)
         self.table = TleTableWidget(self)
@@ -391,8 +386,6 @@ class TagListEditor(QDialog, Ui_TagListEditor):
         vh.sectionResized.connect(self.row_height_changed)
 
         self.table.setColumnCount(4)
-        for col,width in enumerate(self.table_column_widths):
-            self.table.setColumnWidth(col, width)
 
         self.edit_delegate = EditColumnDelegate(self.table, self.check_for_deleted_items)
         self.edit_delegate.editing_finished.connect(self.stop_editing)
@@ -420,7 +413,12 @@ class TagListEditor(QDialog, Ui_TagListEditor):
                 self.not_found_label_timer_event, type=Qt.ConnectionType.QueuedConnection)
 
         self.table.setEditTriggers(QAbstractItemView.EditTrigger.EditKeyPressed)
+
         self.restore_geometry(gprefs, 'tag_list_editor_dialog_geometry')
+        self.table_column_widths = gprefs.get('tag_list_editor_table_widths', None)
+        if self.table_column_widths is not None:
+            for col,width in enumerate(self.table_column_widths):
+                self.table.setColumnWidth(col, width)
 
         self.table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
         self.table.customContextMenuRequested.connect(self.show_context_menu)
@@ -541,7 +539,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
     def do_filter(self):
         self.fill_in_table(None, None, False)
 
-    def table_column_resized(self, col, old, new):
+    def table_column_resized(self, *args):
         self.table_column_widths = []
         for c in range(0, self.table.columnCount()):
             self.table_column_widths.append(self.table.columnWidth(c))

From a5969493e12f626d2f08254f3ec5c75137044c8e Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Fri, 5 May 2023 20:47:40 +0530
Subject: [PATCH 0672/2055] A couple more feeds for Irish Independent

---
 recipes/irish_independent.recipe | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/recipes/irish_independent.recipe b/recipes/irish_independent.recipe
index 681e62646b..105260fd02 100644
--- a/recipes/irish_independent.recipe
+++ b/recipes/irish_independent.recipe
@@ -25,7 +25,9 @@ class IrishIndependent(BasicNewsRecipe):
     ]
 
     feeds = [
+        ('Frontpage News', 'http://www.independent.ie/rss'),
         ('News', 'http://www.independent.ie/rss'),
+        ('World News', 'http://www.independent.ie/world-news/rss'),
         ('Opinion', 'http://www.independent.ie/opinion/rss'),
         ('Business', 'http://www.independent.ie/business/rss'),
         ('Sport', 'http://www.independent.ie/sport/rss'),

From dbc9e64fb38dac5bb78a549cfea5d53ea1fb9cb7 Mon Sep 17 00:00:00 2001
From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com>
Date: Fri, 5 May 2023 23:12:12 +0530
Subject: [PATCH 0673/2055] Update irish_times.recipe

---
 recipes/irish_times.recipe | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/recipes/irish_times.recipe b/recipes/irish_times.recipe
index 3c3c97d0c1..5574f975b9 100644
--- a/recipes/irish_times.recipe
+++ b/recipes/irish_times.recipe
@@ -32,12 +32,11 @@ class IrishTimes(BasicNewsRecipe):
     no_stylesheets = True
     temp_files = []
     keep_only_tags = [
-        dict(name=['h1', 'h2']),
-        classes('lead-art-wrapper article-body-wrapper byline-text'),
+        classes('custom-headline custom-subheadline lead-art-wrapper article-body-wrapper byline-text'),
     ]
     remove_tags = [
         dict(name='button'),
-        classes('sm-promo-headline top-table-list-container'),
+        classes('sm-promo-headline top-table-list-container single-divider interstitial-link'),
     ]
     remove_attributes = ['width', 'height']
 

From 303a43fc9ba816958f53d23c1073a93d997e9ea8 Mon Sep 17 00:00:00 2001
From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com>
Date: Fri, 5 May 2023 23:24:37 +0530
Subject: [PATCH 0674/2055] Update irish_independent.recipe

removed duplicate feed and articles.
---
 recipes/irish_independent.recipe | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/recipes/irish_independent.recipe b/recipes/irish_independent.recipe
index 105260fd02..902beff249 100644
--- a/recipes/irish_independent.recipe
+++ b/recipes/irish_independent.recipe
@@ -15,6 +15,7 @@ class IrishIndependent(BasicNewsRecipe):
     oldest_article = 2
     max_articles_per_feed = 100
     no_stylesheets = True
+    ignore_duplicate_articles = {'url'}
 
     keep_only_tags = [
         dict(name='div', attrs={'class':lambda x: x and '_contentwrapper' in x})
@@ -26,7 +27,6 @@ class IrishIndependent(BasicNewsRecipe):
 
     feeds = [
         ('Frontpage News', 'http://www.independent.ie/rss'),
-        ('News', 'http://www.independent.ie/rss'),
         ('World News', 'http://www.independent.ie/world-news/rss'),
         ('Opinion', 'http://www.independent.ie/opinion/rss'),
         ('Business', 'http://www.independent.ie/business/rss'),

From cc7e2d76625c278dc981fb8c1d87e4c3ca9064bf Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Sat, 6 May 2023 10:51:25 +0100
Subject: [PATCH 0675/2055] Bug #2018630: Menu item duplication glitch

---
 src/calibre/gui2/library/views.py | 32 ++++++++++++++++++++-----------
 1 file changed, 21 insertions(+), 11 deletions(-)

diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py
index 31598cd3e5..0c40fa3bd4 100644
--- a/src/calibre/gui2/library/views.py
+++ b/src/calibre/gui2/library/views.py
@@ -680,23 +680,33 @@ class BooksView(QTableView):  # {{{
             view.column_header_context_menu = self.create_context_menu(col, name, view)
         has_context_menu = hasattr(view, 'column_header_context_menu')
         if self.is_library_view and has_context_menu:
-            view.column_header_context_menu.addSeparator()
-            if not hasattr(view.column_header_context_menu, 'bl_split_action'):
-                view.column_header_context_menu.bl_split_action = view.column_header_context_menu.addAction(
-                        QIcon.ic('split.png'), 'xxx', partial(self.column_header_context_handler, action='split', column='title'))
-            ac = view.column_header_context_menu.bl_split_action
+            m = view.column_header_context_menu
+            m.addSeparator()
+            if not hasattr(m, 'bl_split_action'):
+                m.bl_split_action = m.addAction(QIcon.ic('split.png'), 'xxx',
+                            partial(self.column_header_context_handler, action='split', column='title'))
+            ac = m.bl_split_action
             if self.pin_view.isVisible():
                 ac.setText(_('Un-split the book list'))
             else:
                 ac.setText(_('Split the book list'))
+                # QIcon.ic('drm-locked.png'),
+            if not hasattr(m, 'column_mouse_move_action'):
+                m.column_mouse_move_action = m.addAction('xxx')
+
+            def set_action_attributes(icon, text, slot):
+                m.column_mouse_move_action.setText(text)
+                m.column_mouse_move_action.setIcon(icon)
+                m.column_mouse_move_action.triggered.connect(slot)
+
             if view.column_header.sectionsMovable():
-                view.column_header_context_menu.addAction(
-                        QIcon.ic('drm-locked.png'), _("Don't allow moving columns with the mouse"),
-                        partial(self.column_header_context_handler, action='lock'))
+                set_action_attributes(QIcon.ic('drm-locked.png'),
+                                      _("Don't allow moving columns with the mouse"),
+                                      partial(self.column_header_context_handler, action='lock'))
             else:
-                view.column_header_context_menu.addAction(
-                        QIcon.ic('drm-unlocked.png'), _("Allow moving columns with the mouse"),
-                        partial(self.column_header_context_handler, action='unlock'))
+                set_action_attributes(QIcon.ic('drm-unlocked.png'),
+                                      _("Allow moving columns with the mouse"),
+                                      partial(self.column_header_context_handler, action='unlock'))
         if has_context_menu:
             view.column_header_context_menu.popup(view.column_header.mapToGlobal(pos))
     # }}}

From bbd11ff153734c5cbf96900878c3ebee3e480852 Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Sat, 6 May 2023 12:20:23 +0100
Subject: [PATCH 0676/2055] Enhancement: add preference (in Search) to disable
 QTableView keyboardSearch().

---
 src/calibre/gui2/__init__.py           |  1 +
 src/calibre/gui2/library/views.py      |  4 ++++
 src/calibre/gui2/preferences/search.py |  1 +
 src/calibre/gui2/preferences/search.ui | 14 ++++++++++++++
 4 files changed, 20 insertions(+)

diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index 768312bd1b..89754a0245 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -398,6 +398,7 @@ def create_defs():
     # JSON dumps converts integer keys to strings, so do it explicitly
     defs['tb_search_order'] = {'0': 1, '1': 2, '2': 3, '3': 4, '4': 0}
     defs['search_tool_bar_shows_text'] = True
+    defs['allow_keyboard_search_in_library_views'] = True
 
     def migrate_tweak(tweak_name, pref_name):
         # If the tweak has been changed then leave the tweak in the file so
diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py
index 0c40fa3bd4..4199a044f9 100644
--- a/src/calibre/gui2/library/views.py
+++ b/src/calibre/gui2/library/views.py
@@ -756,6 +756,10 @@ class BooksView(QTableView):  # {{{
         gprefs[pname] = previous
         self.sort_by_named_field(field, previous[field])
 
+    def keyboardSearch(self, search):
+        if gprefs.get('allow_keyboard_search_in_library_views', True):
+            super().keyboardSearch(search)
+
     def sort_by_named_field(self, field, order, reset=True):
         if isinstance(order, Qt.SortOrder):
             order = order == Qt.SortOrder.AscendingOrder
diff --git a/src/calibre/gui2/preferences/search.py b/src/calibre/gui2/preferences/search.py
index 2899861a2a..961c7cc1a6 100644
--- a/src/calibre/gui2/preferences/search.py
+++ b/src/calibre/gui2/preferences/search.py
@@ -33,6 +33,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
         r('limit_search_columns', prefs)
         r('use_primary_find_in_search', prefs)
         r('search_tool_bar_shows_text', gprefs)
+        r('allow_keyboard_search_in_library_views', gprefs)
         ossm = self.opt_saved_search_menu_is_hierarchical
         ossm.setChecked('search' in db.new_api.pref('categories_using_hierarchy', []))
         ossm.stateChanged.connect(self.changed_signal)
diff --git a/src/calibre/gui2/preferences/search.ui b/src/calibre/gui2/preferences/search.ui
index a646297174..e2f6bd99db 100644
--- a/src/calibre/gui2/preferences/search.ui
+++ b/src/calibre/gui2/preferences/search.ui
@@ -160,6 +160,20 @@
          
         
        
+       
+        
+         
+          Use keyboard searching in the book list
+         
+         
+          <p>If enabled then pressing a key in the library view
+                  will search for the next cell in the current column where the
+                  value starts with the letter pressed. If there is no such cell
+                  then nothing happens. If the letter is a shortcut then the
+                  shortcut takes precedence.</p>
+         
+        
+       
       
      
      

From 012b01c9a3c6fe90229c87dcb3ea12a86fa14ecd Mon Sep 17 00:00:00 2001
From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com>
Date: Sun, 7 May 2023 06:17:57 +0530
Subject: [PATCH 0677/2055] Update irish_independent.recipe

---
 recipes/irish_independent.recipe | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/recipes/irish_independent.recipe b/recipes/irish_independent.recipe
index 902beff249..f82aee6f55 100644
--- a/recipes/irish_independent.recipe
+++ b/recipes/irish_independent.recipe
@@ -22,7 +22,8 @@ class IrishIndependent(BasicNewsRecipe):
     ]
 
     remove_tags = [
-        dict(name='div', attrs={'data-testid':['article-share', 'embed-video']})
+        dict(name=['svg', 'button']),
+        dict(name='div', attrs={'data-testid':['article-share', 'embed-video', 'inline-related-wrapper']})
     ]
 
     feeds = [

From 8732f9da0cc7892c3766cca83555be030e0246b3 Mon Sep 17 00:00:00 2001
From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com>
Date: Sun, 7 May 2023 06:28:48 +0530
Subject: [PATCH 0678/2055] Create irish_times_free.recipe

---
 recipes/irish_times_free.recipe | 80 +++++++++++++++++++++++++++++++++
 1 file changed, 80 insertions(+)
 create mode 100644 recipes/irish_times_free.recipe

diff --git a/recipes/irish_times_free.recipe b/recipes/irish_times_free.recipe
new file mode 100644
index 0000000000..7e150dfbef
--- /dev/null
+++ b/recipes/irish_times_free.recipe
@@ -0,0 +1,80 @@
+from calibre.web.feeds.news import BasicNewsRecipe, classes
+from calibre.ptempfile import PersistentTemporaryFile
+
+class IrishTimes(BasicNewsRecipe):
+    title          = 'The Irish Times (free)'
+    __author__    = 'unkn0wn'
+    description = 'Daily news from The Irish Times'
+    language = 'en_IE'
+
+    masthead_url = 'http://www.irishtimes.com/assets/images/generic/website/logo_theirishtimes.png'
+
+    encoding = 'utf-8'
+    max_articles_per_feed = 50
+    remove_empty_feeds = True
+    no_stylesheets = True
+
+    keep_only_tags = [
+        classes('custom-headline custom-subheadline lead-art-wrapper article-body-wrapper byline-text'),
+    ]
+    remove_tags = [
+        dict(name=['button', 'svg']),
+        classes('sm-promo-headline top-table-list-container single-divider interstitial-link'),
+    ]
+
+    remove_attributes = ['width', 'height']
+    ignore_duplicate_articles = {'title'}
+    resolve_internal_links  = True
+    articles_are_obfuscated = True
+
+    def get_cover_url(self):
+        from datetime import date
+        cover = 'https://img.kiosko.net/' + date.today().strftime('%Y/%m/%d') + '/ie/irish_times.750.jpg'
+        br = BasicNewsRecipe.get_browser(self, verify_ssl_certificates=False) 
+        try:
+            br.open(cover)
+        except:
+            index = 'https://en.kiosko.net/ie/np/irish_times.html'
+            soup = self.index_to_soup(index)
+            for image in soup.find('img', attrs={'src': lambda x: x and x.endswith('750.jpg')}):
+                if image['src'].startswith('/'):
+                    return 'https:' + image['src']
+                return image['src']
+            self.log("\nCover unavailable")
+            cover = None
+        return cover
+
+    def get_obfuscated_article(self, url):
+        br = self.get_browser()
+        try:
+            br.open(url)
+        except Exception as e:
+            url = e.hdrs.get('location')
+        soup = self.index_to_soup(url)
+        link = soup.find('a', href=True)
+        skip_sections =[ # add sections you want to skip
+            '/video/', '/videos/', '/media/', '/podcast'
+        ]
+        if any(x in link['href'] for x in skip_sections):
+            self.log('Aborting Article', link['href'])
+            self.abort_article('skipping video links')
+
+        self.log('Found', link['href'])
+        html = br.open(link['href']).read()
+        pt = PersistentTemporaryFile('.html')
+        pt.write(html)
+        pt.close()
+        return pt.name
+
+    feeds = []
+
+    sections = [
+        'ireland', 'world', 'opinion', 'politics', 'crime-law', 'culture', 'business', 
+        'life-style', 'health', 'sport', 'property', 'food', 'abroad', 'environment', 
+        'obituaries'
+    ]
+
+    for sec in sections:
+        a = 'https://news.google.com/rss/search?q=when:27h+allinurl:irishtimes.com{}&hl=en-IE&gl=IE&ceid=IE:en'
+        feeds.append((sec.capitalize(), a.format('%2F' + sec + '%2F')))
+    feeds.append(('Others', a.format('')))

From dc9d99ba31f799a38abe8bf164425b8b01229cf7 Mon Sep 17 00:00:00 2001
From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com>
Date: Sun, 7 May 2023 13:11:43 +0530
Subject: [PATCH 0679/2055] Fix SSL verification error for covers

Fix SSL verification error for covers being fetched from kiosko.net
---
 recipes/boston_globe_print_edition.recipe    | 2 +-
 recipes/eenadu_ap.recipe                     | 2 +-
 recipes/financial_times.recipe               | 2 +-
 recipes/financial_times_print_edition.recipe | 2 +-
 recipes/scmp.recipe                          | 2 +-
 5 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/recipes/boston_globe_print_edition.recipe b/recipes/boston_globe_print_edition.recipe
index 86aa99bcf5..a311f2b781 100644
--- a/recipes/boston_globe_print_edition.recipe
+++ b/recipes/boston_globe_print_edition.recipe
@@ -59,7 +59,7 @@ class BostonGlobePrint(BasicNewsRecipe):
             date.today().year
         ) + '/' + date.today().strftime('%m') + '/' + date.today(
         ).strftime('%d') + '/us/boston_globe.750.jpg'
-        br = BasicNewsRecipe.get_browser(self)
+        br = BasicNewsRecipe.get_browser(self, verify_ssl_certificates=False)
         try:
             br.open(cover)
         except:
diff --git a/recipes/eenadu_ap.recipe b/recipes/eenadu_ap.recipe
index c204b4e71c..657ccb4ed0 100644
--- a/recipes/eenadu_ap.recipe
+++ b/recipes/eenadu_ap.recipe
@@ -47,7 +47,7 @@ class eenadu_ap(BasicNewsRecipe):
             date.today().year
         ) + '/' + date.today().strftime('%m') + '/' + date.today(
         ).strftime('%d') + '/in/eenadu.750.jpg'
-        br = BasicNewsRecipe.get_browser(self)
+        br = BasicNewsRecipe.get_browser(self, verify_ssl_certificates=False)
         try:
             br.open(cover)
         except:
diff --git a/recipes/financial_times.recipe b/recipes/financial_times.recipe
index 66e71b8f77..7a2bbe0dc4 100644
--- a/recipes/financial_times.recipe
+++ b/recipes/financial_times.recipe
@@ -46,7 +46,7 @@ class ft(BasicNewsRecipe):
             date.today().year
         ) + '/' + date.today().strftime('%m') + '/' + date.today(
         ).strftime('%d') + '/uk/ft_uk.750.jpg'
-        br = BasicNewsRecipe.get_browser(self)
+        br = BasicNewsRecipe.get_browser(self, verify_ssl_certificates=False)
         try:
             br.open(cover)
         except:
diff --git a/recipes/financial_times_print_edition.recipe b/recipes/financial_times_print_edition.recipe
index 8a2e4440fb..223edd4166 100644
--- a/recipes/financial_times_print_edition.recipe
+++ b/recipes/financial_times_print_edition.recipe
@@ -44,7 +44,7 @@ class ft(BasicNewsRecipe):
             date.today().year
         ) + '/' + date.today().strftime('%m') + '/' + date.today(
         ).strftime('%d') + '/uk/ft_uk.750.jpg'
-        br = BasicNewsRecipe.get_browser(self)
+        br = BasicNewsRecipe.get_browser(self, verify_ssl_certificates=False)
         try:
             br.open(cover)
         except:
diff --git a/recipes/scmp.recipe b/recipes/scmp.recipe
index e41c669545..62738beea8 100644
--- a/recipes/scmp.recipe
+++ b/recipes/scmp.recipe
@@ -80,7 +80,7 @@ class SCMP(BasicNewsRecipe):
             date.today().year
         ) + '/' + date.today().strftime('%m') + '/' + date.today(
         ).strftime('%d') + '/cn/scmp.750.jpg'
-        br = BasicNewsRecipe.get_browser(self)
+        br = BasicNewsRecipe.get_browser(self, verify_ssl_certificates=False)
         try:
             br.open(cover)
         except:

From 1fd1d1cdefaa93bb815083a311697631c062d3ec Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Sun, 7 May 2023 14:56:54 +0530
Subject: [PATCH 0680/2055] Fix a regression in previous release that broke
 scrolling when using the scroll_per_row tweak. Fixes #2018660 [Opening the
 Edit Metadata dialog causes the book list to scroll down too
 much](https://bugs.launchpad.net/calibre/+bug/2018660)

---
 src/calibre/gui2/library/views.py | 31 +++++++++++++++++++++++++------
 1 file changed, 25 insertions(+), 6 deletions(-)

diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py
index 4199a044f9..aabef4bb24 100644
--- a/src/calibre/gui2/library/views.py
+++ b/src/calibre/gui2/library/views.py
@@ -1319,19 +1319,38 @@ class BooksView(QTableView):  # {{{
             vertical_offset = vh.offset()
             vertical_position = vh.sectionPosition(row)
             cell_height = vh.sectionSize(row)
-
             pos = 'center'
             if vertical_position - vertical_offset < 0 or cell_height > viewport_height:
                 pos = 'top'
             elif vertical_position - vertical_offset + cell_height > viewport_height:
                 pos = 'bottom'
             vsb = self.verticalScrollBar()
-            if pos == 'top':
-                vsb.setValue(vertical_position)
-            elif pos == 'bottom':
-                vsb.setValue(vertical_position - viewport_height + cell_height)
+
+            if self.verticalScrollMode() == QAbstractItemView.ScrollMode.ScrollPerPixel:
+                if pos == 'top':
+                    vsb.setValue(vertical_position)
+                elif pos == 'bottom':
+                    vsb.setValue(vertical_position - viewport_height + cell_height)
+                else:
+                    vsb.setValue(vertical_position - ((viewport_height - cell_height) // 2))
             else:
-                vsb.setValue(vertical_position - ((viewport_height - cell_height) // 2))
+                vertical_index = vh.visualIndex(row)
+                if pos in ('bottom', 'center'):
+                    h = (viewport_height // 2) if pos == 'center' else viewport_height
+                    y = cell_height
+                    while vertical_index > 0:
+                        r = vh.logicalIndex(vertical_index - 1)
+                        y += vh.sectionSize(r)
+                        if y > h:
+                            break
+                        vertical_index -= 1
+                hidden_sections = 0
+                if vh.sectionsHidden():
+                    for s in range(vertical_index - 1, -1, -1):
+                        r = vh.logicalIndex(s)
+                        if vh.isSectionHidden(row):
+                            hidden_sections += 1
+                vsb.setValue(vertical_index - hidden_sections)
 
     @property
     def current_book(self):

From b2d0fb5cf756d25126f3f56249f6e857b9d13433 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Sun, 7 May 2023 15:48:53 +0530
Subject: [PATCH 0681/2055] ...

---
 src/calibre/gui2/library/views.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py
index aabef4bb24..daef65d54c 100644
--- a/src/calibre/gui2/library/views.py
+++ b/src/calibre/gui2/library/views.py
@@ -1348,7 +1348,7 @@ class BooksView(QTableView):  # {{{
                 if vh.sectionsHidden():
                     for s in range(vertical_index - 1, -1, -1):
                         r = vh.logicalIndex(s)
-                        if vh.isSectionHidden(row):
+                        if vh.isSectionHidden(r):
                             hidden_sections += 1
                 vsb.setValue(vertical_index - hidden_sections)
 

From cd085fc844bcbf52e8a0ac416b5d3f9bfbbc0702 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Sun, 7 May 2023 15:51:53 +0530
Subject: [PATCH 0682/2055] Cleanup code

---
 src/calibre/gui2/library/views.py | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py
index daef65d54c..db6dad443a 100644
--- a/src/calibre/gui2/library/views.py
+++ b/src/calibre/gui2/library/views.py
@@ -1339,18 +1339,15 @@ class BooksView(QTableView):  # {{{
                     h = (viewport_height // 2) if pos == 'center' else viewport_height
                     y = cell_height
                     while vertical_index > 0:
-                        r = vh.logicalIndex(vertical_index - 1)
-                        y += vh.sectionSize(r)
+                        y += vh.sectionSize(vh.logicalIndex(vertical_index -1))
                         if y > h:
                             break
                         vertical_index -= 1
-                hidden_sections = 0
                 if vh.sectionsHidden():
                     for s in range(vertical_index - 1, -1, -1):
-                        r = vh.logicalIndex(s)
-                        if vh.isSectionHidden(r):
-                            hidden_sections += 1
-                vsb.setValue(vertical_index - hidden_sections)
+                        if vh.isSectionHidden(vh.logicalIndex(s)):
+                            vertical_index -= 1
+                vsb.setValue(vertical_index)
 
     @property
     def current_book(self):

From 750f77ebac065a78173d6bd7b08449c9344b0bae Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Sun, 7 May 2023 17:50:22 +0100
Subject: [PATCH 0683/2055] Improve the name and tooltip of the preference for
 disabling editing composites in the booklist other than with F2

---
 src/calibre/gui2/preferences/look_feel.ui | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui
index fa0609bb6f..e64bcd9cb7 100644
--- a/src/calibre/gui2/preferences/look_feel.ui
+++ b/src/calibre/gui2/preferences/look_feel.ui
@@ -1096,12 +1096,12 @@ will be elided, otherwise they will be word wrapped.
            
             
              
-              <p>Check this box to disable editing the template for
-a "Column built from other columns" in the book list when the column is entered
-using the Tab key. The F2 (Edit) key will still open the template editor.</p>
+              <p>Check this box to allow only the F2 (Edit) key to
+open the template editor in the book list for a "Column built from other
+columns". Editing with mouse clicks and the Tab key will be disabled.</p>
              
              
-              &Tab key doesn't edit column templates in the book list
+              Only the &F2 (Edit) key edits column templates in the book list
              
             
            

From 9507713ae3e28d02636d2b148c27d24c3fde6386 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Mon, 8 May 2023 16:51:57 +0530
Subject: [PATCH 0684/2055] ...

---
 src/calibre/customize/conversion.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/calibre/customize/conversion.py b/src/calibre/customize/conversion.py
index 05dc02404a..aa2dd48b9e 100644
--- a/src/calibre/customize/conversion.py
+++ b/src/calibre/customize/conversion.py
@@ -350,7 +350,7 @@ class OutputFormatPlugin(Plugin):
 
     def specialize_css_for_output(self, log, opts, item, stylizer):
         '''
-        Can be used to make changes to the css during the CSS flattening
+        Can be used to make changes to the CSS during the CSS flattening
         process.
 
         :param item: The item (HTML file) being processed

From 63101aa07a74dbea79f441b901e724da6c944f8a Mon Sep 17 00:00:00 2001
From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com>
Date: Mon, 8 May 2023 20:43:01 +0530
Subject: [PATCH 0685/2055] Update wash_post_print.recipe

---
 recipes/wash_post_print.recipe | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/recipes/wash_post_print.recipe b/recipes/wash_post_print.recipe
index 83c4fa0cee..bf033691b3 100644
--- a/recipes/wash_post_print.recipe
+++ b/recipes/wash_post_print.recipe
@@ -37,23 +37,23 @@ class wapoprint(BasicNewsRecipe):
 
     def parse_index(self):
         soup = self.index_to_soup('https://www.washingtonpost.com/todays_paper/updates/')
-        if cover := soup.find('div', attrs={'class':lambda x: x and 'todays-content-image' in x.split()}):
-            self.cover_url  = cover.img['src']
-        main = soup.find('div', attrs={'id':'todays-paper'})
+        if img := soup.find('img', attrs={'src':lambda x: x and x.endswith('_FrontPage.png')}):
+            self.cover_url  = img['src']
 
         feeds = []
 
-        for div in main.findAll('div', attrs={'class': lambda x: x and 'todays-content' in x.split()}):
-            h2 = div.find('p', attrs={'class':lambda x: x and 'heading2' in x.split()})
-            secname = self.tag_to_string(h2)
+        for div in soup.findAll('section', attrs={'id': True}):
+            secname = self.tag_to_string(div.find('label')).strip()
             self.log(secname)
             articles = []
-            for a in div.findAll('a', href=True, attrs={'class':'headline'}):
+            for a in div.findAll('a', href=True):
                 url = a['href']
-                title = self.tag_to_string(a)
-                articles.append({'title': title, 'url': url})
+                title = self.tag_to_string(a).strip()
+                if not title or not url:
+                    continue
                 self.log('\t', title)
                 self.log('\t\t', url)
+                articles.append({'title': title, 'url': url})
             if articles:
                 feeds.append((secname, articles))
         return feeds

From 49ff2bde2d697f500a3ae0b0043bf18758937c91 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Mon, 8 May 2023 22:50:55 +0530
Subject: [PATCH 0686/2055] Update NYTimes

---
 recipes/nytimes.recipe     | 22 ++++++++++++++++++++++
 recipes/nytimes_sub.recipe | 22 ++++++++++++++++++++++
 2 files changed, 44 insertions(+)

diff --git a/recipes/nytimes.recipe b/recipes/nytimes.recipe
index be947f6591..0f9c866e4d 100644
--- a/recipes/nytimes.recipe
+++ b/recipes/nytimes.recipe
@@ -197,6 +197,28 @@ class NewYorkTimes(BasicNewsRecipe):
     def parse_article_group(self, container):
         for li in container.findAll('li'):
             article = li.find('article')
+            if article is None:
+                a = li.find('a', href=True)
+                if a is not None:
+                    title = self.tag_to_string(li.find('h3'))
+                    url = a['href']
+                    if url.startswith('/'):
+                        url = 'https://www.nytimes.com' + url
+                    desc = ''
+                    p = li.find('p')
+                    if p is not None:
+                        desc = self.tag_to_string(p)
+                    date = ''
+                    d = date_from_url(url)
+                    if d is not None:
+                        date = format_date(d)
+                        today = datetime.date.today()
+                        delta = today - d
+                        if delta.days > oldest_web_edition_article:
+                            self.log.debug('\tSkipping article', title, 'as it is too old')
+                            continue
+                    yield {'title': title, 'url': url, 'description': desc, 'date': date}
+                continue
             h2 = article.find('h2')
             if h2 is not None:
                 title = self.tag_to_string(h2)
diff --git a/recipes/nytimes_sub.recipe b/recipes/nytimes_sub.recipe
index 4f26842323..5db1a02a57 100644
--- a/recipes/nytimes_sub.recipe
+++ b/recipes/nytimes_sub.recipe
@@ -197,6 +197,28 @@ class NewYorkTimes(BasicNewsRecipe):
     def parse_article_group(self, container):
         for li in container.findAll('li'):
             article = li.find('article')
+            if article is None:
+                a = li.find('a', href=True)
+                if a is not None:
+                    title = self.tag_to_string(li.find('h3'))
+                    url = a['href']
+                    if url.startswith('/'):
+                        url = 'https://www.nytimes.com' + url
+                    desc = ''
+                    p = li.find('p')
+                    if p is not None:
+                        desc = self.tag_to_string(p)
+                    date = ''
+                    d = date_from_url(url)
+                    if d is not None:
+                        date = format_date(d)
+                        today = datetime.date.today()
+                        delta = today - d
+                        if delta.days > oldest_web_edition_article:
+                            self.log.debug('\tSkipping article', title, 'as it is too old')
+                            continue
+                    yield {'title': title, 'url': url, 'description': desc, 'date': date}
+                continue
             h2 = article.find('h2')
             if h2 is not None:
                 title = self.tag_to_string(h2)

From dcd5978707379c1ca292a778b4f5c0cbe0ac126e Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Tue, 9 May 2023 10:29:12 +0530
Subject: [PATCH 0687/2055] ...

---
 src/calibre/gui2/book_details.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py
index 71f44ffbe1..947bf1b48c 100644
--- a/src/calibre/gui2/book_details.py
+++ b/src/calibre/gui2/book_details.py
@@ -99,7 +99,7 @@ def copy_all(text_browser):
     simplified_html = etree.tostring(root, encoding='unicode')
     txt = html2text(simplified_html, single_line_break=True).strip()
     if iswindows:
-        txt = '\r\n'.join(txt.splitlines())
+        txt = os.linesep.join(txt.splitlines())
     # print(simplified_html)
     # print(txt)
     md.setText(txt)

From 5993dca2f27d9a6746523eaca7f416aa9bb89c9b Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Tue, 9 May 2023 16:32:18 +0530
Subject: [PATCH 0688/2055] Book details: Fix formatting of text when copying
 all book details in narrow mode

---
 src/calibre/gui2/book_details.py | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py
index 947bf1b48c..ebcf474fa9 100644
--- a/src/calibre/gui2/book_details.py
+++ b/src/calibre/gui2/book_details.py
@@ -84,15 +84,22 @@ def copy_all(text_browser):
     from html5_parser import parse
     from lxml import etree
     root = parse(html)
-    for x in ('table', 'tr', 'tbody'):
-        for tag in root.iterdescendants(x):
-            tag.tag = 'div'
-    for tag in root.iterdescendants('td'):
+    tables = tuple(root.iterdescendants('table'))
+    for tag in root.iterdescendants(('table', 'tr', 'tbody')):
+        tag.tag = 'div'
+    parent = root
+    is_vertical = getattr(text_browser, 'vertical', True)
+    if not is_vertical:
+        parent = tables[1]
+    for tag in parent.iterdescendants('td'):
         tt = etree.tostring(tag, method='text', encoding='unicode')
         tag.tag = 'span'
         for child in tuple(tag):
             tag.remove(child)
         tag.text = tt.strip()
+    if not is_vertical:
+        for tag in root.iterdescendants('td'):
+            tag.tag = 'div'
     for tag in root.iterdescendants('a'):
         tag.attrib.pop('href', None)
     from calibre.utils.html2text import html2text

From 942bceaa61365a5af2b52a388f2633af68efe265 Mon Sep 17 00:00:00 2001
From: Charles Haley 
Date: Tue, 9 May 2023 18:29:30 +0100
Subject: [PATCH 0689/2055] Improvements to template function
 identifier_in_list()

---
 manual/template_lang.rst                 |  1 +
 src/calibre/utils/formatter_functions.py | 48 +++++++++++++++---------
 2 files changed, 31 insertions(+), 18 deletions(-)

diff --git a/manual/template_lang.rst b/manual/template_lang.rst
index c11df09e88..7a376818f4 100644
--- a/manual/template_lang.rst
+++ b/manual/template_lang.rst
@@ -558,6 +558,7 @@ In `GPM` the functions described in `Single Function Mode` all require an additi
 * ``fractional_part(x)`` -- returns the value after the decimal point. For example, ``fractional_part(3.14)`` returns ``0.14``. Throws an exception if ``x`` is not a number.
 * ``has_cover()`` -- return ``'Yes'`` if the book has a cover, otherwise the empty string.
 * ``has_extra_files([pattern])`` -- returns the count of extra files, otherwise '' (the empty string). If the optional parameter ``pattern`` (a regular expression) is supplied then the list is filtered to files that match ``pattern`` before the files are counted. The pattern match is case insensitive. See also the functions ``extra_file_names()``, ``extra_file_size()`` and ``extra_file_modtime()``. This function can be used only in the GUI.
+* ``identifier_in_list(val, id_name [, found_val, not_found_val])`` -- treat ``val`` as a list of identifiers separated by commas. An identifier has the format ``id_name:value``. The ``id_name`` parameter is the id_name text to search for, either ``id_name`` or ``id_name:regexp``. The first case matches if there is any identifier matching that id_name. The second case matches if id_name matches an identifier and the regexp matches the identifier's value. If ``found_val`` and ``not_found_val`` are provided then if there is a match then return ``found_val``, otherwise return ``not_found_val``. If ``found_val`` and ``not_found_val`` are not provided then if there is a match then return the ``indentfier:value`` pair, otherwise the empty string (``''``).
 * ``is_marked()`` -- check whether the book is `marked` in calibre. If it is then return the value of the mark, either ``'true'`` (lower case) or a comma-separated list of named marks. Returns ``''`` (the empty string) if the book is not marked. This function works only in the GUI.
 * ``language_codes(lang_strings)`` -- return the `language codes `_ for the language names passed in `lang_strings`. The strings must be in the language of the current locale. ``Lang_strings`` is a comma-separated list.
 * ``list_contains(value, separator, [ pattern, found_val, ]* not_found_val)`` -- (Alias of ``in_list``) Interpreting the value as a list of items separated by ``separator``, evaluate the ``pattern`` against each value in the list. If the ``pattern`` matches any value then return ``found_val``, otherwise return ``not_found_val``. The ``pattern`` and ``found_value`` can be repeated as many times as desired, permitting returning different values depending on the search. The patterns are checked in order. The first match is returned. Aliases: ``in_list()``, ``list_contains()``
diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py
index 6f82cf7dda..814c314139 100644
--- a/src/calibre/utils/formatter_functions.py
+++ b/src/calibre/utils/formatter_functions.py
@@ -796,28 +796,40 @@ class BuiltinStrInList(BuiltinFormatterFunction):
 
 class BuiltinIdentifierInList(BuiltinFormatterFunction):
     name = 'identifier_in_list'
-    arg_count = 4
+    arg_count = -1
     category = 'List lookup'
-    __doc__ = doc = _('identifier_in_list(val, id, found_val, not_found_val) -- '
-            'treat val as a list of identifiers separated by commas, '
-            'comparing the string against each value in the list. An identifier '
-            'has the format "identifier:value". The id parameter should be '
-            'either "id" or "id:regexp". The first case matches if there is any '
-            'identifier with that id. The second case matches if the regexp '
-            'matches the identifier\'s value. If there is a match, '
-            'return found_val, otherwise return not_found_val.')
+    __doc__ = doc = _('identifier_in_list(val, id_name [, found_val, not_found_val]) -- '
+            'treat val as a list of identifiers separated by commas. An identifier '
+            'has the format "id_name:value". The id_name parameter is the id_name '
+            'text to search for, either "id_name" or "id_name:regexp". The first case '
+            'matches if there is any identifier matching that id_name. The second '
+            'case matches if id_name matches an identifier and the regexp '
+            'matches the identifier\'s value. If found_val and not_found_val '
+            'are provided then if there is a match then return found_val, otherwise '
+            'return not_found_val. If found_val and not_found_val are not '
+            'provided then if there is a match then return the indentfier:value '
+            'pair, otherwise the empty string.')
+
+    def evaluate(self, formatter, kwargs, mi, locals, val, ident, *args):
+        if len(args) == 0:
+            fv_is_id = True
+            nfv = ''
+        elif len(args) == 2:
+            fv_is_id = False
+            fv = args[0]
+            nfv = args[1]
+        else:
+            raise ValueError(_("{} requires 2 or 4 arguments").format(self.name))
 
-    def evaluate(self, formatter, kwargs, mi, locals, val, ident, fv, nfv):
         l = [v.strip() for v in val.split(',') if v.strip()]
-        (id, _, regexp) = ident.partition(':')
-        if not id:
+        (id_, _, regexp) = ident.partition(':')
+        if not id_:
             return nfv
-        id += ':'
-        if l:
-            for v in l:
-                if v.startswith(id):
-                    if not regexp or re.search(regexp, v[len(id):], flags=re.I):
-                        return fv
+        for candidate in l:
+            i, _, v =  candidate.partition(':')
+            if v and i == id_:
+                if not regexp or re.search(regexp, v, flags=re.I):
+                    return candidate if fv_is_id else fv
         return nfv
 
 

From dcbb72a2b8c606cd1c4456ff9b0e08f98ecffade Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Wed, 10 May 2023 07:06:21 +0530
Subject: [PATCH 0690/2055] Fix copy all book details not respecting line
 breaks in fields

---
 src/calibre/gui2/book_details.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py
index ebcf474fa9..6999a7772c 100644
--- a/src/calibre/gui2/book_details.py
+++ b/src/calibre/gui2/book_details.py
@@ -92,6 +92,9 @@ def copy_all(text_browser):
     if not is_vertical:
         parent = tables[1]
     for tag in parent.iterdescendants('td'):
+        for child in tag.iterdescendants('br'):
+            child.tag = 'span'
+            child.text = '\ue000'
         tt = etree.tostring(tag, method='text', encoding='unicode')
         tag.tag = 'span'
         for child in tuple(tag):
@@ -105,6 +108,7 @@ def copy_all(text_browser):
     from calibre.utils.html2text import html2text
     simplified_html = etree.tostring(root, encoding='unicode')
     txt = html2text(simplified_html, single_line_break=True).strip()
+    txt = txt.replace('\ue000', '\n\t')
     if iswindows:
         txt = os.linesep.join(txt.splitlines())
     # print(simplified_html)

From 9f3fa2a62d72584a233890c36cc317a50e52e909 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Fri, 12 May 2023 07:55:31 +0530
Subject: [PATCH 0691/2055] Update Economist for website changes

Fixes #2019248 [error with Economist recipe](https://bugs.launchpad.net/calibre/+bug/2019248)
---
 recipes/economist.recipe      | 8 +++++---
 recipes/economist_free.recipe | 8 +++++---
 2 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/recipes/economist.recipe b/recipes/economist.recipe
index 977a94c143..0b0bcc294f 100644
--- a/recipes/economist.recipe
+++ b/recipes/economist.recipe
@@ -269,6 +269,7 @@ class Economist(BasicNewsRecipe):
             self.timefmt = ' [' + edition_date + ']'
         else:
             url = 'https://www.economist.com/printedition'
+        # raw = open('/t/raw.html').read()
         raw = self.index_to_soup(url, raw=True)
         # with open('/t/raw.html', 'wb') as f:
         #     f.write(raw)
@@ -293,18 +294,19 @@ class Economist(BasicNewsRecipe):
         script_tag = soup.find("script", id="__NEXT_DATA__")
         if script_tag is not None:
             data = json.loads(script_tag.string)
+            # open('/t/raw.json', 'w').write(json.dumps(data, indent=2, sort_keys=True))
             self.cover_url = safe_dict(data, "props", "pageProps", "content", "image", "main", "url", "canonical")
             self.log('Got cover:', self.cover_url)
 
             feeds_dict = defaultdict(list)
             for part in safe_dict(data, "props", "pageProps", "content", "hasPart", "parts"):
                 section = safe_dict(part, "print", "section", "headline") or ''
-                title = safe_dict(part, "print", "headline") or ''
+                title = safe_dict(part, "headline") or ''
                 url = safe_dict(part, "url", "canonical") or ''
                 if not section or not title or not url:
                     continue
-                desc = safe_dict(part, "print", "description") or ''
-                sub = safe_dict(part, "print", "subheadline") or ''
+                desc = safe_dict(part, "description") or ''
+                sub = safe_dict(part, "subheadline") or ''
                 if sub and section != sub:
                     desc = sub + ' :: ' + desc
                 if '/interactive/' in url:
diff --git a/recipes/economist_free.recipe b/recipes/economist_free.recipe
index 977a94c143..0b0bcc294f 100644
--- a/recipes/economist_free.recipe
+++ b/recipes/economist_free.recipe
@@ -269,6 +269,7 @@ class Economist(BasicNewsRecipe):
             self.timefmt = ' [' + edition_date + ']'
         else:
             url = 'https://www.economist.com/printedition'
+        # raw = open('/t/raw.html').read()
         raw = self.index_to_soup(url, raw=True)
         # with open('/t/raw.html', 'wb') as f:
         #     f.write(raw)
@@ -293,18 +294,19 @@ class Economist(BasicNewsRecipe):
         script_tag = soup.find("script", id="__NEXT_DATA__")
         if script_tag is not None:
             data = json.loads(script_tag.string)
+            # open('/t/raw.json', 'w').write(json.dumps(data, indent=2, sort_keys=True))
             self.cover_url = safe_dict(data, "props", "pageProps", "content", "image", "main", "url", "canonical")
             self.log('Got cover:', self.cover_url)
 
             feeds_dict = defaultdict(list)
             for part in safe_dict(data, "props", "pageProps", "content", "hasPart", "parts"):
                 section = safe_dict(part, "print", "section", "headline") or ''
-                title = safe_dict(part, "print", "headline") or ''
+                title = safe_dict(part, "headline") or ''
                 url = safe_dict(part, "url", "canonical") or ''
                 if not section or not title or not url:
                     continue
-                desc = safe_dict(part, "print", "description") or ''
-                sub = safe_dict(part, "print", "subheadline") or ''
+                desc = safe_dict(part, "description") or ''
+                sub = safe_dict(part, "subheadline") or ''
                 if sub and section != sub:
                     desc = sub + ' :: ' + desc
                 if '/interactive/' in url:

From 57794b179f21d991f1bb185c919b9a08659d98a8 Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Sat, 13 May 2023 06:10:44 +0530
Subject: [PATCH 0692/2055] ...

---
 src/calibre/utils/copy_files.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py
index 262f096678..856ff79704 100644
--- a/src/calibre/utils/copy_files.py
+++ b/src/calibre/utils/copy_files.py
@@ -47,7 +47,8 @@ class UnixFileCopier:
 
     def delete_all_source_files(self) -> None:
         for src_path in self.copy_map:
-            os.unlink(src_path)
+            with suppress(FileNotFoundError):
+                os.unlink(src_path)
 
 
 class WindowsFileCopier:

From dc8be3bbcdcd530552efe80571a462c190abb5bf Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Mon, 15 May 2023 12:44:57 +0530
Subject: [PATCH 0693/2055] Remove deprecated import

---
 src/calibre/library/database.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/calibre/library/database.py b/src/calibre/library/database.py
index 7f43fbd8e1..2058cc6c2a 100644
--- a/src/calibre/library/database.py
+++ b/src/calibre/library/database.py
@@ -8,7 +8,6 @@ Backend that implements storage of ebooks in an sqlite database.
 import datetime
 import re
 import sqlite3 as sqlite
-import sre_constants
 from zlib import compress, decompress
 
 from calibre import isbytestring
@@ -1515,7 +1514,7 @@ def text_to_tokens(text):
     for i in tokens:
         try:
             ans.append(SearchToken(i))
-        except sre_constants.error:
+        except re.error:
             continue
     return ans, OR
 

From 84ec9819f2fe89f6834cb0b91125fb364b742c9b Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Tue, 16 May 2023 06:55:01 +0530
Subject: [PATCH 0694/2055] Use the mechanize browser for downloading external
 resources instead of python stdlib

Also remove deprecated import of cgi
---
 src/calibre/ebooks/oeb/polish/download.py | 25 ++++++++++++-----------
 1 file changed, 13 insertions(+), 12 deletions(-)

diff --git a/src/calibre/ebooks/oeb/polish/download.py b/src/calibre/ebooks/oeb/polish/download.py
index ec409e96d1..26de19e1ef 100644
--- a/src/calibre/ebooks/oeb/polish/download.py
+++ b/src/calibre/ebooks/oeb/polish/download.py
@@ -2,7 +2,6 @@
 # License: GPLv3 Copyright: 2016, Kovid Goyal 
 
 
-import cgi
 import mimetypes
 import os
 import posixpath
@@ -15,7 +14,7 @@ from io import BytesIO
 from multiprocessing.dummy import Pool
 from tempfile import NamedTemporaryFile
 
-from calibre import as_unicode, sanitize_file_name as sanitize_file_name_base
+from calibre import as_unicode, browser, sanitize_file_name as sanitize_file_name_base
 from calibre.constants import iswindows
 from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES, barename, iterlinks
 from calibre.ebooks.oeb.polish.utils import guess_type
@@ -23,7 +22,7 @@ from calibre.ptempfile import TemporaryDirectory
 from calibre.web import get_download_filename_from_response
 from polyglot.binary import from_base64_bytes
 from polyglot.builtins import iteritems
-from polyglot.urllib import unquote, urlopen, urlparse
+from polyglot.urllib import unquote, urlparse
 
 
 def is_external(url):
@@ -57,15 +56,17 @@ def get_external_resources(container):
 
 def get_filename(original_url_parsed, response):
     ans = get_download_filename_from_response(response) or posixpath.basename(original_url_parsed.path) or 'unknown'
-    ct = response.info().get('Content-Type', '')
+    headers = response.info()
+    try:
+        ct = headers.get_params()[0][0].lower()
+    except Exception:
+        ct = ''
     if ct:
-        ct = cgi.parse_header(ct)[0].lower()
-        if ct:
-            mt = guess_type(ans)
-            if mt != ct:
-                exts = mimetypes.guess_all_extensions(ct)
-                if exts:
-                    ans += exts[0]
+        mt = guess_type(ans)
+        if mt != ct:
+            exts = mimetypes.guess_all_extensions(ct)
+            if exts:
+                ans += exts[0]
     return ans
 
 
@@ -137,7 +138,7 @@ def download_one(tdir, timeout, progress_report, data_uri_map, url):
                             break
                 filename = 'data-uri.' + ext
             else:
-                src = urlopen(url, timeout=timeout)
+                src = browser().open(url, timeout=timeout)
                 filename = get_filename(purl, src)
                 sz = get_content_length(src)
             progress_report(url, 0, sz)

From f9aa200d72de1b63f3dc1cce0c477111640ee34d Mon Sep 17 00:00:00 2001
From: Kovid Goyal 
Date: Tue, 16 May 2023 18:11:42 +0530
Subject: [PATCH 0695/2055] string changes

---
 src/calibre/gui2/preferences/template_functions.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/calibre/gui2/preferences/template_functions.py b/src/calibre/gui2/preferences/template_functions.py
index a373b64b53..294e6747f9 100644
--- a/src/calibre/gui2/preferences/template_functions.py
+++ b/src/calibre/gui2/preferences/template_functions.py
@@ -88,7 +88,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
         in template processing. You use a stored template in another template as
         if it were a template function, for example 'some_name(arg1, arg2...)'.

-

Stored templates must use either General Program Mode -- they must +

Stored templates must use General Program Mode -- they must either begin with the text '{0}' or be {1}. You retrieve arguments passed to a GPM stored template using the '{2}()' template function, as in '{2}(var1, var2, ...)'. The passed arguments are copied to the named From 5b0c506b07af7e47fffc10f906e9386160794ca6 Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Wed, 17 May 2023 23:55:41 +0530 Subject: [PATCH 0696/2055] ... Cover for Irish Times & other small changes. --- recipes/hindu.recipe | 2 +- recipes/icons/livemint.png | Bin 612 -> 343 bytes recipes/irish_times.recipe | 17 +++++++++++++++++ recipes/irish_times_free.recipe | 9 +++++++++ recipes/livemint.recipe | 2 ++ 5 files changed, 29 insertions(+), 1 deletion(-) diff --git a/recipes/hindu.recipe b/recipes/hindu.recipe index c5e0c07801..9c82b8dd03 100644 --- a/recipes/hindu.recipe +++ b/recipes/hindu.recipe @@ -27,7 +27,7 @@ class TheHindu(BasicNewsRecipe): extra_css = ''' .caption {font-size:small; text-align:center;} .author, .dateLine {font-size:small; font-weight:bold;} - .subhead, .subhead_lead {font-weight:bold;} + .subhead, .subhead_lead, .bold {font-weight:bold;} img {display:block; margin:0 auto;} .italic {font-style:italic; color:#202020;} ''' diff --git a/recipes/icons/livemint.png b/recipes/icons/livemint.png index 68f745e747673ce518e6d42b2fe8f2563108d52e..3d4457826c0a232736f920ef338a205e28dfd7b0 100644 GIT binary patch literal 343 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyP60k4u0ENCpC>V3qW}N@e_QMD z=gIz`CmX&lGx#!7;NOq;Ul%C+`*{Dy?u1|G`~SW^|9wlq@4M^etU9ZJrm>d<`2{l= zI21I@->>(;ECVRC*VDx@q~g}u$?ihU20X3@eRo`(bm#vn##)861Ip7XxlP>eCtqFW zBI#zjUBjFG+p0|2xdm@;u7BC2`FC30hNk@i5~~($|FMEgsWjwh;lGdxHEogwVI3FB zeR}?W;{9o>m*!JFr-Y+w2EWe&(LEF5?($Y7pDx^e_>kRs`$kRHiA*IQ&$G;V)?l9~ zW!bIyp-?WsON6WIE`z^|Mrmxq#Wimnt!8e`_sZICW6&e!<|h%A`Q~-=+ND>kckOxf btDZ6Hw|HSo(ycW>k1%+;`njxgN@xNAekPjm literal 612 zcmV-q0-ODbP)9oymEF^x2=nA`fJ*1RkYEX5~(%^1{EWuEo1%Oh}ziNjKVS9>qePs zW@yA-iJXv%8$=0Nm=XeM+4pQVrg(Pt?%8{u&-eL0&pBkXW>w0+N`-W}_<3W!RC~G& z_@0`W8#GwR;(?Pd5REbU|nf=zVR&Zev^*v{nh z)O(yLzKa5=tgVD>jZuqg>@tsCVn|Xt8G@l^I&NXGG}yv=_4LtEGH~U{#`~~e&4DZ{pYHH zury*i5Ht|f9W>B)CXqC9K>&ScDM?vHPA_Wi_TUFiCuxT`Vg y*_ZSmPK_B>b2Hl?U(G6)O2zylcBNdYJ^cr79Pj{ib+hCE0000LmmK diff --git a/recipes/irish_times.recipe b/recipes/irish_times.recipe index 5574f975b9..b868d152da 100644 --- a/recipes/irish_times.recipe +++ b/recipes/irish_times.recipe @@ -39,6 +39,23 @@ class IrishTimes(BasicNewsRecipe): classes('sm-promo-headline top-table-list-container single-divider interstitial-link'), ] remove_attributes = ['width', 'height'] + + def get_cover_url(self): + from datetime import date + cover = 'https://img.kiosko.net/' + date.today().strftime('%Y/%m/%d') + '/ie/irish_times.750.jpg' + br = BasicNewsRecipe.get_browser(self, verify_ssl_certificates=False) + try: + br.open(cover) + except: + index = 'https://en.kiosko.net/ie/np/irish_times.html' + soup = self.index_to_soup(index) + for image in soup.find('img', attrs={'src': lambda x: x and x.endswith('750.jpg')}): + if image['src'].startswith('/'): + return 'https:' + image['src'] + return image['src'] + self.log("\nCover unavailable") + cover = None + return cover def parse_index(self): soup = self.index_to_soup('https://www.irishtimes.com/') diff --git a/recipes/irish_times_free.recipe b/recipes/irish_times_free.recipe index 7e150dfbef..cf1d16ae69 100644 --- a/recipes/irish_times_free.recipe +++ b/recipes/irish_times_free.recipe @@ -66,6 +66,15 @@ class IrishTimes(BasicNewsRecipe): pt.close() return pt.name + def __init__(self, *args, **kwargs): + BasicNewsRecipe.__init__(self, *args, **kwargs) + if self.output_profile.short_name.startswith('kindle'): + # Reduce image sizes to get file size below amazon's email + # sending threshold + self.web2disk_options.compress_news_images = True + self.web2disk_options.compress_news_images_auto_size = 5 + self.log.warn('Kindle Output profile being used, reducing image quality to keep file size below amazon email threshold') + feeds = [] sections = [ diff --git a/recipes/livemint.recipe b/recipes/livemint.recipe index 4b990afd37..584b701843 100644 --- a/recipes/livemint.recipe +++ b/recipes/livemint.recipe @@ -129,6 +129,8 @@ class LiveMint(BasicNewsRecipe): return raw def preprocess_html(self, soup): + for h2 in soup.find('h2'): + h2.name = 'p' for span in soup.findAll('figcaption'): span['id'] = 'img-cap' for auth in soup.findAll('span', attrs={'class':['articleInfo pubtime','articleInfo author']}): From 04fa47ddaf000f702649044b0502765a834241e2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 19 May 2023 07:17:05 +0530 Subject: [PATCH 0697/2055] E-book viewer: Handle horizontal wheel events as section jumps in paged mode Also handle horizontal wheel events in cover flow. Fixes #2019426 [vertical mousewheel function not implemented](https://bugs.launchpad.net/calibre/+bug/2019426) --- src/calibre/gui2/cover_flow.py | 5 ++++- src/pyj/read_book/paged_mode.pyj | 20 ++++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py index f60e12d825..b662f2e55f 100644 --- a/src/calibre/gui2/cover_flow.py +++ b/src/calibre/gui2/cover_flow.py @@ -231,7 +231,10 @@ class CoverFlow(pictureflow.PictureFlow): return self.minimumSize() def wheelEvent(self, ev): - d = ev.angleDelta().y() + if ev.angleDelta().x(): + d = ev.angleDelta().x() + if ev.angleDelta().y(): + d = ev.angleDelta().y() if abs(d) > 0: ev.accept() (self.showNext if d < 0 else self.showPrevious)() diff --git a/src/pyj/read_book/paged_mode.pyj b/src/pyj/read_book/paged_mode.pyj index c868191f5a..23ecb1ee65 100644 --- a/src/pyj/read_book/paged_mode.pyj +++ b/src/pyj/read_book/paged_mode.pyj @@ -691,13 +691,18 @@ class HandleWheel: self.accumulated_scroll = 0 def onwheel(self, evt): - if not evt.deltaY: + if not (evt.deltaY or evt.deltaX): return - backward = evt.deltaY < 0 - if evt.deltaMode is window.WheelEvent.DOM_DELTA_PIXEL: - self.add_pixel_scroll(backward, Math.abs(evt.deltaY)) - else: - self.do_scroll(backward) + if evt.deltaY: + backward = evt.deltaY < 0 + if evt.deltaMode is window.WheelEvent.DOM_DELTA_PIXEL: + self.add_pixel_scroll(backward, Math.abs(evt.deltaY)) + else: + self.do_scroll(backward) + if evt.deltaX: + backward = evt.deltaX < 0 + self.do_section_jump(backward) + def add_pixel_scroll(self, backward, deltaY): now = window.performance.now() @@ -721,6 +726,9 @@ class HandleWheel: else: scroll_to_pos(pos) + def do_section_jump(self, backward): + self.reset() + next_spine_item(backward) wheel_handler = HandleWheel() onwheel = wheel_handler.onwheel From c574efa80836e8b4ba9cbc70c55d336eca6aab58 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 19 May 2023 07:19:30 +0530 Subject: [PATCH 0698/2055] Fix #2019101 [MDE: Inaccurate tooltip for 'Paste' button](https://bugs.launchpad.net/calibre/+bug/2019101) --- src/calibre/gui2/metadata/single.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index 85f41d0c0d..600cb37503 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -252,8 +252,8 @@ class MetadataSingleDialogBase(QDialog): self.paste_isbn_button = b = RightClickButton(self) b.setToolTip('

' + _('Paste the contents of the clipboard into the ' - 'identifiers prefixed with isbn: or url:. Or right click, ' - 'to choose a different prefix.') + '

') + 'identifiers prefixed with an auto-detected prefix such as isbn: or url:. Or right click, ' + 'and choose a specific prefix to use.') + '

') b.setIcon(QIcon.ic('edit-paste.png')) b.clicked.connect(self.identifiers.paste_identifier) b.setPopupMode(QToolButton.ToolButtonPopupMode.DelayedPopup) From 15debf7ebf651c40dab5d6fd25f8148e9add52fe Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 19 May 2023 14:10:07 +0530 Subject: [PATCH 0699/2055] Replace use of deprecated contents() function --- src/calibre/test_build.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/test_build.py b/src/calibre/test_build.py index 2c32c24a6c..406448b534 100644 --- a/src/calibre/test_build.py +++ b/src/calibre/test_build.py @@ -135,8 +135,8 @@ class BuildTest(unittest.TestCase): # libusb fails to initialize in containers without USB subsystems exclusions.update(set('libusb libmtp'.split())) from importlib import import_module - from importlib.resources import contents - for name in contents('calibre_extensions'): + from importlib.resources import files + for name in (path.name for path in files('calibre_extensions').iterdir()): if name in exclusions: if name in ('libusb', 'libmtp'): # Just check that the DLL can be loaded From 26ab48337bb5e31fc7ae55ff9aec64fc9676817f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 19 May 2023 14:21:22 +0530 Subject: [PATCH 0700/2055] Install tk on Arch CI to fix warning from PIL about missing libtk --- setup/arch-ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/arch-ci.sh b/setup/arch-ci.sh index c97c71f6d0..c68ea6636c 100755 --- a/setup/arch-ci.sh +++ b/setup/arch-ci.sh @@ -5,7 +5,7 @@ set -xe -pacman -S --noconfirm --needed base-devel sudo git sip pyqt-builder cmake chmlib icu jxrlib hunspell libmtp libusb libwmf optipng podofo python-apsw python-beautifulsoup4 python-cssselect python-css-parser python-dateutil python-jeepney python-dnspython python-feedparser python-html2text python-html5-parser python-lxml python-markdown python-mechanize python-msgpack python-netifaces python-unrardll python-pillow python-psutil python-pygments python-pyqt6 python-regex python-zeroconf python-pyqt6-webengine qt6-svg qt6-imageformats udisks2 hyphen python-pychm python-pycryptodome speech-dispatcher python-sphinx python-urllib3 python-py7zr python-pip python-fonttools uchardet libstemmer poppler +pacman -S --noconfirm --needed base-devel sudo git sip pyqt-builder cmake chmlib icu jxrlib hunspell libmtp libusb libwmf optipng podofo python-apsw python-beautifulsoup4 python-cssselect python-css-parser python-dateutil python-jeepney python-dnspython python-feedparser python-html2text python-html5-parser python-lxml python-markdown python-mechanize python-msgpack python-netifaces python-unrardll python-pillow python-psutil python-pygments python-pyqt6 python-regex python-zeroconf python-pyqt6-webengine qt6-svg qt6-imageformats udisks2 hyphen python-pychm python-pycryptodome speech-dispatcher python-sphinx python-urllib3 python-py7zr python-pip python-fonttools uchardet libstemmer poppler tk useradd -m ci chown -R ci:users $GITHUB_WORKSPACE From c1e5dd0fbb2f1f7e38289832657c69b858bb9bce Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 19 May 2023 14:26:39 +0530 Subject: [PATCH 0701/2055] Suppress the warning about cgi deprecation that is causing tests with warnings turned into errors to fail --- setup/test.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup/test.py b/setup/test.py index 6855681be6..7a142f94d5 100644 --- a/setup/test.py +++ b/setup/test.py @@ -40,6 +40,11 @@ class Test(Command): self.info(f'Re-execing with LD_PRELOAD={os.environ["LD_PRELOAD"]}') sys.stdout.flush() os.execl('setup.py', *sys.argv) + + # cgi is used by feedparser and possibly other dependencies + import warnings + warnings.filterwarnings('ignore', message="'cgi' is deprecated and slated for removal in Python 3.13") + if is_ci and ismacos: import ctypes sys.libxml2_dylib = ctypes.CDLL(os.path.join(os.environ['SW'], 'lib', 'libxml2.dylib')) From 68b931d3d8b191c9b2f931edf5510963a4ea2f8c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 20 May 2023 13:10:01 +0530 Subject: [PATCH 0702/2055] Allow running test_rs with libasan preloaded --- setup/test.py | 52 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/setup/test.py b/setup/test.py index 7a142f94d5..48a9fc1147 100644 --- a/setup/test.py +++ b/setup/test.py @@ -10,25 +10,7 @@ from setup import Command, ismacos, is_ci TEST_MODULES = frozenset('srv db polish opf css docx cfi matcher icu smartypants build misc dbcli ebooks'.split()) -class Test(Command): - - description = 'Run the calibre test suite' - - def add_options(self, parser): - parser.add_option('--test-verbosity', type=int, default=4, help='Test verbosity (0-4)') - parser.add_option('--test-module', '--test-group', default=[], action='append', type='choice', choices=sorted(map(str, TEST_MODULES)), - help='The test module to run (can be specified more than once for multiple modules). Choices: %s' % ', '.join(sorted(TEST_MODULES))) - parser.add_option('--test-name', '-n', default=[], action='append', - help='The name of an individual test to run. Can be specified more than once for multiple tests. The name of the' - ' test is the name of the test function without the leading test_. For example, the function test_something()' - ' can be run by specifying the name "something".') - parser.add_option('--exclude-test-module', default=[], action='append', type='choice', choices=sorted(map(str, TEST_MODULES)), - help='A test module to be excluded from the test run (can be specified more than once for multiple modules).' - ' Choices: %s' % ', '.join(sorted(TEST_MODULES))) - parser.add_option('--exclude-test-name', default=[], action='append', - help='The name of an individual test to be excluded from the test run. Can be specified more than once for multiple tests.') - parser.add_option('--under-sanitize', default=False, action='store_true', - help='Run the test suite with the sanitizer preloaded') +class BaseTest(Command): def run(self, opts): if opts.under_sanitize and 'CALIBRE_EXECED_UNDER_SANITIZE' not in os.environ: @@ -41,6 +23,32 @@ class Test(Command): sys.stdout.flush() os.execl('setup.py', *sys.argv) + def add_options(self, parser): + parser.add_option('--under-sanitize', default=False, action='store_true', + help='Run the test suite with the sanitizer preloaded') + + +class Test(BaseTest): + + description = 'Run the calibre test suite' + + def add_options(self, parser): + super().add_options(parser) + parser.add_option('--test-verbosity', type=int, default=4, help='Test verbosity (0-4)') + parser.add_option('--test-module', '--test-group', default=[], action='append', type='choice', choices=sorted(map(str, TEST_MODULES)), + help='The test module to run (can be specified more than once for multiple modules). Choices: %s' % ', '.join(sorted(TEST_MODULES))) + parser.add_option('--test-name', '-n', default=[], action='append', + help='The name of an individual test to run. Can be specified more than once for multiple tests. The name of the' + ' test is the name of the test function without the leading test_. For example, the function test_something()' + ' can be run by specifying the name "something".') + parser.add_option('--exclude-test-module', default=[], action='append', type='choice', choices=sorted(map(str, TEST_MODULES)), + help='A test module to be excluded from the test run (can be specified more than once for multiple modules).' + ' Choices: %s' % ', '.join(sorted(TEST_MODULES))) + parser.add_option('--exclude-test-name', default=[], action='append', + help='The name of an individual test to be excluded from the test run. Can be specified more than once for multiple tests.') + + def run(self, opts): + super().run(opts) # cgi is used by feedparser and possibly other dependencies import warnings warnings.filterwarnings('ignore', message="'cgi' is deprecated and slated for removal in Python 3.13") @@ -62,10 +70,14 @@ class Test(Command): run_cli(tests, verbosity=opts.test_verbosity, buffer=not opts.test_name) -class TestRS(Command): +class TestRS(BaseTest): description = 'Run tests for RapydScript code' + def add_options(self, parser): + super().add_options(parser) + def run(self, opts): + super().run(opts) from calibre.utils.rapydscript import run_rapydscript_tests run_rapydscript_tests() From fc553b9c2cb3575ea1f7cbbb597e60f840155b2a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 20 May 2023 13:11:09 +0530 Subject: [PATCH 0703/2055] See if running test_rs fixes the interminable hang in Arch CI --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93d8705156..cc54f28dec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,4 +67,5 @@ jobs: run: | set -xe runuser -u ci -- python setup.py test --under-sanitize - runuser -u ci -- python setup.py test_rs + echo "Running test_rs" + runuser -u ci -- python setup.py test_rs --under-sanitize From 10afcea57f73afb5f5f60d9659056344cf49c3c5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 20 May 2023 13:31:51 +0530 Subject: [PATCH 0704/2055] Disable testing on Arch Linux Something is hanging on CI after the test completes successfully cant be bothered figuring out what --- .github/workflows/ci.yml | 66 ++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc54f28dec..00078c0da6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,36 +36,36 @@ jobs: run: python setup/unix-ci.py test - archtest: - name: Test on Arch - runs-on: ubuntu-latest - container: - image: 'archlinux/archlinux:latest' - env: - CI: 'true' - steps: - - name: Setup container - run: | - pacman -Syu --noconfirm - pacman -S --noconfirm tar - - - name: Checkout source code - uses: actions/checkout@master - with: - fetch-depth: 10 - - - name: Install calibre dependencies - run: setup/arch-ci.sh - - - name: Bootstrap calibre - run: runuser -u ci -- python setup.py bootstrap --ephemeral --debug --sanitize - - - name: Test calibre - env: - PYTHONWARNINGS: error - CALIBRE_SHOW_DEPRECATION_WARNINGS: 1 - run: | - set -xe - runuser -u ci -- python setup.py test --under-sanitize - echo "Running test_rs" - runuser -u ci -- python setup.py test_rs --under-sanitize + # archtest: + # name: Test on Arch + # runs-on: ubuntu-latest + # container: + # image: 'archlinux/archlinux:latest' + # env: + # CI: 'true' + # steps: + # - name: Setup container + # run: | + # pacman -Syu --noconfirm + # pacman -S --noconfirm tar + # + # - name: Checkout source code + # uses: actions/checkout@master + # with: + # fetch-depth: 10 + # + # - name: Install calibre dependencies + # run: setup/arch-ci.sh + # + # - name: Bootstrap calibre + # run: runuser -u ci -- python setup.py bootstrap --ephemeral --debug --sanitize + # + # - name: Test calibre + # env: + # PYTHONWARNINGS: error + # CALIBRE_SHOW_DEPRECATION_WARNINGS: 1 + # run: | + # set -xe + # runuser -u ci -- python setup.py test --under-sanitize + # echo "Running test_rs" + # runuser -u ci -- python setup.py test_rs From 76fbbef9d08d4d335c5408584ed26bfc165b5fdc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 11 May 2023 13:50:59 +0530 Subject: [PATCH 0705/2055] Start work on porting to new PoDoFo API --- setup/build.py | 8 +- setup/build_environment.py | 1 + setup/extensions.json | 4 +- src/calibre/utils/podofo/doc.cpp | 442 ++++++++++++-------------- src/calibre/utils/podofo/fonts.cpp | 216 +++++++------ src/calibre/utils/podofo/global.h | 47 ++- src/calibre/utils/podofo/images.cpp | 43 +-- src/calibre/utils/podofo/impose.cpp | 26 +- src/calibre/utils/podofo/outline.cpp | 28 +- src/calibre/utils/podofo/outlines.cpp | 36 +-- src/calibre/utils/podofo/output.cpp | 24 +- src/calibre/utils/podofo/podofo.cpp | 27 -- src/calibre/utils/podofo/utils.cpp | 15 +- 13 files changed, 454 insertions(+), 463 deletions(-) diff --git a/setup/build.py b/setup/build.py index b67803b90f..b6e7aed512 100644 --- a/setup/build.py +++ b/setup/build.py @@ -231,8 +231,12 @@ class Environment(NamedTuple): def lib_dirs_to_ldflags(self, dirs) -> List[str]: return [self.libdir_prefix+x for x in dirs if x] - def libraries_to_ldflags(self, dirs): - return [self.lib_prefix+x+self.lib_suffix for x in dirs] + def libraries_to_ldflags(self, libs): + def map_name(x): + if '/' in x: + return x + return self.lib_prefix+x+self.lib_suffix + return list(map(map_name, libs)) diff --git a/setup/build_environment.py b/setup/build_environment.py index dc0027bb72..e9d5ddfa85 100644 --- a/setup/build_environment.py +++ b/setup/build_environment.py @@ -209,6 +209,7 @@ else: podofo_lib = os.environ.get('PODOFO_LIB_DIR', podofo_lib) podofo_inc = os.environ.get('PODOFO_INC_DIR', podofo_inc) +podofo = os.environ.get('PODOFO_LIB_NAME', 'podofo') podofo_error = None if os.path.exists(os.path.join(podofo_inc, 'podofo.h')) else \ ('PoDoFo not found on your system. Various PDF related', ' functionality will not work. Use the PODOFO_INC_DIR and', diff --git a/setup/extensions.json b/setup/extensions.json index c7260f8d51..cf7d145b66 100644 --- a/setup/extensions.json +++ b/setup/extensions.json @@ -120,11 +120,11 @@ "name": "podofo", "sources": "calibre/utils/podofo/utils.cpp calibre/utils/podofo/output.cpp calibre/utils/podofo/doc.cpp calibre/utils/podofo/outline.cpp calibre/utils/podofo/fonts.cpp calibre/utils/podofo/impose.cpp calibre/utils/podofo/images.cpp calibre/utils/podofo/outlines.cpp calibre/utils/podofo/podofo.cpp", "headers": "calibre/utils/podofo/global.h", - "libraries": "podofo", + "libraries": "!podofo", "lib_dirs": "!podofo_lib_dirs", "inc_dirs": "!podofo_inc_dirs", "error": "!podofo_error", - "needs_c++": "11" + "needs_c++": "17" }, { "name": "html_as_json", diff --git a/src/calibre/utils/podofo/doc.cpp b/src/calibre/utils/podofo/doc.cpp index 25f814a3c4..1b9eeac68b 100644 --- a/src/calibre/utils/podofo/doc.cpp +++ b/src/calibre/utils/podofo/doc.cpp @@ -7,6 +7,8 @@ #include "global.h" #include +#include +#include using namespace pdf; @@ -41,11 +43,7 @@ PDFDoc_load(PDFDoc *self, PyObject *args) { if (!PyArg_ParseTuple(args, "y#", &buffer, &size)) return NULL; try { -#if PODOFO_VERSION <= 0x000905 - self->doc->Load(buffer, (long)size); -#else - self->doc->LoadFromBuffer(buffer, (long)size); -#endif + self->doc->LoadFromBuffer(bufferview(buffer, size)); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; @@ -84,7 +82,7 @@ PDFDoc_save(PDFDoc *self, PyObject *args) { if (PyArg_ParseTuple(args, "s", &buffer)) { try { - self->doc->Write(buffer); + self->doc->Save(buffer); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; @@ -94,16 +92,43 @@ PDFDoc_save(PDFDoc *self, PyObject *args) { Py_RETURN_NONE; } +class BytesOutputDevice : public OutputStreamDevice { + private: + pyunique_ptr bytes; + size_t written; + public: + BytesOutputDevice() : bytes(PyBytes_FromStringAndSize(NULL, 1 * 1024 *1024)) { SetAccess(DeviceAccess::Write); } + size_t GetLength() const { return written; } + size_t GetPosition() const { return written; } + size_t capacity() const { return bytes ? PyBytes_GET_SIZE(bytes.get()) : 0; } + bool Eof() const { return false; } + + void writeBuffer(const char* src, size_t src_sz) { + if (written + src_sz > capacity()) { + PyObject* old = bytes.release(); + if (_PyBytes_Resize(&old, std::max(written + src_sz, 2 * capacity())) != 0) { + return; + } + bytes.reset(old); + } + if (bytes) { + memcpy(PyBytes_AS_STRING(bytes.get()), src, src_sz); + written += src_sz; + } + } + + void Flush() { } + PyObject* Release() { return bytes.release(); } +}; + static PyObject * PDFDoc_write(PDFDoc *self, PyObject *args) { PyObject *ans; + BytesOutputDevice d; try { - PdfRefCountedBuffer buffer(1*1024*1024); - PdfOutputDevice out(&buffer); - self->doc->Write(&out); - ans = PyBytes_FromStringAndSize(buffer.GetBuffer(), out.Tell()); - if (ans == NULL) PyErr_NoMemory(); + self->doc->Save(d); + return d.Release(); } catch(const PdfError &err) { podofo_set_exception(err); return NULL; @@ -124,11 +149,25 @@ PDFDoc_save_to_fileobj(PDFDoc *self, PyObject *args) { static PyObject * PDFDoc_uncompress_pdf(PDFDoc *self, PyObject *args) { - for (auto &it : self->doc->GetObjects()) { - if(it->HasStream()) { - PdfMemStream* stream = dynamic_cast(it->GetStream()); - stream->Uncompress(); + try { + auto& objects = self->doc->GetObjects(); + for (auto obj : objects) { + auto stream = obj->GetStream(); + if (stream == nullptr) continue; + try { + try { + stream->Unwrap(); + } catch (PdfError& e) { + if (e.GetCode() != PdfErrorCode::Flate) throw e; + } + } + catch (PdfError& e) { + if (e.GetCode() != PdfErrorCode::UnsupportedFilter) throw e; + } } + } catch(const PdfError & err) { + podofo_set_exception(err); + return NULL; } Py_RETURN_NONE; } @@ -140,7 +179,8 @@ PDFDoc_uncompress_pdf(PDFDoc *self, PyObject *args) { static PyObject * PDFDoc_extract_first_page(PDFDoc *self, PyObject *args) { try { - while (self->doc->GetPageCount() > 1) self->doc->GetPagesTree()->DeletePage(1); + auto pages = &self->doc->GetPages(); + while (pages->GetCount() > 1) pages->RemovePageAt(1); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; @@ -154,7 +194,7 @@ static PyObject * PDFDoc_page_count(PDFDoc *self, PyObject *args) { int count; try { - count = self->doc->GetPageCount(); + count = self->doc->GetPages().GetCount(); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; @@ -173,8 +213,8 @@ PDFDoc_image_count(PDFDoc *self, PyObject *args) { if( it->IsDictionary() ) { obj_type = it->GetDictionary().GetKey( PdfName::KeyType ); obj_sub_type = it->GetDictionary().GetKey( PdfName::KeySubtype ); - if( ( obj_type && obj_type->IsName() && ( obj_type->GetName().GetName() == "XObject" ) ) || - ( obj_sub_type && obj_sub_type->IsName() && ( obj_sub_type->GetName().GetName() == "Image" ) ) ) count++; + if( ( obj_type && obj_type->IsName() && ( obj_type->GetName().GetString() == "XObject" ) ) || + ( obj_sub_type && obj_sub_type->IsName() && ( obj_sub_type->GetName().GetString() == "Image" ) ) ) count++; } } } catch(const PdfError & err) { @@ -190,7 +230,9 @@ PDFDoc_delete_pages(PDFDoc *self, PyObject *args) { int page = 0, count = 1; if (PyArg_ParseTuple(args, "i|i", &page, &count)) { try { - self->doc->DeletePages(page - 1, count); + while (count > 0) { + self->doc->GetPages().RemovePageAt(page - 1); + } } catch(const PdfError & err) { podofo_set_exception(err); return NULL; @@ -207,10 +249,9 @@ PDFDoc_get_page_box(PDFDoc *self, PyObject *args) { const char *which; if (PyArg_ParseTuple(args, "si", &which, &pagenum)) { try { - PdfPagesTree* tree = self->doc->GetPagesTree(); - PdfPage* page = tree->GetPage(pagenum - 1); - if (!page) { PyErr_Format(PyExc_ValueError, "page number %d not found in PDF file", pagenum); return NULL; } - PdfRect rect; + auto page = get_page(self->doc, pagenum-1); + if (!page) { PyErr_Format(PyExc_ValueError, "page number %d not found in PDF file", pagenum); return NULL; } + Rect rect; if (strcmp(which, "MediaBox") == 0) { rect = page->GetMediaBox(); } else if (strcmp(which, "CropBox") == 0) { @@ -225,7 +266,7 @@ PDFDoc_get_page_box(PDFDoc *self, PyObject *args) { PyErr_Format(PyExc_KeyError, "%s is not a known box", which); return NULL; } - return Py_BuildValue("dddd", rect.GetLeft(), rect.GetBottom(), rect.GetWidth(), rect.GetHeight()); + return Py_BuildValue("dddd", rect.GetLeft(), rect.GetBottom(), rect.Width, rect.Height); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; @@ -243,13 +284,12 @@ PDFDoc_set_page_box(PDFDoc *self, PyObject *args) { const char *which; if (PyArg_ParseTuple(args, "sidddd", &which, &pagenum, &left, &bottom, &width, &height)) { try { - PdfPagesTree* tree = self->doc->GetPagesTree(); - PdfPage* page = tree->GetPage(pagenum - 1); - if (!page) { PyErr_Format(PyExc_ValueError, "page number %d not found in PDF file", pagenum); return NULL; } - PdfRect rect(left, bottom, width, height); - PdfObject box; - rect.ToVariant(box); - page->GetObject()->GetDictionary().AddKey(PdfName(which), box); + PdfPage* page = get_page(self->doc, pagenum-1); + if (!page) { PyErr_Format(PyExc_ValueError, "page number %d not found in PDF file", pagenum); return NULL; } + Rect rect(left, bottom, width, height); + PdfArray box; + rect.ToArray(box); + page->GetObject().GetDictionary().AddKey(PdfName(which), box); Py_RETURN_NONE; } catch(const PdfError & err) { podofo_set_exception(err); @@ -266,9 +306,7 @@ PDFDoc_copy_page(PDFDoc *self, PyObject *args) { int from = 0, to = 0; if (!PyArg_ParseTuple(args, "ii", &from, &to)) return NULL; try { - PdfPagesTree* tree = self->doc->GetPagesTree(); - PdfPage* page = tree->GetPage(from - 1); - tree->InsertPage(to - 1, page); + self->doc->GetPages().InsertDocumentPageAt(to - 1, *self->doc, from - 1); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; @@ -287,14 +325,14 @@ PDFDoc_append(PDFDoc *self, PyObject *args) { typ = PyObject_IsInstance(doc, (PyObject*)&PDFDocType); if (typ == -1) return NULL; if (typ == 0) { PyErr_SetString(PyExc_TypeError, "You must pass a PDFDoc instance to this method"); return NULL; } + PDFDoc *pdfdoc = (PDFDoc*)doc; try { - self->doc->Append(*((PDFDoc*)doc)->doc, true); + self->doc->GetPages().AppendDocumentPages(*pdfdoc->doc); } catch (const PdfError & err) { podofo_set_exception(err); return NULL; } - Py_RETURN_NONE; } // }}} @@ -307,7 +345,7 @@ PDFDoc_insert_existing_page(PDFDoc *self, PyObject *args) { if (!PyArg_ParseTuple(args, "O!|ii", &PDFDocType, &src_doc, &src_page, &at)) return NULL; try { - self->doc->InsertExistingPageAt(*src_doc->doc, src_page, at); + self->doc->GetPages().InsertDocumentPageAt(at, *src_doc->doc, src_page); } catch (const PdfError & err) { podofo_set_exception(err); return NULL; @@ -323,12 +361,11 @@ PDFDoc_set_box(PDFDoc *self, PyObject *args) { double left, bottom, width, height; char *box; if (!PyArg_ParseTuple(args, "isdddd", &num, &box, &left, &bottom, &width, &height)) return NULL; - try { - PdfRect r(left, bottom, width, height); - PdfObject o; - r.ToVariant(o); - self->doc->GetPage(num)->GetObject()->GetDictionary().AddKey(PdfName(box), o); + Rect r(left, bottom, width, height); + PdfArray o; + r.ToArray(o); + self->doc->GetPages().GetPageAt(num).GetObject().GetDictionary().AddKey(PdfName(box), o); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; @@ -336,41 +373,21 @@ PDFDoc_set_box(PDFDoc *self, PyObject *args) { PyErr_SetString(PyExc_ValueError, "An unknown error occurred while trying to set the box"); return NULL; } - Py_RETURN_NONE; } // }}} // get_xmp_metadata() {{{ static PyObject * PDFDoc_get_xmp_metadata(PDFDoc *self, PyObject *args) { - PoDoFo::PdfObject *metadata = NULL; - PoDoFo::PdfStream *str = NULL; - PoDoFo::pdf_long len = 0; - char *buf = NULL; - PyObject *ans = NULL; - try { - if ((metadata = self->doc->GetMetadata()) != NULL) { - if ((str = metadata->GetStream()) != NULL) { - str->GetFilteredCopy(&buf, &len); - if (buf != NULL) { - Py_ssize_t psz = len; - ans = Py_BuildValue("y#", buf, psz); - free(buf); buf = NULL; - if (ans == NULL) goto error; - } - } - } + auto s = self->doc->GetCatalog().GetMetadataStreamValue(); + return PyBytes_FromStringAndSize(s.data(), s.size()); } catch(const PdfError & err) { - podofo_set_exception(err); goto error; + podofo_set_exception(err); return NULL; } catch (...) { - PyErr_SetString(PyExc_ValueError, "An unknown error occurred while trying to read the XML metadata"); goto error; + PyErr_SetString(PyExc_ValueError, "An unknown error occurred while trying to read the XML metadata"); return NULL; } - - if (ans != NULL) return ans; Py_RETURN_NONE; -error: - return NULL; } // }}} // set_xmp_metadata() {{{ @@ -378,85 +395,58 @@ static PyObject * PDFDoc_set_xmp_metadata(PDFDoc *self, PyObject *args) { const char *raw = NULL; Py_ssize_t len = 0; - PoDoFo::PdfObject *metadata = NULL, *catalog = NULL; - PoDoFo::PdfStream *str = NULL; - TVecFilters compressed(1); - compressed[0] = ePdfFilter_FlateDecode; - if (!PyArg_ParseTuple(args, "y#", &raw, &len)) return NULL; try { - if ((metadata = self->doc->GetMetadata()) != NULL) { - if ((str = metadata->GetStream()) == NULL) { PyErr_NoMemory(); goto error; } - str->Set(raw, len, compressed); - } else { - if ((catalog = self->doc->GetCatalog()) == NULL) { PyErr_SetString(PyExc_ValueError, "Cannot set XML metadata as this document has no catalog"); goto error; } - if ((metadata = self->doc->GetObjects().CreateObject("Metadata")) == NULL) { PyErr_NoMemory(); goto error; } - if ((str = metadata->GetStream()) == NULL) { PyErr_NoMemory(); goto error; } - metadata->GetDictionary().AddKey(PoDoFo::PdfName("Subtype"), PoDoFo::PdfName("XML")); - str->Set(raw, len, compressed); - catalog->GetDictionary().AddKey(PoDoFo::PdfName("Metadata"), metadata->Reference()); - } + self->doc->GetCatalog().SetMetadataStreamValue(std::string_view(raw, len)); } catch(const PdfError & err) { - podofo_set_exception(err); goto error; + podofo_set_exception(err); return NULL; } catch (...) { - PyErr_SetString(PyExc_ValueError, "An unknown error occurred while trying to set the XML metadata"); - goto error; + PyErr_SetString(PyExc_ValueError, "An unknown error occurred while trying to set the XML metadata"); return NULL; } Py_RETURN_NONE; -error: - return NULL; - } // }}} // extract_anchors() {{{ static PyObject * PDFDoc_extract_anchors(PDFDoc *self, PyObject *args) { - const PdfObject* catalog = NULL; PyObject *ans = PyDict_New(); if (ans == NULL) return NULL; try { - if ((catalog = self->doc->GetCatalog()) != NULL) { - const PdfObject *dests_ref = catalog->GetDictionary().GetKey("Dests"); - PdfPagesTree *tree = self->doc->GetPagesTree(); - if (dests_ref && dests_ref->IsReference()) { - const PdfObject *dests_obj = self->doc->GetObjects().GetObject(dests_ref->GetReference()); - if (dests_obj && dests_obj->IsDictionary()) { - const PdfDictionary &dests = dests_obj->GetDictionary(); - const TKeyMap &keys = dests.GetKeys(); - for (TCIKeyMap itres = keys.begin(); itres != keys.end(); ++itres) { - if (itres->second->IsArray()) { - const PdfArray &dest = itres->second->GetArray(); - // see section 8.2 of PDF spec for different types of destination arrays - // but chromium apparently generates only [page /XYZ left top zoom] type arrays - if (dest.GetSize() > 4 && dest[1].IsName() && dest[1].GetName().GetName() == "XYZ") { - const PdfPage *page = tree->GetPage(dest[0].GetReference()); - if (page) { - unsigned int pagenum = page->GetPageNumber(); - double left = dest[2].GetReal(), top = dest[3].GetReal(); - long long zoom = dest[4].GetNumber(); - const std::string &anchor = itres->first.GetName(); - PyObject *key = PyUnicode_DecodeUTF8(anchor.c_str(), anchor.length(), "replace"); - PyObject *tuple = Py_BuildValue("IddL", pagenum, left, top, zoom); - if (!tuple || !key) { break; } - int ret = PyDict_SetItem(ans, key, tuple); - Py_DECREF(key); Py_DECREF(tuple); - if (ret != 0) break; - } - } - } - } - } - } - } + const PdfObject *dests_ref = self->doc->GetCatalog().GetDictionary().GetKey("Dests"); + auto& pages = self->doc->GetPages(); + if (dests_ref && dests_ref->IsReference()) { + const PdfObject *dests_obj = self->doc->GetObjects().GetObject(dests_ref->GetReference()); + if (dests_obj && dests_obj->IsDictionary()) { + const PdfDictionary &dests = dests_obj->GetDictionary(); + for (auto itres: dests) { + if (itres.second.IsArray()) { + const PdfArray &dest = itres.second.GetArray(); + // see section 8.2 of PDF spec for different types of destination arrays + // but chromium apparently generates only [page /XYZ left top zoom] type arrays + if (dest.GetSize() > 4 && dest[1].IsName() && dest[1].GetName().GetString() == "XYZ") { + const PdfPage *page = get_page(pages, dest[0].GetReference()); + if (page) { + unsigned int pagenum = page->GetPageNumber(); + double left = dest[2].GetReal(), top = dest[3].GetReal(); + long long zoom = dest[4].GetNumber(); + const std::string &anchor = itres.first.GetString(); + PyObject *key = PyUnicode_DecodeUTF8(anchor.c_str(), anchor.length(), "replace"); + PyObject *tuple = Py_BuildValue("IddL", pagenum, left, top, zoom); + if (!tuple || !key) { break; } + int ret = PyDict_SetItem(ans, key, tuple); + Py_DECREF(key); Py_DECREF(tuple); + if (ret != 0) break; + } + } + } + } + } + } } catch(const PdfError & err) { podofo_set_exception(err); - Py_CLEAR(ans); - return NULL; } catch (...) { PyErr_SetString(PyExc_ValueError, "An unknown error occurred while trying to set the box"); - Py_CLEAR(ans); - return NULL; } if (PyErr_Occurred()) { Py_CLEAR(ans); return NULL; } return ans; @@ -472,28 +462,22 @@ alter_link(PDFDoc *self, PdfDictionary &link, PyObject *alter_callback, bool mar } PdfDictionary &A = link.GetKey("A")->GetDictionary(); PdfObject *uo = A.GetKey("URI"); - const std::string &uri = uo->GetString().GetStringUtf8(); + const std::string &uri = uo->GetString().GetString(); pyunique_ptr ret(PyObject_CallObject(alter_callback, Py_BuildValue("(N)", PyUnicode_DecodeUTF8(uri.c_str(), uri.length(), "replace")))); if (!ret) { return; } if (PyTuple_Check(ret.get()) && PyTuple_GET_SIZE(ret.get()) == 4) { int pagenum; double left, top, zoom; if (PyArg_ParseTuple(ret.get(), "iddd", &pagenum, &left, &top, &zoom)) { - PdfPage *page = NULL; - try { - page = self->doc->GetPage(pagenum - 1); - } catch(const PdfError &err) { - (void)err; - PyErr_Format(PyExc_ValueError, "No page number %d in the PDF file of %d pages", pagenum, self->doc->GetPageCount()); - return ; + const PdfPage *page = get_page(self->doc, pagenum - 1); + if (page == NULL) { + PyErr_Format(PyExc_ValueError, "No page number %d in the PDF file of %d pages", pagenum, self->doc->GetPages().GetCount()); + return; } - if (page) { - PdfDestination dest(page, left, top, zoom); link.RemoveKey("A"); + PdfDestination dest(*page, left, top, zoom); dest.AddToDictionary(link); - } } } - } static PyObject * @@ -504,8 +488,8 @@ PDFDoc_alter_links(PDFDoc *self, PyObject *args) { bool mark_links = PyObject_IsTrue(py_mark_links); try { PdfArray border, link_color; - border.push_back((PoDoFo::pdf_int64)16); border.push_back((PoDoFo::pdf_int64)16); border.push_back((PoDoFo::pdf_int64)1); - link_color.push_back(1.); link_color.push_back(0.); link_color.push_back(0.); + border.Add(int64_t(16)); border.Add(int64_t(16)); border.Add(int64_t(1)); + link_color.Add(1.); link_color.Add(0.); link_color.Add(0.); std::vector links; for (auto &it : self->doc->GetObjects()) { if(it->IsDictionary()) { @@ -516,7 +500,7 @@ PDFDoc_alter_links(PDFDoc *self, PyObject *args) { if (dictionary_has_key_name(A, PdfName::KeyType, "Action") && dictionary_has_key_name(A, "S", "URI")) { PdfObject *uo = A.GetKey("URI"); if (uo && uo->IsString()) { - links.push_back(it->Reference()); + links.push_back(it->GetReference()); } } } @@ -547,153 +531,137 @@ PDFDoc_alter_links(PDFDoc *self, PyObject *args) { static PyObject * PDFDoc_pages_getter(PDFDoc *self, void *closure) { - int pages = self->doc->GetPageCount(); - PyObject *ans = PyLong_FromLong(static_cast(pages)); + unsigned long pages = self->doc->GetPages().GetCount(); + PyObject *ans = PyLong_FromUnsignedLong(pages); if (ans != NULL) Py_INCREF(ans); return ans; } static PyObject * PDFDoc_version_getter(PDFDoc *self, void *closure) { - int version; + PdfVersion version; try { - version = self->doc->GetPdfVersion(); + version = self->doc->GetMetadata().GetPdfVersion(); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; } switch(version) { - case ePdfVersion_1_0: - return Py_BuildValue("s", "1.0"); - case ePdfVersion_1_1: - return Py_BuildValue("s", "1.1"); - case ePdfVersion_1_2: - return Py_BuildValue("s", "1.2"); - case ePdfVersion_1_3: - return Py_BuildValue("s", "1.3"); - case ePdfVersion_1_4: - return Py_BuildValue("s", "1.4"); - case ePdfVersion_1_5: - return Py_BuildValue("s", "1.5"); - case ePdfVersion_1_6: - return Py_BuildValue("s", "1.6"); - case ePdfVersion_1_7: - return Py_BuildValue("s", "1.7"); - default: - return Py_BuildValue(""); + case PdfVersion::V1_0: + return PyUnicode_FromString("1.0"); + case PdfVersion::V1_1: + return PyUnicode_FromString("1.1"); + case PdfVersion::V1_2: + return PyUnicode_FromString("1.2"); + case PdfVersion::V1_3: + return PyUnicode_FromString("1.3"); + case PdfVersion::V1_4: + return PyUnicode_FromString("1.4"); + case PdfVersion::V1_5: + return PyUnicode_FromString("1.5"); + case PdfVersion::V1_6: + return PyUnicode_FromString("1.6"); + case PdfVersion::V1_7: + return PyUnicode_FromString("1.7"); + case PdfVersion::V2_0: + return PyUnicode_FromString("2.0"); + case PdfVersion::Unknown: + return PyUnicode_FromString(""); } - return Py_BuildValue(""); + return PyUnicode_FromString(""); } - -static PyObject * -PDFDoc_getter(PDFDoc *self, int field) -{ - PdfString s; - PdfInfo *info = self->doc->GetInfo(); - if (info == NULL) { - PyErr_SetString(PyExc_Exception, "You must first load a PDF Document"); - return NULL; - } - switch (field) { - case 0: - s = info->GetTitle(); break; - case 1: - s = info->GetAuthor(); break; - case 2: - s = info->GetSubject(); break; - case 3: - s = info->GetKeywords(); break; - case 4: - s = info->GetCreator(); break; - case 5: - s = info->GetProducer(); break; - default: - PyErr_SetString(PyExc_Exception, "Bad field"); - return NULL; - } - - return podofo_convert_pdfstring(s); -} - -static int -PDFDoc_setter(PDFDoc *self, PyObject *val, int field) { - if (val == NULL || !PyUnicode_Check(val)) { - PyErr_SetString(PyExc_ValueError, "Must use unicode objects to set metadata"); - return -1; - } - PdfInfo *info = self->doc->GetInfo(); - if (!info) { PyErr_SetString(Error, "You must first load a PDF Document"); return -1; } - const PdfString s = podofo_convert_pystring(val); - - switch (field) { - case 0: - info->SetTitle(s); break; - case 1: - info->SetAuthor(s); break; - case 2: - info->SetSubject(s); break; - case 3: - info->SetKeywords(s); break; - case 4: - info->SetCreator(s); break; - case 5: - info->SetProducer(s); break; - default: - PyErr_SetString(Error, "Bad field"); - return -1; - } - - return 0; +static inline PyObject* +string_metadata_getter(const nullable& t) { + if (t.has_value()) return podofo_convert_pdfstring(t.value()); + return PyUnicode_FromString(""); } static PyObject * PDFDoc_title_getter(PDFDoc *self, void *closure) { - return PDFDoc_getter(self, 0); + return string_metadata_getter(self->doc->GetMetadata().GetTitle()); } + static PyObject * PDFDoc_author_getter(PDFDoc *self, void *closure) { - return PDFDoc_getter(self, 1); + return string_metadata_getter(self->doc->GetMetadata().GetAuthor()); } + static PyObject * PDFDoc_subject_getter(PDFDoc *self, void *closure) { - return PDFDoc_getter(self, 2); + return string_metadata_getter(self->doc->GetMetadata().GetSubject()); } + static PyObject * PDFDoc_keywords_getter(PDFDoc *self, void *closure) { - return PDFDoc_getter(self, 3); + auto kw = self->doc->GetMetadata().GetKeywords(); + pyunique_ptr ans(PyTuple_New(kw.size())); + if (!ans) return NULL; + for (size_t i = 0; i < kw.size(); i++) { + pyunique_ptr t(PyUnicode_FromString(kw[i].c_str())); + if (!t) return NULL; + PyTuple_SET_ITEM(ans.get(), i, t.release()); + } + return ans.release(); } + static PyObject * PDFDoc_creator_getter(PDFDoc *self, void *closure) { - return PDFDoc_getter(self, 4); + return string_metadata_getter(self->doc->GetMetadata().GetCreator()); } + static PyObject * PDFDoc_producer_getter(PDFDoc *self, void *closure) { - return PDFDoc_getter(self, 5); + return string_metadata_getter(self->doc->GetMetadata().GetProducer()); } + static int PDFDoc_title_setter(PDFDoc *self, PyObject *val, void *closure) { - return PDFDoc_setter(self, val, 0); + if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "Must use unicode to set metadata"); return -1; } + self->doc->GetMetadata().SetTitle(podofo_convert_pystring(val)); + return 0; } + static int PDFDoc_author_setter(PDFDoc *self, PyObject *val, void *closure) { - return PDFDoc_setter(self, val, 1); + if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "Must use unicode to set metadata"); return -1; } + self->doc->GetMetadata().SetAuthor(podofo_convert_pystring(val)); + return 0; } + static int PDFDoc_subject_setter(PDFDoc *self, PyObject *val, void *closure) { - return PDFDoc_setter(self, val, 2); + if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "Must use unicode to set metadata"); return -1; } + self->doc->GetMetadata().SetSubject(podofo_convert_pystring(val)); + return 0; } + static int PDFDoc_keywords_setter(PDFDoc *self, PyObject *val, void *closure) { - return PDFDoc_setter(self, val, 3); + pyunique_ptr f(PySequence_Fast(val, "Need a sequence to set keywords")); + if (!f) return -1; + std::vector keywords(PySequence_Fast_GET_SIZE(f.get())); + for (Py_ssize_t i = 0; i < PySequence_Fast_GET_SIZE(f.get()); i++) { + PyObject *x = PySequence_Fast_GET_ITEM(f.get(), i); + if (!PyUnicode_Check(x)) { PyErr_SetString(PyExc_TypeError, "keywords sequence must contain only unicode objects"); return -1; } + keywords.emplace_back(podofo_convert_pystring(x)); + } + self->doc->GetMetadata().SetKeywords(keywords); + return 0; } + static int PDFDoc_creator_setter(PDFDoc *self, PyObject *val, void *closure) { - return PDFDoc_setter(self, val, 4); + if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "Must use unicode to set metadata"); return -1; } + self->doc->GetMetadata().SetCreator(podofo_convert_pystring(val)); + return 0; } + static int PDFDoc_producer_setter(PDFDoc *self, PyObject *val, void *closure) { - return PDFDoc_setter(self, val, 5); + if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "Must use unicode to set metadata"); return -1; } + self->doc->GetMetadata().SetProducer(podofo_convert_pystring(val)); + return 0; } static PyGetSetDef PDFDoc_getsetters[] = { diff --git a/src/calibre/utils/podofo/fonts.cpp b/src/calibre/utils/podofo/fonts.cpp index 6c18dca914..ba673f4d92 100644 --- a/src/calibre/utils/podofo/fonts.cpp +++ b/src/calibre/utils/podofo/fonts.cpp @@ -7,6 +7,7 @@ #include "global.h" #include +#include #include using namespace pdf; @@ -18,47 +19,61 @@ ref_as_tuple(const PdfReference &ref) { } static inline PdfObject* -get_font_file(const PdfObject *descriptor) { - PdfObject *ff = descriptor->GetIndirectKey("FontFile"); - if (!ff) ff = descriptor->GetIndirectKey("FontFile2"); - if (!ff) ff = descriptor->GetIndirectKey("FontFile3"); +get_font_file(PdfObject *descriptor) { + PdfDictionary *dict; + PdfObject *ff = NULL; + if (descriptor->TryGetDictionary(dict)) { + ff = dict->FindKey("FontFile"); + if (!ff) ff = dict->FindKey("FontFile2"); + if (!ff) ff = dict->FindKey("FontFile3"); + } return ff; } -static inline void -remove_font(PdfVecObjects &objects, PdfObject *font) { - PdfObject *descriptor = font->GetIndirectKey("FontDescriptor"); - if (descriptor) { - const PdfObject *ff = get_font_file(descriptor); - if (ff) delete objects.RemoveObject(ff->Reference()); - delete objects.RemoveObject(descriptor->Reference()); +static inline const PdfObject* +get_font_file(const PdfObject *descriptor) { + const PdfDictionary *dict; + const PdfObject *ff = NULL; + if (descriptor->TryGetDictionary(dict)) { + ff = dict->FindKey("FontFile"); + if (!ff) ff = dict->FindKey("FontFile2"); + if (!ff) ff = dict->FindKey("FontFile3"); } - delete objects.RemoveObject(font->Reference()); + return ff; } -static inline uint64_t -ref_as_integer(pdf_objnum num, pdf_gennum gen) { - return static_cast(num) | (static_cast(gen) << 32); -} -static inline uint64_t -ref_as_integer(const PdfReference &ref) { return ref_as_integer(ref.ObjectNumber(), ref.GenerationNumber()); } +static inline void +remove_font(PdfIndirectObjectList &objects, PdfObject *font) { + PdfDictionary *dict; + if (font->TryGetDictionary(dict)) { + PdfObject *descriptor = dict->FindKey("FontDescriptor"); + if (descriptor) { + const PdfObject *ff = get_font_file(descriptor); + if (ff) objects.RemoveObject(ff->GetReference()).reset(); + objects.RemoveObject(descriptor->GetReference()).reset(); + } + } + objects.RemoveObject(font->GetReference()).reset(); +} static void -used_fonts_in_canvas(PdfCanvas *canvas, unordered_reference_set &ans) { - PdfContentsTokenizer tokenizer(canvas); +used_fonts_in_canvas(const PdfCanvas &canvas, unordered_reference_set &ans) { + PdfPostScriptTokenizer tokenizer; + PdfCanvasInputDevice input(canvas); bool in_text_block = false; - const char* token = NULL; - EPdfContentsType contents_type; + PdfPostScriptTokenType contents_type; PdfVariant var; std::stack stack; - const PdfDictionary &resources = canvas->GetResources()->GetDictionary(); + const PdfDictionary &resources = canvas.GetResources()->GetDictionary(); if (!resources.HasKey("Font")) return; const PdfDictionary &fonts_dict = resources.GetKey("Font")->GetDictionary(); + std::string_view keyword; - while (tokenizer.ReadNext(contents_type, token, var)) { - if (contents_type == ePdfContentsType_Variant) stack.push(var); - if (contents_type != ePdfContentsType_Keyword) continue; + while (tokenizer.TryReadNext(input, contents_type, keyword, var)) { + if (contents_type == PdfPostScriptTokenType::Variant) stack.push(var); + if (contents_type != PdfPostScriptTokenType::Keyword) continue; + const char *token = keyword.data(); if (strcmp(token, "BT") == 0) { in_text_block = true; continue; @@ -88,10 +103,10 @@ convert_w_array(const PdfArray &w) { pyunique_ptr item; if ((*it).IsArray()) { item.reset(convert_w_array((*it).GetArray())); + } else if ((*it).IsRealStrict()) { + item.reset(PyFloat_FromDouble((*it).GetReal())); } else if ((*it).IsNumber()) { item.reset(PyLong_FromLongLong((long long)(*it).GetNumber())); - } else if ((*it).IsReal()) { - item.reset(PyFloat_FromDouble((*it).GetReal())); } else PyErr_SetString(PyExc_ValueError, "Unknown datatype in w array"); if (!item) return NULL; if (PyList_Append(ans.get(), item.get()) != 0) return NULL; @@ -105,16 +120,16 @@ list_fonts(PDFDoc *self, PyObject *args) { if (!PyArg_ParseTuple(args, "|i", &get_font_data)) return NULL; pyunique_ptr ans(PyList_New(0)); if (!ans) return NULL; - const PdfVecObjects &objects = self->doc->GetObjects(); + const PdfIndirectObjectList &objects = self->doc->GetObjects(); for (auto &it : objects) { if (it->IsDictionary()) { const PdfDictionary &dict = it->GetDictionary(); if (dictionary_has_key_name(dict, PdfName::KeyType, "Font") && dict.HasKey("BaseFont")) { - const std::string &name = dict.GetKey("BaseFont")->GetName().GetName(); - const std::string &subtype = dict.GetKey(PdfName::KeySubtype)->GetName().GetName(); - const PdfReference &ref = it->Reference(); + const std::string &name = dict.GetKey("BaseFont")->GetName().GetString(); + const std::string &subtype = dict.GetKey(PdfName::KeySubtype)->GetName().GetString(); + const PdfReference &ref = it->GetReference(); unsigned long num = ref.ObjectNumber(), generation = ref.GenerationNumber(); - const PdfObject *descriptor = it->GetIndirectKey("FontDescriptor"); + const PdfObject *descriptor = dict.FindKey("FontDescriptor"); pyunique_ptr descendant_font, stream_ref, encoding, w, w2; PyBytesOutputStream stream_data, to_unicode, cid_gid_map; if (dict.HasKey("W")) { @@ -126,21 +141,21 @@ list_fonts(PDFDoc *self, PyObject *args) { if (!w2) return NULL; } if (dict.HasKey("Encoding") && dict.GetKey("Encoding")->IsName()) { - encoding.reset(PyUnicode_FromString(dict.GetKey("Encoding")->GetName().GetName().c_str())); + encoding.reset(PyUnicode_FromString(dict.GetKey("Encoding")->GetName().GetString().c_str())); if (!encoding) return NULL; } - if (dict.HasKey("CIDToGIDMap") && (!dict.GetKey("CIDToGIDMap")->IsName() || strcmp(dict.GetKey("CIDToGIDMap")->GetName().GetName().c_str(), "Identity") != 0)) { - const PdfStream *stream = dict.GetKey("CIDToGIDMap")->GetStream(); - if (stream) stream->GetFilteredCopy(&cid_gid_map); + if (dict.HasKey("CIDToGIDMap") && (!dict.GetKey("CIDToGIDMap")->IsName() || strcmp(dict.GetKey("CIDToGIDMap")->GetName().GetString().c_str(), "Identity") != 0)) { + const PdfObjectStream *stream = dict.GetKey("CIDToGIDMap")->GetStream(); + if (stream) stream->CopyToSafe(cid_gid_map); } if (descriptor) { const PdfObject *ff = get_font_file(descriptor); if (ff) { - stream_ref.reset(ref_as_tuple(ff->Reference())); + stream_ref.reset(ref_as_tuple(ff->GetReference())); if (!stream_ref) return NULL; - const PdfStream *stream = ff->GetStream(); + const PdfObjectStream *stream = ff->GetStream(); if (stream && get_font_data) { - stream->GetFilteredCopy(&stream_data); + stream->CopyToSafe(stream_data); } } } else if (dict.HasKey("DescendantFonts")) { @@ -151,8 +166,8 @@ list_fonts(PDFDoc *self, PyObject *args) { const PdfReference &uref = dict.GetKey("ToUnicode")->GetReference(); PdfObject *t = objects.GetObject(uref); if (t) { - PdfStream *stream = t->GetStream(); - if (stream) stream->GetFilteredCopy(&to_unicode); + PdfObjectStream *stream = t->GetStream(); + if (stream) stream->CopyToSafe(to_unicode); } } } @@ -186,18 +201,18 @@ remove_unused_fonts(PDFDoc *self, PyObject *args) { unsigned long count = 0; unordered_reference_set used_fonts; // Look in Pages - for (int i = 0; i < self->doc->GetPageCount(); i++) { - PdfPage *page = self->doc->GetPage(i); - if (page) used_fonts_in_canvas(page, used_fonts); + PdfPageCollection *pages = &self->doc->GetPages(); + for (unsigned i = 0; i < pages->GetCount(); i++) { + used_fonts_in_canvas(self->doc->GetPages().GetPageAt(i), used_fonts); } // Look in XObjects - PdfVecObjects &objects = self->doc->GetObjects(); - for (auto &k : objects) { + PdfIndirectObjectList &objects = self->doc->GetObjects(); + for (PdfObject *k : objects) { if (k->IsDictionary()) { const PdfDictionary &dict = k->GetDictionary(); if (dictionary_has_key_name(dict, PdfName::KeyType, "XObject") && dictionary_has_key_name(dict, PdfName::KeySubtype, "Form")) { - PdfXObject xo(k); - used_fonts_in_canvas(&xo, used_fonts); + std::unique_ptr xo; + if (PdfXObject::TryCreateFromObject(*k, xo)) used_fonts_in_canvas(*xo, used_fonts); } } } @@ -208,14 +223,14 @@ remove_unused_fonts(PDFDoc *self, PyObject *args) { if (k->IsDictionary()) { const PdfDictionary &dict = k->GetDictionary(); if (dictionary_has_key_name(dict, PdfName::KeyType, "Font")) { - const std::string &font_type = dict.GetKey(PdfName::KeySubtype)->GetName().GetName(); + const std::string &font_type = dict.GetKey(PdfName::KeySubtype)->GetName().GetString(); if (font_type == "Type0") { - all_fonts.insert(k->Reference()); + all_fonts.insert(k->GetReference()); } else if (font_type == "Type3") { - all_fonts.insert(k->Reference()); - type3_fonts.insert(k->Reference()); - for (auto &x : dict.GetKey("CharProcs")->GetDictionary().GetKeys()) { - const PdfReference &ref = x.second->GetReference(); + all_fonts.insert(k->GetReference()); + type3_fonts.insert(k->GetReference()); + for (auto &x : dict.GetKey("CharProcs")->GetDictionary()) { + const PdfReference &ref = x.second.GetReference(); if (charprocs_usage.find(ref) == charprocs_usage.end()) charprocs_usage[ref] = 1; else charprocs_usage[ref] += 1; } @@ -229,16 +244,18 @@ remove_unused_fonts(PDFDoc *self, PyObject *args) { PdfObject *font = objects.GetObject(ref); if (font) { count++; + PdfDictionary *dict; + if (font->TryGetDictionary(dict)) { if (type3_fonts.find(ref) != type3_fonts.end()) { - for (auto &x : font->GetIndirectKey("CharProcs")->GetDictionary().GetKeys()) { - charprocs_usage[x.second->GetReference()] -= 1; + for (auto &x : dict->FindKey("CharProcs")->GetDictionary()) { + charprocs_usage[x.second.GetReference()] -= 1; } } else { - for (auto &x : font->GetIndirectKey("DescendantFonts")->GetArray()) { + for (auto &x : dict->FindKey("DescendantFonts")->GetArray()) { PdfObject *dfont = objects.GetObject(x.GetReference()); if (dfont) remove_font(objects, dfont); } - } + }} remove_font(objects, font); } } @@ -246,7 +263,7 @@ remove_unused_fonts(PDFDoc *self, PyObject *args) { for (auto &x : charprocs_usage) { if (x.second == 0u) { - delete objects.RemoveObject(x.first); + objects.RemoveObject(x.first).reset(); } } @@ -258,14 +275,16 @@ replace_font_data(PDFDoc *self, PyObject *args) { const char *data; Py_ssize_t sz; unsigned long num, gen; if (!PyArg_ParseTuple(args, "y#kk", &data, &sz, &num, &gen)) return NULL; - const PdfVecObjects &objects = self->doc->GetObjects(); - PdfObject *font = objects.GetObject(PdfReference(num, static_cast(gen))); + const PdfIndirectObjectList &objects = self->doc->GetObjects(); + PdfObject *font = objects.GetObject(PdfReference(num, static_cast(gen))); if (!font) { PyErr_SetString(PyExc_KeyError, "No font with the specified reference found"); return NULL; } - const PdfObject *descriptor = font->GetIndirectKey("FontDescriptor"); + PdfDictionary *dict; + if (!font->TryGetDictionary(dict)) { PyErr_SetString(PyExc_ValueError, "Font does not have a descriptor"); return NULL; } + PdfObject *descriptor = dict->FindKey("FontDescriptor"); if (!descriptor) { PyErr_SetString(PyExc_ValueError, "Font does not have a descriptor"); return NULL; } PdfObject *ff = get_font_file(descriptor); - PdfStream *stream = ff->GetStream(); - stream->Set(data, sz); + PdfObjectStream *stream = ff->GetStream(); + stream->SetData(bufferview(data, sz)); Py_RETURN_NONE; } @@ -274,60 +293,61 @@ merge_fonts(PDFDoc *self, PyObject *args) { const char *data; Py_ssize_t sz; PyObject *references; if (!PyArg_ParseTuple(args, "y#O!", &data, &sz, &PyTuple_Type, &references)) return NULL; - PdfVecObjects &objects = self->doc->GetObjects(); + PdfIndirectObjectList &objects = self->doc->GetObjects(); PdfObject *font_file = NULL; + PdfDictionary *dict; for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(references); i++) { unsigned long num, gen; if (!PyArg_ParseTuple(PyTuple_GET_ITEM(references, i), "kk", &num, &gen)) return NULL; - PdfObject *font = objects.GetObject(PdfReference(num, static_cast(gen))); + PdfObject *font = objects.GetObject(PdfReference(num, static_cast(gen))); if (!font) { PyErr_SetString(PyExc_KeyError, "No font with the specified reference found"); return NULL; } - PdfObject *dobj = font->GetIndirectKey("FontDescriptor"); + + PdfObject *dobj = NULL; + if (font->TryGetDictionary(dict)) { dobj = dict->FindKey("FontDescriptor"); } if (!dobj) { PyErr_SetString(PyExc_ValueError, "Font does not have a descriptor"); return NULL; } if (!dobj->IsDictionary()) { PyErr_SetString(PyExc_ValueError, "Font does not have a dictionary descriptor"); return NULL; } PdfDictionary &descriptor = dobj->GetDictionary(); const char *font_file_key = NULL; - if (descriptor.HasKey("FontFile")) font_file_key = "FontFile"; - else if (descriptor.HasKey("FontFile2")) font_file_key = "FontFile2"; - else if (descriptor.HasKey("FontFile3")) font_file_key = "FontFile3"; - else { PyErr_SetString(PyExc_ValueError, "Font descriptor does not have file data"); return NULL; } - PdfObject *ff = dobj->GetIndirectKey(font_file_key); + PdfObject *ff = NULL; + if ((ff = descriptor.FindKey("FontFile"))) { font_file_key = "FontFile"; } + else if ((ff = descriptor.FindKey("FontFile2"))) { font_file_key = "FontFile2"; } + else if ((ff = descriptor.FindKey("FontFile3"))) { font_file_key = "FontFile3"; } + else { PyErr_SetString(PyExc_ValueError, "Font descriptor does not have file data"); return NULL; } if (i == 0) { font_file = ff; - PdfStream *stream = ff->GetStream(); - stream->Set(data, sz); + PdfObjectStream *stream = ff->GetStream(); + stream->SetData(bufferview(data, sz)); } else { - delete objects.RemoveObject(ff->Reference()); - descriptor.AddKey(font_file_key, font_file->Reference()); + objects.RemoveObject(ff->GetReference()).reset(); + descriptor.AddKey(font_file_key, font_file->GetReference()); } } Py_RETURN_NONE; } class CharProc { - char *buf; pdf_long sz; + charbuff buf; PdfReference ref; CharProc( const CharProc & ) ; CharProc & operator=( const CharProc & ) ; public: - CharProc(const PdfReference &reference, const PdfObject *o) : buf(NULL), sz(0), ref(reference) { - const PdfStream *stream = o->GetStream(); - stream->GetFilteredCopy(&buf, &sz); + CharProc(const PdfReference &reference, const PdfObject *o) : buf(), ref(reference) { + const PdfObjectStream *stream = o->GetStream(); + buf = stream->GetCopySafe(); } CharProc(CharProc &&other) noexcept : - buf(other.buf), sz(other.sz), ref(other.ref) { - other.buf = NULL; + buf(std::move(other.buf)), ref(other.ref) { + other.buf = charbuff(); } CharProc& operator=(CharProc &&other) noexcept { - if (buf) podofo_free(buf); - buf = other.buf; other.buf = NULL; sz = other.sz; ref = other.ref; + buf = std::move(other.buf); other.buf = charbuff(); ref = other.ref; return *this; } - ~CharProc() noexcept { if (buf) podofo_free(buf); buf = NULL; } bool operator==(const CharProc &other) const noexcept { - return other.sz == sz && memcmp(buf, other.buf, sz) == 0; + return buf.size() == other.buf.size() && memcmp(buf.data(), other.buf.data(), buf.size()) == 0; } - std::size_t hash() const noexcept { return sz; } + std::size_t hash() const noexcept { return buf.size(); } const PdfReference& reference() const noexcept { return ref; } }; @@ -344,16 +364,16 @@ dedup_type3_fonts(PDFDoc *self, PyObject *args) { unordered_reference_set all_type3_fonts; char_proc_reference_map cp_map; - PdfVecObjects &objects = self->doc->GetObjects(); + PdfIndirectObjectList &objects = self->doc->GetObjects(); for (auto &k : objects) { if (!k->IsDictionary()) continue; const PdfDictionary &dict = k->GetDictionary(); if (dictionary_has_key_name(dict, PdfName::KeyType, "Font")) { - const std::string &font_type = dict.GetKey(PdfName::KeySubtype)->GetName().GetName(); + const std::string &font_type = dict.GetKey(PdfName::KeySubtype)->GetName().GetString(); if (font_type == "Type3") { - all_type3_fonts.insert(k->Reference()); - for (auto &x : dict.GetKey("CharProcs")->GetDictionary().GetKeys()) { - const PdfReference &ref = x.second->GetReference(); + all_type3_fonts.insert(k->GetReference()); + for (auto &x : dict.GetKey("CharProcs")->GetDictionary()) { + const PdfReference &ref = x.second.GetReference(); const PdfObject *cpobj = objects.GetObject(ref); if (!cpobj || !cpobj->HasStream()) continue; CharProc cp(ref, cpobj); @@ -373,7 +393,7 @@ dedup_type3_fonts(PDFDoc *self, PyObject *args) { for (auto &ref : x.second) { if (ref != canonical_ref) { ref_map[ref] = x.first.reference(); - delete objects.RemoveObject(ref); + objects.RemoveObject(ref).reset(); count++; } } @@ -382,11 +402,13 @@ dedup_type3_fonts(PDFDoc *self, PyObject *args) { if (count > 0) { for (auto &ref : all_type3_fonts) { PdfObject *font = objects.GetObject(ref); - PdfDictionary dict = font->GetIndirectKey("CharProcs")->GetDictionary(); + PdfDictionary *d; + if (!font->TryGetDictionary(d)) continue; + PdfDictionary dict = d->FindKey("CharProcs")->GetDictionary(); PdfDictionary new_dict = PdfDictionary(dict); bool changed = false; - for (auto &k : dict.GetKeys()) { - auto it = ref_map.find(k.second->GetReference()); + for (auto &k : dict) { + auto it = ref_map.find(k.second.GetReference()); if (it != ref_map.end()) { new_dict.AddKey(k.first, (*it).second); changed = true; diff --git a/src/calibre/utils/podofo/global.h b/src/calibre/utils/podofo/global.h index b723ef8504..77905af8a2 100644 --- a/src/calibre/utils/podofo/global.h +++ b/src/calibre/utils/podofo/global.h @@ -15,6 +15,7 @@ #include #include using namespace PoDoFo; +using namespace std::literals; namespace pdf { @@ -52,7 +53,7 @@ struct PyObjectDeleter { // unique_ptr that uses Py_XDECREF as the destructor function. typedef std::unique_ptr pyunique_ptr; -class PyBytesOutputStream : public PdfOutputStream { +class PyBytesOutputStream : public OutputStream { private: pyunique_ptr bytes; PyBytesOutputStream( const PyBytesOutputStream & ) ; @@ -62,18 +63,18 @@ class PyBytesOutputStream : public PdfOutputStream { void Close() {} operator bool() const { return bool(bytes); } PyObject* get() const { return bytes.get(); } - pdf_long Write(const char *buf, const pdf_long sz){ + protected: + void writeBuffer(const char *buf, size_t sz){ if (!bytes) { bytes.reset(PyBytes_FromStringAndSize(buf, sz)); - if (!bytes) throw PdfError(ePdfError_OutOfMemory, __FILE__, __LINE__, NULL); + if (!bytes) throw PdfError(PdfErrorCode::OutOfMemory, __FILE__, __LINE__, NULL); } else { size_t old_sz = PyBytes_GET_SIZE(bytes.get()); PyObject *old = bytes.release(); - if (_PyBytes_Resize(&old, old_sz + sz) != 0) throw PdfError(ePdfError_OutOfMemory, __FILE__, __LINE__, NULL); + if (_PyBytes_Resize(&old, old_sz + sz) != 0) throw PdfError(PdfErrorCode::OutOfMemory, __FILE__, __LINE__, NULL); memcpy(PyBytes_AS_STRING(old) + old_sz, buf, sz); bytes.reset(old); } - return sz; } }; @@ -82,10 +83,44 @@ template static inline bool dictionary_has_key_name(const PdfDictionary &d, T key, const char *name) { const PdfObject *val = d.GetKey(key); - if (val && val->IsName() && val->GetName().GetName() == name) return true; + if (val && val->IsName() && val->GetName().GetString() == name) return true; return false; } +static inline const PdfPage* +get_page(const PdfPageCollection &pages, const PdfReference &ref) { + try { + return &pages.GetPage(ref); + } catch(PdfError &) { } + return nullptr; +} + +static inline const PdfPage* +get_page(const PdfDocument *doc, const PdfReference &ref) { + try { + return &doc->GetPages().GetPage(ref); + } catch(PdfError &) { } + return nullptr; +} + +static inline const PdfPage* +get_page(const PdfDocument *doc, const unsigned num) { + try { + return &doc->GetPages().GetPageAt(num); + } catch(PdfError &) { } + return nullptr; +} + +static inline PdfPage* +get_page(PdfDocument *doc, const unsigned num) { + try { + return &doc->GetPages().GetPageAt(num); + } catch(PdfError &) { } + return nullptr; +} + + + class PdfReferenceHasher { public: size_t operator()(const PdfReference & obj) const { diff --git a/src/calibre/utils/podofo/images.cpp b/src/calibre/utils/podofo/images.cpp index 6868b2c3a7..f5d948a33d 100644 --- a/src/calibre/utils/podofo/images.cpp +++ b/src/calibre/utils/podofo/images.cpp @@ -10,39 +10,40 @@ using namespace pdf; class Image { - char *buf; pdf_long sz; - pdf_int64 width, height; + charbuff buf; + int64_t width, height; PdfReference ref; Image( const Image & ) ; Image & operator=( const Image & ) ; + bool is_valid; public: - Image(const PdfReference &reference, const PdfObject *o) : buf(NULL), sz(0), width(0), height(0), ref(reference) { - const PdfStream *stream = o->GetStream(); + Image(const PdfReference &reference, const PdfObject *o) : buf(), width(0), height(0), ref(reference) { + const PdfObjectStream *stream = o->GetStream(); try { - stream->GetFilteredCopy(&buf, &sz); + buf = stream->GetCopySafe(); + is_valid = true; } catch(...) { - buf = NULL; sz = -1; + buf = charbuff(); + is_valid = false; } const PdfDictionary &dict = o->GetDictionary(); if (dict.HasKey("Width") && dict.GetKey("Width")->IsNumber()) width = dict.GetKey("Width")->GetNumber(); if (dict.HasKey("Height") && dict.GetKey("Height")->IsNumber()) height = dict.GetKey("Height")->GetNumber(); } Image(Image &&other) noexcept : - buf(other.buf), sz(other.sz), width(other.width), height(other.height), ref(other.ref) { - other.buf = NULL; + buf(std::move(other.buf)), width(other.width), height(other.height), ref(other.ref) { + other.buf = charbuff(); is_valid = other.is_valid; } Image& operator=(Image &&other) noexcept { - if (buf) podofo_free(buf); - buf = other.buf; other.buf = NULL; sz = other.sz; ref = other.ref; - width = other.width; height = other.height; + buf = std::move(other.buf); other.buf = charbuff(); ref = other.ref; + width = other.width; height = other.height; is_valid = other.is_valid; return *this; } - ~Image() noexcept { if (buf) podofo_free(buf); buf = NULL; } bool operator==(const Image &other) const noexcept { - return other.sz == sz && sz > -1 && other.width == width && other.height == height && memcmp(buf, other.buf, sz) == 0; + return other.width == width && is_valid && other.is_valid && other.height == height && other.buf == buf; } - std::size_t hash() const noexcept { return sz; } + std::size_t hash() const noexcept { return buf.size(); } const PdfReference& reference() const noexcept { return ref; } }; @@ -56,14 +57,14 @@ typedef std::unordered_map, ImageHasher> image_ static PyObject* dedup_images(PDFDoc *self, PyObject *args) { unsigned long count = 0; - PdfVecObjects &objects = self->doc->GetObjects(); + PdfIndirectObjectList &objects = self->doc->GetObjects(); image_reference_map image_map; for (auto &k : objects) { if (!k->IsDictionary()) continue; const PdfDictionary &dict = k->GetDictionary(); if (dictionary_has_key_name(dict, PdfName::KeyType, "XObject") && dictionary_has_key_name(dict, PdfName::KeySubtype, "Image")) { - Image img(k->Reference(), k); + Image img(k->GetReference(), k); auto it = image_map.find(img); if (it == image_map.end()) { std::vector vals; @@ -78,7 +79,7 @@ dedup_images(PDFDoc *self, PyObject *args) { for (auto &ref : x.second) { if (ref != canonical_ref) { ref_map[ref] = x.first.reference(); - delete objects.RemoveObject(ref); + objects.RemoveObject(ref).reset(); count++; } } @@ -95,11 +96,11 @@ dedup_images(PDFDoc *self, PyObject *args) { const PdfDictionary &xobject = resources.GetKey("XObject")->GetDictionary(); PdfDictionary new_xobject = PdfDictionary(xobject); bool changed = false; - for (auto &x : xobject.GetKeys()) { - if (x.second->IsReference()) { + for (const auto &x : xobject) { + if (x.second.IsReference()) { try { - const PdfReference &r = ref_map.at(x.second->GetReference()); - new_xobject.AddKey(x.first.GetName(), r); + const PdfReference &r = ref_map.at(x.second.GetReference()); + new_xobject.AddKey(x.first, r); changed = true; } catch (const std::out_of_range &err) { (void)err; continue; } } diff --git a/src/calibre/utils/podofo/impose.cpp b/src/calibre/utils/podofo/impose.cpp index 2eea6d525c..67050375d4 100644 --- a/src/calibre/utils/podofo/impose.cpp +++ b/src/calibre/utils/podofo/impose.cpp @@ -6,24 +6,19 @@ */ #include "global.h" +#include using namespace pdf; static void -impose_page(PdfMemDocument *doc, unsigned long dest_page_num, unsigned long src_page_num) { - PdfXObject *xobj = new PdfXObject(doc, src_page_num, "HeaderFooter"); - PdfPage *dest = doc->GetPage(dest_page_num); - dest->AddResource(xobj->GetIdentifier(), xobj->GetObject()->Reference(), "XObject"); - PdfStream *stream = dest->GetContents()->GetStream(); - char *buffer = NULL; pdf_long sz; - stream->GetFilteredCopy(&buffer, &sz); - stream->BeginAppend(); - stream->Append("q\n1 0 0 1 0 0 cm\n/"); - stream->Append(xobj->GetIdentifier().GetName()); - stream->Append(" Do\nQ\n"); - stream->Append(buffer, sz); - stream->EndAppend(); - podofo_free(buffer); +impose_page(PdfMemDocument *doc, unsigned int dest_page_num, unsigned int src_page_num) { + auto xobj = doc->CreateXObjectForm(Rect(), "HeaderFooter"); + xobj->FillFromPage(doc->GetPages().GetPageAt(src_page_num)); + auto dest = &doc->GetPages().GetPageAt(dest_page_num); + static unsigned counter = 0; + dest->GetOrCreateResources().AddResource("XObject", "Imp"s + std::to_string(++counter), xobj->GetObject()); + auto data = "q\n1 0 0 1 0 0 cm\n/"s + xobj->GetIdentifier().GetEscapedName() + " Do\nQ\n"s; + dest->GetOrCreateContents().GetStreamForAppending().SetData(data); } static PyObject* @@ -33,7 +28,8 @@ impose(PDFDoc *self, PyObject *args) { for (unsigned long i = 0; i < count; i++) { impose_page(self->doc, dest_page_num - 1 + i, src_page_num - 1 + i); } - self->doc->DeletePages(src_page_num - 1, count); + auto& pages = self->doc->GetPages(); + while (count-- && src_page_num <= pages.GetCount()) pages.RemovePageAt(src_page_num - 1); Py_RETURN_NONE; } diff --git a/src/calibre/utils/podofo/outline.cpp b/src/calibre/utils/podofo/outline.cpp index 42c6c7b810..c3de7efced 100644 --- a/src/calibre/utils/podofo/outline.cpp +++ b/src/calibre/utils/podofo/outline.cpp @@ -6,6 +6,7 @@ */ #include "global.h" +#include using namespace pdf; @@ -45,43 +46,36 @@ erase(PDFOutlineItem *self, PyObject *args) { static PyObject * create(PDFOutlineItem *self, PyObject *args) { PyObject *as_child; - PDFOutlineItem *ans; + PDFOutlineItem *ans = NULL; unsigned int num; double left = 0, top = 0, zoom = 0; - PdfPage *page; PyObject *title_buf; if (!PyArg_ParseTuple(args, "UIO|ddd", &title_buf, &num, &as_child, &left, &top, &zoom)) return NULL; ans = PyObject_New(PDFOutlineItem, &PDFOutlineItemType); - if (ans == NULL) goto error; + if (ans == NULL) return NULL; ans->doc = self->doc; + pyunique_ptr decref_ans_on_exit((PyObject*)ans); try { PdfString title = podofo_convert_pystring(title_buf); - try { - page = self->doc->GetPage(num - 1); - } catch(const PdfError &err) { (void)err; page = NULL; } - if (page == NULL) { PyErr_Format(PyExc_ValueError, "Invalid page number: %u", num); goto error; } - PdfDestination dest(page, left, top, zoom); + const PdfPage *page = get_page(self->doc, num - 1); + if (!page) { PyErr_Format(PyExc_ValueError, "Invalid page number: %u", num); return NULL; } + auto dest = std::make_shared(*page, left, top, zoom); if (PyObject_IsTrue(as_child)) { ans->item = self->item->CreateChild(title, dest); } else ans->item = self->item->CreateNext(title, dest); } catch (const PdfError &err) { - podofo_set_exception(err); goto error; + podofo_set_exception(err); return NULL; } catch(const std::exception & err) { - PyErr_Format(PyExc_ValueError, "An error occurred while trying to create the outline: %s", err.what()); - goto error; + PyErr_Format(PyExc_ValueError, "An error occurred while trying to create the outline: %s", err.what()); return NULL; } catch (...) { - PyErr_SetString(PyExc_Exception, "An unknown error occurred while trying to create the outline item"); - goto error; + PyErr_SetString(PyExc_Exception, "An unknown error occurred while trying to create the outline item"); return NULL; } - return (PyObject*) ans; -error: - Py_XDECREF(ans); - return NULL; + return (PyObject*) decref_ans_on_exit.release(); } static PyMethodDef methods[] = { diff --git a/src/calibre/utils/podofo/outlines.cpp b/src/calibre/utils/podofo/outlines.cpp index 674cf5d432..c8ef0999e0 100644 --- a/src/calibre/utils/podofo/outlines.cpp +++ b/src/calibre/utils/podofo/outlines.cpp @@ -15,43 +15,37 @@ create_outline(PDFDoc *self, PyObject *args) { PyObject *title_buf; unsigned int pagenum; double left = 0, top = 0, zoom = 0; - PdfPage *page; if (!PyArg_ParseTuple(args, "UI|ddd", &title_buf, &pagenum, &left, &top, &zoom)) return NULL; ans = PyObject_New(PDFOutlineItem, &PDFOutlineItemType); - if (ans == NULL) goto error; + if (ans == NULL) return NULL; + pyunique_ptr decref_ans_on_exit((PyObject*)ans); try { PdfString title = podofo_convert_pystring(title_buf); PdfOutlines *outlines = self->doc->GetOutlines(); - if (outlines == NULL) {PyErr_NoMemory(); goto error;} + if (outlines == NULL) {PyErr_NoMemory(); return NULL;} ans->item = outlines->CreateRoot(title); - if (ans->item == NULL) {PyErr_NoMemory(); goto error;} + if (ans->item == NULL) {PyErr_NoMemory(); return NULL;} ans->doc = self->doc; - try { - page = self->doc->GetPage(pagenum - 1); - } catch (const PdfError &err) { - (void)err; - PyErr_Format(PyExc_ValueError, "Invalid page number: %u", pagenum - 1); goto error; + auto page = get_page(self->doc, pagenum -1); + if (!page) { + PyErr_Format(PyExc_ValueError, "Invalid page number: %u", pagenum - 1); return NULL; } - PdfDestination dest(page, left, top, zoom); + auto dest = std::make_shared(*page, left, top, zoom); ans->item->SetDestination(dest); } catch(const PdfError & err) { - podofo_set_exception(err); goto error; + podofo_set_exception(err); return NULL; } catch(const std::exception & err) { PyErr_Format(PyExc_ValueError, "An error occurred while trying to create the outline: %s", err.what()); - goto error; + return NULL; } catch (...) { PyErr_SetString(PyExc_ValueError, "An unknown error occurred while trying to create the outline"); - goto error; + return NULL; } - return (PyObject*)ans; -error: - Py_XDECREF(ans); - return NULL; - + return decref_ans_on_exit.release(); } static PyObject* @@ -71,9 +65,9 @@ convert_outline(PDFDoc *self, PyObject *parent, PdfOutlineItem *item) { pyunique_ptr node(create_outline_node()); if (!node) return; if (PyDict_SetItemString(node.get(), "title", title.get()) != 0) return; - PdfDestination* dest = item->GetDestination(self->doc); + auto dest = item->GetDestination(); if (dest) { - PdfPage *page = dest->GetPage(self->doc); + PdfPage *page = dest->GetPage(); long pnum = page ? page->GetPageNumber() : -1; pyunique_ptr d(Py_BuildValue("{sl sd sd sd}", "page", pnum, "top", dest->GetTop(), "left", dest->GetLeft(), "zoom", dest->GetZoom())); if (!d) return; @@ -95,7 +89,7 @@ convert_outline(PDFDoc *self, PyObject *parent, PdfOutlineItem *item) { static PyObject * get_outline(PDFDoc *self, PyObject *args) { - PdfOutlines *root = self->doc->GetOutlines(PoDoFo::ePdfDontCreateObject); + PdfOutlines *root = self->doc->GetOutlines(); if (!root || !root->First()) Py_RETURN_NONE; PyObject *ans = create_outline_node(); if (!ans) return NULL; diff --git a/src/calibre/utils/podofo/output.cpp b/src/calibre/utils/podofo/output.cpp index b464833b84..492bee16ef 100644 --- a/src/calibre/utils/podofo/output.cpp +++ b/src/calibre/utils/podofo/output.cpp @@ -10,11 +10,12 @@ using namespace PoDoFo; #define NUKE(x) { Py_XDECREF(x); x = NULL; } +#define PODOFO_RAISE_ERROR(code) throw ::PoDoFo::PdfError(code, __FILE__, __LINE__) class pyerr : public std::exception { }; -class OutputDevice : public PdfOutputDevice { +class MyOutputDevice : public OutputStreamDevice { private: PyObject *tell_func; @@ -26,12 +27,13 @@ class OutputDevice : public PdfOutputDevice { void update_written() { size_t pos; - pos = Tell(); + pos = GetPosition(); if (pos > written) written = pos; } public: - OutputDevice(PyObject *file) : tell_func(0), seek_func(0), read_func(0), write_func(0), flush_func(0), written(0) { + MyOutputDevice(PyObject *file) : tell_func(0), seek_func(0), read_func(0), write_func(0), flush_func(0), written(0) { + SetAccess(DeviceAccess::Write); #define GA(f, a) { if((f = PyObject_GetAttrString(file, a)) == NULL) throw pyerr(); } GA(tell_func, "tell"); GA(seek_func, "seek"); @@ -39,7 +41,7 @@ class OutputDevice : public PdfOutputDevice { GA(write_func, "write"); GA(flush_func, "flush"); } - ~OutputDevice() { + ~MyOutputDevice() { NUKE(tell_func); NUKE(seek_func); NUKE(read_func); NUKE(write_func); NUKE(flush_func); } @@ -47,7 +49,7 @@ class OutputDevice : public PdfOutputDevice { long PrintVLen(const char* pszFormat, va_list args) { - if( !pszFormat ) { PODOFO_RAISE_ERROR( ePdfError_InvalidHandle ); } + if( !pszFormat ) { PODOFO_RAISE_ERROR(PdfErrorCode::InvalidHandle); } #ifdef _MSC_VER return _vscprintf(pszFormat, args) + 1; @@ -60,7 +62,7 @@ class OutputDevice : public PdfOutputDevice { char *buf; int res; - if( !pszFormat ) { PODOFO_RAISE_ERROR( ePdfError_InvalidHandle ); } + if( !pszFormat ) { PODOFO_RAISE_ERROR(PdfErrorCode::InvalidHandle); } buf = new (std::nothrow) char[lBytes+1]; if (buf == NULL) { PyErr_NoMemory(); throw pyerr(); } @@ -129,7 +131,7 @@ class OutputDevice : public PdfOutputDevice { Py_DECREF(ret); } - size_t Tell() const { + size_t GetPosition() const { PyObject *ret; unsigned long ans; @@ -151,7 +153,9 @@ class OutputDevice : public PdfOutputDevice { return static_cast(ans); } - void Write(const char* pBuffer, size_t lLen) { + bool Eof() const { return false; } + + void writeBuffer(const char* pBuffer, size_t lLen) { PyObject *ret, *temp = NULL; temp = PyBytes_FromStringAndSize(pBuffer, static_cast(lLen)); @@ -177,10 +181,10 @@ class OutputDevice : public PdfOutputDevice { PyObject* pdf::write_doc(PdfMemDocument *doc, PyObject *f) { - OutputDevice d(f); + MyOutputDevice d(f); try { - doc->Write(&d); + doc->Save(d); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; } catch (...) { diff --git a/src/calibre/utils/podofo/podofo.cpp b/src/calibre/utils/podofo/podofo.cpp index 7769f9e1a6..4be6c410b9 100644 --- a/src/calibre/utils/podofo/podofo.cpp +++ b/src/calibre/utils/podofo/podofo.cpp @@ -10,30 +10,6 @@ using namespace PoDoFo; PyObject *pdf::Error = NULL; -class PyLogMessage : public PdfError::LogMessageCallback { - - public: - ~PyLogMessage() {} - - void LogMessage(ELogSeverity severity, const char* prefix, const char* msg, va_list & args ) { - if (severity > eLogSeverity_Warning) return; - if (prefix) - fprintf(stderr, "%s", prefix); - - vfprintf(stderr, msg, args); - } - - void LogMessage(ELogSeverity severity, const wchar_t* prefix, const wchar_t* msg, va_list & args ) { - if (severity > eLogSeverity_Warning) return; - if (prefix) - fwprintf(stderr, prefix); - - vfwprintf(stderr, msg, args); - } -}; - -PyLogMessage log_message; - static char podofo_doc[] = "Wrapper for the PoDoFo PDF library"; static int @@ -45,9 +21,6 @@ exec_module(PyObject *m) { if (pdf::Error == NULL) return -1; PyModule_AddObject(m, "Error", pdf::Error); - PdfError::SetLogMessageCallback((PdfError::LogMessageCallback*)&log_message); - PdfError::EnableDebug(false); - Py_INCREF(&pdf::PDFDocType); PyModule_AddObject(m, "PDFDoc", (PyObject *)&pdf::PDFDocType); return 0; diff --git a/src/calibre/utils/podofo/utils.cpp b/src/calibre/utils/podofo/utils.cpp index 1f706b6386..8db22fc29f 100644 --- a/src/calibre/utils/podofo/utils.cpp +++ b/src/calibre/utils/podofo/utils.cpp @@ -6,29 +6,28 @@ */ #include "global.h" +#include using namespace pdf; void pdf::podofo_set_exception(const PdfError &err) { - const char *msg = PdfError::ErrorMessage(err.GetError()); - if (msg == NULL) msg = err.what(); + const char *msg = err.what(); std::stringstream stream; stream << msg << "\n"; - const TDequeErrorInfo &s = err.GetCallstack(); - for (TDequeErrorInfo::const_iterator it = s.begin(); it != s.end(); it++) { - const PdfErrorInfo &info = (*it); - stream << "File: " << info.GetFilename() << " Line: " << info.GetLine() << " " << info.GetInformation() << "\n"; + const PdErrorInfoStack &s = err.GetCallStack(); + for (auto info : s) { + stream << "File: " << info.GetFilePath() << " Line: " << info.GetLine() << " " << info.GetInformation() << "\n"; } PyErr_SetString(Error, stream.str().c_str()); } PyObject * pdf::podofo_convert_pdfstring(const PdfString &s) { - return PyUnicode_FromString(s.GetStringUtf8().c_str()); + return PyUnicode_FromString(s.GetString().c_str()); } const PdfString pdf::podofo_convert_pystring(PyObject *val) { - return PdfString(reinterpret_cast(PyUnicode_AsUTF8(val))); + return PdfString(reinterpret_cast(PyUnicode_AsUTF8(val))); } From 2891687f6aee19ee3cc99696c74b4378cb2d2268 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 11 May 2023 13:57:50 +0530 Subject: [PATCH 0706/2055] Fix the bytes output device --- src/calibre/utils/podofo/__init__.py | 11 ++++++----- src/calibre/utils/podofo/doc.cpp | 22 +++++++++++++++++----- src/calibre/utils/podofo/output.cpp | 27 +++++++++++++-------------- src/calibre/utils/podofo/utils.cpp | 6 +++++- 4 files changed, 41 insertions(+), 25 deletions(-) diff --git a/src/calibre/utils/podofo/__init__.py b/src/calibre/utils/podofo/__init__.py index 1c315145d4..140e0124b3 100644 --- a/src/calibre/utils/podofo/__init__.py +++ b/src/calibre/utils/podofo/__init__.py @@ -189,7 +189,6 @@ def test_save_to(src, dest): def test_podofo(): import tempfile - from io import BytesIO from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.xmp import metadata_to_xmp_packet # {{{ @@ -203,11 +202,13 @@ def test_podofo(): p.title = mi.title p.author = mi.authors[0] p.set_xmp_metadata(xmp_packet) - buf = BytesIO() - p.save_to_fileobj(buf) - raw = buf.getvalue() with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as f: - f.write(raw) + p.save_to_fileobj(f) + f.seek(0) + fraw = f.read() + wraw = p.write() + if fraw != wraw: + raise ValueError("write() and save_to_fileobj() resulted in different output") try: p = podofo.PDFDoc() p.open(f.name) diff --git a/src/calibre/utils/podofo/doc.cpp b/src/calibre/utils/podofo/doc.cpp index 1b9eeac68b..9272fc5739 100644 --- a/src/calibre/utils/podofo/doc.cpp +++ b/src/calibre/utils/podofo/doc.cpp @@ -8,6 +8,7 @@ #include "global.h" #include #include +#include #include using namespace pdf; @@ -97,7 +98,7 @@ class BytesOutputDevice : public OutputStreamDevice { pyunique_ptr bytes; size_t written; public: - BytesOutputDevice() : bytes(PyBytes_FromStringAndSize(NULL, 1 * 1024 *1024)) { SetAccess(DeviceAccess::Write); } + BytesOutputDevice() : bytes(), written(0) { SetAccess(DeviceAccess::Write); } size_t GetLength() const { return written; } size_t GetPosition() const { return written; } size_t capacity() const { return bytes ? PyBytes_GET_SIZE(bytes.get()) : 0; } @@ -106,19 +107,30 @@ class BytesOutputDevice : public OutputStreamDevice { void writeBuffer(const char* src, size_t src_sz) { if (written + src_sz > capacity()) { PyObject* old = bytes.release(); - if (_PyBytes_Resize(&old, std::max(written + src_sz, 2 * capacity())) != 0) { - return; + static const size_t initial_capacity = 1024 * 1024; + if (old) { + if (_PyBytes_Resize(&old, std::max(written + src_sz, 2 * std::max(capacity(), initial_capacity))) != 0) { + throw std::bad_alloc(); + } + } else { + old = PyBytes_FromStringAndSize(NULL, std::max(written + src_sz, initial_capacity)); + if (!old) throw std::bad_alloc(); } bytes.reset(old); } if (bytes) { - memcpy(PyBytes_AS_STRING(bytes.get()), src, src_sz); + memcpy(PyBytes_AS_STRING(bytes.get()) + written, src, src_sz); written += src_sz; } } void Flush() { } - PyObject* Release() { return bytes.release(); } + PyObject* Release() { + auto ans = bytes.release(); + _PyBytes_Resize(&ans, written); + written = 0; + return ans; + } }; static PyObject * diff --git a/src/calibre/utils/podofo/output.cpp b/src/calibre/utils/podofo/output.cpp index 492bee16ef..a70c8bc26d 100644 --- a/src/calibre/utils/podofo/output.cpp +++ b/src/calibre/utils/podofo/output.cpp @@ -12,8 +12,6 @@ using namespace PoDoFo; #define NUKE(x) { Py_XDECREF(x); x = NULL; } #define PODOFO_RAISE_ERROR(code) throw ::PoDoFo::PdfError(code, __FILE__, __LINE__) -class pyerr : public std::exception { -}; class MyOutputDevice : public OutputStreamDevice { @@ -34,7 +32,7 @@ class MyOutputDevice : public OutputStreamDevice { public: MyOutputDevice(PyObject *file) : tell_func(0), seek_func(0), read_func(0), write_func(0), flush_func(0), written(0) { SetAccess(DeviceAccess::Write); -#define GA(f, a) { if((f = PyObject_GetAttrString(file, a)) == NULL) throw pyerr(); } +#define GA(f, a) { if((f = PyObject_GetAttrString(file, a)) == NULL) throw std::exception(); } GA(tell_func, "tell"); GA(seek_func, "seek"); GA(read_func, "read"); @@ -65,7 +63,7 @@ class MyOutputDevice : public OutputStreamDevice { if( !pszFormat ) { PODOFO_RAISE_ERROR(PdfErrorCode::InvalidHandle); } buf = new (std::nothrow) char[lBytes+1]; - if (buf == NULL) { PyErr_NoMemory(); throw pyerr(); } + if (buf == NULL) { PyErr_NoMemory(); throw std::exception(); } // Note: PyOS_vsnprintf produces broken output on windows res = vsnprintf(buf, lBytes, pszFormat, args); @@ -73,7 +71,7 @@ class MyOutputDevice : public OutputStreamDevice { if (res < 0) { PyErr_SetString(PyExc_Exception, "Something bad happened while calling vsnprintf"); delete[] buf; - throw pyerr(); + throw std::exception(); } Write(buf, static_cast(res)); @@ -99,7 +97,7 @@ class MyOutputDevice : public OutputStreamDevice { char *buf = NULL; Py_ssize_t len = 0; - if ((temp = PyLong_FromSize_t(lLen)) == NULL) throw pyerr(); + if ((temp = PyLong_FromSize_t(lLen)) == NULL) throw std::exception(); ret = PyObject_CallFunctionObjArgs(read_func, temp, NULL); NUKE(temp); if (ret != NULL) { @@ -114,19 +112,19 @@ class MyOutputDevice : public OutputStreamDevice { if (PyErr_Occurred() == NULL) PyErr_SetString(PyExc_Exception, "Failed to read data from python file object"); - throw pyerr(); + throw std::exception(); } void Seek(size_t offset) { PyObject *ret, *temp; - if ((temp = PyLong_FromSize_t(offset)) == NULL) throw pyerr(); + if ((temp = PyLong_FromSize_t(offset)) == NULL) throw std::exception(); ret = PyObject_CallFunctionObjArgs(seek_func, temp, NULL); NUKE(temp); if (ret == NULL) { if (PyErr_Occurred() == NULL) PyErr_SetString(PyExc_Exception, "Failed to seek in python file object"); - throw pyerr(); + throw std::exception(); } Py_DECREF(ret); } @@ -139,16 +137,16 @@ class MyOutputDevice : public OutputStreamDevice { if (ret == NULL) { if (PyErr_Occurred() == NULL) PyErr_SetString(PyExc_Exception, "Failed to call tell() on python file object"); - throw pyerr(); + throw std::exception(); } if (!PyNumber_Check(ret)) { Py_DECREF(ret); PyErr_SetString(PyExc_Exception, "tell() method did not return a number"); - throw pyerr(); + throw std::exception(); } ans = PyLong_AsUnsignedLongMask(ret); Py_DECREF(ret); - if (PyErr_Occurred() != NULL) throw pyerr(); + if (PyErr_Occurred() != NULL) throw std::exception(); return static_cast(ans); } @@ -159,7 +157,7 @@ class MyOutputDevice : public OutputStreamDevice { PyObject *ret, *temp = NULL; temp = PyBytes_FromStringAndSize(pBuffer, static_cast(lLen)); - if (temp == NULL) throw pyerr(); + if (temp == NULL) throw std::exception(); ret = PyObject_CallFunctionObjArgs(write_func, temp, NULL); NUKE(temp); @@ -167,7 +165,7 @@ class MyOutputDevice : public OutputStreamDevice { if (ret == NULL) { if (PyErr_Occurred() == NULL) PyErr_SetString(PyExc_Exception, "Failed to call write() on python file object"); - throw pyerr(); + throw std::exception(); } Py_DECREF(ret); update_written(); @@ -185,6 +183,7 @@ PyObject* pdf::write_doc(PdfMemDocument *doc, PyObject *f) { try { doc->Save(d); + d.Flush(); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; } catch (...) { diff --git a/src/calibre/utils/podofo/utils.cpp b/src/calibre/utils/podofo/utils.cpp index 8db22fc29f..ea11132025 100644 --- a/src/calibre/utils/podofo/utils.cpp +++ b/src/calibre/utils/podofo/utils.cpp @@ -7,6 +7,7 @@ #include "global.h" #include +#include using namespace pdf; @@ -29,5 +30,8 @@ pdf::podofo_convert_pdfstring(const PdfString &s) { const PdfString pdf::podofo_convert_pystring(PyObject *val) { - return PdfString(reinterpret_cast(PyUnicode_AsUTF8(val))); + Py_ssize_t len; + const char *data = PyUnicode_AsUTF8AndSize(val, &len); + if (data == NULL) throw std::runtime_error("Failed to convert python string to UTF-8, possibly not a string object"); + return PdfString::FromRaw(bufferview(data, len)); } From 696afc4410c56d8298f0853e9b66559e21bafde4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 11 May 2023 16:20:09 +0530 Subject: [PATCH 0707/2055] Improve the test for setting XMP metadata --- src/calibre/utils/podofo/__init__.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/calibre/utils/podofo/__init__.py b/src/calibre/utils/podofo/__init__.py index 140e0124b3..ae6e5992eb 100644 --- a/src/calibre/utils/podofo/__init__.py +++ b/src/calibre/utils/podofo/__init__.py @@ -195,13 +195,11 @@ def test_podofo(): raw = b"%PDF-1.1\n%\xe2\xe3\xcf\xd3\n1 0 obj<>\nendobj\n2 0 obj<>\nendobj\n3 0 obj<>>>>>>>\nendobj\n4 0 obj<>\nstream\n BT\n /F1 18 Tf\n 0 0 Td\n (Hello World) Tj\n ET\nendstream\nendobj\n5 0 obj<>\nendobj\n6 0 obj<>\nstream\nx\x9c\xed\x98\xcd\xb2\x930\x14\xc7\xf7}\n&.\x1d\x1ahoGa\x80\x8e\xb6\xe3x\x17ua\xaf\xe3\xd2\t\xc9i\x1b\x0b\x81&a\xc0\xfbj.|$_\xc1\xd0r\xe9\xb7V\x9d\xbb\x83\x15\x9c\x9c\xff\xff\x97\x8fs\xb2 \x18W9\xa1k\xd0V\x0cK.B\xf4\xf3\xfb\x0fdq\x16\xa2\xcf\xa3\x993\xcb'\xb0\xe2\xef\x1f%\xcc\x1f?<\xd0\xc75\xf5\x18\x1aG\xbd\xa0\xf2\xab4OA\x13\xabJ\x13\xa1\xfc*D\x84e1\xf8\xe6\xbd\x0ec\x14\xf5,+\x90l\xe1\x7f\x9c\xbek\x92\xccW\x88VZ\xe7>\xc6eY\xf6\xcba?\x93K\xecz\x9e\x87\x9d\x01\x1e\x0cl\x93a\xaboB\x93\xca\x16\xea\xc5\xd6\xa3q\x99\x82\xa2\x92\xe7\x9ag\xa2qc\xb45\xcb\x0b\x99l\xad\x18\xc5\x90@\nB+\xec\xf6]\x8c\xacZK\xe2\xac\xd0!j\xec\x8c!\xa3>\xdb\xfb=\x85\x1b\xd2\x9bD\xef#M,\xe15\xd4O\x88X\x86\xa8\xb2\x19,H\x91h\x14\x05x7z`\x81O<\x02|\x99VOBs\x9d\xc0\x7f\xe0\x05\x94\xfa\xd6)\x1c\xb1jx^\xc4\tW+\x90'\x13xK\x96\xf8Hy\x96X\xabU\x11\x7f\x05\xaa\xff\xa4=I\xab\x95T\x02\xd1\xd9)u\x0e\x9b\x0b\xcb\x8e>\x89\xb5\xc8Jqm\x91\x07\xaa-\xee\xc8{\x972=\xdd\xfa+\xe5d\xea\xb9\xad'\xa1\xfa\xdbj\xee\xd3,\xc5\x15\xc9M-9\xa6\x96\xdaD\xce6Wr\xd3\x1c\xdf3S~|\xc1A\xe2MA\x92F{\xb1\x0eM\xba?3\xdd\xc2\x88&S\xa2!\x1a8\xee\x9d\xedx\xb6\xeb=\xb8C\xff\xce\xf1\x87\xaf\xfb\xde\xe0\xd5\xc8\xf3^:#\x7f\xe8\x04\xf8L\xf2\x0fK\xcd%W\xe9\xbey\xea/\xa5\x89`D\xb2m\x17\t\x92\x822\xb7\x02(\x1c\x13\xc5)\x1e\x9c-\x01\xff\x1e\xc0\x16\xd5\xe5\r\xaaG\xcc\x8e\x0c\xff\xca\x8e\x92\x84\xc7\x12&\x93\xd6\xb3\x89\xd8\x10g\xd9\xfai\xe7\xedv\xde6-\x94\xceR\x9bfI\x91\n\x85\x8e}nu9\x91\xcd\xefo\xc6+\x90\x1c\x94\xcd\x05\x83\xea\xca\xd17\x16\xbb\xb6\xfc\xa22\xa9\x9bn\xbe0p\xfd\x88wAs\xc3\x9a+\x19\xb7w\xf2a#=\xdf\xd3A:H\x07\xe9 \x1d\xa4\x83t\x90\x0e\xd2A:H\x07yNH/h\x7f\xd6\x80`!*\xd18\xfa\x05\x94\x80P\xb0\nendstream\nendobj\nxref\n0 7\n0000000000 65535 f \n0000000015 00000 n \n0000000074 00000 n \n0000000148 00000 n \n0000000280 00000 n \n0000000382 00000 n \n0000000522 00000 n \ntrailer\n<<4D028D512DEBEFD964756764AD8FF726>]/Info 5 0 R/Root 1 0 R/Size 7>>\nstartxref\n1199\n%%EOF\n" # noqa # }}} mi = Metadata('title1', ['author1']) - xmp_packet = metadata_to_xmp_packet(mi) podofo = get_podofo() p = podofo.PDFDoc() p.load(raw) p.title = mi.title p.author = mi.authors[0] - p.set_xmp_metadata(xmp_packet) with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as f: p.save_to_fileobj(f) f.seek(0) @@ -215,8 +213,15 @@ def test_podofo(): if (p.title, p.author) != (mi.title, mi.authors[0]): raise ValueError('podofo failed to set title and author in Info dict {} != {}'.format( (p.title, p.author), (mi.title, mi.authors[0]))) - if not p.get_xmp_metadata(): - raise ValueError('podofo failed to write XMP packet') + xmp_packet = metadata_to_xmp_packet(mi) + xmp_packet = xmp_packet.replace(b'author1', b'changed_author') + p.set_xmp_metadata(xmp_packet) + raw = p.write() + p = podofo.PDFDoc() + p.load(raw) + xmp = p.get_xmp_metadata().decode() + if 'changed_author' not in xmp: + raise ValueError('Failed to set XML block, received:', xmp) del p finally: os.remove(f.name) From bea63dc6c468deb4b0a559070adc33888b9561c3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 11 May 2023 16:27:57 +0530 Subject: [PATCH 0708/2055] Fix delete_pages() --- src/calibre/utils/podofo/doc.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/calibre/utils/podofo/doc.cpp b/src/calibre/utils/podofo/doc.cpp index 9272fc5739..9129f6c816 100644 --- a/src/calibre/utils/podofo/doc.cpp +++ b/src/calibre/utils/podofo/doc.cpp @@ -239,12 +239,11 @@ PDFDoc_image_count(PDFDoc *self, PyObject *args) { // delete_page() {{{ static PyObject * PDFDoc_delete_pages(PDFDoc *self, PyObject *args) { - int page = 0, count = 1; - if (PyArg_ParseTuple(args, "i|i", &page, &count)) { + unsigned int page, count = 1; + if (PyArg_ParseTuple(args, "I|I", &page, &count)) { try { - while (count > 0) { - self->doc->GetPages().RemovePageAt(page - 1); - } + auto &pages = self->doc->GetPages(); + while (count-- > 0) pages.RemovePageAt(page - 1); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; From 39d27b62457329d49ab02e43ba267b2574f2c9d6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 11 May 2023 16:45:40 +0530 Subject: [PATCH 0709/2055] Restore suppression of informational log messages from PoDoFo --- src/calibre/utils/podofo/podofo.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/calibre/utils/podofo/podofo.cpp b/src/calibre/utils/podofo/podofo.cpp index 4be6c410b9..ba9216b390 100644 --- a/src/calibre/utils/podofo/podofo.cpp +++ b/src/calibre/utils/podofo/podofo.cpp @@ -7,11 +7,20 @@ using namespace PoDoFo; #include "global.h" +#include PyObject *pdf::Error = NULL; static char podofo_doc[] = "Wrapper for the PoDoFo PDF library"; +static void +pdf_log_message(PdfLogSeverity logSeverity, const std::string_view& msg) { + if (logSeverity == PdfLogSeverity::Error || logSeverity == PdfLogSeverity::Warning) { + const char *level = logSeverity == PdfLogSeverity::Error ? "ERROR" : "WARNING"; + std::cerr << "PoDoFo" << level << ": " << msg << std::endl; + } +} + static int exec_module(PyObject *m) { if (PyType_Ready(&pdf::PDFDocType) < 0) return -1; @@ -23,6 +32,8 @@ exec_module(PyObject *m) { Py_INCREF(&pdf::PDFDocType); PyModule_AddObject(m, "PDFDoc", (PyObject *)&pdf::PDFDocType); + + PdfCommon::SetLogMessageCallback(pdf_log_message); return 0; } From 226718cead1632eb135d0abc21b50e2823559192 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 12 May 2023 12:47:20 +0530 Subject: [PATCH 0710/2055] Fix PdfObject::GetReference semantics changed It now raises an exception if the object is not a reference. Instead we have to call GetIndirectReference on it. ... --- src/calibre/utils/podofo/doc.cpp | 20 +++++++------- src/calibre/utils/podofo/fonts.cpp | 41 ++++++++++++++--------------- src/calibre/utils/podofo/global.h | 9 +++++++ src/calibre/utils/podofo/images.cpp | 4 +-- 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/src/calibre/utils/podofo/doc.cpp b/src/calibre/utils/podofo/doc.cpp index 9129f6c816..c38dbaa668 100644 --- a/src/calibre/utils/podofo/doc.cpp +++ b/src/calibre/utils/podofo/doc.cpp @@ -427,7 +427,7 @@ PDFDoc_extract_anchors(PDFDoc *self, PyObject *args) { const PdfObject *dests_ref = self->doc->GetCatalog().GetDictionary().GetKey("Dests"); auto& pages = self->doc->GetPages(); if (dests_ref && dests_ref->IsReference()) { - const PdfObject *dests_obj = self->doc->GetObjects().GetObject(dests_ref->GetReference()); + const PdfObject *dests_obj = self->doc->GetObjects().GetObject(object_as_reference(dests_ref)); if (dests_obj && dests_obj->IsDictionary()) { const PdfDictionary &dests = dests_obj->GetDictionary(); for (auto itres: dests) { @@ -436,7 +436,7 @@ PDFDoc_extract_anchors(PDFDoc *self, PyObject *args) { // see section 8.2 of PDF spec for different types of destination arrays // but chromium apparently generates only [page /XYZ left top zoom] type arrays if (dest.GetSize() > 4 && dest[1].IsName() && dest[1].GetName().GetString() == "XYZ") { - const PdfPage *page = get_page(pages, dest[0].GetReference()); + const PdfPage *page = get_page(pages, object_as_reference(dest[0])); if (page) { unsigned int pagenum = page->GetPageNumber(); double left = dest[2].GetReal(), top = dest[3].GetReal(); @@ -503,15 +503,15 @@ PDFDoc_alter_links(PDFDoc *self, PyObject *args) { link_color.Add(1.); link_color.Add(0.); link_color.Add(0.); std::vector links; for (auto &it : self->doc->GetObjects()) { - if(it->IsDictionary()) { - PdfDictionary &link = it->GetDictionary(); - if (dictionary_has_key_name(link, PdfName::KeyType, "Annot") && dictionary_has_key_name(link, PdfName::KeySubtype, "Link")) { - if (link.HasKey("A") && link.GetKey("A")->IsDictionary()) { - PdfDictionary &A = link.GetKey("A")->GetDictionary(); - if (dictionary_has_key_name(A, PdfName::KeyType, "Action") && dictionary_has_key_name(A, "S", "URI")) { - PdfObject *uo = A.GetKey("URI"); + PdfDictionary *link; + if(it->TryGetDictionary(link)) { + if (dictionary_has_key_name(*link, PdfName::KeyType, "Annot") && dictionary_has_key_name(*link, PdfName::KeySubtype, "Link")) { + PdfObject *akey; PdfDictionary *A; + if ((akey = link->GetKey("A")) && akey->TryGetDictionary(A)) { + if (dictionary_has_key_name(*A, PdfName::KeyType, "Action") && dictionary_has_key_name(*A, "S", "URI")) { + PdfObject *uo = A->GetKey("URI"); if (uo && uo->IsString()) { - links.push_back(it->GetReference()); + links.push_back(object_as_reference(it)); } } } diff --git a/src/calibre/utils/podofo/fonts.cpp b/src/calibre/utils/podofo/fonts.cpp index ba673f4d92..14ccca6b35 100644 --- a/src/calibre/utils/podofo/fonts.cpp +++ b/src/calibre/utils/podofo/fonts.cpp @@ -50,11 +50,11 @@ remove_font(PdfIndirectObjectList &objects, PdfObject *font) { PdfObject *descriptor = dict->FindKey("FontDescriptor"); if (descriptor) { const PdfObject *ff = get_font_file(descriptor); - if (ff) objects.RemoveObject(ff->GetReference()).reset(); - objects.RemoveObject(descriptor->GetReference()).reset(); + if (ff) objects.RemoveObject(object_as_reference(ff)).reset(); + objects.RemoveObject(object_as_reference(descriptor)).reset(); } } - objects.RemoveObject(font->GetReference()).reset(); + objects.RemoveObject(object_as_reference(font)).reset(); } static void @@ -86,9 +86,8 @@ used_fonts_in_canvas(const PdfCanvas &canvas, unordered_reference_set &ans) { stack.pop(); if (stack.size() > 0 && stack.top().IsName()) { const PdfName &reference_name = stack.top().GetName(); - if (fonts_dict.HasKey(reference_name)) { - ans.insert(fonts_dict.GetKey(reference_name)->GetReference()); - } + const PdfObject *f = fonts_dict.GetKey(reference_name); + if (f) ans.insert(object_as_reference(f)); } } } @@ -127,7 +126,7 @@ list_fonts(PDFDoc *self, PyObject *args) { if (dictionary_has_key_name(dict, PdfName::KeyType, "Font") && dict.HasKey("BaseFont")) { const std::string &name = dict.GetKey("BaseFont")->GetName().GetString(); const std::string &subtype = dict.GetKey(PdfName::KeySubtype)->GetName().GetString(); - const PdfReference &ref = it->GetReference(); + const PdfReference &ref = object_as_reference(it); unsigned long num = ref.ObjectNumber(), generation = ref.GenerationNumber(); const PdfObject *descriptor = dict.FindKey("FontDescriptor"); pyunique_ptr descendant_font, stream_ref, encoding, w, w2; @@ -151,7 +150,7 @@ list_fonts(PDFDoc *self, PyObject *args) { if (descriptor) { const PdfObject *ff = get_font_file(descriptor); if (ff) { - stream_ref.reset(ref_as_tuple(ff->GetReference())); + stream_ref.reset(ref_as_tuple(object_as_reference(ff))); if (!stream_ref) return NULL; const PdfObjectStream *stream = ff->GetStream(); if (stream && get_font_data) { @@ -160,10 +159,10 @@ list_fonts(PDFDoc *self, PyObject *args) { } } else if (dict.HasKey("DescendantFonts")) { const PdfArray &df = dict.GetKey("DescendantFonts")->GetArray(); - descendant_font.reset(ref_as_tuple(df[0].GetReference())); + descendant_font.reset(ref_as_tuple(object_as_reference(df[0]))); if (!descendant_font) return NULL; if (get_font_data && dict.HasKey("ToUnicode")) { - const PdfReference &uref = dict.GetKey("ToUnicode")->GetReference(); + const PdfReference &uref = object_as_reference(dict.GetKey("ToUnicode")); PdfObject *t = objects.GetObject(uref); if (t) { PdfObjectStream *stream = t->GetStream(); @@ -225,12 +224,12 @@ remove_unused_fonts(PDFDoc *self, PyObject *args) { if (dictionary_has_key_name(dict, PdfName::KeyType, "Font")) { const std::string &font_type = dict.GetKey(PdfName::KeySubtype)->GetName().GetString(); if (font_type == "Type0") { - all_fonts.insert(k->GetReference()); + all_fonts.insert(object_as_reference(k)); } else if (font_type == "Type3") { - all_fonts.insert(k->GetReference()); - type3_fonts.insert(k->GetReference()); + all_fonts.insert(object_as_reference(k)); + type3_fonts.insert(object_as_reference(k)); for (auto &x : dict.GetKey("CharProcs")->GetDictionary()) { - const PdfReference &ref = x.second.GetReference(); + const PdfReference &ref = object_as_reference(x.second); if (charprocs_usage.find(ref) == charprocs_usage.end()) charprocs_usage[ref] = 1; else charprocs_usage[ref] += 1; } @@ -248,11 +247,11 @@ remove_unused_fonts(PDFDoc *self, PyObject *args) { if (font->TryGetDictionary(dict)) { if (type3_fonts.find(ref) != type3_fonts.end()) { for (auto &x : dict->FindKey("CharProcs")->GetDictionary()) { - charprocs_usage[x.second.GetReference()] -= 1; + charprocs_usage[object_as_reference(x.second)] -= 1; } } else { for (auto &x : dict->FindKey("DescendantFonts")->GetArray()) { - PdfObject *dfont = objects.GetObject(x.GetReference()); + PdfObject *dfont = objects.GetObject(object_as_reference(x)); if (dfont) remove_font(objects, dfont); } }} @@ -318,8 +317,8 @@ merge_fonts(PDFDoc *self, PyObject *args) { PdfObjectStream *stream = ff->GetStream(); stream->SetData(bufferview(data, sz)); } else { - objects.RemoveObject(ff->GetReference()).reset(); - descriptor.AddKey(font_file_key, font_file->GetReference()); + objects.RemoveObject(object_as_reference(ff)).reset(); + descriptor.AddKey(font_file_key, object_as_reference(font_file)); } } Py_RETURN_NONE; @@ -371,9 +370,9 @@ dedup_type3_fonts(PDFDoc *self, PyObject *args) { if (dictionary_has_key_name(dict, PdfName::KeyType, "Font")) { const std::string &font_type = dict.GetKey(PdfName::KeySubtype)->GetName().GetString(); if (font_type == "Type3") { - all_type3_fonts.insert(k->GetReference()); + all_type3_fonts.insert(object_as_reference(k)); for (auto &x : dict.GetKey("CharProcs")->GetDictionary()) { - const PdfReference &ref = x.second.GetReference(); + const PdfReference &ref = object_as_reference(x.second); const PdfObject *cpobj = objects.GetObject(ref); if (!cpobj || !cpobj->HasStream()) continue; CharProc cp(ref, cpobj); @@ -408,7 +407,7 @@ dedup_type3_fonts(PDFDoc *self, PyObject *args) { PdfDictionary new_dict = PdfDictionary(dict); bool changed = false; for (auto &k : dict) { - auto it = ref_map.find(k.second.GetReference()); + auto it = ref_map.find(object_as_reference(k.second)); if (it != ref_map.end()) { new_dict.AddKey(k.first, (*it).second); changed = true; diff --git a/src/calibre/utils/podofo/global.h b/src/calibre/utils/podofo/global.h index 77905af8a2..17cf64a8f9 100644 --- a/src/calibre/utils/podofo/global.h +++ b/src/calibre/utils/podofo/global.h @@ -119,6 +119,15 @@ get_page(PdfDocument *doc, const unsigned num) { return nullptr; } +static inline PdfReference +object_as_reference(const PdfObject &o) { + return o.IsReference() ? o.GetReference() : o.GetIndirectReference(); +} + +static inline PdfReference +object_as_reference(const PdfObject *o) { + return o->IsReference() ? o->GetReference() : o->GetIndirectReference(); +} class PdfReferenceHasher { diff --git a/src/calibre/utils/podofo/images.cpp b/src/calibre/utils/podofo/images.cpp index f5d948a33d..dc9cf32bd9 100644 --- a/src/calibre/utils/podofo/images.cpp +++ b/src/calibre/utils/podofo/images.cpp @@ -64,7 +64,7 @@ dedup_images(PDFDoc *self, PyObject *args) { if (!k->IsDictionary()) continue; const PdfDictionary &dict = k->GetDictionary(); if (dictionary_has_key_name(dict, PdfName::KeyType, "XObject") && dictionary_has_key_name(dict, PdfName::KeySubtype, "Image")) { - Image img(k->GetReference(), k); + Image img(object_as_reference(k), k); auto it = image_map.find(img); if (it == image_map.end()) { std::vector vals; @@ -99,7 +99,7 @@ dedup_images(PDFDoc *self, PyObject *args) { for (const auto &x : xobject) { if (x.second.IsReference()) { try { - const PdfReference &r = ref_map.at(x.second.GetReference()); + const PdfReference &r = ref_map.at(object_as_reference(x.second)); new_xobject.AddKey(x.first, r); changed = true; } catch (const std::out_of_range &err) { (void)err; continue; } From 0517092f52180e4aaaaba3723b187c2a10345fd1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 14 May 2023 07:35:01 +0530 Subject: [PATCH 0711/2055] Get PDF metadata setting working again Bypass the new podofo GetMetadata machinery as it completely clobbers custom XML metadata. --- src/calibre/utils/podofo/__init__.py | 27 ++++---- src/calibre/utils/podofo/doc.cpp | 100 ++++++++++++++------------- src/calibre/utils/podofo/global.h | 2 + src/calibre/utils/podofo/output.cpp | 2 +- 4 files changed, 69 insertions(+), 62 deletions(-) diff --git a/src/calibre/utils/podofo/__init__.py b/src/calibre/utils/podofo/__init__.py index ae6e5992eb..aae0c9ba82 100644 --- a/src/calibre/utils/podofo/__init__.py +++ b/src/calibre/utils/podofo/__init__.py @@ -71,7 +71,7 @@ def set_metadata_implementation(pdf_doc, title, authors, bkp, tags, xmp_packet): touched = True try: - tags = prep(', '.join([x.strip() for x in tags if x.strip()])) + tags = prep(', '.join(x.strip() for x in tags if x.strip())) if tags != pdf_doc.keywords: pdf_doc.keywords = tags touched = True @@ -194,12 +194,17 @@ def test_podofo(): # {{{ raw = b"%PDF-1.1\n%\xe2\xe3\xcf\xd3\n1 0 obj<>\nendobj\n2 0 obj<>\nendobj\n3 0 obj<>>>>>>>\nendobj\n4 0 obj<>\nstream\n BT\n /F1 18 Tf\n 0 0 Td\n (Hello World) Tj\n ET\nendstream\nendobj\n5 0 obj<>\nendobj\n6 0 obj<>\nstream\nx\x9c\xed\x98\xcd\xb2\x930\x14\xc7\xf7}\n&.\x1d\x1ahoGa\x80\x8e\xb6\xe3x\x17ua\xaf\xe3\xd2\t\xc9i\x1b\x0b\x81&a\xc0\xfbj.|$_\xc1\xd0r\xe9\xb7V\x9d\xbb\x83\x15\x9c\x9c\xff\xff\x97\x8fs\xb2 \x18W9\xa1k\xd0V\x0cK.B\xf4\xf3\xfb\x0fdq\x16\xa2\xcf\xa3\x993\xcb'\xb0\xe2\xef\x1f%\xcc\x1f?<\xd0\xc75\xf5\x18\x1aG\xbd\xa0\xf2\xab4OA\x13\xabJ\x13\xa1\xfc*D\x84e1\xf8\xe6\xbd\x0ec\x14\xf5,+\x90l\xe1\x7f\x9c\xbek\x92\xccW\x88VZ\xe7>\xc6eY\xf6\xcba?\x93K\xecz\x9e\x87\x9d\x01\x1e\x0cl\x93a\xaboB\x93\xca\x16\xea\xc5\xd6\xa3q\x99\x82\xa2\x92\xe7\x9ag\xa2qc\xb45\xcb\x0b\x99l\xad\x18\xc5\x90@\nB+\xec\xf6]\x8c\xacZK\xe2\xac\xd0!j\xec\x8c!\xa3>\xdb\xfb=\x85\x1b\xd2\x9bD\xef#M,\xe15\xd4O\x88X\x86\xa8\xb2\x19,H\x91h\x14\x05x7z`\x81O<\x02|\x99VOBs\x9d\xc0\x7f\xe0\x05\x94\xfa\xd6)\x1c\xb1jx^\xc4\tW+\x90'\x13xK\x96\xf8Hy\x96X\xabU\x11\x7f\x05\xaa\xff\xa4=I\xab\x95T\x02\xd1\xd9)u\x0e\x9b\x0b\xcb\x8e>\x89\xb5\xc8Jqm\x91\x07\xaa-\xee\xc8{\x972=\xdd\xfa+\xe5d\xea\xb9\xad'\xa1\xfa\xdbj\xee\xd3,\xc5\x15\xc9M-9\xa6\x96\xdaD\xce6Wr\xd3\x1c\xdf3S~|\xc1A\xe2MA\x92F{\xb1\x0eM\xba?3\xdd\xc2\x88&S\xa2!\x1a8\xee\x9d\xedx\xb6\xeb=\xb8C\xff\xce\xf1\x87\xaf\xfb\xde\xe0\xd5\xc8\xf3^:#\x7f\xe8\x04\xf8L\xf2\x0fK\xcd%W\xe9\xbey\xea/\xa5\x89`D\xb2m\x17\t\x92\x822\xb7\x02(\x1c\x13\xc5)\x1e\x9c-\x01\xff\x1e\xc0\x16\xd5\xe5\r\xaaG\xcc\x8e\x0c\xff\xca\x8e\x92\x84\xc7\x12&\x93\xd6\xb3\x89\xd8\x10g\xd9\xfai\xe7\xedv\xde6-\x94\xceR\x9bfI\x91\n\x85\x8e}nu9\x91\xcd\xefo\xc6+\x90\x1c\x94\xcd\x05\x83\xea\xca\xd17\x16\xbb\xb6\xfc\xa22\xa9\x9bn\xbe0p\xfd\x88wAs\xc3\x9a+\x19\xb7w\xf2a#=\xdf\xd3A:H\x07\xe9 \x1d\xa4\x83t\x90\x0e\xd2A:H\x07yNH/h\x7f\xd6\x80`!*\xd18\xfa\x05\x94\x80P\xb0\nendstream\nendobj\nxref\n0 7\n0000000000 65535 f \n0000000015 00000 n \n0000000074 00000 n \n0000000148 00000 n \n0000000280 00000 n \n0000000382 00000 n \n0000000522 00000 n \ntrailer\n<<4D028D512DEBEFD964756764AD8FF726>]/Info 5 0 R/Root 1 0 R/Size 7>>\nstartxref\n1199\n%%EOF\n" # noqa # }}} - mi = Metadata('title1', ['author1']) + mi = Metadata('title1', ['xmp_author']) podofo = get_podofo() p = podofo.PDFDoc() p.load(raw) - p.title = mi.title - p.author = mi.authors[0] + p.title = 'info title' + p.author = 'info author' + p.keywords = 'a, b' + xmp_packet = metadata_to_xmp_packet(mi) + # print(p.get_xmp_metadata().decode()) + p.set_xmp_metadata(xmp_packet) + # print(p.get_xmp_metadata().decode()) with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as f: p.save_to_fileobj(f) f.seek(0) @@ -210,18 +215,12 @@ def test_podofo(): try: p = podofo.PDFDoc() p.open(f.name) - if (p.title, p.author) != (mi.title, mi.authors[0]): + if (p.title, p.author, p.keywords) != ('info title', 'info author', 'a, b'): raise ValueError('podofo failed to set title and author in Info dict {} != {}'.format( - (p.title, p.author), (mi.title, mi.authors[0]))) - xmp_packet = metadata_to_xmp_packet(mi) - xmp_packet = xmp_packet.replace(b'author1', b'changed_author') - p.set_xmp_metadata(xmp_packet) - raw = p.write() - p = podofo.PDFDoc() - p.load(raw) + (p.title, p.author, p.keywords), ('info title', 'info author', 'a, b'))) xmp = p.get_xmp_metadata().decode() - if 'changed_author' not in xmp: - raise ValueError('Failed to set XML block, received:', xmp) + if 'xmp_author' not in xmp: + raise ValueError('Failed to set XML block, received:\n' + xmp) del p finally: os.remove(f.name) diff --git a/src/calibre/utils/podofo/doc.cpp b/src/calibre/utils/podofo/doc.cpp index c38dbaa668..1f6afacbe2 100644 --- a/src/calibre/utils/podofo/doc.cpp +++ b/src/calibre/utils/podofo/doc.cpp @@ -83,7 +83,7 @@ PDFDoc_save(PDFDoc *self, PyObject *args) { if (PyArg_ParseTuple(args, "s", &buffer)) { try { - self->doc->Save(buffer); + self->doc->Save(buffer, save_options); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; @@ -139,7 +139,7 @@ PDFDoc_write(PDFDoc *self, PyObject *args) { BytesOutputDevice d; try { - self->doc->Save(d); + self->doc->Save(d, save_options); return d.Release(); } catch(const PdfError &err) { podofo_set_exception(err); @@ -391,7 +391,13 @@ PDFDoc_set_box(PDFDoc *self, PyObject *args) { static PyObject * PDFDoc_get_xmp_metadata(PDFDoc *self, PyObject *args) { try { - auto s = self->doc->GetCatalog().GetMetadataStreamValue(); + auto obj = self->doc->GetCatalog().GetDictionary().FindKey("Metadata"); + if (obj == nullptr) Py_RETURN_NONE; + auto stream = obj->GetStream(); + if (stream == nullptr) Py_RETURN_NONE; + std::string s; + StringStreamDevice ouput(s); + stream->CopyTo(ouput); return PyBytes_FromStringAndSize(s.data(), s.size()); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; @@ -408,7 +414,10 @@ PDFDoc_set_xmp_metadata(PDFDoc *self, PyObject *args) { Py_ssize_t len = 0; if (!PyArg_ParseTuple(args, "y#", &raw, &len)) return NULL; try { - self->doc->GetCatalog().SetMetadataStreamValue(std::string_view(raw, len)); + auto& metadata = self->doc->GetCatalog().GetOrCreateMetadataObject(); + auto& stream = metadata.GetOrCreateStream(); + stream.SetData(std::string_view(raw, len), true); + metadata.GetDictionary().RemoveKey(PdfName::KeyFilter); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; } catch (...) { @@ -582,97 +591,94 @@ PDFDoc_version_getter(PDFDoc *self, void *closure) { return PyUnicode_FromString(""); } -static inline PyObject* -string_metadata_getter(const nullable& t) { - if (t.has_value()) return podofo_convert_pdfstring(t.value()); - return PyUnicode_FromString(""); +static PdfDictionary& +get_or_create_info(PDFDoc *self) { + PdfObject *info = self->doc->GetTrailer().GetDictionary().FindKey("Info"); + if (info && info->IsDictionary()) return info->GetDictionary(); + auto ninfo = self->doc->GetObjects().CreateDictionaryObject(); + self->doc->GetTrailer().GetDictionary().AddKeyIndirect("Info", ninfo); + return ninfo.GetDictionary(); } +static inline PyObject* +string_metadata_getter(PDFDoc *self, const std::string_view name) { + auto info = get_or_create_info(self); + auto obj = info.FindKey(name); + const PdfString* str; + return (obj == nullptr || !obj->TryGetString(str)) ? PyUnicode_FromString("") : podofo_convert_pdfstring(*str); +} + + static PyObject * PDFDoc_title_getter(PDFDoc *self, void *closure) { - return string_metadata_getter(self->doc->GetMetadata().GetTitle()); + return string_metadata_getter(self, "Title"); } static PyObject * PDFDoc_author_getter(PDFDoc *self, void *closure) { - return string_metadata_getter(self->doc->GetMetadata().GetAuthor()); + return string_metadata_getter(self, "Author"); } static PyObject * PDFDoc_subject_getter(PDFDoc *self, void *closure) { - return string_metadata_getter(self->doc->GetMetadata().GetSubject()); + return string_metadata_getter(self, "Subject"); } static PyObject * PDFDoc_keywords_getter(PDFDoc *self, void *closure) { - auto kw = self->doc->GetMetadata().GetKeywords(); - pyunique_ptr ans(PyTuple_New(kw.size())); - if (!ans) return NULL; - for (size_t i = 0; i < kw.size(); i++) { - pyunique_ptr t(PyUnicode_FromString(kw[i].c_str())); - if (!t) return NULL; - PyTuple_SET_ITEM(ans.get(), i, t.release()); - } - return ans.release(); + return string_metadata_getter(self, "Keywords"); } static PyObject * PDFDoc_creator_getter(PDFDoc *self, void *closure) { - return string_metadata_getter(self->doc->GetMetadata().GetCreator()); + return string_metadata_getter(self, "Creator"); } static PyObject * PDFDoc_producer_getter(PDFDoc *self, void *closure) { - return string_metadata_getter(self->doc->GetMetadata().GetProducer()); + return string_metadata_getter(self, "Producer"); } +static inline int +string_metadata_setter(PDFDoc *self, const std::string_view name, PyObject *val) { + if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "Must use unicode to set metadata"); return -1; } + auto& info = get_or_create_info(self); + const char *raw; Py_ssize_t sz; + raw = PyUnicode_AsUTF8AndSize(val, &sz); + if (sz == 0) info.RemoveKey(name); + else info.AddKey(name, PdfString(std::string_view(raw, sz))); + return 0; +} + + static int PDFDoc_title_setter(PDFDoc *self, PyObject *val, void *closure) { - if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "Must use unicode to set metadata"); return -1; } - self->doc->GetMetadata().SetTitle(podofo_convert_pystring(val)); - return 0; + return string_metadata_setter(self, "Title", val); } static int PDFDoc_author_setter(PDFDoc *self, PyObject *val, void *closure) { - if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "Must use unicode to set metadata"); return -1; } - self->doc->GetMetadata().SetAuthor(podofo_convert_pystring(val)); - return 0; + return string_metadata_setter(self, "Author", val); } static int PDFDoc_subject_setter(PDFDoc *self, PyObject *val, void *closure) { - if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "Must use unicode to set metadata"); return -1; } - self->doc->GetMetadata().SetSubject(podofo_convert_pystring(val)); - return 0; + return string_metadata_setter(self, "Subject", val); } static int PDFDoc_keywords_setter(PDFDoc *self, PyObject *val, void *closure) { - pyunique_ptr f(PySequence_Fast(val, "Need a sequence to set keywords")); - if (!f) return -1; - std::vector keywords(PySequence_Fast_GET_SIZE(f.get())); - for (Py_ssize_t i = 0; i < PySequence_Fast_GET_SIZE(f.get()); i++) { - PyObject *x = PySequence_Fast_GET_ITEM(f.get(), i); - if (!PyUnicode_Check(x)) { PyErr_SetString(PyExc_TypeError, "keywords sequence must contain only unicode objects"); return -1; } - keywords.emplace_back(podofo_convert_pystring(x)); - } - self->doc->GetMetadata().SetKeywords(keywords); - return 0; + return string_metadata_setter(self, "Keywords", val); } static int PDFDoc_creator_setter(PDFDoc *self, PyObject *val, void *closure) { - if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "Must use unicode to set metadata"); return -1; } - self->doc->GetMetadata().SetCreator(podofo_convert_pystring(val)); - return 0; + return string_metadata_setter(self, "Creator", val); } static int PDFDoc_producer_setter(PDFDoc *self, PyObject *val, void *closure) { - if (!PyUnicode_Check(val)) { PyErr_SetString(PyExc_TypeError, "Must use unicode to set metadata"); return -1; } - self->doc->GetMetadata().SetProducer(podofo_convert_pystring(val)); - return 0; + return string_metadata_setter(self, "Producer", val); } static PyGetSetDef PDFDoc_getsetters[] = { diff --git a/src/calibre/utils/podofo/global.h b/src/calibre/utils/podofo/global.h index 17cf64a8f9..3743f7640b 100644 --- a/src/calibre/utils/podofo/global.h +++ b/src/calibre/utils/podofo/global.h @@ -129,6 +129,8 @@ object_as_reference(const PdfObject *o) { return o->IsReference() ? o->GetReference() : o->GetIndirectReference(); } +// Needed to avoid PoDoFo clobbering the /Info and XMP metadata with its own nonsense +static const PdfSaveOptions save_options = PdfSaveOptions::NoModifyDateUpdate; class PdfReferenceHasher { public: diff --git a/src/calibre/utils/podofo/output.cpp b/src/calibre/utils/podofo/output.cpp index a70c8bc26d..ede83b9d6a 100644 --- a/src/calibre/utils/podofo/output.cpp +++ b/src/calibre/utils/podofo/output.cpp @@ -182,7 +182,7 @@ PyObject* pdf::write_doc(PdfMemDocument *doc, PyObject *f) { MyOutputDevice d(f); try { - doc->Save(d); + doc->Save(d, save_options); d.Flush(); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; From 541c1d20365cdb730d809607c2b57db92c2fab25 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 14 May 2023 10:26:34 +0530 Subject: [PATCH 0712/2055] Get creating outlines working again --- src/calibre/utils/podofo/outlines.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/calibre/utils/podofo/outlines.cpp b/src/calibre/utils/podofo/outlines.cpp index c8ef0999e0..4f80d25801 100644 --- a/src/calibre/utils/podofo/outlines.cpp +++ b/src/calibre/utils/podofo/outlines.cpp @@ -24,9 +24,8 @@ create_outline(PDFDoc *self, PyObject *args) { try { PdfString title = podofo_convert_pystring(title_buf); - PdfOutlines *outlines = self->doc->GetOutlines(); - if (outlines == NULL) {PyErr_NoMemory(); return NULL;} - ans->item = outlines->CreateRoot(title); + PdfOutlines &outlines = self->doc->GetOrCreateOutlines(); + ans->item = outlines.CreateRoot(title); if (ans->item == NULL) {PyErr_NoMemory(); return NULL;} ans->doc = self->doc; auto page = get_page(self->doc, pagenum -1); From 7969be34980e708f44a56e15bddda8fa6ef2d9e9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 14 May 2023 15:55:40 +0530 Subject: [PATCH 0713/2055] Add a test for PDf version --- src/calibre/utils/podofo/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/utils/podofo/__init__.py b/src/calibre/utils/podofo/__init__.py index aae0c9ba82..c57c943078 100644 --- a/src/calibre/utils/podofo/__init__.py +++ b/src/calibre/utils/podofo/__init__.py @@ -201,6 +201,8 @@ def test_podofo(): p.title = 'info title' p.author = 'info author' p.keywords = 'a, b' + if p.version != '1.1': + raise ValueError('Incorrect PDF version') xmp_packet = metadata_to_xmp_packet(mi) # print(p.get_xmp_metadata().decode()) p.set_xmp_metadata(xmp_packet) From cb1c6efd61241e04104bf7cdba5f340c09299702 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 19 May 2023 15:16:14 +0530 Subject: [PATCH 0714/2055] Podofo wrapper to add an image page --- src/calibre/utils/podofo/__init__.py | 17 ++++++++++++++ src/calibre/utils/podofo/doc.cpp | 33 ++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/calibre/utils/podofo/__init__.py b/src/calibre/utils/podofo/__init__.py index c57c943078..95f7fbc3a6 100644 --- a/src/calibre/utils/podofo/__init__.py +++ b/src/calibre/utils/podofo/__init__.py @@ -166,6 +166,23 @@ def test_dedup_type3_fonts(src): print(f'Modified pdf with {num} glyphs removed saved to:', dest) +def add_image_page(pdf_doc, image_data, page_size=None, page_num=1, preserve_aspect_ratio=True): + if page_size is None: + from qt.core import QPageSize + page_size = QPageSize(QPageSize.PageSizeId.A4) + page = page_size.rect(QPageSize.Unit.Point) + pdf_doc.add_image_page( + image_data, page.left(), page.top(), page.width(), page.height(), page.left(), page.top(), page.width(), page.height(), page_num, preserve_aspect_ratio) + + +def test_add_image_page(image='/t/t.jpg', dest='/t/t.pdf', **kw): + image_data = open(image, 'rb').read() + podofo = get_podofo() + p = podofo.PDFDoc() + add_image_page(p, image_data, **kw) + p.save(dest) + + def test_list_fonts(src): podofo = get_podofo() p = podofo.PDFDoc() diff --git a/src/calibre/utils/podofo/doc.cpp b/src/calibre/utils/podofo/doc.cpp index 1f6afacbe2..4b6a880225 100644 --- a/src/calibre/utils/podofo/doc.cpp +++ b/src/calibre/utils/podofo/doc.cpp @@ -407,6 +407,35 @@ PDFDoc_get_xmp_metadata(PDFDoc *self, PyObject *args) { Py_RETURN_NONE; } // }}} +// add_image_page() {{{ +static PyObject * +PDFDoc_add_image_page(PDFDoc *self, PyObject *args) { + const char *image_data; Py_ssize_t image_data_sz; + double page_x, page_y, page_width, page_height; + double image_x, image_y, image_canvas_width, image_canvas_height; + unsigned int page_num = 1; int preserve_aspect_ratio = 1; + if (!PyArg_ParseTuple(args, "y#dddddddd|Ip", &image_data, &image_data_sz, &page_x, &page_y, &page_width, &page_height, &image_x, &image_y, &image_canvas_width, &image_canvas_height, &page_num, &preserve_aspect_ratio)) return NULL; + auto img = self->doc->CreateImage(); + img->LoadFromBuffer(bufferview(image_data, image_data_sz)); + auto &page = self->doc->GetPages().CreatePageAt(page_num-1, Rect(page_x, page_y, page_width, page_height)); + PdfPainter painter; + painter.SetCanvas(page); + auto scaling_x = image_canvas_width, scaling_y = image_canvas_height; + if (preserve_aspect_ratio) { + auto page_ar = page_width / page_height, img_ar = img->GetRect().Width / img->GetRect().Height; + if (page_ar > img_ar) { + scaling_x = img_ar * image_canvas_height; + image_x = (image_canvas_width - scaling_x) / 2.; + } else if (page_ar < img_ar) { + scaling_y = image_canvas_width / img_ar; + image_y = (image_canvas_height - scaling_y) / 2.; + } + } + painter.DrawImage(*img, image_x, image_y, scaling_x / img->GetRect().Width, scaling_y / img->GetRect().Height); + return Py_BuildValue("dd", img->GetRect().Width, img->GetRect().Height); +} +// }}} + // set_xmp_metadata() {{{ static PyObject * PDFDoc_set_xmp_metadata(PDFDoc *self, PyObject *args) { @@ -809,6 +838,10 @@ static PyMethodDef PDFDoc_methods[] = { {"set_xmp_metadata", (PyCFunction)PDFDoc_set_xmp_metadata, METH_VARARGS, "set_xmp_metadata(raw) -> Set the XMP metadata to the raw bytes (which must be a valid XML packet)" }, + {"add_image_page", (PyCFunction)PDFDoc_add_image_page, METH_VARARGS, + "add_image_page(image_data, page_idx=0) -> Add the specified image as a full page image, will use the size of the first existing page as page size." + }, + {NULL} /* Sentinel */ }; From 59fab1d482252dc9fc73776be099770327ff3ed5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 19 May 2023 15:26:49 +0530 Subject: [PATCH 0715/2055] Use PoDoFo to insert the cover page, bypassing https://github.com/podofo/podofo/issues/76 --- src/calibre/ebooks/pdf/html_writer.py | 21 +++++---------------- src/calibre/utils/podofo/__init__.py | 6 +++--- src/calibre/utils/podofo/doc.cpp | 1 + 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/calibre/ebooks/pdf/html_writer.py b/src/calibre/ebooks/pdf/html_writer.py index 6da4ef1d29..4dce448c65 100644 --- a/src/calibre/ebooks/pdf/html_writer.py +++ b/src/calibre/ebooks/pdf/html_writer.py @@ -10,7 +10,6 @@ import sys from collections import namedtuple from functools import lru_cache from html5_parser import parse -from io import BytesIO from itertools import count, repeat from qt.core import ( QApplication, QByteArray, QMarginsF, QObject, QPageLayout, Qt, QTimer, QUrl, @@ -29,10 +28,7 @@ from calibre.ebooks.oeb.base import XHTML, XPath from calibre.ebooks.oeb.polish.container import Container as ContainerBase from calibre.ebooks.oeb.polish.toc import get_toc from calibre.ebooks.oeb.polish.utils import guess_type -from calibre.ebooks.pdf.image_writer import ( - Image, PDFMetadata, draw_image_page, get_page_layout, -) -from calibre.ebooks.pdf.render.serialize import PDFStream +from calibre.ebooks.pdf.image_writer import PDFMetadata, get_page_layout from calibre.gui2 import setup_unix_signals from calibre.srv.render_book import check_for_maths from calibre.utils.fonts.sfnt.container import Sfnt, UnsupportedFont @@ -42,9 +38,9 @@ from calibre.utils.fonts.sfnt.subset import pdf_subset from calibre.utils.logging import default_log from calibre.utils.monotonic import monotonic from calibre.utils.podofo import ( - dedup_type3_fonts, get_podofo, remove_unused_fonts, set_metadata_implementation, + add_image_page, dedup_type3_fonts, get_podofo, remove_unused_fonts, + set_metadata_implementation, ) - from calibre.utils.resources import get_path as P from calibre.utils.short_uuid import uuid4 from calibre.utils.webengine import secure_webengine, send_reply, setup_profile @@ -481,15 +477,8 @@ def update_metadata(pdf_doc, pdf_metadata): def add_cover(pdf_doc, cover_data, page_layout, opts): - buf = BytesIO() - page_size = page_layout.fullRectPoints().size() - img = Image(cover_data) - writer = PDFStream(buf, (page_size.width(), page_size.height()), compress=True) - writer.apply_fill(color=(1, 1, 1)) - draw_image_page(writer, img, preserve_aspect_ratio=opts.preserve_cover_aspect_ratio) - writer.end() - cover_pdf_doc = data_as_pdf_doc(buf.getvalue()) - pdf_doc.insert_existing_page(cover_pdf_doc) + r = page_layout.fullRect(QPageLayout.Unit.Point) + add_image_page(pdf_doc, cover_data, page_size=(r.left(), r.top(), r.width(), r.height()), preserve_aspect_ratio=opts.preserve_cover_aspect_ratio) # }}} diff --git a/src/calibre/utils/podofo/__init__.py b/src/calibre/utils/podofo/__init__.py index 95f7fbc3a6..7356874e6b 100644 --- a/src/calibre/utils/podofo/__init__.py +++ b/src/calibre/utils/podofo/__init__.py @@ -169,10 +169,10 @@ def test_dedup_type3_fonts(src): def add_image_page(pdf_doc, image_data, page_size=None, page_num=1, preserve_aspect_ratio=True): if page_size is None: from qt.core import QPageSize - page_size = QPageSize(QPageSize.PageSizeId.A4) - page = page_size.rect(QPageSize.Unit.Point) + p = QPageSize(QPageSize.PageSizeId.A4).rect(QPageSize.Unit.Point) + page_size = p.left(), p.top(), p.width(), p.height() pdf_doc.add_image_page( - image_data, page.left(), page.top(), page.width(), page.height(), page.left(), page.top(), page.width(), page.height(), page_num, preserve_aspect_ratio) + image_data, *page_size, *page_size, page_num, preserve_aspect_ratio) def test_add_image_page(image='/t/t.jpg', dest='/t/t.pdf', **kw): diff --git a/src/calibre/utils/podofo/doc.cpp b/src/calibre/utils/podofo/doc.cpp index 4b6a880225..adf8899e02 100644 --- a/src/calibre/utils/podofo/doc.cpp +++ b/src/calibre/utils/podofo/doc.cpp @@ -432,6 +432,7 @@ PDFDoc_add_image_page(PDFDoc *self, PyObject *args) { } } painter.DrawImage(*img, image_x, image_y, scaling_x / img->GetRect().Width, scaling_y / img->GetRect().Height); + painter.FinishDrawing(); return Py_BuildValue("dd", img->GetRect().Width, img->GetRect().Height); } // }}} From 20e4db008e51f841ab17a9e7bc20872e1ca9585c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 20 May 2023 10:49:41 +0530 Subject: [PATCH 0716/2055] Fix drawing of header/footer --- src/calibre/utils/podofo/impose.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/calibre/utils/podofo/impose.cpp b/src/calibre/utils/podofo/impose.cpp index 67050375d4..e695fe9c0f 100644 --- a/src/calibre/utils/podofo/impose.cpp +++ b/src/calibre/utils/podofo/impose.cpp @@ -12,13 +12,14 @@ using namespace pdf; static void impose_page(PdfMemDocument *doc, unsigned int dest_page_num, unsigned int src_page_num) { - auto xobj = doc->CreateXObjectForm(Rect(), "HeaderFooter"); - xobj->FillFromPage(doc->GetPages().GetPageAt(src_page_num)); - auto dest = &doc->GetPages().GetPageAt(dest_page_num); - static unsigned counter = 0; - dest->GetOrCreateResources().AddResource("XObject", "Imp"s + std::to_string(++counter), xobj->GetObject()); - auto data = "q\n1 0 0 1 0 0 cm\n/"s + xobj->GetIdentifier().GetEscapedName() + " Do\nQ\n"s; - dest->GetOrCreateContents().GetStreamForAppending().SetData(data); + auto &src_page = doc->GetPages().GetPageAt(src_page_num); + auto xobj = doc->CreateXObjectForm(src_page.GetMediaBox(), "HeaderFooter"); + xobj->FillFromPage(src_page); + auto &dest = doc->GetPages().GetPageAt(dest_page_num); + PdfPainter painter; + painter.SetCanvas(dest); + painter.DrawXObject(*xobj, 0, 0); + painter.FinishDrawing(); } static PyObject* From 9aa1f4add6940e78abb18fb6c6a995ccd9800c0f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 20 May 2023 11:56:10 +0530 Subject: [PATCH 0717/2055] Fix loading of PDF from data causing memory corruption because PoDoFo now expects the data to outlive the document --- src/calibre/utils/podofo/doc.cpp | 3 +++ src/calibre/utils/podofo/global.h | 1 + 2 files changed, 4 insertions(+) diff --git a/src/calibre/utils/podofo/doc.cpp b/src/calibre/utils/podofo/doc.cpp index adf8899e02..67f49b110b 100644 --- a/src/calibre/utils/podofo/doc.cpp +++ b/src/calibre/utils/podofo/doc.cpp @@ -18,6 +18,7 @@ static void PDFDoc_dealloc(PDFDoc* self) { if (self->doc != NULL) delete self->doc; + Py_CLEAR(self->load_buffer_ref); Py_TYPE(self)->tp_free((PyObject*)self); } @@ -45,6 +46,8 @@ PDFDoc_load(PDFDoc *self, PyObject *args) { try { self->doc->LoadFromBuffer(bufferview(buffer, size)); + self->load_buffer_ref = args; + Py_INCREF(args); } catch(const PdfError & err) { podofo_set_exception(err); return NULL; diff --git a/src/calibre/utils/podofo/global.h b/src/calibre/utils/podofo/global.h index 3743f7640b..87a56cb04b 100644 --- a/src/calibre/utils/podofo/global.h +++ b/src/calibre/utils/podofo/global.h @@ -26,6 +26,7 @@ typedef struct { PyObject_HEAD /* Type-specific fields go here. */ PdfMemDocument *doc; + PyObject *load_buffer_ref; } PDFDoc; From a79795dca9c0576e6a71c8b01240621f871eff4e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 20 May 2023 12:34:34 +0530 Subject: [PATCH 0718/2055] Bump version of podofo --- bypy/sources.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bypy/sources.json b/bypy/sources.json index 36395f7d0e..c732379111 100644 --- a/bypy/sources.json +++ b/bypy/sources.json @@ -382,9 +382,9 @@ { "name": "podofo", "unix": { - "filename": "podofo-0.9.7.tar.gz", - "hash": "sha256:7cf2e716daaef89647c54ffcd08940492fd40c385ef040ce7529396bfadc1eb8", - "urls": ["https://downloads.sourceforge.net/podofo/{filename}"] + "filename": "podofo-0.10.0.tar.gz", + "hash": "sha256:c9bf607fe4862b5cc6eac1754f0e39674c553350e8bbde68f5fff3e3eb2ed4bc", + "urls": ["https://github.com/podofo/podofo/archive/refs/tags/0.10.0.tar.gz"] } }, From 0638359f0fe760712f830c5bbc71b032af4e29c4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 20 May 2023 14:12:25 +0530 Subject: [PATCH 0719/2055] Fix mem leak on loading multiple PDFs with buffe rin single doc --- src/calibre/utils/podofo/doc.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/utils/podofo/doc.cpp b/src/calibre/utils/podofo/doc.cpp index 67f49b110b..2177cd9766 100644 --- a/src/calibre/utils/podofo/doc.cpp +++ b/src/calibre/utils/podofo/doc.cpp @@ -46,6 +46,7 @@ PDFDoc_load(PDFDoc *self, PyObject *args) { try { self->doc->LoadFromBuffer(bufferview(buffer, size)); + Py_CLEAR(self->load_buffer_ref); self->load_buffer_ref = args; Py_INCREF(args); } catch(const PdfError & err) { From 9925b1caf4c1a5698a78d624bed1dd91b2b8c155 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 20 May 2023 21:20:20 +0530 Subject: [PATCH 0720/2055] Fix #2018942 [Calibre won't build with PoDoFo 0.10.0](https://bugs.launchpad.net/calibre/+bug/2018942) --- bypy/macos/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bypy/macos/__main__.py b/bypy/macos/__main__.py index c3c2643486..e4b152b3f7 100644 --- a/bypy/macos/__main__.py +++ b/bypy/macos/__main__.py @@ -476,7 +476,7 @@ class Freeze: @flush def add_podofo(self): print('\nAdding PoDoFo') - pdf = join(PREFIX, 'lib', 'libpodofo.0.9.7.dylib') + pdf = join(PREFIX, 'lib', 'libpodofo.0.10.0.dylib') self.install_dylib(pdf) @flush From bdc01df4630618bff56e5f440796f4b4e55c0ac8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 21 May 2023 05:02:59 +0530 Subject: [PATCH 0721/2055] Install up to date podofo on Arch CI --- setup/arch-ci.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup/arch-ci.sh b/setup/arch-ci.sh index c68ea6636c..f0a2a5df2e 100755 --- a/setup/arch-ci.sh +++ b/setup/arch-ci.sh @@ -5,7 +5,11 @@ set -xe -pacman -S --noconfirm --needed base-devel sudo git sip pyqt-builder cmake chmlib icu jxrlib hunspell libmtp libusb libwmf optipng podofo python-apsw python-beautifulsoup4 python-cssselect python-css-parser python-dateutil python-jeepney python-dnspython python-feedparser python-html2text python-html5-parser python-lxml python-markdown python-mechanize python-msgpack python-netifaces python-unrardll python-pillow python-psutil python-pygments python-pyqt6 python-regex python-zeroconf python-pyqt6-webengine qt6-svg qt6-imageformats udisks2 hyphen python-pychm python-pycryptodome speech-dispatcher python-sphinx python-urllib3 python-py7zr python-pip python-fonttools uchardet libstemmer poppler tk +pacman -S --noconfirm --needed base-devel sudo git sip pyqt-builder cmake chmlib icu jxrlib hunspell libmtp libusb libwmf optipng python-apsw python-beautifulsoup4 python-cssselect python-css-parser python-dateutil python-jeepney python-dnspython python-feedparser python-html2text python-html5-parser python-lxml python-markdown python-mechanize python-msgpack python-netifaces python-unrardll python-pillow python-psutil python-pygments python-pyqt6 python-regex python-zeroconf python-pyqt6-webengine qt6-svg qt6-imageformats udisks2 hyphen python-pychm python-pycryptodome speech-dispatcher python-sphinx python-urllib3 python-py7zr python-pip python-fonttools uchardet libstemmer poppler tk + +curl -fSsL https://github.com/podofo/podofo/archive/refs/tags/0.10.0.tar.gz | tar xz +cd podofo* && mkdir build && cd build +cmake -DCMAKE_INSTALL_PREFIX=/usr .. && make -j4 && make install useradd -m ci chown -R ci:users $GITHUB_WORKSPACE From 310df5e73501e1c3f41d1990b3aafd9cdb7119b4 Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Sun, 21 May 2023 10:23:28 +0530 Subject: [PATCH 0722/2055] removing print editions of Business Standard and IE. . --- .../business_standard_print_edition.recipe | 92 ------------------ .../icons/business_standard_print_edition.png | Bin 1000 -> 0 bytes recipes/indian_express_print_edition.recipe | 87 ----------------- 3 files changed, 179 deletions(-) delete mode 100644 recipes/business_standard_print_edition.recipe delete mode 100644 recipes/icons/business_standard_print_edition.png delete mode 100644 recipes/indian_express_print_edition.recipe diff --git a/recipes/business_standard_print_edition.recipe b/recipes/business_standard_print_edition.recipe deleted file mode 100644 index 9158a49152..0000000000 --- a/recipes/business_standard_print_edition.recipe +++ /dev/null @@ -1,92 +0,0 @@ -''' -www.business-standard.com -''' - -from calibre.web.feeds.news import BasicNewsRecipe, classes - - -class BusinessStandard(BasicNewsRecipe): - title = 'Business Standard | Print Edition' - __author__ = 'unkn0wn' - description = "India's most respected business daily" - no_stylesheets = True - use_embedded_content = False - encoding = 'utf-8' - publisher = 'Business Standard Limited' - category = 'news, business, money, india, world' - language = 'en_IN' - extra_css = ''' - .article__desc{font-size:small;} - .article_image{font-size:small; font-style:italic;} - .article__dateline{font-size:small;} - .full-img{font-size:small; font-style:italic; text-align:center;} - .pubDate{font-size:small; text-align:center;} - ''' - - masthead_url = 'https://bsmedia.business-standard.com/include/_mod/site/html5/images/business-standard-logo.png' - - def get_cover_url(self): - soup = self.index_to_soup( - 'https://www.magzter.com/IN/Business-Standard-Private-Ltd/Business-Standard/Newspaper/' - ) - for citem in soup.findAll( - 'meta', content=lambda s: s and s.endswith('view/3.jpg') - ): - return citem['content'] - - remove_attributes = ['width', 'height', 'style'] - - keep_only_tags = [ - classes( - 'article__title article__content article_content article_image article__dateline headline' - ' alternativeHeadline full-img article-content__img pubDate' - ), - dict(name='section', attrs={'subscriptions-section': 'content'}), - dict(name='span', attrs={'class': 'p-content'}) - ] - remove_tags = [ - classes('also-read-panel related-keyword more-stories-pagination'), - dict(name='br') - ] - - def parse_index(self): - soup = self.index_to_soup('https://www.business-standard.com/todays-paper') - ans = self.bs_parse_index(soup) - return ans - - def bs_parse_index(self, soup): - feeds = [] - div = soup.find('div', attrs={'class': 'main-cont-left'}) - for section in div.findAll('div', attrs={'class': 'row-inner'}): - h2 = section.find('h2') - secname = self.tag_to_string(h2) - self.log(secname) - articles = [] - for a in section.findAll( - 'a', href=lambda x: x and x.startswith('/article/') - ): - url = a['href'].replace('article', 'article-amp') - url = 'https://wap.business-standard.com' + url - title = self.tag_to_string(a).strip().replace('Premium Content', '') - articles.append({'title': title, 'url': url}) - self.log('\t', title, '\n\t\t', url) - if articles: - feeds.append((secname, articles)) - return feeds - - def preprocess_html(self, soup): - subs = soup.find('section', attrs={'subscriptions-section': 'content'}) - if subs: - art = soup.find(**classes('article_image')) - if art: - art.extract() - div = soup.find(**classes('article_content')) - if div: - div.extract() - h2 = soup.find('h2') - if h2: - h2.name = 'h4' - for img in soup.findAll('amp-img', src=True): - img.name = 'img' - img['src'] = img['src'].replace('\\', '').split('?')[0] - return soup diff --git a/recipes/icons/business_standard_print_edition.png b/recipes/icons/business_standard_print_edition.png deleted file mode 100644 index 83a1b55c065330fd087d9300071ef505be4970ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1000 zcmV>P)+^deIOYb2k!q^?H^5X4YRNouaT?+X%8eM#!ZfBc4{=<;U^Wr?J={(9!3H0>$Vd(2?xPI+A(y0vo8XUo#;TTQJSK{u~YmkgWBm;F3 zG)QASfKo>%&WF9|$jnA%d<>T^Uq{obb?EMI$N7t$`0eryB(`kFeOtF+$(uW&){3e+ z^&lUkJfnsymk!{<%BRs%fr~vk^vzn1r3a58X%a>oaCFzh2=*OA=ge-*+j|(lWQNrgX=e<=h>Y&_v-6N6+g$){eR*{m_=7yz|YUELh;0D+~{9` z)rXFw)LEY%1jR-Opsmz__HsK)4?TyDVAh(|7!H%@d;cq(+_n{e ze0mTIK0AQ2ore(xnbm8MQ&~lL)8)dUA%>0_glD?YQduCH(}tl zUHEQj5G{ovm`K(|kd#8PSV1n+iko2uVKRsAN)^8zJb~s)3wl?qK;`&HIDP0H^geJe zTJP+^_#H8Bbv2VGQ#It`5Vffoi7?>yX%~f1 zO+vB)VO$?UDIUgXwGmyV3{)X%0#O;E^nV|2XH>&TwSZFCj5d%N%|kAY;dG`Rf`0&} WL0{*r$FV2?0000pI diff --git a/recipes/indian_express_print_edition.recipe b/recipes/indian_express_print_edition.recipe deleted file mode 100644 index cc5bbe74a4..0000000000 --- a/recipes/indian_express_print_edition.recipe +++ /dev/null @@ -1,87 +0,0 @@ -from calibre.web.feeds.news import BasicNewsRecipe, classes -from collections import defaultdict - - -class IndianExpressPrint(BasicNewsRecipe): - title = u'Indian Express | Print Edition' - language = 'en_IN' - __author__ = 'unkn0wn' - masthead_url = 'https://indianexpress.com/wp-content/themes/indianexpress/images/indian-express-logo-n.svg' - no_stylesheets = True - use_embedded_content = False - remove_attributes = ['style', 'height', 'width'] - ignore_duplicate_articles = {'url'} - - extra_css = ''' - #storycenterbyline {font-size:small;} - #img-cap {font-size:small;} - blockquote{color:#404040;} - em{font-style:italic; color:#202020;} - #sub-d{color:#202020; font-style:italic;} - .ie-authorbox{font-size:small;} - ''' - - resolve_internal_links = True - remove_empty_feeds = True - - keep_only_tags = [classes('heading-part full-details')] - remove_tags = [ - dict(name='div', attrs={'id': 'ie_story_comments'}), - dict(name='div', attrs={'class': lambda x: x and 'related-widget' in x}), - dict(name='img', attrs={'src':lambda x: x and x.endswith('-button-300-ie.jpeg')}), - dict(name='a', attrs={'href':lambda x: x and x.endswith('/?utm_source=newbanner')}), - classes( - 'share-social appstext ie-int-campign-ad ie-breadcrumb custom_read_button unitimg copyright' - ' storytags pdsc-related-modify news-guard premium-story append_social_share' - ' digital-subscriber-only h-text-widget ie-premium ie-first-publish adboxtop adsizes immigrationimg' - 'next-story-wrap ie-ie-share next-story-box brand-logo quote_section ie-customshare' - ' custom-share o-story-paper-quite ie-network-commenting audio-player-tts-sec' - ) - ] - - def parse_index(self): - soup = self.index_to_soup('https://indianexpress.com/todays-paper/') - feeds_dict = defaultdict(list) - div = soup.find('div', attrs={'class':'today-paper'}) - for a in div.findAll('a', attrs={'href':lambda x: x and x.startswith( - ('https://indianexpress.com/article/', 'https://indianexpress.com/elections/') - )}): - if not a.find('img'): - url = a['href'] - title = self.tag_to_string(a) - section = 'Front Page' - if str := a.findParent('strong'): - if span := str.find_previous_sibling('span'): - section = self.tag_to_string(span) - # if 'City' in section: - # url = '' - if not url or not title: - continue - self.log(section, '\n\t', title, '\n\t\t', url) - feeds_dict[section].append({"title": title, "url": url}) - return [(section, articles) for section, articles in feeds_dict.items()] - - def get_cover_url(self): - soup = self.index_to_soup( - 'https://www.magzter.com/IN/The-Indian-Express-Ltd./The-Indian-Express-Mumbai/Newspaper/' - ) - for citem in soup.findAll('meta', content=lambda s: s and s.endswith('view/3.jpg')): - return citem['content'] - - def preprocess_html(self, soup): - h2 = soup.find('h2') - if h2: - h2.name = 'p' - h2['id'] = 'sub-d' - for span in soup.findAll( - 'span', attrs={'class': ['ie-custom-caption', 'custom-caption']} - ): - span['id'] = 'img-cap' - for img in soup.findAll('img'): - noscript = img.findParent('noscript') - if noscript is not None: - lazy = noscript.findPreviousSibling('img') - if lazy is not None: - lazy.extract() - noscript.name = 'div' - return soup From 625fcca0ae040e8c37adc8f7e8798792466f5ecf Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 21 May 2023 08:17:05 +0100 Subject: [PATCH 0723/2055] Bug #2020002: improve error message --- src/calibre/gui2/dialogs/tag_categories.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_categories.py b/src/calibre/gui2/dialogs/tag_categories.py index 13172b73c7..5e27c37364 100644 --- a/src/calibre/gui2/dialogs/tag_categories.py +++ b/src/calibre/gui2/dialogs/tag_categories.py @@ -284,11 +284,19 @@ class TagCategories(QDialog, Ui_TagCategories): 'or after periods.')).exec() return False for c in sorted(self.user_categories.keys(), key=primary_sort_key): - if strcmp(c, cat_name) == 0 or \ - (icu_lower(cat_name).startswith(icu_lower(c) + '.') and - not cat_name.startswith(c + '.')): + if strcmp(c, cat_name) == 0: error_dialog(self, _('Name already used'), - _('That name is already used, perhaps with different case.')).exec() + _('The user category name is already used, perhaps with different case.'), + det_msg=_('Existing category: {existing}\nNew category: {new}').format(existing=c, new=cat_name), + show=True) + return False + if icu_lower(cat_name).startswith(icu_lower(c) + '.') and not cat_name.startswith(c + '.'): + error_dialog(self, _('Name already used'), + _('The hierarchical prefix of the new category is already used, ' + 'perhaps with different case.'), + det_msg=_('Existing prefix: {prefix}\n' + 'New category: {new}').format(prefix=c, new=cat_name), + show=True) return False if cat_name not in self.user_categories: self.user_categories[cat_name] = set() @@ -316,7 +324,10 @@ class TagCategories(QDialog, Ui_TagCategories): for c in self.user_categories: if strcmp(c, cat_name) == 0: error_dialog(self, _('Name already used'), - _('That name is already used, perhaps with different case.')).exec() + _('The user category name is already used, perhaps with different case.'), + det_msg=_('Existing category: {existing}\n' + 'New category: {new}').format(existing=c, new=cat_name), + show=True) return # The order below is important because of signals self.user_categories[cat_name] = self.user_categories[self.current_cat_name] From b82b2fde9752de8a318a875bac3386515b88542c Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 21 May 2023 08:40:02 +0100 Subject: [PATCH 0724/2055] Enhancement #2019513: category editor: allow selections only in the current column instead of reporting an error. --- src/calibre/gui2/dialogs/tag_list_editor.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index 0c78e25cab..e5d2fb5a17 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -647,16 +647,11 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.table.blockSignals(False) def selection_changed(self): - col0 = tuple(item for item in self.table.selectedItems() if item.column() == 0) - col3 = tuple(item for item in self.table.selectedItems() if item.column() == 3) - if col0 and col3: - error_dialog(self, _('Cannot select in multiple columns'), - '

'+_('Selection of items in multiple columns is not supported. ' - 'The selection will be cleared')+'
', - show=True) - sm = self.table.selectionModel() + if self.table.currentIndex().isValid(): + col = self.table.currentIndex().column() self.table.blockSignals(True) - sm.clear() + for itm in (item for item in self.table.selectedItems() if item.column() != col): + itm.setSelected(False) self.table.blockSignals(False) def check_for_deleted_items(self, show_error=False): From 1e78b9fe7c295c43376425e574a4b3644cc25820 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 21 May 2023 09:28:29 +0100 Subject: [PATCH 0725/2055] Bug #2019457: Tag browser - transient right click error. The search category can disappear from field_metadata. Perhaps other dynamic categories can as well. The implication is that dynamic categories are being removed, but how that would happen is a mystery. I suspect a plugin is operating on the "real" field_metadata instead of a copy, thereby changing the dict used by the rest of calibre. As it can happen, to avoid key errors check that a category exists before offering to unhide it. --- src/calibre/gui2/tag_browser/view.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/tag_browser/view.py b/src/calibre/gui2/tag_browser/view.py index 7e21e755f3..dad222f99a 100644 --- a/src/calibre/gui2/tag_browser/view.py +++ b/src/calibre/gui2/tag_browser/view.py @@ -722,7 +722,16 @@ class TagsView(QTreeView): # {{{ added_show_hidden_categories = True m = self.context_menu.addMenu(_('Show category')) m.setIcon(QIcon.ic('plus.png')) - for col in sorted(self.hidden_categories, + # The search category can disappear from field_metadata. Perhaps + # other dynamic categories can as well. The implication is that + # dynamic categories are being removed, but how that would + # happen is a mystery. I suspect a plugin is operating on the + # "real" field_metadata instead of a copy, thereby changing the + # dict used by the rest of calibre. + # + # As it can happen, to avoid key errors check that a category + # exists before offering to unhide it. + for col in sorted((c for c in self.hidden_categories if c in self.db.field_metadata), key=lambda x: sort_key(self.db.field_metadata[x]['name'])): ac = m.addAction(self.db.field_metadata[col]['name'], partial(self.context_menu_handler, action='show', category=col)) From b1be2296c24207f690648278a49f856df0b64cd8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 21 May 2023 21:39:49 +0530 Subject: [PATCH 0726/2055] string changes --- src/calibre/db/cache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index c4fa065c01..b546a6145c 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2377,7 +2377,7 @@ class Cache: @read_api def get_link_map(self, for_field): ''' - Return a dict of links for the supplied field. + Return a dictionary of links for the supplied field. :param for_field: the lookup name of the field for which the link map is desired @@ -2438,7 +2438,7 @@ class Cache: @write_api def set_link_map(self, field, value_to_link_map, only_set_if_no_existing_link=False): ''' - Sets links for item values in field + Sets links for item values in field. Note: this method doesn't change values not in the value_to_link_map :param field: the lookup name From ef2443cb968a97556a70dfe4e1838847934fd175 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 21 May 2023 17:43:26 +0100 Subject: [PATCH 0727/2055] String changes/corrections --- .../gui2/preferences/template_functions.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/calibre/gui2/preferences/template_functions.py b/src/calibre/gui2/preferences/template_functions.py index 294e6747f9..ef6d37b655 100644 --- a/src/calibre/gui2/preferences/template_functions.py +++ b/src/calibre/gui2/preferences/template_functions.py @@ -88,14 +88,15 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): in template processing. You use a stored template in another template as if it were a template function, for example 'some_name(arg1, arg2...)'.

-

Stored templates must use General Program Mode -- they must - either begin with the text '{0}' or be {1}. You retrieve arguments - passed to a GPM stored template using the '{2}()' template function, as - in '{2}(var1, var2, ...)'. The passed arguments are copied to the named - variables. Arguments passed to a Python template are in the '{2}' - parameter. Arguments are always strings.

+

Stored templates must use General Program Mode or Python Template + Mode -- they must begin with the text '{0}' or '{1}'. You retrieve + arguments passed to a GPM stored template using the '{2}()' template + function, as in '{2}(var1, var2, ...)'. The passed arguments are copied + to the named variables. Arguments passed to a Python template are in the + '{2}' attribute (a list) of the '{3}' parameter. Arguments are always + strings.

-

For example, this stored template checks if any items are in a +

For example, this stored GPM template checks if any items are in a list, returning '1' if any are found and '' if not.

Template name: items_in_list
@@ -115,7 +116,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): See the template language tutorial for more information.

''') - self.st_textBrowser.setHtml(help_text.format('program:', 'python templates', 'arguments')) + self.st_textBrowser.setHtml(help_text.format('program:', 'python:', 'arguments', 'context')) self.st_textBrowser.adjustSize() self.st_show_hide_help_button.clicked.connect(self.st_show_hide_help) self.st_textBrowser_height = self.st_textBrowser.height() From 1eb3d9aee55fc1236e522c5625ed254e9b7f15dc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 22 May 2023 07:00:04 +0530 Subject: [PATCH 0728/2055] string changes --- src/calibre/gui2/dialogs/tag_categories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/tag_categories.py b/src/calibre/gui2/dialogs/tag_categories.py index 5e27c37364..4c68173b78 100644 --- a/src/calibre/gui2/dialogs/tag_categories.py +++ b/src/calibre/gui2/dialogs/tag_categories.py @@ -295,7 +295,7 @@ class TagCategories(QDialog, Ui_TagCategories): _('The hierarchical prefix of the new category is already used, ' 'perhaps with different case.'), det_msg=_('Existing prefix: {prefix}\n' - 'New category: {new}').format(prefix=c, new=cat_name), + 'New prefix: {new}').format(prefix=c, new=cat_name), show=True) return False if cat_name not in self.user_categories: From 306f81356633390d076c50300b4ac3283578d0c5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 22 May 2023 11:15:41 +0530 Subject: [PATCH 0729/2055] Comic Input: When grayscaling comic images use 16bit gray for better fidelity. When using the PNG format for images this results in larger files but with better grayscaling fidelity. Fixes #1898 (Optimized edge case for comic conversion) --- src/calibre/ebooks/comic/input.py | 36 ++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/calibre/ebooks/comic/input.py b/src/calibre/ebooks/comic/input.py index 3c28346a5e..9fc8925d4a 100644 --- a/src/calibre/ebooks/comic/input.py +++ b/src/calibre/ebooks/comic/input.py @@ -6,14 +6,16 @@ __docformat__ = 'restructuredtext en' Based on ideas from comiclrf created by FangornUK. ''' -import os, traceback, time +import os +import time +import traceback from calibre import extract, prints, walk from calibre.constants import filesystem_encoding from calibre.ptempfile import PersistentTemporaryDirectory from calibre.utils.icu import numeric_sort_key -from calibre.utils.ipc.server import Server from calibre.utils.ipc.job import ParallelJob +from calibre.utils.ipc.server import Server from polyglot.queue import Empty # If the specified screen has either dimension larger than this value, no image @@ -41,6 +43,7 @@ def extract_comic(path_to_comic_file): def generate_entries_from_dir(path): from functools import partial + from calibre import walk ans = {} for x in walk(path): @@ -105,18 +108,21 @@ class PageProcessor(list): # {{{ self.dest = dest self.rotate = False self.src_img_was_grayscaled = False + self.src_img_format = None self.render() def render(self): from qt.core import QImage - from calibre.utils.img import image_from_data, scale_image, crop_image + + from calibre.utils.img import crop_image, image_from_data, scale_image with open(self.path_to_page, 'rb') as f: img = image_from_data(f.read()) width, height = img.width(), img.height() if self.num == 0: # First image so create a thumbnail from it with open(os.path.join(self.dest, 'thumbnail.png'), 'wb') as f: f.write(scale_image(img, as_png=True)[-1]) - self.src_img_was_grayscaled = img.format() in (QImage.Format.Format_Grayscale8, QImage.Format.Format_Grayscale16) or ( + self.src_img_format = img.format() + self.src_img_was_grayscaled = self.src_img_format in (QImage.Format.Format_Grayscale8, QImage.Format.Format_Grayscale16) or ( img.format() == QImage.Format.Format_Indexed8 and img.allGray()) self.pages = [img] if width > height: @@ -131,10 +137,11 @@ class PageProcessor(list): # {{{ def process_pages(self): from qt.core import QImage + from calibre.utils.img import ( - image_to_data, rotate_image, remove_borders_from_image, normalize_image, - add_borders_to_image, resize_image, gaussian_sharpen_image, grayscale_image, - despeckle_image, quantize_image + add_borders_to_image, despeckle_image, gaussian_sharpen_image, + image_to_data, normalize_image, quantize_image, remove_borders_from_image, + resize_image, rotate_image, ) for i, img in enumerate(self.pages): if self.rotate: @@ -205,17 +212,22 @@ class PageProcessor(list): # {{{ if not self.opts.dont_sharpen: img = gaussian_sharpen_image(img, 0.0, 1.0) - if not self.opts.dont_grayscale: - img = grayscale_image(img) - if self.opts.despeckle: img = despeckle_image(img) + img_is_grayscale = self.src_img_was_grayscaled + if not self.opts.dont_grayscale: + img = img.convertToFormat(QImage.Format.Format_Grayscale16) + img_is_grayscale = True + if self.opts.output_format.lower() == 'png': if self.opts.colors: img = quantize_image(img, max_colors=min(256, self.opts.colors)) - elif self.src_img_was_grayscaled: - img = img.convertToFormat(QImage.Format.Format_Grayscale8) + elif img_is_grayscale: + uses_256_colors = self.src_img_format in (QImage.Format.Format_Indexed8, QImage.Format.Format_Grayscale8) + final_fmt = QImage.Format.Format_Indexed8 if uses_256_colors else QImage.Format.Format_Grayscale16 + if img.format() != final_fmt: + img = img.convertToFormat(final_fmt) dest = '%d_%d.%s'%(self.num, i, self.opts.output_format) dest = os.path.join(self.dest, dest) with open(dest, 'wb') as f: From 4f44527eb1a2c1b5c309809ba12f30ec9bdf5f0f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 22 May 2023 11:22:34 +0530 Subject: [PATCH 0730/2055] ... --- src/calibre/ebooks/comic/input.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calibre/ebooks/comic/input.py b/src/calibre/ebooks/comic/input.py index 9fc8925d4a..1bf7af23af 100644 --- a/src/calibre/ebooks/comic/input.py +++ b/src/calibre/ebooks/comic/input.py @@ -107,7 +107,7 @@ class PageProcessor(list): # {{{ self.num = num self.dest = dest self.rotate = False - self.src_img_was_grayscaled = False + self.src_img_was_grayscale = False self.src_img_format = None self.render() @@ -122,7 +122,7 @@ class PageProcessor(list): # {{{ with open(os.path.join(self.dest, 'thumbnail.png'), 'wb') as f: f.write(scale_image(img, as_png=True)[-1]) self.src_img_format = img.format() - self.src_img_was_grayscaled = self.src_img_format in (QImage.Format.Format_Grayscale8, QImage.Format.Format_Grayscale16) or ( + self.src_img_was_grayscale = self.src_img_format in (QImage.Format.Format_Grayscale8, QImage.Format.Format_Grayscale16) or ( img.format() == QImage.Format.Format_Indexed8 and img.allGray()) self.pages = [img] if width > height: @@ -215,7 +215,7 @@ class PageProcessor(list): # {{{ if self.opts.despeckle: img = despeckle_image(img) - img_is_grayscale = self.src_img_was_grayscaled + img_is_grayscale = self.src_img_was_grayscale if not self.opts.dont_grayscale: img = img.convertToFormat(QImage.Format.Format_Grayscale16) img_is_grayscale = True From a2a0848bb18955a00e7b4bc7954bfc825a4f6a82 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 22 May 2023 12:12:47 +0530 Subject: [PATCH 0731/2055] Fix #2018698 [Toolbar button wordwrap glitch](https://bugs.launchpad.net/calibre/+bug/2018698) --- src/calibre/gui2/bars.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/bars.py b/src/calibre/gui2/bars.py index c45cb779f7..7108eafedc 100644 --- a/src/calibre/gui2/bars.py +++ b/src/calibre/gui2/bars.py @@ -85,7 +85,13 @@ def wrap_button_text(text, max_len=MAX_TEXT_LENGTH): broken = True else: ans = word - if not broken: + if broken: + prefix, suffix = ans.split('\n', 1) + if len(suffix) > len(prefix) and len(suffix) > MAX_TEXT_LENGTH and len(prefix) < MAX_TEXT_LENGTH and suffix.count(' ') > 1: + word, rest = suffix.split(' ', 1) + if len(word) + len(prefix) <= len(rest): + ans = prefix + ' ' + word + '\n' + rest + else: if ' ' in ans: ans = '\n'.join(ans.split(' ', 1)) elif '/' in ans: From 7b759f96d62f5a9fadeb504ff6ca214b6b7eac7d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 22 May 2023 13:26:04 +0530 Subject: [PATCH 0732/2055] Fix #2020233 [Enhancement Request: Send-to-Device: Ask first if book already on device](https://bugs.launchpad.net/calibre/+bug/2020233) --- src/calibre/gui2/device.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index e1a6db40c3..f7179be0de 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -1043,6 +1043,28 @@ class DeviceMixin: # {{{ def _sync_action_triggered(self, *args): m = getattr(self, '_sync_menu', None) if m is not None: + ids = self.library_view.get_selected_ids(as_set=True) + db = self.current_db.new_api + already_on_device = db.all_field_for('ondevice', ids, default_value='') + books_on_device = {book_id for book_id, val in already_on_device.items() if val} + if books_on_device: + if len(books_on_device) == 1: + if not question_dialog(self, _('Book already on device'), _( + 'The book {} is already present on the device. Resending it might cause any' + ' annotations/bookmarks on the device for this book to be lost. Are you sure?').format( + db.field_for('title', tuple(books_on_device)[0])), skip_dialog_name='confirm-resend-existing-books' + ): + return + else: + title_sorts = db.all_field_for('sort', books_on_device) + titles = sorted(db.all_field_for('title', books_on_device).items(), key=lambda x: title_sorts[x[0]]) + details = '\n'.join(title for book_id, title in titles) + if not question_dialog(self, _('Some books already on device'), _( + 'Some of the selected books are already on the device. Resending them might cause any annotations/bookmarks on the' + ' device for these books to be lost. Click "Show details" to see the books already on the device. Are you sure?'), + skip_dialog_name='confirm-resend-existing-books', det_msg=details + ): + return m.trigger_default() def create_device_menu(self): From 031f4c2bb2a108495ad37e8bc08f685ed9943ba8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 22 May 2023 14:25:44 +0530 Subject: [PATCH 0733/2055] ... --- recipes/high_country_news.recipe | 1 + 1 file changed, 1 insertion(+) diff --git a/recipes/high_country_news.recipe b/recipes/high_country_news.recipe index 547c8b728c..d95834e8e9 100644 --- a/recipes/high_country_news.recipe +++ b/recipes/high_country_news.recipe @@ -48,6 +48,7 @@ class HighCountryNews(BasicNewsRecipe): auto_cleanup = False remove_javascript = True remove_empty_feeds = True + remove_attributes = ['width', 'height'] use_embedded_content = False masthead_url = 'http://www.hcn.org/logo.jpg' From 71d2fd05338dbd4869b6ee3e1c3471fd4772f2ea Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 22 May 2023 19:51:17 +0530 Subject: [PATCH 0734/2055] Revert string change, making the message a bit clearer --- src/calibre/gui2/dialogs/tag_categories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/tag_categories.py b/src/calibre/gui2/dialogs/tag_categories.py index 4c68173b78..a79758f8d9 100644 --- a/src/calibre/gui2/dialogs/tag_categories.py +++ b/src/calibre/gui2/dialogs/tag_categories.py @@ -295,7 +295,7 @@ class TagCategories(QDialog, Ui_TagCategories): _('The hierarchical prefix of the new category is already used, ' 'perhaps with different case.'), det_msg=_('Existing prefix: {prefix}\n' - 'New prefix: {new}').format(prefix=c, new=cat_name), + 'New category name: {new}').format(prefix=c, new=cat_name), show=True) return False if cat_name not in self.user_categories: From 9bfcd7c3bd51bb9460e432b02f81224f208ee0c6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 23 May 2023 07:10:57 +0530 Subject: [PATCH 0735/2055] ... --- src/calibre/gui2/dialogs/tag_categories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/tag_categories.py b/src/calibre/gui2/dialogs/tag_categories.py index a79758f8d9..915f946926 100644 --- a/src/calibre/gui2/dialogs/tag_categories.py +++ b/src/calibre/gui2/dialogs/tag_categories.py @@ -287,7 +287,7 @@ class TagCategories(QDialog, Ui_TagCategories): if strcmp(c, cat_name) == 0: error_dialog(self, _('Name already used'), _('The user category name is already used, perhaps with different case.'), - det_msg=_('Existing category: {existing}\nNew category: {new}').format(existing=c, new=cat_name), + det_msg=_('Existing category: {existing}\nNew category name: {new}').format(existing=c, new=cat_name), show=True) return False if icu_lower(cat_name).startswith(icu_lower(c) + '.') and not cat_name.startswith(c + '.'): From 924ba7adfeb23b5497e0992f490f95156ba44b28 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 23 May 2023 17:55:51 +0530 Subject: [PATCH 0736/2055] Content server: Allow disabling full text search via the web interface. Fixes #2020237 [[content server] Disable fulltext search activation via web interface](https://bugs.launchpad.net/calibre/+bug/2020237) --- src/calibre/srv/fts.py | 8 ++++++++ src/pyj/book_list/fts.pyj | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/calibre/srv/fts.py b/src/calibre/srv/fts.py index 5186f0f276..c3959d7cab 100644 --- a/src/calibre/srv/fts.py +++ b/src/calibre/srv/fts.py @@ -54,6 +54,14 @@ def fts_search(ctx, rd): return ans +@endpoint('/fts/disable', needs_db_write=True) +def fts_disable(ctx, rd): + db = get_library_data(ctx, rd)[0] + if db.is_fts_enabled(): + db.enable_fts(enabled=False) + return '' + + @endpoint('/fts/reindex', needs_db_write=True, methods=('POST',)) def fts_reindex(ctx, rd): db = get_library_data(ctx, rd)[0] diff --git a/src/pyj/book_list/fts.pyj b/src/pyj/book_list/fts.pyj index d3837b40e4..d806266d9c 100644 --- a/src/pyj/book_list/fts.pyj +++ b/src/pyj/book_list/fts.pyj @@ -8,7 +8,7 @@ from ajax import ajax, ajax_send from book_list.cover_grid import THUMBNAIL_MAX_HEIGHT, THUMBNAIL_MAX_WIDTH from book_list.globals import get_current_query, get_session_data from book_list.library_data import current_library_id, download_url -from book_list.router import back, open_book_url, push_state +from book_list.router import back, home, open_book_url, push_state from book_list.top_bar import create_top_bar from book_list.ui import set_panel_handler from book_list.views import create_image @@ -34,7 +34,9 @@ add_extra_css(def(): ) def component(name): - return document.getElementById(overall_container_id)?.querySelector(f'[data-component="{name}"]') + c = document.getElementById(overall_container_id) + if c: + return c.querySelector(f'[data-component="{name}"]') def showing_search_panel(): @@ -157,6 +159,8 @@ def clear_to_help(): container.appendChild(E.div( style='margin-top: 1ex', E.a(_('Re-index all books in this library'), class_='blue-link', href='javascript:void(0)', onclick=reindex_all), + E.span('\xa0\xa0'), + E.a(_('Disable full text search'), class_='blue-link', href='javascript:void(0)', onclick=disable_fts), )) container = container.firstChild fts_url = 'https://www.sqlite.org/fts5.html#full_text_query_syntax' @@ -245,6 +249,32 @@ def reindex_all(): ) +def disable_fts_backend(): + def on_response(end_type, xhr, ev): + if end_type is 'abort' or not showing_search_panel(): + return + if end_type is not 'load': + if xhr.status is 403: + return error_dialog(_('Permission denied'), _( + 'You do not have permission to disable FTS. Only logged in users with write permission are allowed to disable FTS.'), xhr.error_html) + return error_dialog(_('Disabling FTS failed'), _('Disabling FTS failed. Click "Show details" for more information.'), xhr.error_html) + info_dialog(_('Full text searching disabled'), _('Full text searching for this library has been disabled. In the future the entire library will have to be re-indexed when re-enabling full text searching.'), on_close=def(): + window.setTimeout(home) + ) + + ajax(f'fts/disable', on_response, bypass_cache=True).send() + + + +def disable_fts(): + question_dialog(_('Are you sure?'), _('Disabling full text search means that in the future the entire library will have to be re-indexed to use full text search. Are you sure you want to proceed?'), + def (yes): + if yes: + disable_fts_backend() + ) + + + def book_result_tile_clicked(ev): result_tile = ev.currentTarget bid = int(result_tile.dataset.bookId) From 4ecd1a2b7d271573dc8eb968d4f3a28adabe76d3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 24 May 2023 08:23:22 +0530 Subject: [PATCH 0737/2055] Cover download: Allowing saving alternate covers to disk or in the book's data folder. Fixes #2020603 [[Enhancement] Cover Image Downloader: Save cover to disk/data folder](https://bugs.launchpad.net/calibre/+bug/2020603) --- src/calibre/gui2/metadata/single_download.py | 54 +++++++++++++++++--- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index ee98e224cc..bfadbb4696 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -24,16 +24,20 @@ from threading import Event, Thread from calibre import force_unicode from calibre.customize.ui import metadata_plugins +from calibre.db.constants import COVER_FILE_NAME, DATA_DIR_NAME from calibre.ebooks.metadata import authors_to_string, rating_to_stars from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.opf2 import OPF from calibre.ebooks.metadata.sources.identify import urls_from_identifiers -from calibre.gui2 import error_dialog, gprefs, rating_font +from calibre.gui2 import ( + choose_save_file, error_dialog, gprefs, rating_font, +) from calibre.gui2.progress_indicator import SpinAnimator from calibre.gui2.widgets2 import HTMLDisplay from calibre.library.comments import comments_to_html from calibre.ptempfile import TemporaryDirectory from calibre.utils.date import UNDEFINED_DATE, as_utc, format_date, fromordinal, utcnow +from calibre.utils.img import image_to_data, save_image from calibre.utils.ipc.simple_worker import WorkerError, fork_job from calibre.utils.logging import GUILog as Log from calibre.utils.resources import get_image_path as I @@ -772,6 +776,7 @@ class CoversView(QListView): # {{{ def __init__(self, current_cover, parent=None): QListView.__init__(self, parent) + self.book_id = getattr(parent, 'book_id', 0) self.m = CoversModel(current_cover, self) self.setModel(self.m) @@ -828,26 +833,58 @@ class CoversView(QListView): # {{{ m = QMenu(self) m.addAction(QIcon.ic('view.png'), _('View this cover at full size'), self.show_cover) m.addAction(QIcon.ic('edit-copy.png'), _('Copy this cover to clipboard'), self.copy_cover) + m.addAction(QIcon.ic('save.png'), _('Save this cover to disk'), self.save_to_disk) + if self.book_id: + m.addAction(QIcon.ic('save.png'), _('Save this cover as in the book extra files'), self.save_alternate_cover) m.exec(QCursor.pos()) - def show_cover(self): + @property + def current_pixmap(self): idx = self.currentIndex() pmap = self.model().cover_pixmap(idx) if pmap is None and idx.row() == 0: pmap = self.model().cc + return pmap + + def show_cover(self): + pmap = self.current_pixmap if pmap is not None: from calibre.gui2.image_popup import ImageView - d = ImageView(self, pmap, str(idx.data(Qt.ItemDataRole.DisplayRole) or ''), geom_name='metadata_download_cover_popup_geom') + d = ImageView(self, pmap, str(self.currentIndex().data(Qt.ItemDataRole.DisplayRole) or ''), geom_name='metadata_download_cover_popup_geom') d(use_exec=True) def copy_cover(self): - idx = self.currentIndex() - pmap = self.model().cover_pixmap(idx) - if pmap is None and idx.row() == 0: - pmap = self.model().cc + pmap = self.current_pixmap if pmap is not None: QApplication.clipboard().setPixmap(pmap) + def save_to_disk(self): + pmap = self.current_pixmap + if pmap: + path = choose_save_file(self, 'save-downloaded-cover-to-disk', _('Save cover image'), filters=[ + (_('Images'), ['jpg', 'jpeg', 'png', 'webp'])], all_files=False, initial_filename=COVER_FILE_NAME) + if path: + save_image(pmap.toImage(), path) + + def save_alternate_cover(self): + pmap = self.current_pixmap + if pmap: + from calibre.gui2.ui import get_gui + db = get_gui().current_db.new_api + existing = {x[0] for x in db.list_extra_files(self.book_id)} + h, ext = os.path.splitext(COVER_FILE_NAME) + template = f'{DATA_DIR_NAME}/{h}-{{:03d}}{ext}' + for i in range(1, 1000): + q = template.format(i) + if q not in existing: + cdata = image_to_data(pmap.toImage()) + db.add_extra_files(self.book_id, {q: BytesIO(cdata)}, replace=False, auto_rename=True) + break + else: + error_dialog(self, _('Too many covers'), _( + 'Could not save cover as there are too many existing covers'), show=True) + return + def keyPressEvent(self, ev): if ev.key() in (Qt.Key.Key_Enter, Qt.Key.Key_Return): self.chosen.emit() @@ -873,6 +910,7 @@ class CoversWidget(QWidget): # {{{ self.msg = QLabel() self.msg.setWordWrap(True) + self.book_id = getattr(parent, 'book_id', 0) l.addWidget(self.msg, 0, 0) self.covers_view = CoversView(current_cover, self) @@ -1024,6 +1062,7 @@ class FullFetch(QDialog): # {{{ def __init__(self, current_cover=None, parent=None): QDialog.__init__(self, parent) self.current_cover = current_cover + self.book_id = getattr(parent, 'book_id', 0) self.log = Log() self.book = self.cover_pixmap = None @@ -1138,6 +1177,7 @@ class CoverFetch(QDialog): # {{{ def __init__(self, current_cover=None, parent=None): QDialog.__init__(self, parent) self.current_cover = current_cover + self.book_id = getattr(parent, 'book_id', 0) self.log = Log() self.cover_pixmap = None From 85ff696f22bd6efca737d70b5971d5ee32aa5785 Mon Sep 17 00:00:00 2001 From: chocolatechipcats <47759676+chocolatechipcats@users.noreply.github.com> Date: Wed, 24 May 2023 02:09:41 -0300 Subject: [PATCH 0738/2055] Update single_download.py Removing an extraneous word --- src/calibre/gui2/metadata/single_download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index bfadbb4696..efc8916fb9 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -835,7 +835,7 @@ class CoversView(QListView): # {{{ m.addAction(QIcon.ic('edit-copy.png'), _('Copy this cover to clipboard'), self.copy_cover) m.addAction(QIcon.ic('save.png'), _('Save this cover to disk'), self.save_to_disk) if self.book_id: - m.addAction(QIcon.ic('save.png'), _('Save this cover as in the book extra files'), self.save_alternate_cover) + m.addAction(QIcon.ic('save.png'), _('Save this cover in the book extra files'), self.save_alternate_cover) m.exec(QCursor.pos()) @property From 25c935568cc795a9d9fe421537c8c611546ea364 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 24 May 2023 12:15:16 +0530 Subject: [PATCH 0739/2055] Tag browser: Fix using F2 to edit items not allowing completion Also fixes incorrect completion data when using F2 after using rename on an item from another category. Rather than setting the completion data in the context menu handler, load it when actually creating the editor. --- src/calibre/gui2/tag_browser/view.py | 51 +++++++++++++++------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/calibre/gui2/tag_browser/view.py b/src/calibre/gui2/tag_browser/view.py index dad222f99a..1a7ddf125b 100644 --- a/src/calibre/gui2/tag_browser/view.py +++ b/src/calibre/gui2/tag_browser/view.py @@ -5,24 +5,31 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, re, traceback +import os +import re +import traceback +from contextlib import suppress from functools import partial - from qt.core import ( - QStyledItemDelegate, Qt, QTreeView, pyqtSignal, QSize, QIcon, QApplication, QStyle, QAbstractItemView, - QMenu, QPoint, QToolTip, QCursor, QDrag, QRect, QModelIndex, QPointF, QStyleOptionViewItem, - QLinearGradient, QPalette, QColor, QPen, QBrush, QFont, QTimer + QAbstractItemView, QApplication, QBrush, QColor, QCursor, QDrag, QFont, QIcon, + QLinearGradient, QMenu, QModelIndex, QPalette, QPen, QPoint, QPointF, QRect, QSize, + QStyle, QStyledItemDelegate, QStyleOptionViewItem, Qt, QTimer, QToolTip, QTreeView, + pyqtSignal, ) from calibre import sanitize_file_name from calibre.constants import config_dir from calibre.ebooks.metadata import rating_to_stars +from calibre.gui2 import ( + FunctionDispatcher, choose_files, config, empty_index, gprefs, pixmap_to_data, + question_dialog, rating_font, +) from calibre.gui2.complete2 import EditWithComplete -from calibre.gui2.tag_browser.model import (TagTreeItem, TAG_SEARCH_STATES, - TagsModel, DRAG_IMAGE_ROLE, COUNT_ROLE, rename_only_in_vl_question) +from calibre.gui2.tag_browser.model import ( + COUNT_ROLE, DRAG_IMAGE_ROLE, TAG_SEARCH_STATES, TagsModel, TagTreeItem, + rename_only_in_vl_question, +) from calibre.gui2.widgets import EnLineEdit -from calibre.gui2 import (config, gprefs, choose_files, pixmap_to_data, - rating_font, empty_index, question_dialog, FunctionDispatcher) from calibre.utils.icu import sort_key from calibre.utils.serialize import json_loads @@ -34,7 +41,6 @@ class TagDelegate(QStyledItemDelegate): # {{{ self.old_look = False self.rating_pat = re.compile(r'[%s]' % rating_to_stars(3, True)) self.rating_font = QFont(rating_font()) - self.completion_data = None self.tags_view = tags_view def draw_average_rating(self, item, style, painter, option, widget): @@ -123,9 +129,6 @@ class TagDelegate(QStyledItemDelegate): # {{{ if item.type == TagTreeItem.TAG and item.tag.state == 0 and config['show_avg_rating']: self.draw_average_rating(item, style, painter, option, widget) - def set_completion_data(self, data): - self.completion_data = data - def createEditor(self, parent, option, index): item = self.tags_view.model().get_node(index) if not item.ignore_vl: @@ -143,10 +146,19 @@ class TagDelegate(QStyledItemDelegate): # {{{ yes_text=_('Yes, apply in entire library'), no_text=_('No, apply only in Virtual library'), skip_dialog_name='tag_item_rename_in_entire_library') - if self.completion_data: + key, completion_data = '', None + if item.type == TagTreeItem.CATEGORY: + key = item.category_key + elif item.type == TagTreeItem.TAG: + key = getattr(item.tag, 'category', '') + if key: + from calibre.gui2.ui import get_gui + with suppress(Exception): + completion_data = get_gui().current_db.new_api.all_field_names(key) + if completion_data: editor = EditWithComplete(parent) editor.set_separator(None) - editor.update_items_cache(self.completion_data) + editor.update_items_cache(completion_data) else: editor = EnLineEdit(parent) return editor @@ -546,25 +558,16 @@ class TagsView(QTreeView): # {{{ self.recount() return - def set_completion_data(category): - try: - completion_data = self.db.new_api.all_field_names(category) - except: - completion_data = None - self.itemDelegate().set_completion_data(completion_data) - if action == 'edit_item_no_vl': item = self.model().get_node(index) item.use_vl = False item.ignore_vl = ignore_vl - set_completion_data(category) self.edit(index) return if action == 'edit_item_in_vl': item = self.model().get_node(index) item.use_vl = True item.ignore_vl = ignore_vl - set_completion_data(category) self.edit(index) return if action == 'delete_item_in_vl': From 7ec739064f2be3cbc2c59e93533cce05600ec408 Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Wed, 24 May 2023 22:10:07 +0530 Subject: [PATCH 0740/2055] ... --- recipes/icons/bar_and_bench.png | Bin 0 -> 1497 bytes recipes/icons/bloomberg-business-week.png | Bin 0 -> 15406 bytes recipes/icons/bloomberg.png | Bin 0 -> 15406 bytes recipes/icons/wash_post_print.png | Bin 0 -> 566 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 recipes/icons/bar_and_bench.png create mode 100644 recipes/icons/bloomberg-business-week.png create mode 100644 recipes/icons/bloomberg.png create mode 100644 recipes/icons/wash_post_print.png diff --git a/recipes/icons/bar_and_bench.png b/recipes/icons/bar_and_bench.png new file mode 100644 index 0000000000000000000000000000000000000000..abd3c4c13d151b7859e9e587e72d6c669f240f61 GIT binary patch literal 1497 zcmV;~1t$85P)+@frkg4&z|Q7-gD1A56J2DdMOcM z&_p1HZ$>Y02_GdQ`1~2jVFX^Um!?zaX*|(LTT>gZ&K?9I=9Xw~ofOaH)JY`Voj^on z1lM6VutFqDXBz$D0pGtK;IWf?7vt=x$3e0KsX zN|rOlI++j*B_bLy2~dChEG5fI>FMg>W{4U)2K&|(q~4c85JQQG222KBIMdALU%W_9 zR}VMSm~V~YH#=98G(C|3h7u7CnE1F|Zk~JSd0HD=2{u0d+gKtjgwxmKp}Vu2;lxcG z&xXD07&l=YgA64i8ZhyxDy-(i@*09IT)lwImGc-$cV{_5rhw*8$;4K0L{k(EwP z(NYE(N<=hZwx4fj{R7W1;29v8!)Zs`tX!|Yo<3fAw1m22^#oH&vF`1)-0{O7;A1Ee z(STW1Sj~s!HH6}D+R-*Eqj9O>5*r@aNGKUu>EskG#m7)0qP+uN^T{8tq3cQ)p*Wm& zw9SfN-LZP!D%wMTUqAn+P)d=II*DIyT#T|Ph7`(1l}=94 zQbUP|E?-k&9(wCAH=>|2pX9V8TvuI`EGy;gXAKNT&#~gzW;c|G=<=s3%tLz)6OM?0 zaE-Dkf)!Nelbn`>pR3bF<)$kB@z*1Sqvu$0Y_l6mM0DkIRc1{^E#YJrEg|)%DGYHs z%BVknmOurS`6Q<$agD&1Wu=@vaFS5cAG(J}U(7a?i0FOgHRi6uYQk|i?P!}7zkeP4 zlI^Q^;OcY{XlCXNa*K0NN-^Z*fm3YB-AX8#dGlDfYJs6dMAv^>Z*E@v65%+UcC^ik zU)kfYa{Rr|2xappFAz6zJVRQ}wXlBfGla6T>@lpfrx{8_^nho;{B1@7uB$FWaX9U0 zn-#ydrZ#GK?#JWq=Nbh`(-XEiHLCFbTc1U9pu29 zAK~ry5^VZh&10o27-T3B(SV6hcSkoHGdI!E(m^1L#lrXR{2msIg>XE59b5)loU1UOOq z1!a%FipTBYW{8N4Abax??pZLCAchhV4VVO}|FoX6Cw9=;*2#^Cjf*9>IER#b?;-?4 ziHHVFLg>2OMb-1WIr{D~M1;}Ob2N)LEMmg#69~glA~F>?%`loyHSxh~`!L`CoW9;Z zh7%bTiFNigY}xmd^rJ*Zkgh}opWVRE8G+m7=FHJL8c#IR`gI#_mm9yR=qTdvxRr#v z6Sy;dD$%z@a~(ba9#kU2ps7F%-;7@1G$8*2Tb*6aV)M6n00000NkvXXu0mjfeo@q> literal 0 HcmV?d00001 diff --git a/recipes/icons/bloomberg-business-week.png b/recipes/icons/bloomberg-business-week.png new file mode 100644 index 0000000000000000000000000000000000000000..22fcd0ce9b497ed356aed06b40e89e50cf0033c9 GIT binary patch literal 15406 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 Ef5)%KC;$Ke literal 0 HcmV?d00001 diff --git a/recipes/icons/bloomberg.png b/recipes/icons/bloomberg.png new file mode 100644 index 0000000000000000000000000000000000000000..22fcd0ce9b497ed356aed06b40e89e50cf0033c9 GIT binary patch literal 15406 zcmeI2X^d4x7RMjZtSXCu3ku!1AkqYM7(hluL`{qe?wWBOg3%F-;)ov{1lq(%Kw~sS zz#w#MBBF>Icfk>jf+7hj2?DW6H)&8=R1lG!Gyh*{z1(--t@my(^2tj6@7{NBRh?6( z&N+4J)Xij?Wm;r9cFgEnmic!{CNn6L$&{7-{=avpOlF>*opsh9?|Wr3%e!VWy;Vm| z)Z+K&x}+)1Rp}qXEMdKHK-ed&6J`nn1nMM}4th9SpuPpLN6+a8)}n>~O9d+}Ew#%p zzdQ#+hYq!l9Xm$*q%QO*@I#;O{n)W%?eO8l4u=jMGH~$VLHq8z@9ehQZnI|1n)!a< zm9~rtzOwC2W!}7bj)zgBM%jZ8K4?Gx{ImV~>#ug^nP>X;j1yU9Y10SvDGxmGfUoDS zGiJeM;mCv zL-x6E|HvbcSerI&tX;cycFi@{*#7gwC2s5XX~mRV@C$rXTE(!MMd^m=gysN z$BrFVTU+b;jMPW=%t2NLzWv*8zwPE?{P^((@94zh#fz;)ix$3L=G^Nuy5OGQb=O@^ zU(gwV%=hlyYb#c)u=4V9-wnIXk zPO<;YZ=Enz=q}J_n$kj*h6qapWLu0*vG>Sqv49-uyHMf%C}FM8kTHjNp)dNR?+_2j z2Og=H?dPbYjxv13tXZ=PV%oH6cE=re*x6^FZO0sQOx&OO{^%RoCd~zNgq(85-L-31 z`|!gL-FoHMt)wgdX!q{jwr$%sd+oK??7|B#w3aPfdb<8f-^^>N5H08^dXlH#&|kWA zsavD`Pnjr%$%8NdMRrb4|fdV5`_mUfnqTFTVJ~%F4>}>a}m*-i8euW*>k2 zv0GD+2kXPci4*hciVpKoCE(ZaRrrgfdX|8GF0&OfozjW^yH>|3;0$I!)KojCpK>S`CiTzl=c<}qf> z7<>5Phwal(KXvgOgC|CrKYxBaK91924K3*7cqQ%&K9FbTjPM>&r)v8ra*2w#_&ptEe1X$-c+qxZ{qqE3drLKKke*H}A+{{rdHG?z!g{lL5Mq z4Wy>OX3d(APc`;qKmGJmRx_qgpB|6P<8-ind_dm1<9Uk9zYzT{UAnk&dj^n4hYlU$ zH2r^k9=;DBh%fZdbFOLWqicf(4RZS;PydroJ}G2>_U=1WD z@L0KWrES@=#glc{UAuPKb=O@Nii23|S@V5qr<-X)o-c~i&mj`{1MSBidn`2f=o+z3 zJT5QQvn4{#JV2lQA$A?Yo;`b94glR>zI?geefQngsZ*z{4ArNgC8m|9u|~xzZPX(swBS z)+<+d50A(x=oj=ZEg$F<^NMVX(N+~#kPmZ&o}jDPOImuq9%II)(CbnmBy+tA7IYLl z!B+7X!8lFtJY8glKdTauBevDpf}iLo;4kM0tQ*8F-dj`W4a0# z2-q!t4}SLuQC>aI#jdoWt&bHebU#q&ETr{ks+y``=Y53=0Uv@K8^UvFOw7-A0bem$ zC>NmF*n%9o2$udnD2(p1$oz#g)zbkVYkqfG7pUv8rTqX zF;3_q)HerAecDYCss-;$LNx!3=d8^e{@|fLV_?2K@2ssk`a(R<9+EvQXFK&luU@@e zj)K=!F=!JdVEL{(LlO`bg2 zuDa?fJL8Nq?AT+EEtE%LEbve*pvTx^p@Pk0H|SnsY{eN7`9sc{{FzIdD|yg;`}Vnb z{Kp@Ew7R-Fw;!J}Ws1v9F&_o;0_ZO`c$pCLOUzBVfbC;H(Rs|?!#{ER=FOV}yOi{g z84vxu_10T1=fmEjz!>l!_(1%4k{+>cOcv^d!$Owl=zSsn{n=^KeDE$*$eoZM9XWDj zC?^On@Pz-a5YTaA0bf|R2MXv-L0=Q%pZrvfa?_uG{&}8Fuy-SWv|+;rx1WXIXg-;7 zan{RzuwTD^h4xw4!Ww}XfH=mNZn~)uSOXK~?w^PFN2fVM9XN1co=+mz4PWP=|F7wl43Jr2pgz*dKF7 z66%AqXnZf@;e0%vTVTKa#v5-q-yG^g&(R}duZsj?VB%L|z->a1wtp9re;WRMALRe~ z_wVn{Z_qnmn1i)z*E)Yxz>eXOxRKbG_?;M>IF?xOi0}`c6Hh$R=FXk#^xpH&6&qrG ziG4Q^=L=pJ3T47zfml4~bM!9TzA5BSf8+!oee_ZHoko;@z8T<*tO)<`%ASLL6MLar zAf3QXLp>5l?E`QgzZ(n=ji6?UW53(nwxbx0CT}+gM zA7TXd+BHI&{EZu5NdDWmZ#TYy!LOWp>Zv*4dlKZ$cQp?^^pMNXQs4W3cqd26`ROUA zoRTIkg%|eNn*?NE)bFcKLH^MLo&lKVceDCY8 zzwYc7`FL7IuGlcKLMp%G`wGe5>u{PYyc4@I2iPlX8EbRkJT&ciQq^k;{?VHn`T3`w zdde=j&y>zIhT9qop)^X=+V}rM~^}= zTWDP9Ji3n!U<;voInP786|9S{>CN{OQXtzwFim@ltFI?6uf) zbrRTvu@_q_6xE?Z{ImaMEuu~otgCzzfo!k1;tCi4;BTXawHUkRTvvP)0)Bs`FjOcJ znhEF-dt!W2p|-*JlXdHrS6;E(Z@=B;hwzmix7>1z`#zlSu~`TF8sPb79#{uBS5FZ` zkYlJ2$T|2zF5&`#+(f}xE+l{aI6A;LR;*J|5VP_9Av*5;zUQ4Q>of5iYgmzeFYy|( zC)X1_t6>hh3R48uc0F_8z&zJKy?{4`qKK+9ALbxwXkcXCYq5$&s5QN4Hx@ z?58RChi|@FAl@bhy87y?-F~=;?1>S`ZI%m3`9nQ+xzWpXC0EK^<&6RU`92MOjmBuj z?(y&VSiT`6_q$ZznGp9388XD>(u?p;E*ZVKR0!p7^~x1;)MJEdAJFYjvN3iE}|VK>-*osekPdfpV{LSOLT_(1#!wwP9DR5e(z z`Ep^Bz+7w-no_5bIcxtKVTynsFXm@drCtR&ux|WafQBCjv0y{=iZ-;}DiC|k5QqVo z@1~Hsx=Ado+uekr!hZ$gSYq*7fjtp&Bp-$@c%6b*_N&+s{B9K3b1W2yeJ>Kq1mYNC zfg@2$M1=U27<{n6UTCiHflwnb4nHs040_F8yHdDc7$KAi#P7twM~ZKe`m6jUf!`(Y Ef5)%KC;$Ke literal 0 HcmV?d00001 diff --git a/recipes/icons/wash_post_print.png b/recipes/icons/wash_post_print.png new file mode 100644 index 0000000000000000000000000000000000000000..a2b47a8249ebdbec6bf986f7c2e411a8c7d25fa4 GIT binary patch literal 566 zcmV-60?GY}P)9NQeU@M8Zml1SB9K5|BXQN_M$Uu9t@CpH7+S%-1A zBfl z9ZV!yTS-ZqU24GI09#`QY?69Fg_<%^>sv>ZKi`D(=p;g|7onx72;SxP+BUX*K%YRU zokU0mP~k|?RsfA+1!Nin10o~~bb_Q1z0-(756mz}!USswI=hVdh=O-aUOWY=kZ0&i z#6$2oZ=53?08z{vJp;#H62YW8ivsimT}r(s(Wi2jUE%8I?F?ASNq7e24hUxeO@jdY zK_J`%_QlZ)V4r|Dfb63)AQ9dJiCzN>E&z=x`f&{A)PK;99)`#)lK&c-V%c`2y&n8ypS*fLWgamLkJsB$$KN zGtdKovwAKRp0p~j%E3$hR{(8zXy>Sbo@y){xB8t<0pDNqtkmoOeAc7*1fY-=C_?Q- zsXfuQm28aw$5vLs>FN|fn(s=|)YnJQ`m_&Lz@OZexs)guy99mxI&=8ZWqW^M3f#D@ z|1Dr01RU%Tuw=#vz;#zl_W_pR7C<2n0aN^?-;Mvk2cfl&5&|6bB>(^b07*qoM6N<$ Eg3MU&uK)l5 literal 0 HcmV?d00001 From 2eebd8b5612fbd65d433f3c0ac0e3415aec54caa Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 25 May 2023 07:22:45 +0530 Subject: [PATCH 0741/2055] ... --- src/calibre/utils/podofo/global.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/utils/podofo/global.h b/src/calibre/utils/podofo/global.h index 87a56cb04b..b1b1e699b5 100644 --- a/src/calibre/utils/podofo/global.h +++ b/src/calibre/utils/podofo/global.h @@ -131,6 +131,7 @@ object_as_reference(const PdfObject *o) { } // Needed to avoid PoDoFo clobbering the /Info and XMP metadata with its own nonsense +// rename to NoMetadataUdate after https://github.com/podofo/podofo/commit/96689eb6e45b71eae1577ecb2d4a796c52e9a813 static const PdfSaveOptions save_options = PdfSaveOptions::NoModifyDateUpdate; class PdfReferenceHasher { From db74bfa41eb3714452e2fc46e7747b16ace87863 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 25 May 2023 08:12:14 +0530 Subject: [PATCH 0742/2055] A more convenient way to build against a custom podofo 0.10 --- setup/build_environment.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup/build_environment.py b/setup/build_environment.py index e9d5ddfa85..3918e048bb 100644 --- a/setup/build_environment.py +++ b/setup/build_environment.py @@ -207,9 +207,14 @@ else: uchardet_libs = pkgconfig_libs('uchardet', '', '') +if 'PODOFO_PREFIX' in os.environ: + os.environ['PODOFO_LIB_DIR'] = os.path.join(os.environ['PODOFO_PREFIX'], 'lib') + os.environ['PODOFO_INC_DIR'] = os.path.join(os.environ['PODOFO_PREFIX'], 'include', 'podofo') + os.environ['PODOFO_LIB_NAME'] = os.path.join(os.environ['PODOFO_PREFIX'], 'lib', 'libpodofo.so.1') podofo_lib = os.environ.get('PODOFO_LIB_DIR', podofo_lib) podofo_inc = os.environ.get('PODOFO_INC_DIR', podofo_inc) podofo = os.environ.get('PODOFO_LIB_NAME', 'podofo') + podofo_error = None if os.path.exists(os.path.join(podofo_inc, 'podofo.h')) else \ ('PoDoFo not found on your system. Various PDF related', ' functionality will not work. Use the PODOFO_INC_DIR and', From b7795f989eb86cfa3ef5e2920e008f7ad26126e0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 25 May 2023 08:32:41 +0530 Subject: [PATCH 0743/2055] Fix invocation of ruff since its CLI has changed --- setup/check.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/check.py b/setup/check.py index 22c88ccd31..2b64eb6bd3 100644 --- a/setup/check.py +++ b/setup/check.py @@ -77,7 +77,7 @@ class Check(Command): def file_has_errors(self, f): ext = os.path.splitext(f)[1] if ext in {'.py', '.recipe'}: - p2 = subprocess.Popen(['ruff', '--no-update-check', f]) + p2 = subprocess.Popen(['ruff', 'check', f]) return p2.wait() != 0 if ext == '.pyj': p = subprocess.Popen(['rapydscript', 'lint', f]) From fd207da67c69bca03dc1bdf04a43c88d9c7dd04c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 25 May 2023 08:33:02 +0530 Subject: [PATCH 0744/2055] Fix use of _ as local variable and gettext in same scope --- src/calibre/utils/formatter_functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 814c314139..0bfd0cb000 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -822,11 +822,11 @@ class BuiltinIdentifierInList(BuiltinFormatterFunction): raise ValueError(_("{} requires 2 or 4 arguments").format(self.name)) l = [v.strip() for v in val.split(',') if v.strip()] - (id_, _, regexp) = ident.partition(':') + (id_, __, regexp) = ident.partition(':') if not id_: return nfv for candidate in l: - i, _, v = candidate.partition(':') + i, __, v = candidate.partition(':') if v and i == id_: if not regexp or re.search(regexp, v, flags=re.I): return candidate if fv_is_id else fv From 9edf0308bb11df0e03214051fbd492d3d7ead95d Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Thu, 25 May 2023 10:07:54 +0530 Subject: [PATCH 0745/2055] ... --- recipes/icons/bloomberg-business-week.png | Bin 15406 -> 707 bytes recipes/icons/bloomberg.png | Bin 15406 -> 707 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/recipes/icons/bloomberg-business-week.png b/recipes/icons/bloomberg-business-week.png index 22fcd0ce9b497ed356aed06b40e89e50cf0033c9..820a6767c75d3913d96814417825df7771f0232f 100644 GIT binary patch literal 707 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dya{_!qT!Hle|NlirMgRT#w}1bB zAeWDi4@jbc>C>nG{Q0w}sOb9j>u6FyE-Nc*SXh|7z5Um(U(cL5bM)xZB}m|7qr9UUDeCMK_6zy9#yLsC+bpPyfLcJ_-GFQ!hN>R#aE*gxA7sLrG$ z$S)Y^$p8PZ|F`-3LhsjH5PSEZj#y515DN-?W#vEoo_zWkjQ^kEg~{SH1_s7Oo-U3d z7N=V$-<;O0AmAb$z|*Bx=y^xz?7sZT|Nn1ibWu$^H?u16mbZ4Q>7MAbcV^6w;MlF) ze|L#?^FFnA2fsO--n?^i4b$w7w+AnDxJC;XER*9A@tZ3baa;Mvr4w=68|IOu%ziB2l-E1;5zilPf#)@yV#T#Smsg1GxWW+{BAps_ zmx=#j$YsX}TkALM3}p8y8r+H literal 15406 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 Ef5)%KC;$Ke diff --git a/recipes/icons/bloomberg.png b/recipes/icons/bloomberg.png index 22fcd0ce9b497ed356aed06b40e89e50cf0033c9..820a6767c75d3913d96814417825df7771f0232f 100644 GIT binary patch literal 707 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dya{_!qT!Hle|NlirMgRT#w}1bB zAeWDi4@jbc>C>nG{Q0w}sOb9j>u6FyE-Nc*SXh|7z5Um(U(cL5bM)xZB}m|7qr9UUDeCMK_6zy9#yLsC+bpPyfLcJ_-GFQ!hN>R#aE*gxA7sLrG$ z$S)Y^$p8PZ|F`-3LhsjH5PSEZj#y515DN-?W#vEoo_zWkjQ^kEg~{SH1_s7Oo-U3d z7N=V$-<;O0AmAb$z|*Bx=y^xz?7sZT|Nn1ibWu$^H?u16mbZ4Q>7MAbcV^6w;MlF) ze|L#?^FFnA2fsO--n?^i4b$w7w+AnDxJC;XER*9A@tZ3baa;Mvr4w=68|IOu%ziB2l-E1;5zilPf#)@yV#T#Smsg1GxWW+{BAps_ zmx=#j$YsX}TkALM3}p8y8r+H literal 15406 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 Ef5)%KC;$Ke From 2670f912c1e39833de0c5f9133751f59f8723b3e Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Thu, 25 May 2023 22:48:14 +0530 Subject: [PATCH 0746/2055] ... --- recipes/{ => icons}/TheMITPressReader.png | Bin recipes/{ => icons}/rabble_ca.png | Bin recipes/{ => icons}/radio_prague.png | Bin recipes/{ => icons}/radio_praha.png | Bin recipes/{ => icons}/readers_digest.png | Bin .../{ => icons}/readersdigest_thehealthy.png | Bin recipes/{ => icons}/real_clear.png | Bin recipes/{ => icons}/reason_magazine.png | Bin recipes/{ => icons}/red_voltaire.png | Bin recipes/{ => icons}/regina_leader_post.png | Bin recipes/{ => icons}/republica.png | Bin recipes/{ => icons}/respekt_magazine.png | Bin recipes/{ => icons}/reuters.png | Bin recipes/{ => icons}/revista_cromos.png | Bin recipes/{ => icons}/revista_muy.png | Bin recipes/{ => icons}/revista_piaui.png | Bin recipes/{ => icons}/revista_semana.png | Bin recipes/{ => icons}/revista_summa.png | Bin recipes/{ => icons}/rga.png | Bin recipes/{ => icons}/rian_eng.png | Bin recipes/{ => icons}/rian_spa.png | Bin recipes/{ => icons}/roger_ebert.png | Bin recipes/{ => icons}/roger_ebert_blog.png | Bin recipes/{ => icons}/rt.png | Bin recipes/{ => icons}/rubikon_de.png | Bin recipes/{ => icons}/sabit_fikir.png | Bin recipes/{ => icons}/saechsische.png | Bin recipes/{ => icons}/sage_news_opinion.png | Bin recipes/{ => icons}/salonica_press_news.png | Bin recipes/{ => icons}/samanyolu_teknoloji.png | Bin recipes/{ => icons}/sardinia_post.png | Bin .../{ => icons}/saskatoon_star_phoenix.png | Bin recipes/{ => icons}/satmagazine.png | Bin recipes/{ => icons}/schwarzerpfeil.png | Bin recipes/{ => icons}/science_advances.png | Bin recipes/{ => icons}/science_news.png | Bin recipes/{ => icons}/scientific_american.png | Bin recipes/{ => icons}/scprint.png | Bin recipes/{ => icons}/seanhannity.png | Bin recipes/{ => icons}/seminar_magazine.png | Bin recipes/{ => icons}/serverside.png | Bin recipes/{ => icons}/sfbg.png | Bin recipes/{ => icons}/shacknews.png | Bin recipes/{ => icons}/shortlist.png | Bin recipes/{ => icons}/sigma_live.png | Bin recipes/{ => icons}/sign_on_sd.png | Bin recipes/{ => icons}/singtao_daily.png | Bin recipes/{ => icons}/singtaohk.png | Bin recipes/{ => icons}/sisainlive.png | Bin recipes/{ => icons}/sizinti_derigisi.png | Bin recipes/{ => icons}/skeptic.png | Bin recipes/{ => icons}/skeptical_enquirer.png | Bin recipes/{ => icons}/slate.png | Bin recipes/{ => icons}/slate_star_codex.png | Bin recipes/{ => icons}/slovo.png | Bin recipes/{ => icons}/sme.png | Bin recipes/{ => icons}/smith.png | Bin recipes/{ => icons}/sn_dk.png | Bin recipes/{ => icons}/snopes.png | Bin recipes/{ => icons}/sol_haber.png | Bin recipes/{ => icons}/southernstar.png | Bin recipes/{ => icons}/spectator_magazine.png | Bin recipes/{ => icons}/spin_magazine.png | Bin recipes/{ => icons}/sports_illustrated.png | Bin recipes/{ => icons}/sportstar.png | Bin recipes/{ => icons}/sporza_be.png | Bin recipes/{ => icons}/star_gazetesi.png | Bin recipes/{ => icons}/stars_and_stripes.png | Bin recipes/{ => icons}/stnn.png | Bin recipes/{ => icons}/strange_horizons.png | Bin recipes/{ => icons}/strategy-business.png | Bin recipes/{ => icons}/substack.png | Bin recipes/{ => icons}/sueddeutsche_mobil.png | Bin recipes/{ => icons}/sunday_times_magazine.png | Bin recipes/{ => icons}/superesportes.png | Bin recipes/{ => icons}/svt_nyheter.png | Bin recipes/{ => icons}/swarajya.png | Bin recipes/{ => icons}/t3n_de.png | Bin recipes/{ => icons}/t_online.png | Bin recipes/{ => icons}/taipei.png | Bin recipes/{ => icons}/tanea.png | Bin recipes/{ => icons}/taz.png | Bin recipes/{ => icons}/technology_review_de.png | Bin recipes/{ => icons}/techtarget.png | Bin recipes/{ => icons}/tedneward.png | Bin recipes/{ => icons}/thai_post_daily.png | Bin recipes/{ => icons}/thairath.png | Bin recipes/{ => icons}/the_athletic.png | Bin recipes/{ => icons}/the_baffler.png | Bin .../{ => icons}/the_budget_fashionista.png | Bin recipes/{ => icons}/the_clinic_online.png | Bin recipes/{ => icons}/the_conversation.png | Bin recipes/{ => icons}/the_daily_news_egypt.png | Bin recipes/{ => icons}/the_diplomat.png | Bin recipes/{ => icons}/the_feature.png | Bin recipes/{ => icons}/the_federalist.png | Bin recipes/{ => icons}/the_freeman.png | Bin recipes/{ => icons}/the_friday_times.png | Bin recipes/{ => icons}/the_journal.png | Bin recipes/{ => icons}/the_manila_bulletin.png | Bin recipes/{ => icons}/the_manila_times.png | Bin recipes/{ => icons}/the_marker.png | Bin recipes/{ => icons}/the_new_republic.png | Bin .../the_philippine_daily_inquirer.png | Bin recipes/{ => icons}/the_philippine_star.png | Bin recipes/{ => icons}/the_register.png | Bin recipes/{ => icons}/the_saturday_paper.png | Bin recipes/{ => icons}/the_sun.png | Bin recipes/{ => icons}/the_verge.png | Bin recipes/{ => icons}/the_week.png | Bin recipes/{ => icons}/thecodelesscode.png | Bin recipes/{ => icons}/thecultofghoul.png | Bin recipes/{ => icons}/thedgesingapore.png | Bin .../theeconomictimes_india_print_edition.png | Bin recipes/{ => icons}/themorningpaper.png | Bin recipes/{ => icons}/thenews.png | Bin recipes/{ => icons}/theprint.png | Bin recipes/{ => icons}/thn.png | Bin recipes/{ => icons}/tijolaco.png | Bin recipes/{ => icons}/tillsonburg.png | Bin recipes/{ => icons}/today_online.png | Bin recipes/{ => icons}/tomshardware_it.png | Bin recipes/{ => icons}/toyokeizai.png | Bin recipes/{ => icons}/tri_city_herald.png | Bin recipes/{ => icons}/tyzden.png | Bin recipes/{ => icons}/unperiodico.png | Bin recipes/{ => icons}/upi.png | Bin recipes/{ => icons}/usatoday.png | Bin recipes/{ => icons}/valbybladet_dk.png | Bin recipes/{ => icons}/vancouver_province.png | Bin recipes/{ => icons}/vancouver_sun.png | Bin recipes/{ => icons}/vanloesebladet_dk.png | Bin recipes/{ => icons}/veintitres.png | Bin recipes/{ => icons}/vesterbrobladet_dk.png | Bin recipes/{ => icons}/vic_times.png | Bin recipes/{ => icons}/villagevoice.png | Bin recipes/{ => icons}/vnexpress.png | Bin recipes/{ => icons}/voetbal_belgie.png | Bin recipes/icons/wsj_free.png | Bin 0 -> 134 bytes recipes/{ => icons}/xkcd.png | Bin recipes/{ => icons}/yakima_herald.png | Bin recipes/{ => icons}/yementimes.png | Bin recipes/{ => icons}/ynet.png | Bin recipes/{ => icons}/yomiuri_world.png | Bin recipes/{ => icons}/zackzack.png | Bin recipes/{ => icons}/zaobao.png | Bin recipes/{ => icons}/zeitde_sub.png | Bin recipes/{ => icons}/zerocalcare.png | Bin recipes/{ => icons}/zita_be.png | Bin recipes/{ => icons}/znadplanszy_pl.png | Bin recipes/{ => icons}/zougla.png | Bin recipes/wsj.recipe | 19 +++++++++++++----- recipes/wsj_free.recipe | 19 +++++++++++++----- 153 files changed, 28 insertions(+), 10 deletions(-) rename recipes/{ => icons}/TheMITPressReader.png (100%) rename recipes/{ => icons}/rabble_ca.png (100%) rename recipes/{ => icons}/radio_prague.png (100%) rename recipes/{ => icons}/radio_praha.png (100%) rename recipes/{ => icons}/readers_digest.png (100%) rename recipes/{ => icons}/readersdigest_thehealthy.png (100%) rename recipes/{ => icons}/real_clear.png (100%) rename recipes/{ => icons}/reason_magazine.png (100%) rename recipes/{ => icons}/red_voltaire.png (100%) rename recipes/{ => icons}/regina_leader_post.png (100%) rename recipes/{ => icons}/republica.png (100%) rename recipes/{ => icons}/respekt_magazine.png (100%) rename recipes/{ => icons}/reuters.png (100%) rename recipes/{ => icons}/revista_cromos.png (100%) rename recipes/{ => icons}/revista_muy.png (100%) rename recipes/{ => icons}/revista_piaui.png (100%) rename recipes/{ => icons}/revista_semana.png (100%) rename recipes/{ => icons}/revista_summa.png (100%) rename recipes/{ => icons}/rga.png (100%) rename recipes/{ => icons}/rian_eng.png (100%) rename recipes/{ => icons}/rian_spa.png (100%) rename recipes/{ => icons}/roger_ebert.png (100%) rename recipes/{ => icons}/roger_ebert_blog.png (100%) rename recipes/{ => icons}/rt.png (100%) rename recipes/{ => icons}/rubikon_de.png (100%) rename recipes/{ => icons}/sabit_fikir.png (100%) rename recipes/{ => icons}/saechsische.png (100%) rename recipes/{ => icons}/sage_news_opinion.png (100%) rename recipes/{ => icons}/salonica_press_news.png (100%) rename recipes/{ => icons}/samanyolu_teknoloji.png (100%) rename recipes/{ => icons}/sardinia_post.png (100%) rename recipes/{ => icons}/saskatoon_star_phoenix.png (100%) rename recipes/{ => icons}/satmagazine.png (100%) rename recipes/{ => icons}/schwarzerpfeil.png (100%) rename recipes/{ => icons}/science_advances.png (100%) rename recipes/{ => icons}/science_news.png (100%) rename recipes/{ => icons}/scientific_american.png (100%) rename recipes/{ => icons}/scprint.png (100%) rename recipes/{ => icons}/seanhannity.png (100%) rename recipes/{ => icons}/seminar_magazine.png (100%) rename recipes/{ => icons}/serverside.png (100%) rename recipes/{ => icons}/sfbg.png (100%) rename recipes/{ => icons}/shacknews.png (100%) rename recipes/{ => icons}/shortlist.png (100%) rename recipes/{ => icons}/sigma_live.png (100%) rename recipes/{ => icons}/sign_on_sd.png (100%) rename recipes/{ => icons}/singtao_daily.png (100%) rename recipes/{ => icons}/singtaohk.png (100%) rename recipes/{ => icons}/sisainlive.png (100%) rename recipes/{ => icons}/sizinti_derigisi.png (100%) rename recipes/{ => icons}/skeptic.png (100%) rename recipes/{ => icons}/skeptical_enquirer.png (100%) rename recipes/{ => icons}/slate.png (100%) rename recipes/{ => icons}/slate_star_codex.png (100%) rename recipes/{ => icons}/slovo.png (100%) rename recipes/{ => icons}/sme.png (100%) rename recipes/{ => icons}/smith.png (100%) rename recipes/{ => icons}/sn_dk.png (100%) rename recipes/{ => icons}/snopes.png (100%) rename recipes/{ => icons}/sol_haber.png (100%) rename recipes/{ => icons}/southernstar.png (100%) rename recipes/{ => icons}/spectator_magazine.png (100%) rename recipes/{ => icons}/spin_magazine.png (100%) rename recipes/{ => icons}/sports_illustrated.png (100%) rename recipes/{ => icons}/sportstar.png (100%) rename recipes/{ => icons}/sporza_be.png (100%) rename recipes/{ => icons}/star_gazetesi.png (100%) rename recipes/{ => icons}/stars_and_stripes.png (100%) rename recipes/{ => icons}/stnn.png (100%) rename recipes/{ => icons}/strange_horizons.png (100%) rename recipes/{ => icons}/strategy-business.png (100%) rename recipes/{ => icons}/substack.png (100%) rename recipes/{ => icons}/sueddeutsche_mobil.png (100%) rename recipes/{ => icons}/sunday_times_magazine.png (100%) rename recipes/{ => icons}/superesportes.png (100%) rename recipes/{ => icons}/svt_nyheter.png (100%) rename recipes/{ => icons}/swarajya.png (100%) rename recipes/{ => icons}/t3n_de.png (100%) rename recipes/{ => icons}/t_online.png (100%) rename recipes/{ => icons}/taipei.png (100%) rename recipes/{ => icons}/tanea.png (100%) rename recipes/{ => icons}/taz.png (100%) rename recipes/{ => icons}/technology_review_de.png (100%) rename recipes/{ => icons}/techtarget.png (100%) rename recipes/{ => icons}/tedneward.png (100%) rename recipes/{ => icons}/thai_post_daily.png (100%) rename recipes/{ => icons}/thairath.png (100%) rename recipes/{ => icons}/the_athletic.png (100%) rename recipes/{ => icons}/the_baffler.png (100%) rename recipes/{ => icons}/the_budget_fashionista.png (100%) rename recipes/{ => icons}/the_clinic_online.png (100%) rename recipes/{ => icons}/the_conversation.png (100%) rename recipes/{ => icons}/the_daily_news_egypt.png (100%) rename recipes/{ => icons}/the_diplomat.png (100%) rename recipes/{ => icons}/the_feature.png (100%) rename recipes/{ => icons}/the_federalist.png (100%) rename recipes/{ => icons}/the_freeman.png (100%) rename recipes/{ => icons}/the_friday_times.png (100%) rename recipes/{ => icons}/the_journal.png (100%) rename recipes/{ => icons}/the_manila_bulletin.png (100%) rename recipes/{ => icons}/the_manila_times.png (100%) rename recipes/{ => icons}/the_marker.png (100%) rename recipes/{ => icons}/the_new_republic.png (100%) rename recipes/{ => icons}/the_philippine_daily_inquirer.png (100%) rename recipes/{ => icons}/the_philippine_star.png (100%) rename recipes/{ => icons}/the_register.png (100%) rename recipes/{ => icons}/the_saturday_paper.png (100%) rename recipes/{ => icons}/the_sun.png (100%) rename recipes/{ => icons}/the_verge.png (100%) rename recipes/{ => icons}/the_week.png (100%) rename recipes/{ => icons}/thecodelesscode.png (100%) rename recipes/{ => icons}/thecultofghoul.png (100%) rename recipes/{ => icons}/thedgesingapore.png (100%) rename recipes/{ => icons}/theeconomictimes_india_print_edition.png (100%) rename recipes/{ => icons}/themorningpaper.png (100%) rename recipes/{ => icons}/thenews.png (100%) rename recipes/{ => icons}/theprint.png (100%) rename recipes/{ => icons}/thn.png (100%) rename recipes/{ => icons}/tijolaco.png (100%) rename recipes/{ => icons}/tillsonburg.png (100%) rename recipes/{ => icons}/today_online.png (100%) rename recipes/{ => icons}/tomshardware_it.png (100%) rename recipes/{ => icons}/toyokeizai.png (100%) rename recipes/{ => icons}/tri_city_herald.png (100%) rename recipes/{ => icons}/tyzden.png (100%) rename recipes/{ => icons}/unperiodico.png (100%) rename recipes/{ => icons}/upi.png (100%) rename recipes/{ => icons}/usatoday.png (100%) rename recipes/{ => icons}/valbybladet_dk.png (100%) rename recipes/{ => icons}/vancouver_province.png (100%) rename recipes/{ => icons}/vancouver_sun.png (100%) rename recipes/{ => icons}/vanloesebladet_dk.png (100%) rename recipes/{ => icons}/veintitres.png (100%) rename recipes/{ => icons}/vesterbrobladet_dk.png (100%) rename recipes/{ => icons}/vic_times.png (100%) rename recipes/{ => icons}/villagevoice.png (100%) rename recipes/{ => icons}/vnexpress.png (100%) rename recipes/{ => icons}/voetbal_belgie.png (100%) create mode 100644 recipes/icons/wsj_free.png rename recipes/{ => icons}/xkcd.png (100%) rename recipes/{ => icons}/yakima_herald.png (100%) rename recipes/{ => icons}/yementimes.png (100%) rename recipes/{ => icons}/ynet.png (100%) rename recipes/{ => icons}/yomiuri_world.png (100%) rename recipes/{ => icons}/zackzack.png (100%) rename recipes/{ => icons}/zaobao.png (100%) rename recipes/{ => icons}/zeitde_sub.png (100%) rename recipes/{ => icons}/zerocalcare.png (100%) rename recipes/{ => icons}/zita_be.png (100%) rename recipes/{ => icons}/znadplanszy_pl.png (100%) rename recipes/{ => icons}/zougla.png (100%) diff --git a/recipes/TheMITPressReader.png b/recipes/icons/TheMITPressReader.png similarity index 100% rename from recipes/TheMITPressReader.png rename to recipes/icons/TheMITPressReader.png diff --git a/recipes/rabble_ca.png b/recipes/icons/rabble_ca.png similarity index 100% rename from recipes/rabble_ca.png rename to recipes/icons/rabble_ca.png diff --git a/recipes/radio_prague.png b/recipes/icons/radio_prague.png similarity index 100% rename from recipes/radio_prague.png rename to recipes/icons/radio_prague.png diff --git a/recipes/radio_praha.png b/recipes/icons/radio_praha.png similarity index 100% rename from recipes/radio_praha.png rename to recipes/icons/radio_praha.png diff --git a/recipes/readers_digest.png b/recipes/icons/readers_digest.png similarity index 100% rename from recipes/readers_digest.png rename to recipes/icons/readers_digest.png diff --git a/recipes/readersdigest_thehealthy.png b/recipes/icons/readersdigest_thehealthy.png similarity index 100% rename from recipes/readersdigest_thehealthy.png rename to recipes/icons/readersdigest_thehealthy.png diff --git a/recipes/real_clear.png b/recipes/icons/real_clear.png similarity index 100% rename from recipes/real_clear.png rename to recipes/icons/real_clear.png diff --git a/recipes/reason_magazine.png b/recipes/icons/reason_magazine.png similarity index 100% rename from recipes/reason_magazine.png rename to recipes/icons/reason_magazine.png diff --git a/recipes/red_voltaire.png b/recipes/icons/red_voltaire.png similarity index 100% rename from recipes/red_voltaire.png rename to recipes/icons/red_voltaire.png diff --git a/recipes/regina_leader_post.png b/recipes/icons/regina_leader_post.png similarity index 100% rename from recipes/regina_leader_post.png rename to recipes/icons/regina_leader_post.png diff --git a/recipes/republica.png b/recipes/icons/republica.png similarity index 100% rename from recipes/republica.png rename to recipes/icons/republica.png diff --git a/recipes/respekt_magazine.png b/recipes/icons/respekt_magazine.png similarity index 100% rename from recipes/respekt_magazine.png rename to recipes/icons/respekt_magazine.png diff --git a/recipes/reuters.png b/recipes/icons/reuters.png similarity index 100% rename from recipes/reuters.png rename to recipes/icons/reuters.png diff --git a/recipes/revista_cromos.png b/recipes/icons/revista_cromos.png similarity index 100% rename from recipes/revista_cromos.png rename to recipes/icons/revista_cromos.png diff --git a/recipes/revista_muy.png b/recipes/icons/revista_muy.png similarity index 100% rename from recipes/revista_muy.png rename to recipes/icons/revista_muy.png diff --git a/recipes/revista_piaui.png b/recipes/icons/revista_piaui.png similarity index 100% rename from recipes/revista_piaui.png rename to recipes/icons/revista_piaui.png diff --git a/recipes/revista_semana.png b/recipes/icons/revista_semana.png similarity index 100% rename from recipes/revista_semana.png rename to recipes/icons/revista_semana.png diff --git a/recipes/revista_summa.png b/recipes/icons/revista_summa.png similarity index 100% rename from recipes/revista_summa.png rename to recipes/icons/revista_summa.png diff --git a/recipes/rga.png b/recipes/icons/rga.png similarity index 100% rename from recipes/rga.png rename to recipes/icons/rga.png diff --git a/recipes/rian_eng.png b/recipes/icons/rian_eng.png similarity index 100% rename from recipes/rian_eng.png rename to recipes/icons/rian_eng.png diff --git a/recipes/rian_spa.png b/recipes/icons/rian_spa.png similarity index 100% rename from recipes/rian_spa.png rename to recipes/icons/rian_spa.png diff --git a/recipes/roger_ebert.png b/recipes/icons/roger_ebert.png similarity index 100% rename from recipes/roger_ebert.png rename to recipes/icons/roger_ebert.png diff --git a/recipes/roger_ebert_blog.png b/recipes/icons/roger_ebert_blog.png similarity index 100% rename from recipes/roger_ebert_blog.png rename to recipes/icons/roger_ebert_blog.png diff --git a/recipes/rt.png b/recipes/icons/rt.png similarity index 100% rename from recipes/rt.png rename to recipes/icons/rt.png diff --git a/recipes/rubikon_de.png b/recipes/icons/rubikon_de.png similarity index 100% rename from recipes/rubikon_de.png rename to recipes/icons/rubikon_de.png diff --git a/recipes/sabit_fikir.png b/recipes/icons/sabit_fikir.png similarity index 100% rename from recipes/sabit_fikir.png rename to recipes/icons/sabit_fikir.png diff --git a/recipes/saechsische.png b/recipes/icons/saechsische.png similarity index 100% rename from recipes/saechsische.png rename to recipes/icons/saechsische.png diff --git a/recipes/sage_news_opinion.png b/recipes/icons/sage_news_opinion.png similarity index 100% rename from recipes/sage_news_opinion.png rename to recipes/icons/sage_news_opinion.png diff --git a/recipes/salonica_press_news.png b/recipes/icons/salonica_press_news.png similarity index 100% rename from recipes/salonica_press_news.png rename to recipes/icons/salonica_press_news.png diff --git a/recipes/samanyolu_teknoloji.png b/recipes/icons/samanyolu_teknoloji.png similarity index 100% rename from recipes/samanyolu_teknoloji.png rename to recipes/icons/samanyolu_teknoloji.png diff --git a/recipes/sardinia_post.png b/recipes/icons/sardinia_post.png similarity index 100% rename from recipes/sardinia_post.png rename to recipes/icons/sardinia_post.png diff --git a/recipes/saskatoon_star_phoenix.png b/recipes/icons/saskatoon_star_phoenix.png similarity index 100% rename from recipes/saskatoon_star_phoenix.png rename to recipes/icons/saskatoon_star_phoenix.png diff --git a/recipes/satmagazine.png b/recipes/icons/satmagazine.png similarity index 100% rename from recipes/satmagazine.png rename to recipes/icons/satmagazine.png diff --git a/recipes/schwarzerpfeil.png b/recipes/icons/schwarzerpfeil.png similarity index 100% rename from recipes/schwarzerpfeil.png rename to recipes/icons/schwarzerpfeil.png diff --git a/recipes/science_advances.png b/recipes/icons/science_advances.png similarity index 100% rename from recipes/science_advances.png rename to recipes/icons/science_advances.png diff --git a/recipes/science_news.png b/recipes/icons/science_news.png similarity index 100% rename from recipes/science_news.png rename to recipes/icons/science_news.png diff --git a/recipes/scientific_american.png b/recipes/icons/scientific_american.png similarity index 100% rename from recipes/scientific_american.png rename to recipes/icons/scientific_american.png diff --git a/recipes/scprint.png b/recipes/icons/scprint.png similarity index 100% rename from recipes/scprint.png rename to recipes/icons/scprint.png diff --git a/recipes/seanhannity.png b/recipes/icons/seanhannity.png similarity index 100% rename from recipes/seanhannity.png rename to recipes/icons/seanhannity.png diff --git a/recipes/seminar_magazine.png b/recipes/icons/seminar_magazine.png similarity index 100% rename from recipes/seminar_magazine.png rename to recipes/icons/seminar_magazine.png diff --git a/recipes/serverside.png b/recipes/icons/serverside.png similarity index 100% rename from recipes/serverside.png rename to recipes/icons/serverside.png diff --git a/recipes/sfbg.png b/recipes/icons/sfbg.png similarity index 100% rename from recipes/sfbg.png rename to recipes/icons/sfbg.png diff --git a/recipes/shacknews.png b/recipes/icons/shacknews.png similarity index 100% rename from recipes/shacknews.png rename to recipes/icons/shacknews.png diff --git a/recipes/shortlist.png b/recipes/icons/shortlist.png similarity index 100% rename from recipes/shortlist.png rename to recipes/icons/shortlist.png diff --git a/recipes/sigma_live.png b/recipes/icons/sigma_live.png similarity index 100% rename from recipes/sigma_live.png rename to recipes/icons/sigma_live.png diff --git a/recipes/sign_on_sd.png b/recipes/icons/sign_on_sd.png similarity index 100% rename from recipes/sign_on_sd.png rename to recipes/icons/sign_on_sd.png diff --git a/recipes/singtao_daily.png b/recipes/icons/singtao_daily.png similarity index 100% rename from recipes/singtao_daily.png rename to recipes/icons/singtao_daily.png diff --git a/recipes/singtaohk.png b/recipes/icons/singtaohk.png similarity index 100% rename from recipes/singtaohk.png rename to recipes/icons/singtaohk.png diff --git a/recipes/sisainlive.png b/recipes/icons/sisainlive.png similarity index 100% rename from recipes/sisainlive.png rename to recipes/icons/sisainlive.png diff --git a/recipes/sizinti_derigisi.png b/recipes/icons/sizinti_derigisi.png similarity index 100% rename from recipes/sizinti_derigisi.png rename to recipes/icons/sizinti_derigisi.png diff --git a/recipes/skeptic.png b/recipes/icons/skeptic.png similarity index 100% rename from recipes/skeptic.png rename to recipes/icons/skeptic.png diff --git a/recipes/skeptical_enquirer.png b/recipes/icons/skeptical_enquirer.png similarity index 100% rename from recipes/skeptical_enquirer.png rename to recipes/icons/skeptical_enquirer.png diff --git a/recipes/slate.png b/recipes/icons/slate.png similarity index 100% rename from recipes/slate.png rename to recipes/icons/slate.png diff --git a/recipes/slate_star_codex.png b/recipes/icons/slate_star_codex.png similarity index 100% rename from recipes/slate_star_codex.png rename to recipes/icons/slate_star_codex.png diff --git a/recipes/slovo.png b/recipes/icons/slovo.png similarity index 100% rename from recipes/slovo.png rename to recipes/icons/slovo.png diff --git a/recipes/sme.png b/recipes/icons/sme.png similarity index 100% rename from recipes/sme.png rename to recipes/icons/sme.png diff --git a/recipes/smith.png b/recipes/icons/smith.png similarity index 100% rename from recipes/smith.png rename to recipes/icons/smith.png diff --git a/recipes/sn_dk.png b/recipes/icons/sn_dk.png similarity index 100% rename from recipes/sn_dk.png rename to recipes/icons/sn_dk.png diff --git a/recipes/snopes.png b/recipes/icons/snopes.png similarity index 100% rename from recipes/snopes.png rename to recipes/icons/snopes.png diff --git a/recipes/sol_haber.png b/recipes/icons/sol_haber.png similarity index 100% rename from recipes/sol_haber.png rename to recipes/icons/sol_haber.png diff --git a/recipes/southernstar.png b/recipes/icons/southernstar.png similarity index 100% rename from recipes/southernstar.png rename to recipes/icons/southernstar.png diff --git a/recipes/spectator_magazine.png b/recipes/icons/spectator_magazine.png similarity index 100% rename from recipes/spectator_magazine.png rename to recipes/icons/spectator_magazine.png diff --git a/recipes/spin_magazine.png b/recipes/icons/spin_magazine.png similarity index 100% rename from recipes/spin_magazine.png rename to recipes/icons/spin_magazine.png diff --git a/recipes/sports_illustrated.png b/recipes/icons/sports_illustrated.png similarity index 100% rename from recipes/sports_illustrated.png rename to recipes/icons/sports_illustrated.png diff --git a/recipes/sportstar.png b/recipes/icons/sportstar.png similarity index 100% rename from recipes/sportstar.png rename to recipes/icons/sportstar.png diff --git a/recipes/sporza_be.png b/recipes/icons/sporza_be.png similarity index 100% rename from recipes/sporza_be.png rename to recipes/icons/sporza_be.png diff --git a/recipes/star_gazetesi.png b/recipes/icons/star_gazetesi.png similarity index 100% rename from recipes/star_gazetesi.png rename to recipes/icons/star_gazetesi.png diff --git a/recipes/stars_and_stripes.png b/recipes/icons/stars_and_stripes.png similarity index 100% rename from recipes/stars_and_stripes.png rename to recipes/icons/stars_and_stripes.png diff --git a/recipes/stnn.png b/recipes/icons/stnn.png similarity index 100% rename from recipes/stnn.png rename to recipes/icons/stnn.png diff --git a/recipes/strange_horizons.png b/recipes/icons/strange_horizons.png similarity index 100% rename from recipes/strange_horizons.png rename to recipes/icons/strange_horizons.png diff --git a/recipes/strategy-business.png b/recipes/icons/strategy-business.png similarity index 100% rename from recipes/strategy-business.png rename to recipes/icons/strategy-business.png diff --git a/recipes/substack.png b/recipes/icons/substack.png similarity index 100% rename from recipes/substack.png rename to recipes/icons/substack.png diff --git a/recipes/sueddeutsche_mobil.png b/recipes/icons/sueddeutsche_mobil.png similarity index 100% rename from recipes/sueddeutsche_mobil.png rename to recipes/icons/sueddeutsche_mobil.png diff --git a/recipes/sunday_times_magazine.png b/recipes/icons/sunday_times_magazine.png similarity index 100% rename from recipes/sunday_times_magazine.png rename to recipes/icons/sunday_times_magazine.png diff --git a/recipes/superesportes.png b/recipes/icons/superesportes.png similarity index 100% rename from recipes/superesportes.png rename to recipes/icons/superesportes.png diff --git a/recipes/svt_nyheter.png b/recipes/icons/svt_nyheter.png similarity index 100% rename from recipes/svt_nyheter.png rename to recipes/icons/svt_nyheter.png diff --git a/recipes/swarajya.png b/recipes/icons/swarajya.png similarity index 100% rename from recipes/swarajya.png rename to recipes/icons/swarajya.png diff --git a/recipes/t3n_de.png b/recipes/icons/t3n_de.png similarity index 100% rename from recipes/t3n_de.png rename to recipes/icons/t3n_de.png diff --git a/recipes/t_online.png b/recipes/icons/t_online.png similarity index 100% rename from recipes/t_online.png rename to recipes/icons/t_online.png diff --git a/recipes/taipei.png b/recipes/icons/taipei.png similarity index 100% rename from recipes/taipei.png rename to recipes/icons/taipei.png diff --git a/recipes/tanea.png b/recipes/icons/tanea.png similarity index 100% rename from recipes/tanea.png rename to recipes/icons/tanea.png diff --git a/recipes/taz.png b/recipes/icons/taz.png similarity index 100% rename from recipes/taz.png rename to recipes/icons/taz.png diff --git a/recipes/technology_review_de.png b/recipes/icons/technology_review_de.png similarity index 100% rename from recipes/technology_review_de.png rename to recipes/icons/technology_review_de.png diff --git a/recipes/techtarget.png b/recipes/icons/techtarget.png similarity index 100% rename from recipes/techtarget.png rename to recipes/icons/techtarget.png diff --git a/recipes/tedneward.png b/recipes/icons/tedneward.png similarity index 100% rename from recipes/tedneward.png rename to recipes/icons/tedneward.png diff --git a/recipes/thai_post_daily.png b/recipes/icons/thai_post_daily.png similarity index 100% rename from recipes/thai_post_daily.png rename to recipes/icons/thai_post_daily.png diff --git a/recipes/thairath.png b/recipes/icons/thairath.png similarity index 100% rename from recipes/thairath.png rename to recipes/icons/thairath.png diff --git a/recipes/the_athletic.png b/recipes/icons/the_athletic.png similarity index 100% rename from recipes/the_athletic.png rename to recipes/icons/the_athletic.png diff --git a/recipes/the_baffler.png b/recipes/icons/the_baffler.png similarity index 100% rename from recipes/the_baffler.png rename to recipes/icons/the_baffler.png diff --git a/recipes/the_budget_fashionista.png b/recipes/icons/the_budget_fashionista.png similarity index 100% rename from recipes/the_budget_fashionista.png rename to recipes/icons/the_budget_fashionista.png diff --git a/recipes/the_clinic_online.png b/recipes/icons/the_clinic_online.png similarity index 100% rename from recipes/the_clinic_online.png rename to recipes/icons/the_clinic_online.png diff --git a/recipes/the_conversation.png b/recipes/icons/the_conversation.png similarity index 100% rename from recipes/the_conversation.png rename to recipes/icons/the_conversation.png diff --git a/recipes/the_daily_news_egypt.png b/recipes/icons/the_daily_news_egypt.png similarity index 100% rename from recipes/the_daily_news_egypt.png rename to recipes/icons/the_daily_news_egypt.png diff --git a/recipes/the_diplomat.png b/recipes/icons/the_diplomat.png similarity index 100% rename from recipes/the_diplomat.png rename to recipes/icons/the_diplomat.png diff --git a/recipes/the_feature.png b/recipes/icons/the_feature.png similarity index 100% rename from recipes/the_feature.png rename to recipes/icons/the_feature.png diff --git a/recipes/the_federalist.png b/recipes/icons/the_federalist.png similarity index 100% rename from recipes/the_federalist.png rename to recipes/icons/the_federalist.png diff --git a/recipes/the_freeman.png b/recipes/icons/the_freeman.png similarity index 100% rename from recipes/the_freeman.png rename to recipes/icons/the_freeman.png diff --git a/recipes/the_friday_times.png b/recipes/icons/the_friday_times.png similarity index 100% rename from recipes/the_friday_times.png rename to recipes/icons/the_friday_times.png diff --git a/recipes/the_journal.png b/recipes/icons/the_journal.png similarity index 100% rename from recipes/the_journal.png rename to recipes/icons/the_journal.png diff --git a/recipes/the_manila_bulletin.png b/recipes/icons/the_manila_bulletin.png similarity index 100% rename from recipes/the_manila_bulletin.png rename to recipes/icons/the_manila_bulletin.png diff --git a/recipes/the_manila_times.png b/recipes/icons/the_manila_times.png similarity index 100% rename from recipes/the_manila_times.png rename to recipes/icons/the_manila_times.png diff --git a/recipes/the_marker.png b/recipes/icons/the_marker.png similarity index 100% rename from recipes/the_marker.png rename to recipes/icons/the_marker.png diff --git a/recipes/the_new_republic.png b/recipes/icons/the_new_republic.png similarity index 100% rename from recipes/the_new_republic.png rename to recipes/icons/the_new_republic.png diff --git a/recipes/the_philippine_daily_inquirer.png b/recipes/icons/the_philippine_daily_inquirer.png similarity index 100% rename from recipes/the_philippine_daily_inquirer.png rename to recipes/icons/the_philippine_daily_inquirer.png diff --git a/recipes/the_philippine_star.png b/recipes/icons/the_philippine_star.png similarity index 100% rename from recipes/the_philippine_star.png rename to recipes/icons/the_philippine_star.png diff --git a/recipes/the_register.png b/recipes/icons/the_register.png similarity index 100% rename from recipes/the_register.png rename to recipes/icons/the_register.png diff --git a/recipes/the_saturday_paper.png b/recipes/icons/the_saturday_paper.png similarity index 100% rename from recipes/the_saturday_paper.png rename to recipes/icons/the_saturday_paper.png diff --git a/recipes/the_sun.png b/recipes/icons/the_sun.png similarity index 100% rename from recipes/the_sun.png rename to recipes/icons/the_sun.png diff --git a/recipes/the_verge.png b/recipes/icons/the_verge.png similarity index 100% rename from recipes/the_verge.png rename to recipes/icons/the_verge.png diff --git a/recipes/the_week.png b/recipes/icons/the_week.png similarity index 100% rename from recipes/the_week.png rename to recipes/icons/the_week.png diff --git a/recipes/thecodelesscode.png b/recipes/icons/thecodelesscode.png similarity index 100% rename from recipes/thecodelesscode.png rename to recipes/icons/thecodelesscode.png diff --git a/recipes/thecultofghoul.png b/recipes/icons/thecultofghoul.png similarity index 100% rename from recipes/thecultofghoul.png rename to recipes/icons/thecultofghoul.png diff --git a/recipes/thedgesingapore.png b/recipes/icons/thedgesingapore.png similarity index 100% rename from recipes/thedgesingapore.png rename to recipes/icons/thedgesingapore.png diff --git a/recipes/theeconomictimes_india_print_edition.png b/recipes/icons/theeconomictimes_india_print_edition.png similarity index 100% rename from recipes/theeconomictimes_india_print_edition.png rename to recipes/icons/theeconomictimes_india_print_edition.png diff --git a/recipes/themorningpaper.png b/recipes/icons/themorningpaper.png similarity index 100% rename from recipes/themorningpaper.png rename to recipes/icons/themorningpaper.png diff --git a/recipes/thenews.png b/recipes/icons/thenews.png similarity index 100% rename from recipes/thenews.png rename to recipes/icons/thenews.png diff --git a/recipes/theprint.png b/recipes/icons/theprint.png similarity index 100% rename from recipes/theprint.png rename to recipes/icons/theprint.png diff --git a/recipes/thn.png b/recipes/icons/thn.png similarity index 100% rename from recipes/thn.png rename to recipes/icons/thn.png diff --git a/recipes/tijolaco.png b/recipes/icons/tijolaco.png similarity index 100% rename from recipes/tijolaco.png rename to recipes/icons/tijolaco.png diff --git a/recipes/tillsonburg.png b/recipes/icons/tillsonburg.png similarity index 100% rename from recipes/tillsonburg.png rename to recipes/icons/tillsonburg.png diff --git a/recipes/today_online.png b/recipes/icons/today_online.png similarity index 100% rename from recipes/today_online.png rename to recipes/icons/today_online.png diff --git a/recipes/tomshardware_it.png b/recipes/icons/tomshardware_it.png similarity index 100% rename from recipes/tomshardware_it.png rename to recipes/icons/tomshardware_it.png diff --git a/recipes/toyokeizai.png b/recipes/icons/toyokeizai.png similarity index 100% rename from recipes/toyokeizai.png rename to recipes/icons/toyokeizai.png diff --git a/recipes/tri_city_herald.png b/recipes/icons/tri_city_herald.png similarity index 100% rename from recipes/tri_city_herald.png rename to recipes/icons/tri_city_herald.png diff --git a/recipes/tyzden.png b/recipes/icons/tyzden.png similarity index 100% rename from recipes/tyzden.png rename to recipes/icons/tyzden.png diff --git a/recipes/unperiodico.png b/recipes/icons/unperiodico.png similarity index 100% rename from recipes/unperiodico.png rename to recipes/icons/unperiodico.png diff --git a/recipes/upi.png b/recipes/icons/upi.png similarity index 100% rename from recipes/upi.png rename to recipes/icons/upi.png diff --git a/recipes/usatoday.png b/recipes/icons/usatoday.png similarity index 100% rename from recipes/usatoday.png rename to recipes/icons/usatoday.png diff --git a/recipes/valbybladet_dk.png b/recipes/icons/valbybladet_dk.png similarity index 100% rename from recipes/valbybladet_dk.png rename to recipes/icons/valbybladet_dk.png diff --git a/recipes/vancouver_province.png b/recipes/icons/vancouver_province.png similarity index 100% rename from recipes/vancouver_province.png rename to recipes/icons/vancouver_province.png diff --git a/recipes/vancouver_sun.png b/recipes/icons/vancouver_sun.png similarity index 100% rename from recipes/vancouver_sun.png rename to recipes/icons/vancouver_sun.png diff --git a/recipes/vanloesebladet_dk.png b/recipes/icons/vanloesebladet_dk.png similarity index 100% rename from recipes/vanloesebladet_dk.png rename to recipes/icons/vanloesebladet_dk.png diff --git a/recipes/veintitres.png b/recipes/icons/veintitres.png similarity index 100% rename from recipes/veintitres.png rename to recipes/icons/veintitres.png diff --git a/recipes/vesterbrobladet_dk.png b/recipes/icons/vesterbrobladet_dk.png similarity index 100% rename from recipes/vesterbrobladet_dk.png rename to recipes/icons/vesterbrobladet_dk.png diff --git a/recipes/vic_times.png b/recipes/icons/vic_times.png similarity index 100% rename from recipes/vic_times.png rename to recipes/icons/vic_times.png diff --git a/recipes/villagevoice.png b/recipes/icons/villagevoice.png similarity index 100% rename from recipes/villagevoice.png rename to recipes/icons/villagevoice.png diff --git a/recipes/vnexpress.png b/recipes/icons/vnexpress.png similarity index 100% rename from recipes/vnexpress.png rename to recipes/icons/vnexpress.png diff --git a/recipes/voetbal_belgie.png b/recipes/icons/voetbal_belgie.png similarity index 100% rename from recipes/voetbal_belgie.png rename to recipes/icons/voetbal_belgie.png diff --git a/recipes/icons/wsj_free.png b/recipes/icons/wsj_free.png new file mode 100644 index 0000000000000000000000000000000000000000..5fb5496c5a8e6266eb965a44133866a13ef26c25 GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|zMd|QAr-ggPGaO@P~c#$-etdC zK_YR@(_ Date: Fri, 26 May 2023 06:44:58 +0530 Subject: [PATCH 0747/2055] version 6.18.0 --- Changelog.txt | 54 ++++++++++++++++++++++++++++++++++++++++ src/calibre/constants.py | 2 +- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Changelog.txt b/Changelog.txt index 4ba88bfc70..a14691592a 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -23,6 +23,60 @@ # - title by author # }}} +{{{ 6.18.0 2023-05-26 + +:: new features + +- [2020603] Cover download: Allowing saving alternate covers to disk or in the book's data folder by right clicking on the cover + +- [2020237] Content server: Allow disabling full text search via the web interface + +- [2020233] When sending books to the device confirm the overwrite if the book already exists on the device + +- E-book viewer: Handle horizontal wheel events as section jumps in paged mode + +- Comic Input: When grayscaling comic images use 16bit gray instead of 8bit for better fidelity + + When using the PNG format for images this results in larger files but with better grayscaling fidelity. + +- Add a new option in Preferences->Searching to disable keyboard searching in book list (i.e. you can turn off the behavior that pressing a key will jump to the first book whose title starts with that letter) + +- [2018423] Manage categories dialog: Use alternating row colors and allow adjusting row height + +- Allow assigning a keyboard shortcut in Preferences->Shortcuts to open the data folder of a book + +- Various improvements to syntax highlighting for the Markdown long text editor + +:: bug fixes + +- [2018025] Fix a regression in 6.16 that broke restoring of the database + +- Tag browser: Fix using F2 to edit items not allowing completion + +- Book details: Fix formatting of text when copying all book details in narrow mode + +- Book details: Fix copy all not respecting line breaks in fields + +- [2018660] Fix a regression in previous release that broke scrolling when using the scroll_per_row tweak + +- [2018548] Fix a regression in the previous release that broke the category manager dialog in some situations + + +:: improved recipes +- NYTimes +- Economist +- Washington Post +- Irish Independent and Irish Times +- Live Mint +- Psych +- Hindu + +:: new recipes +- Irish Times Free by unkn0wn +- elEconomista.es and El Confidencial by Hugo Meza + +}}} + {{{ 6.17.0 2023-04-26 :: new features diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 00cefe2d8e..ce1539c382 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -5,7 +5,7 @@ from functools import lru_cache import sys, locale, codecs, os, collections, collections.abc __appname__ = 'calibre' -numeric_version = (6, 17, 0) +numeric_version = (6, 18, 0) __version__ = '.'.join(map(str, numeric_version)) git_version = None __author__ = "Kovid Goyal " From b120cc9c03d7715ce52580619040ee5b515ed4a4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 26 May 2023 11:47:59 +0530 Subject: [PATCH 0748/2055] Harmonize selectall behavior for delegated editors in the book list with other places these editors are used By not having special selectAll behavior in the book list we can control this system wide by changing the widget's initial selectall behavior centrally. --- src/calibre/gui2/library/delegates.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index 63a8b954bb..546178051f 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -318,7 +318,6 @@ class TextDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{ def setEditorData(self, editor, index): editor.setText(get_val_for_textlike_columns(index)) - editor.selectAll() def setModelData(self, editor, model, index): if isinstance(editor, EditWithComplete): @@ -383,7 +382,6 @@ class CompleteDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{ def setEditorData(self, editor, index): editor.setText(get_val_for_textlike_columns(index)) - editor.selectAll() def setModelData(self, editor, model, index): if isinstance(editor, EditWithComplete): @@ -493,12 +491,10 @@ class CcTextDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{ text = index.data(Qt.ItemDataRole.DisplayRole) if text: editor.setText(text) - editor.selectAll() return editor def setEditorData(self, editor, index): editor.setText(get_val_for_textlike_columns(index)) - editor.selectAll() def setModelData(self, editor, model, index): val = editor.text() or '' From dab71f54e31949e3a456ac5a0df58afdc43fb1d3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 26 May 2023 11:50:01 +0530 Subject: [PATCH 0749/2055] Fix the soname used to bundle libpodofo on Linux. Fixes #2020842 [converting epub to pdf fails with error "ImportError: libpodofo.so.1: cannot open shared object file: No such file or directory"](https://bugs.launchpad.net/calibre/+bug/2020842) --- bypy/linux/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bypy/linux/__main__.py b/bypy/linux/__main__.py index a8660ac424..1d14ecf326 100644 --- a/bypy/linux/__main__.py +++ b/bypy/linux/__main__.py @@ -46,7 +46,7 @@ def binary_includes(): ('usb-1.0 mtp expat sqlite3 ffi z lzma openjp2 poppler dbus-1 iconv xml2 xslt jpeg png16' ' webp webpmux webpdemux exslt ncursesw readline chm hunspell-1.7 hyphen' ' icudata icui18n icuuc icuio stemmer gcrypt gpg-error uchardet graphite2' - ' brotlicommon brotlidec brotlienc zstd' + ' brotlicommon brotlidec brotlienc zstd podofo' ' gobject-2.0 glib-2.0 gthread-2.0 gmodule-2.0 gio-2.0 dbus-glib-1').split() )) + [ # debian/ubuntu for for some typical stupid reason use libpcre.so.3 @@ -56,7 +56,7 @@ def binary_includes(): # than libc and libpthread we bundle the Ubuntu one here glob.glob('/usr/lib/*/libpcre.so.3')[0], - get_dll_path('podofo', 3), get_dll_path('bz2', 2), j(PREFIX, 'lib', 'libunrar.so'), + get_dll_path('bz2', 2), j(PREFIX, 'lib', 'libunrar.so'), get_dll_path('ssl', 2), get_dll_path('crypto', 2), get_dll_path('python' + py_ver, 2), # We dont include libstdc++.so as the OpenGL dlls on the target From 23e4bda46ca527dd3924a1f4be065e21eac3ea45 Mon Sep 17 00:00:00 2001 From: un-pogaz <46523284+un-pogaz@users.noreply.github.com> Date: Fri, 26 May 2023 10:49:20 +0200 Subject: [PATCH 0750/2055] Path field can be display on multiline --- src/calibre/ebooks/metadata/book/render.py | 9 +++++---- src/calibre/gui2/preferences/look_feel.py | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index 7bec32db70..cc2f5cb542 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -225,12 +225,13 @@ def mi_to_html( break text = _('Book files') name = ngettext('Folder:', 'Folders:', num_of_folders) - link = '{}{}'.format(action(scheme, book_id=book_id, loc=loc), - prepare_string_for_xml(path, True), text, extra) + links = ['{}{}'.format(action(scheme, book_id=book_id, loc=loc), + prepare_string_for_xml(path, True), text, extra)] if num_of_folders > 1: - link += ', {}'.format( + links.append('{}'.format( action('data-path', book_id=book_id, loc=book_id), - prepare_string_for_xml(data_path, True), _('Data files')) + prepare_string_for_xml(data_path, True), _('Data files'))) + link = value_list(', ', links) else: link = prepare_string_for_xml(path, True) diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 1601f50299..f4ee7707cf 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -483,7 +483,9 @@ class BDVerticalCats(DisplayedFields): # {{{ def initialize(self, use_defaults=False, pref_data_override=None): fm = self.db.field_metadata - cats = [k for k in fm if fm[k]['name'] and fm[k]['is_multiple']] + cats = [k for k in fm if fm[k]['name'] and fm[k]['is_multiple'] and not k.startswith('#')] + cats.append('path') + cats.extend([k for k in fm if fm[k]['name'] and fm[k]['is_multiple'] and k.startswith('#')]) ans = [] if use_defaults: ans = [[k, False] for k in cats] From a4464ef292898246f201ce21b3d247ee38a64d3b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 26 May 2023 15:44:55 +0530 Subject: [PATCH 0751/2055] Fix #2020854 [local variable 'd' referenced before assignment calibre 6.18](https://bugs.launchpad.net/calibre/+bug/2020854) --- src/calibre/gui2/cover_flow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py index b662f2e55f..b088d6da47 100644 --- a/src/calibre/gui2/cover_flow.py +++ b/src/calibre/gui2/cover_flow.py @@ -231,6 +231,7 @@ class CoverFlow(pictureflow.PictureFlow): return self.minimumSize() def wheelEvent(self, ev): + d = 0 if ev.angleDelta().x(): d = ev.angleDelta().x() if ev.angleDelta().y(): From 31629d59649343643a7a5275b154860e40ece60d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 26 May 2023 15:51:39 +0530 Subject: [PATCH 0752/2055] ... --- src/calibre/gui2/cover_flow.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py index b088d6da47..6074f4b506 100644 --- a/src/calibre/gui2/cover_flow.py +++ b/src/calibre/gui2/cover_flow.py @@ -231,10 +231,9 @@ class CoverFlow(pictureflow.PictureFlow): return self.minimumSize() def wheelEvent(self, ev): - d = 0 - if ev.angleDelta().x(): + if abs(ev.angleDelta().x()) > abs(ev.angleDelta().y()): d = ev.angleDelta().x() - if ev.angleDelta().y(): + else: d = ev.angleDelta().y() if abs(d) > 0: ev.accept() From 59ba5f8d1b6eb1283585202709e36e3beba69d51 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 26 May 2023 17:28:58 +0530 Subject: [PATCH 0753/2055] Fix #2020855 [Error while saving book](https://bugs.launchpad.net/calibre/+bug/2020855) --- bypy/macos/__main__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bypy/macos/__main__.py b/bypy/macos/__main__.py index e4b152b3f7..4ac6831aba 100644 --- a/bypy/macos/__main__.py +++ b/bypy/macos/__main__.py @@ -270,7 +270,7 @@ class Freeze: @flush def get_local_dependencies(self, path_to_lib): for x, is_id in self.get_dependencies(path_to_lib): - if x.startswith('@rpath/Qt') or x.startswith('@rpath/libexpat'): + if x.startswith('@rpath/Qt') or x.startswith('@rpath/libexpat') or x.startswith('@rpath/libpodofo') or x.startswith('@rpath/libzstd'): yield x, x[len('@rpath/'):], is_id elif x in ('libunrar.dylib', 'libstemmer.0.dylib', 'libstemmer.dylib') and not is_id: yield x, x, is_id @@ -476,7 +476,7 @@ class Freeze: @flush def add_podofo(self): print('\nAdding PoDoFo') - pdf = join(PREFIX, 'lib', 'libpodofo.0.10.0.dylib') + pdf = join(PREFIX, 'lib', 'libpodofo.1.dylib') self.install_dylib(pdf) @flush From 8a8e2ed8de7fd418eb7cee9f91926db8979df7d1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 26 May 2023 17:35:10 +0530 Subject: [PATCH 0754/2055] version 6.18.1 --- Changelog.txt | 4 +++- src/calibre/constants.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changelog.txt b/Changelog.txt index a14691592a..5255cc1980 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -23,7 +23,7 @@ # - title by author # }}} -{{{ 6.18.0 2023-05-26 +{{{ 6.18.1 2023-05-26 :: new features @@ -61,6 +61,8 @@ - [2018548] Fix a regression in the previous release that broke the category manager dialog in some situations +- 6.18.1 fixes a regression that broke setting metadata and generating PDF files in the macOS and Linux binary builds + :: improved recipes - NYTimes diff --git a/src/calibre/constants.py b/src/calibre/constants.py index ce1539c382..fab7220d8f 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -5,7 +5,7 @@ from functools import lru_cache import sys, locale, codecs, os, collections, collections.abc __appname__ = 'calibre' -numeric_version = (6, 18, 0) +numeric_version = (6, 18, 1) __version__ = '.'.join(map(str, numeric_version)) git_version = None __author__ = "Kovid Goyal " From 1a9ad23b0d99ddc61330803cd9b4fdaadac03518 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Fri, 26 May 2023 21:38:38 +0100 Subject: [PATCH 0755/2055] Bug #2020906: Manage Author Row Height Altered When Category Editor is Blank --- src/calibre/gui2/dialogs/tag_list_editor.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py index e5d2fb5a17..49727aa0a0 100644 --- a/src/calibre/gui2/dialogs/tag_list_editor.py +++ b/src/calibre/gui2/dialogs/tag_list_editor.py @@ -424,9 +424,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.table.customContextMenuRequested.connect(self.show_context_menu) def row_height_changed(self, row, old, new): - self.table.verticalHeader().blockSignals(True) self.table.verticalHeader().setDefaultSectionSize(new) - self.table.verticalHeader().blockSignals(False) def fill_in_table(self, tags, tag_to_match, ttm_is_first_letter): self.create_table() @@ -559,7 +557,7 @@ class TagListEditor(QDialog, Ui_TagListEditor): self.table.setColumnWidth(c, w) def save_geometry(self): - gprefs['general_category_editor_row_height'] = self.table.verticalHeader().sectionSize(0) + gprefs['general_category_editor_row_height'] = self.table.verticalHeader().defaultSectionSize() gprefs['tag_list_editor_table_widths'] = self.table_column_widths super().save_geometry(gprefs, 'tag_list_editor_dialog_geometry') From ba2ccd0e72d65df58fd429eab7a29abdda0a8670 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 May 2023 06:10:40 +0530 Subject: [PATCH 0756/2055] Fix #2021406 [Preferences: Odd tooltip spacing](https://bugs.launchpad.net/calibre/+bug/2021406) --- src/calibre/gui2/preferences/search.ui | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/calibre/gui2/preferences/search.ui b/src/calibre/gui2/preferences/search.ui index e2f6bd99db..eabf77e00b 100644 --- a/src/calibre/gui2/preferences/search.ui +++ b/src/calibre/gui2/preferences/search.ui @@ -166,11 +166,7 @@ Use keyboard searching in the book list - <p>If enabled then pressing a key in the library view - will search for the next cell in the current column where the - value starts with the letter pressed. If there is no such cell - then nothing happens. If the letter is a shortcut then the - shortcut takes precedence.</p> + <p>When enabled, pressing a key in the library view will search for the next cell in the current column where the value starts with the letter pressed. If there is no such cell then nothing happens. If the letter is a shortcut then the shortcut takes precedence.</p>
From 59503c21dc7f59c025d4ace2bfa57643b04dfdab Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 May 2023 09:07:57 +0530 Subject: [PATCH 0757/2055] PDF Output: Fix regression in previous release that caused blank pages when generating headers or footers Needed to prepend the header/footer content stream to the page content stream. Appending only worked with Qt >= 6.5 which is what is present on my dev machine, so didnt catch it. Before 6.5, when drawing header footers chromium does not occlude the body area even though nothing is drawn there, thus prepending is required. --- src/calibre/utils/podofo/impose.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/calibre/utils/podofo/impose.cpp b/src/calibre/utils/podofo/impose.cpp index e695fe9c0f..2923e6369f 100644 --- a/src/calibre/utils/podofo/impose.cpp +++ b/src/calibre/utils/podofo/impose.cpp @@ -6,6 +6,7 @@ */ #include "global.h" +#include #include using namespace pdf; @@ -16,10 +17,14 @@ impose_page(PdfMemDocument *doc, unsigned int dest_page_num, unsigned int src_pa auto xobj = doc->CreateXObjectForm(src_page.GetMediaBox(), "HeaderFooter"); xobj->FillFromPage(src_page); auto &dest = doc->GetPages().GetPageAt(dest_page_num); - PdfPainter painter; - painter.SetCanvas(dest); - painter.DrawXObject(*xobj, 0, 0); - painter.FinishDrawing(); + dest.GetOrCreateResources().AddResource("XObject", xobj->GetIdentifier(), xobj->GetObject().GetIndirectReference()); + // prepend the header footer xobject to the stream. This means header/footer is drawn first then the contents, which works + // since chromium does not draw in margin areas. The reverse, i.e. appending, does not work with older WebEngine before Qt 6.5. + PdfContents *contents = dest.GetContents(); + std::ostringstream s; + s << "q\n1 0 0 1 0 0 cm\n/" << xobj->GetIdentifier().GetString() << " Do\nQ\n" << contents->GetCopy(); + contents->Reset(); + contents->GetStreamForAppending().SetData(s.str()); } static PyObject* From 00681544d8c9ef12930dd6ec8ae5a2118022587e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 May 2023 09:27:03 +0530 Subject: [PATCH 0758/2055] PDF Output: Fix regression in previous release causing non-ascii entries to be incorrectly encoded into the PDF bookmarks --- src/calibre/utils/podofo/utils.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/utils/podofo/utils.cpp b/src/calibre/utils/podofo/utils.cpp index ea11132025..fb1fb01955 100644 --- a/src/calibre/utils/podofo/utils.cpp +++ b/src/calibre/utils/podofo/utils.cpp @@ -8,6 +8,7 @@ #include "global.h" #include #include +#include using namespace pdf; @@ -33,5 +34,5 @@ pdf::podofo_convert_pystring(PyObject *val) { Py_ssize_t len; const char *data = PyUnicode_AsUTF8AndSize(val, &len); if (data == NULL) throw std::runtime_error("Failed to convert python string to UTF-8, possibly not a string object"); - return PdfString::FromRaw(bufferview(data, len)); + return PdfString(std::string_view(data, len)); } From e5786ef652f43e6ee5fd1b93eb60e09629f5bbd1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 May 2023 09:36:36 +0530 Subject: [PATCH 0759/2055] PDF Output: Set /Creator and /Producer in /Info --- src/calibre/ebooks/pdf/html_writer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/pdf/html_writer.py b/src/calibre/ebooks/pdf/html_writer.py index 4dce448c65..021f81a449 100644 --- a/src/calibre/ebooks/pdf/html_writer.py +++ b/src/calibre/ebooks/pdf/html_writer.py @@ -22,7 +22,7 @@ from qt.webengine import ( ) from calibre import detect_ncpus, human_readable, prepare_string_for_xml -from calibre.constants import FAKE_HOST, FAKE_PROTOCOL, __version__, ismacos, iswindows +from calibre.constants import FAKE_HOST, FAKE_PROTOCOL, __version__, ismacos, iswindows, __appname__ from calibre.ebooks.metadata.xmp import metadata_to_xmp_packet from calibre.ebooks.oeb.base import XHTML, XPath from calibre.ebooks.oeb.polish.container import Container as ContainerBase @@ -1196,6 +1196,8 @@ def convert(opf_path, opts, metadata=None, output_path=None, log=default_log, co if metadata is not None: update_metadata(pdf_doc, pdf_metadata) + pdf_doc.creator = __appname__ + ' ' + __version__ + pdf_doc.producer = __appname__ + ' ' + __version__ report_progress(1, _('Updated metadata in PDF')) if opts.uncompressed_pdf: From 340daa300cb92362844478d06cbc5bca29f83fad Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 May 2023 09:37:30 +0530 Subject: [PATCH 0760/2055] ... --- src/calibre/ebooks/pdf/html_writer.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/calibre/ebooks/pdf/html_writer.py b/src/calibre/ebooks/pdf/html_writer.py index 021f81a449..99e7ec2771 100644 --- a/src/calibre/ebooks/pdf/html_writer.py +++ b/src/calibre/ebooks/pdf/html_writer.py @@ -1196,8 +1196,7 @@ def convert(opf_path, opts, metadata=None, output_path=None, log=default_log, co if metadata is not None: update_metadata(pdf_doc, pdf_metadata) - pdf_doc.creator = __appname__ + ' ' + __version__ - pdf_doc.producer = __appname__ + ' ' + __version__ + pdf_doc.creator = pdf_doc.producer = __appname__ + ' ' + __version__ report_progress(1, _('Updated metadata in PDF')) if opts.uncompressed_pdf: From 3f32a66c32224de7016e938a1eeb33b397b9b50f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 May 2023 12:43:29 +0530 Subject: [PATCH 0761/2055] Fix #2021367 [Edit cell not longer select all content](https://bugs.launchpad.net/calibre/+bug/2021367) --- src/calibre/gui2/complete2.py | 12 +++++++++++- src/calibre/gui2/dialogs/authors_edit.py | 4 ++-- src/calibre/gui2/library/delegates.py | 6 ++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/complete2.py b/src/calibre/gui2/complete2.py index b0026a0c88..850733f7ae 100644 --- a/src/calibre/gui2/complete2.py +++ b/src/calibre/gui2/complete2.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' from qt.core import ( QLineEdit, QAbstractListModel, Qt, pyqtSignal, QObject, QKeySequence, QAbstractItemView, - QApplication, QListView, QPoint, QModelIndex, QEvent, + QApplication, QListView, QPoint, QModelIndex, QEvent, pyqtProperty, QStyleOptionComboBox, QStyle, QComboBox, QTimer, sip) from calibre.constants import ismacos @@ -515,6 +515,16 @@ class EditWithComplete(EnComboBox): def text(self): return self.lineEdit().text() + + @pyqtProperty(str) + def currentText(self): + return self.lineEdit().text() + + @currentText.setter + def currentText(self, text): + self.setText(text) + self.lineEdit().selectAll() + def selectAll(self): self.lineEdit().selectAll() diff --git a/src/calibre/gui2/dialogs/authors_edit.py b/src/calibre/gui2/dialogs/authors_edit.py index 2de105021c..d93aad884e 100644 --- a/src/calibre/gui2/dialogs/authors_edit.py +++ b/src/calibre/gui2/dialogs/authors_edit.py @@ -30,8 +30,8 @@ class ItemDelegate(QStyledItemDelegate): def setEditorData(self, editor, index): name = str(index.data(Qt.ItemDataRole.DisplayRole) or '') - editor.setText(name) - editor.lineEdit().selectAll() + n = editor.metaObject().userProperty().name() + editor.setProperty(n, name) def setModelData(self, editor, model, index): authors = string_to_authors(str(editor.text())) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index 546178051f..d025d67b21 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -381,7 +381,8 @@ class CompleteDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{ return editor def setEditorData(self, editor, index): - editor.setText(get_val_for_textlike_columns(index)) + n = editor.metaObject().userProperty().name() + editor.setProperty(n, get_val_for_textlike_columns(index)) def setModelData(self, editor, model, index): if isinstance(editor, EditWithComplete): @@ -494,7 +495,8 @@ class CcTextDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{ return editor def setEditorData(self, editor, index): - editor.setText(get_val_for_textlike_columns(index)) + n = editor.metaObject().userProperty().name() + editor.setProperty(n, get_val_for_textlike_columns(index)) def setModelData(self, editor, model, index): val = editor.text() or '' From 7bde308bd676944ba0c67cc9a23f4c8975aec4c9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 May 2023 13:21:22 +0530 Subject: [PATCH 0762/2055] Forgot to fix TextDelegate --- src/calibre/gui2/library/delegates.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index d025d67b21..416ba3d2ab 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -317,7 +317,8 @@ class TextDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{ return editor def setEditorData(self, editor, index): - editor.setText(get_val_for_textlike_columns(index)) + n = editor.metaObject().userProperty().name() + editor.setProperty(n, get_val_for_textlike_columns(index)) def setModelData(self, editor, model, index): if isinstance(editor, EditWithComplete): From bbbddd2bf4ef4ddb467b0aeb0abe8765ed7f8a6b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 May 2023 14:03:15 +0530 Subject: [PATCH 0763/2055] HTML Input: Dont add resources that exist outside the folder hierarchy rooted at the parent folder of the input HTML file by default --- .../ebooks/conversion/plugins/html_input.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/calibre/ebooks/conversion/plugins/html_input.py b/src/calibre/ebooks/conversion/plugins/html_input.py index ca5b729996..eb26b5c6ed 100644 --- a/src/calibre/ebooks/conversion/plugins/html_input.py +++ b/src/calibre/ebooks/conversion/plugins/html_input.py @@ -64,6 +64,16 @@ class HTMLInput(InputFormatPlugin): ) ), + OptionRecommendation(name='allow_local_files_outside_root', + recommended_value=False, level=OptionRecommendation.LOW, + help=_('Normally, resources linked to by the HTML file or its children will only be allowed' + ' if they are in a sub-folder of the original HTML file. This option allows including' + ' local files from any location on your computer. This can be a security risk if you' + ' are converting untrusted HTML and expecting to distribute the result of the conversion.' + ) + ), + + } def convert(self, stream, opts, file_ext, log, @@ -76,6 +86,7 @@ class HTMLInput(InputFormatPlugin): if hasattr(stream, 'name'): basedir = os.path.dirname(stream.name) fname = os.path.basename(stream.name) + self.root_dir_of_input = os.path.abspath(basedir) + os.sep if file_ext != 'opf': if opts.dont_package: @@ -250,6 +261,11 @@ class HTMLInput(InputFormatPlugin): frag = l.fragment if not link: return None, None + link = os.path.abspath(os.path.realpath(link)) + if not link.startswith(self.root_dir_of_input): + if not self.opts.allow_local_files_outside_root: + self.log.warn('Not adding {} as it is outside the document root: {}'.format(link, self.root_dir_of_input)) + return None, None return link, frag def resource_adder(self, link_, base=None): From d9099743d9eaa5e2104fc6d675450cb2ae4460a8 Mon Sep 17 00:00:00 2001 From: un-pogaz <46523284+un-pogaz@users.noreply.github.com> Date: Sun, 28 May 2023 10:47:06 +0200 Subject: [PATCH 0764/2055] group setEditorData() in a shared class for Texts --- src/calibre/gui2/library/delegates.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index 416ba3d2ab..799e5beac4 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -109,6 +109,11 @@ class UpdateEditorGeometry: initial_geometry.adjust(delta_x, 0, delta_width, 0) editor.setGeometry(initial_geometry) +class EditableTextDelegate: + + def setEditorData(self, editor, index): + n = editor.metaObject().userProperty().name() + editor.setProperty(n, get_val_for_textlike_columns(index)) class DateTimeEdit(DateTimeEditBase): # {{{ @@ -285,7 +290,7 @@ class PubDateDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{ # }}} -class TextDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{ +class TextDelegate(QStyledItemDelegate, UpdateEditorGeometry, EditableTextDelegate): # {{{ use_title_sort = False @@ -316,10 +321,6 @@ class TextDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{ editor = EnLineEdit(parent) return editor - def setEditorData(self, editor, index): - n = editor.metaObject().userProperty().name() - editor.setProperty(n, get_val_for_textlike_columns(index)) - def setModelData(self, editor, model, index): if isinstance(editor, EditWithComplete): val = editor.lineEdit().text() @@ -340,7 +341,7 @@ class SeriesDelegate(TextDelegate): # {{{ # }}} -class CompleteDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{ +class CompleteDelegate(QStyledItemDelegate, UpdateEditorGeometry, EditableTextDelegate): # {{{ def __init__(self, parent, sep, items_func_name, space_before_sep=False): QStyledItemDelegate.__init__(self, parent) @@ -381,10 +382,6 @@ class CompleteDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{ editor = EnLineEdit(parent) return editor - def setEditorData(self, editor, index): - n = editor.metaObject().userProperty().name() - editor.setProperty(n, get_val_for_textlike_columns(index)) - def setModelData(self, editor, model, index): if isinstance(editor, EditWithComplete): val = editor.lineEdit().text() @@ -464,7 +461,7 @@ class CcDateDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{ # }}} -class CcTextDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{ +class CcTextDelegate(QStyledItemDelegate, UpdateEditorGeometry, EditableTextDelegate): # {{{ ''' Delegate for text data. @@ -495,10 +492,6 @@ class CcTextDelegate(QStyledItemDelegate, UpdateEditorGeometry): # {{{ editor.setText(text) return editor - def setEditorData(self, editor, index): - n = editor.metaObject().userProperty().name() - editor.setProperty(n, get_val_for_textlike_columns(index)) - def setModelData(self, editor, model, index): val = editor.text() or '' if not isinstance(editor, EditWithComplete): From 71547e8a117da6c6b591ecf4a7503b302ac53f3f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 May 2023 15:20:58 +0530 Subject: [PATCH 0765/2055] Also restrict resources to root dir for txt input --- src/calibre/ebooks/conversion/plugins/html_input.py | 4 ++-- src/calibre/ebooks/conversion/plugins/txt_input.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/calibre/ebooks/conversion/plugins/html_input.py b/src/calibre/ebooks/conversion/plugins/html_input.py index eb26b5c6ed..5bc4f2577c 100644 --- a/src/calibre/ebooks/conversion/plugins/html_input.py +++ b/src/calibre/ebooks/conversion/plugins/html_input.py @@ -86,7 +86,7 @@ class HTMLInput(InputFormatPlugin): if hasattr(stream, 'name'): basedir = os.path.dirname(stream.name) fname = os.path.basename(stream.name) - self.root_dir_of_input = os.path.abspath(basedir) + os.sep + self.root_dir_of_input = os.path.normcase(os.path.abspath(basedir) + os.sep) if file_ext != 'opf': if opts.dont_package: @@ -262,7 +262,7 @@ class HTMLInput(InputFormatPlugin): if not link: return None, None link = os.path.abspath(os.path.realpath(link)) - if not link.startswith(self.root_dir_of_input): + if not os.path.normcase(link).startswith(self.root_dir_of_input): if not self.opts.allow_local_files_outside_root: self.log.warn('Not adding {} as it is outside the document root: {}'.format(link, self.root_dir_of_input)) return None, None diff --git a/src/calibre/ebooks/conversion/plugins/txt_input.py b/src/calibre/ebooks/conversion/plugins/txt_input.py index 6aa3c30ed4..550e066524 100644 --- a/src/calibre/ebooks/conversion/plugins/txt_input.py +++ b/src/calibre/ebooks/conversion/plugins/txt_input.py @@ -107,12 +107,13 @@ class TXTInput(InputFormatPlugin): from html5_parser import parse root = parse(html) changed = False + base_dir = os.path.normcase(os.path.abspath(base_dir)) + os.sep for img in root.xpath('//img[@src]'): src = img.get('src') prefix = src.split(':', 1)[0].lower() if src and prefix not in ('file', 'http', 'https', 'ftp') and not os.path.isabs(src): src = os.path.join(base_dir, src) - if os.path.isfile(src) and os.access(src, os.R_OK): + if os.path.normcase(src).startswith(base_dir) and os.path.isfile(src) and os.access(src, os.R_OK): with open(src, 'rb') as f: data = f.read() f = self.shift_file(os.path.basename(src), data) From 9ec1f0820a639312833a2d5a011aca5714fc964a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 May 2023 15:42:51 +0530 Subject: [PATCH 0766/2055] Some changes to the amazon.de site markup --- src/calibre/ebooks/metadata/sources/amazon.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/metadata/sources/amazon.py b/src/calibre/ebooks/metadata/sources/amazon.py index 78a268d784..5f11dab781 100644 --- a/src/calibre/ebooks/metadata/sources/amazon.py +++ b/src/calibre/ebooks/metadata/sources/amazon.py @@ -584,6 +584,9 @@ class Worker(Thread): # Get details {{{ span = root.xpath('//*[@id="ebooksTitle"]') if span: return sanitize_title(self.totext(span[0])) + h1 = root.xpath('//h1[@data-feature-name="title"]') + if h1: + return sanitize_title(self.totext(h1[0])) raise ValueError('No title block found') tdiv = tdiv[0] actual_title = tdiv.xpath('descendant::*[@id="btAsinTitle"]') @@ -602,6 +605,7 @@ class Worker(Thread): # Get details {{{ '#bylineInfo .author .contributorNameID', '#bylineInfo .author a.a-link-normal', '#bylineInfo #bylineContributor', + '#bylineInfo #contributorLink', ): matches = tuple(self.selector(sel)) if matches: @@ -1038,7 +1042,7 @@ class Worker(Thread): # Get details {{{ class Amazon(Source): name = 'Amazon.com' - version = (1, 3, 3) + version = (1, 3, 4) minimum_calibre_version = (2, 82, 0) description = _('Downloads metadata and covers from Amazon') From 7444f22e4930b17e7bf7d693762a512d0ca782df Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 May 2023 15:47:46 +0530 Subject: [PATCH 0767/2055] Fix polish test: now need option to allow local files from outside root when building the test book --- src/calibre/ebooks/oeb/polish/tests/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/oeb/polish/tests/base.py b/src/calibre/ebooks/oeb/polish/tests/base.py index db8396076b..91be668ef9 100644 --- a/src/calibre/ebooks/oeb/polish/tests/base.py +++ b/src/calibre/ebooks/oeb/polish/tests/base.py @@ -70,7 +70,7 @@ def get_simple_book(fmt='epub'): with open(x, 'wb') as f: f.write(raw.encode('utf-8')) build_book(x, ans, args=[ - '--level1-toc=//h:h2', '--language=en', '--authors=Kovid Goyal', '--cover=lt.png']) + '--level1-toc=//h:h2', '--language=en', '--authors=Kovid Goyal', '--cover=lt.png', '--allow-local-files-outside-root']) return ans @@ -85,7 +85,7 @@ def get_split_book(fmt='epub'): with open(x, 'wb') as f: f.write(raw.encode('utf-8')) build_book(x, ans, args=['--level1-toc=//h:h2', '--language=en', '--authors=Kovid Goyal', - '--cover=' + I('lt.png')]) + '--cover=' + I('lt.png'), '--allow-local-files-outside-root']) finally: os.remove(x) return ans From 737a7ceeb85bea77563c85298162868698409b1d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 28 May 2023 19:43:53 +0530 Subject: [PATCH 0768/2055] Fix failing test on windows When checking for path inside directory we need to use long path names as the dir path and abspath can use different types of names: short vs. long. --- .../ebooks/conversion/plugins/html_input.py | 9 ++-- src/calibre/ebooks/oeb/polish/tests/base.py | 42 +++++++++++-------- src/calibre/utils/filenames.py | 10 +++++ src/calibre/utils/run_tests.py | 5 ++- 4 files changed, 43 insertions(+), 23 deletions(-) diff --git a/src/calibre/ebooks/conversion/plugins/html_input.py b/src/calibre/ebooks/conversion/plugins/html_input.py index 5bc4f2577c..9119698b61 100644 --- a/src/calibre/ebooks/conversion/plugins/html_input.py +++ b/src/calibre/ebooks/conversion/plugins/html_input.py @@ -13,7 +13,7 @@ from urllib.parse import quote from calibre.constants import isbsd, islinux from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation -from calibre.utils.filenames import ascii_filename +from calibre.utils.filenames import ascii_filename, get_long_path_name from calibre.utils.imghdr import what from calibre.utils.localization import __, get_lang from polyglot.builtins import as_unicode @@ -86,7 +86,7 @@ class HTMLInput(InputFormatPlugin): if hasattr(stream, 'name'): basedir = os.path.dirname(stream.name) fname = os.path.basename(stream.name) - self.root_dir_of_input = os.path.normcase(os.path.abspath(basedir) + os.sep) + self.root_dir_of_input = os.path.normcase(get_long_path_name(os.path.abspath(basedir)) + os.sep) if file_ext != 'opf': if opts.dont_package: @@ -262,9 +262,10 @@ class HTMLInput(InputFormatPlugin): if not link: return None, None link = os.path.abspath(os.path.realpath(link)) - if not os.path.normcase(link).startswith(self.root_dir_of_input): + q = os.path.normcase(get_long_path_name(link)) + if not q.startswith(self.root_dir_of_input): if not self.opts.allow_local_files_outside_root: - self.log.warn('Not adding {} as it is outside the document root: {}'.format(link, self.root_dir_of_input)) + self.log.warn('Not adding {} as it is outside the document root: {}'.format(q, self.root_dir_of_input)) return None, None return link, frag diff --git a/src/calibre/ebooks/oeb/polish/tests/base.py b/src/calibre/ebooks/oeb/polish/tests/base.py index 91be668ef9..dd78065d52 100644 --- a/src/calibre/ebooks/oeb/polish/tests/base.py +++ b/src/calibre/ebooks/oeb/polish/tests/base.py @@ -24,7 +24,14 @@ def get_cache(): return cache +once_per_run = set() + + def needs_recompile(obj, srcs): + is_ci = os.environ.get('CI', '').lower() == 'true' + if is_ci and obj not in once_per_run: + once_per_run.add(obj) + return True if isinstance(srcs, str): srcs = [srcs] try: @@ -39,7 +46,7 @@ def needs_recompile(obj, srcs): def build_book(src, dest, args=()): from calibre.ebooks.conversion.cli import main - main(['ebook-convert', src, dest] + list(args)) + main(['ebook-convert', src, dest, '-vv'] + list(args)) def add_resources(raw, rmap): @@ -55,22 +62,21 @@ def get_simple_book(fmt='epub'): ans = os.path.join(cache, 'simple.'+fmt) src = os.path.join(os.path.dirname(__file__), 'simple.html') if needs_recompile(ans, src): - with TemporaryDirectory('bpt') as tdir: - with CurrentDir(tdir): - with open(src, 'rb') as sf: - raw = sf.read().decode('utf-8') - raw = add_resources(raw, { - 'LMONOI': P('fonts/liberation/LiberationMono-Italic.ttf'), - 'LMONOR': P('fonts/liberation/LiberationMono-Regular.ttf'), - 'IMAGE1': I('marked.png'), - 'IMAGE2': I('textures/light_wood.png'), - }) - shutil.copy2(I('lt.png'), '.') - x = 'index.html' - with open(x, 'wb') as f: - f.write(raw.encode('utf-8')) - build_book(x, ans, args=[ - '--level1-toc=//h:h2', '--language=en', '--authors=Kovid Goyal', '--cover=lt.png', '--allow-local-files-outside-root']) + with TemporaryDirectory('bpt') as tdir, CurrentDir(tdir): + with open(src, 'rb') as sf: + raw = sf.read().decode('utf-8') + raw = add_resources(raw, { + 'LMONOI': P('fonts/liberation/LiberationMono-Italic.ttf'), + 'LMONOR': P('fonts/liberation/LiberationMono-Regular.ttf'), + 'IMAGE1': I('marked.png'), + 'IMAGE2': I('textures/light_wood.png'), + }) + shutil.copy2(I('lt.png'), '.') + x = 'index.html' + with open(x, 'wb') as f: + f.write(raw.encode('utf-8')) + build_book(x, ans, args=[ + '--level1-toc=//h:h2', '--language=en', '--authors=Kovid Goyal', '--cover=lt.png']) return ans @@ -85,7 +91,7 @@ def get_split_book(fmt='epub'): with open(x, 'wb') as f: f.write(raw.encode('utf-8')) build_book(x, ans, args=['--level1-toc=//h:h2', '--language=en', '--authors=Kovid Goyal', - '--cover=' + I('lt.png'), '--allow-local-files-outside-root']) + '--cover=' + I('lt.png')]) finally: os.remove(x) return ans diff --git a/src/calibre/utils/filenames.py b/src/calibre/utils/filenames.py index 90a6fb9cd8..897b0a47f3 100644 --- a/src/calibre/utils/filenames.py +++ b/src/calibre/utils/filenames.py @@ -620,10 +620,20 @@ if iswindows: return False # Values I have seen: FAT32, exFAT, NTFS return tn.upper().startswith('FAT') + + def get_long_path_name(path): + from calibre_extensions.winutil import get_long_path_name + if os.path.isabs(path) and not path.startswith(long_path_prefix): + path = long_path_prefix + path + return get_long_path_name(path) + else: def make_long_path_useable(path): return path + def get_long_path_name(path): + return path + def is_fat_filesystem(path): # TODO: Implement for Linux and macOS return False diff --git a/src/calibre/utils/run_tests.py b/src/calibre/utils/run_tests.py index 65c432a518..58421598e6 100644 --- a/src/calibre/utils/run_tests.py +++ b/src/calibre/utils/run_tests.py @@ -6,6 +6,9 @@ import unittest, functools, importlib, importlib.resources, os from calibre.utils.monotonic import monotonic +is_ci = os.environ.get('CI', '').lower() == 'true' + + def no_endl(f): @functools.wraps(f) def func(*args, **kwargs): @@ -329,6 +332,6 @@ def run_cli(suite, verbosity=4, buffer=True): r = unittest.TextTestRunner r.resultclass = unittest.TextTestResult if verbosity < 2 else TestResult init_env() - result = r(verbosity=verbosity, buffer=buffer).run(suite) + result = r(verbosity=verbosity, buffer=buffer and not is_ci).run(suite) if not result.wasSuccessful(): raise SystemExit(1) From c323aeb69876cb4abfee08a06afd6b9d8e937458 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 29 May 2023 07:59:05 +0530 Subject: [PATCH 0769/2055] Make the fixed size of the Polish dialog a lower bound with its sizeHint() --- src/calibre/gui2/actions/polish.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/actions/polish.py b/src/calibre/gui2/actions/polish.py index 2832c94679..883702986d 100644 --- a/src/calibre/gui2/actions/polish.py +++ b/src/calibre/gui2/actions/polish.py @@ -145,8 +145,11 @@ class Polish(QDialog): # {{{ connect_lambda(b.clicked, self, lambda self: self.select_all(False)) l.addWidget(bb, count+1, 1, 1, -1) self.setup_load_button() + self.resize(self.sizeHint()) - self.resize(QSize(950, 600)) + def sizeHint(self): + sz = super().sizeHint() + return QSize(max(950, sz.width()), max(600, sz.height())) def select_all(self, enable): for action in self.all_actions: From 3d96e98528319e53b489c08511eb11b52cf46b29 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 29 May 2023 10:23:59 +0530 Subject: [PATCH 0770/2055] Switch to storing HTML to ZIP customization as JSON for easy expansion in the future --- src/calibre/ebooks/html/to_zip.py | 46 +++++++++++++++++++------------ 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/calibre/ebooks/html/to_zip.py b/src/calibre/ebooks/html/to_zip.py index 30e3023e77..2233ea57a7 100644 --- a/src/calibre/ebooks/html/to_zip.py +++ b/src/calibre/ebooks/html/to_zip.py @@ -24,6 +24,20 @@ every time you add an HTML file to the library.\ supported_platforms = ['windows', 'osx', 'linux'] on_import = True + def parse_my_settings(self, sc): + if not sc: + sc = '' + if sc.startswith('{'): + import json + try: + return json.loads(sc) + except Exception: + return {} + else: + sc = sc.strip() + enc, _, bfs = sc.partition('|') + return {'encoding': enc, 'breadth_first': bfs == 'bf'} + def run(self, htmlfile): import codecs from calibre import prints @@ -36,8 +50,8 @@ every time you add an HTML file to the library.\ recs =[('debug_pipeline', tdir, OptionRecommendation.HIGH)] recs.append(['keep_ligatures', True, OptionRecommendation.HIGH]) if self.site_customization and self.site_customization.strip(): - sc = self.site_customization.strip() - enc, _, bf = sc.partition('|') + settings = self.parse_my_settings(self.site_customization) + enc = settings.get('encoding') if enc: try: codecs.lookup(enc) @@ -45,9 +59,8 @@ every time you add an HTML file to the library.\ prints('Ignoring invalid input encoding for HTML:', enc) else: recs.append(['input_encoding', enc, OptionRecommendation.HIGH]) - if bf == 'bf': - recs.append(['breadth_first', True, - OptionRecommendation.HIGH]) + if settings.get('breadth_first'): + recs.append(['breadth_first', True, OptionRecommendation.HIGH]) gui_convert(htmlfile, tdir, recs, abort_after_input_dump=True) of = self.temporary_file('_plugin_html2zip.zip') tdir = os.path.join(tdir, 'input') @@ -63,7 +76,7 @@ every time you add an HTML file to the library.\ def customization_help(self, gui=False): return _('Character encoding for the input HTML files. Common choices ' - 'include: cp1252, cp1251, latin1 and utf-8.') + 'include: utf-8, cp1252, cp1251 and latin1.') def do_user_config(self, parent=None): ''' @@ -71,6 +84,7 @@ every time you add an HTML file to the library.\ True if the user clicks OK, False otherwise. The changes are automatically applied. ''' + import json from qt.core import (QDialog, QDialogButtonBox, QVBoxLayout, QLabel, Qt, QLineEdit, QCheckBox) @@ -97,14 +111,9 @@ every time you add an HTML file to the library.\ ' calibre does it depth first, i.e. if file A links to B and ' ' C, but B links to D, the files are added in the order A, B, D, C. ' ' With this option, they will instead be added as A, B, C, D')) - sc = plugin_customization(self) - if not sc: - sc = '' - sc = sc.strip() - enc = sc.partition('|')[0] - bfs = sc.partition('|')[-1] - bf.setChecked(bfs == 'bf') - sc = QLineEdit(enc, config_dialog) + settings = self.parse_my_settings(plugin_customization(self)) + bf.setChecked(bool(settings.get('breadth_first'))) + sc = QLineEdit(str(settings.get('encoding', '')), config_dialog) v.addWidget(sc) v.addWidget(bf) v.addWidget(button_box) @@ -112,9 +121,12 @@ every time you add an HTML file to the library.\ config_dialog.exec() if config_dialog.result() == QDialog.DialogCode.Accepted: - sc = str(sc.text()).strip() + settings = {} + enc = str(sc.text()).strip() + if enc: + settings['encoding'] = enc if bf.isChecked(): - sc += '|bf' - customize_plugin(self, sc) + settings['breadth_first'] = True + customize_plugin(self, json.dumps(settings, ensure_ascii=True)) return config_dialog.result() From 83a1fef4d6499bde87d1de2d93e22d4f947b7999 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 29 May 2023 10:41:53 +0530 Subject: [PATCH 0771/2055] Allow customizing the html to zip plugin to add resources outside the html root folder --- src/calibre/ebooks/html/to_zip.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/calibre/ebooks/html/to_zip.py b/src/calibre/ebooks/html/to_zip.py index 2233ea57a7..d70265bb56 100644 --- a/src/calibre/ebooks/html/to_zip.py +++ b/src/calibre/ebooks/html/to_zip.py @@ -61,6 +61,8 @@ every time you add an HTML file to the library.\ recs.append(['input_encoding', enc, OptionRecommendation.HIGH]) if settings.get('breadth_first'): recs.append(['breadth_first', True, OptionRecommendation.HIGH]) + if settings.get('allow_local_files_outside_root'): + recs.append(['allow_local_files_outside_root', True, OptionRecommendation.HIGH]) gui_convert(htmlfile, tdir, recs, abort_after_input_dump=True) of = self.temporary_file('_plugin_html2zip.zip') tdir = os.path.join(tdir, 'input') @@ -103,6 +105,7 @@ every time you add an HTML file to the library.\ help_text = self.customization_help(gui=True) help_text = QLabel(help_text, config_dialog) help_text.setWordWrap(True) + help_text.setMinimumWidth(300) help_text.setTextInteractionFlags(Qt.TextInteractionFlag.LinksAccessibleByMouse | Qt.TextInteractionFlag.LinksAccessibleByKeyboard) help_text.setOpenExternalLinks(True) v.addWidget(help_text) @@ -111,11 +114,20 @@ every time you add an HTML file to the library.\ ' calibre does it depth first, i.e. if file A links to B and ' ' C, but B links to D, the files are added in the order A, B, D, C. ' ' With this option, they will instead be added as A, B, C, D')) + lr = QCheckBox(_('Allow resources outside the HTML file root folder')) + from calibre.customize.ui import plugin_for_input_format + hi = plugin_for_input_format('html') + for opt in hi.options: + if opt.option.name == 'allow_local_files_outside_root': + lr.setToolTip(opt.help) + break settings = self.parse_my_settings(plugin_customization(self)) bf.setChecked(bool(settings.get('breadth_first'))) + lr.setChecked(bool(settings.get('allow_local_files_outside_root'))) sc = QLineEdit(str(settings.get('encoding', '')), config_dialog) v.addWidget(sc) v.addWidget(bf) + v.addWidget(lr) v.addWidget(button_box) size_dialog() config_dialog.exec() @@ -127,6 +139,8 @@ every time you add an HTML file to the library.\ settings['encoding'] = enc if bf.isChecked(): settings['breadth_first'] = True + if lr.isChecked(): + settings['allow_local_files_outside_root'] = True customize_plugin(self, json.dumps(settings, ensure_ascii=True)) return config_dialog.result() From 5519a2264cc4240a728b56ff06caef0c8f19c9a3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 29 May 2023 11:10:41 +0530 Subject: [PATCH 0772/2055] version 6.19.0 --- Changelog.txt | 24 ++++++++++++++++++++++++ src/calibre/constants.py | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Changelog.txt b/Changelog.txt index 5255cc1980..b931485aa3 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -23,6 +23,30 @@ # - title by author # }}} +{{{ 6.19.0 2023-05-29 + +:: new features + +- HTML Input: Restrict adding of resources like images to only files within the folder hierarchy starting at the parent folder of the root HTML file + Can be controlled by customizing the HTML to Zip plugin in Preferences->Plugins or the --allow-local-files-outside-root option to the + ebook-convert command + +:: bug fixes + +- PDF Output: Fix regression in previous release causing non-English entries to be incorrectly encoded into the PDF bookmarks + +- PDF Output: Fix regression in previous release that caused blank pages when generating headers or footers + +- [2021367] Book list: Fix editing-in-place not pre-selecting existing text for some column types + +- Amazon.de metadata download: Update for site changes + +- PDF Output: Set /Creator and /Producer in /Info + +- [2020906] Fix row height incorrect in Manage category dialog when blank + +}}} + {{{ 6.18.1 2023-05-26 :: new features diff --git a/src/calibre/constants.py b/src/calibre/constants.py index fab7220d8f..45d23b0dde 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -5,7 +5,7 @@ from functools import lru_cache import sys, locale, codecs, os, collections, collections.abc __appname__ = 'calibre' -numeric_version = (6, 18, 1) +numeric_version = (6, 19, 0) __version__ = '.'.join(map(str, numeric_version)) git_version = None __author__ = "Kovid Goyal " From c05013c260415bcffa9485f2f360d72d87388746 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 29 May 2023 13:31:53 +0530 Subject: [PATCH 0773/2055] Fix #2021452 [Can't Open Metadata Edit Window](https://bugs.launchpad.net/calibre/+bug/2021452) --- src/calibre/gui2/complete2.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/calibre/gui2/complete2.py b/src/calibre/gui2/complete2.py index 850733f7ae..945bf5d707 100644 --- a/src/calibre/gui2/complete2.py +++ b/src/calibre/gui2/complete2.py @@ -515,15 +515,11 @@ class EditWithComplete(EnComboBox): def text(self): return self.lineEdit().text() - - @pyqtProperty(str) - def currentText(self): - return self.lineEdit().text() - - @currentText.setter - def currentText(self, text): + def set_current_text(self, text): self.setText(text) - self.lineEdit().selectAll() + self.selectAll() + + current_text = pyqtProperty(str, fget=text, fset=set_current_text, user=True) def selectAll(self): self.lineEdit().selectAll() From 1b6fc0668d0c6380d6ef3a21209c5fcdbe3a74ca Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 29 May 2023 13:48:01 +0530 Subject: [PATCH 0774/2055] version 6.19.1 --- Changelog.txt | 4 +++- src/calibre/constants.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changelog.txt b/Changelog.txt index b931485aa3..3d4ae4ab5e 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -23,7 +23,7 @@ # - title by author # }}} -{{{ 6.19.0 2023-05-29 +{{{ 6.19.1 2023-05-29 :: new features @@ -45,6 +45,8 @@ - [2020906] Fix row height incorrect in Manage category dialog when blank +- [2021452] 6.19.1 fixes a bug in 6.19.0 that broke the edit metadata dialog + }}} {{{ 6.18.1 2023-05-26 diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 45d23b0dde..c794f21b36 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -5,7 +5,7 @@ from functools import lru_cache import sys, locale, codecs, os, collections, collections.abc __appname__ = 'calibre' -numeric_version = (6, 19, 0) +numeric_version = (6, 19, 1) __version__ = '.'.join(map(str, numeric_version)) git_version = None __author__ = "Kovid Goyal " From a4e68799aeee7e32d2ec746f4c06c018c0c855e6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 29 May 2023 13:50:48 +0530 Subject: [PATCH 0775/2055] Add a comment explaining the need for current_text --- src/calibre/gui2/complete2.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/calibre/gui2/complete2.py b/src/calibre/gui2/complete2.py index 945bf5d707..9e4c2b72b0 100644 --- a/src/calibre/gui2/complete2.py +++ b/src/calibre/gui2/complete2.py @@ -519,6 +519,9 @@ class EditWithComplete(EnComboBox): self.setText(text) self.selectAll() + # Create a Qt user property for the current text so that when this widget + # is used as an edit widget in a table view it selects all text, as + # matching the behavior of all other Qt widgets. current_text = pyqtProperty(str, fget=text, fset=set_current_text, user=True) def selectAll(self): From 1e16f53631847b352509092156dadc2800171c8e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 29 May 2023 19:36:51 +0530 Subject: [PATCH 0776/2055] CHM Input: Fix a regression in the previous release that broke conversion of CHM files. Fixes #2021413 [chm->pdf conversion german umlauts TOC problem](https://bugs.launchpad.net/calibre/+bug/2021413) --- src/calibre/ebooks/conversion/plugins/chm_input.py | 1 + src/calibre/ebooks/conversion/plugins/html_input.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/conversion/plugins/chm_input.py b/src/calibre/ebooks/conversion/plugins/chm_input.py index 8a7d8da83c..fbdfb48a52 100644 --- a/src/calibre/ebooks/conversion/plugins/chm_input.py +++ b/src/calibre/ebooks/conversion/plugins/chm_input.py @@ -103,6 +103,7 @@ class CHMInput(InputFormatPlugin): from calibre.customize.builtins import HTMLInput opts.breadth_first = True htmlinput = HTMLInput(None) + htmlinput.set_root_dir_of_input(basedir) oeb = htmlinput.create_oebbook(htmlpath, basedir, opts, log, mi) return oeb diff --git a/src/calibre/ebooks/conversion/plugins/html_input.py b/src/calibre/ebooks/conversion/plugins/html_input.py index 9119698b61..b86348ce4e 100644 --- a/src/calibre/ebooks/conversion/plugins/html_input.py +++ b/src/calibre/ebooks/conversion/plugins/html_input.py @@ -76,6 +76,9 @@ class HTMLInput(InputFormatPlugin): } + def set_root_dir_of_input(self, basedir): + self.root_dir_of_input = os.path.normcase(get_long_path_name(os.path.abspath(basedir)) + os.sep) + def convert(self, stream, opts, file_ext, log, accelerators): self._is_case_sensitive = None @@ -86,7 +89,7 @@ class HTMLInput(InputFormatPlugin): if hasattr(stream, 'name'): basedir = os.path.dirname(stream.name) fname = os.path.basename(stream.name) - self.root_dir_of_input = os.path.normcase(get_long_path_name(os.path.abspath(basedir)) + os.sep) + self.set_root_dir_of_input(basedir) if file_ext != 'opf': if opts.dont_package: From 034fc140d69ba7949b7646692bc444426b93328a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 29 May 2023 22:02:41 +0530 Subject: [PATCH 0777/2055] Fix #2021517 [Foreign Affairs News does not download](https://bugs.launchpad.net/calibre/+bug/2021517) --- recipes/foreignaffairs.recipe | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/foreignaffairs.recipe b/recipes/foreignaffairs.recipe index 04f2e48607..a646c7f1cc 100644 --- a/recipes/foreignaffairs.recipe +++ b/recipes/foreignaffairs.recipe @@ -155,7 +155,7 @@ class ForeignAffairsRecipe(BasicNewsRecipe): self.timefmt = u' [%s]' % date link = soup.find('link', rel='canonical', href=True)['href'] year, volnum, issue_vol = link.split('/')[-3:] - self.cover_url = soup.find(**classes('subscribe-callout-image'))['data-src'].split("|")[-1] + self.cover_url = soup.find(**classes('subscribe-callout-image'))['srcset'].split()[-3] self.cover_url = self.cover_url.split('?')[0] self.cover_url = self.cover_url.replace('_webp_issue_small_2x', '_webp_issue_large_2x') From bb3bb53270c7864a40cbb89a8116f6f321330491 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 30 May 2023 19:35:12 +0530 Subject: [PATCH 0778/2055] string changes --- manual/template_lang.rst | 2 +- src/calibre/utils/formatter_functions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/manual/template_lang.rst b/manual/template_lang.rst index 7a376818f4..3df43dcb83 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -558,7 +558,7 @@ In `GPM` the functions described in `Single Function Mode` all require an additi * ``fractional_part(x)`` -- returns the value after the decimal point. For example, ``fractional_part(3.14)`` returns ``0.14``. Throws an exception if ``x`` is not a number. * ``has_cover()`` -- return ``'Yes'`` if the book has a cover, otherwise the empty string. * ``has_extra_files([pattern])`` -- returns the count of extra files, otherwise '' (the empty string). If the optional parameter ``pattern`` (a regular expression) is supplied then the list is filtered to files that match ``pattern`` before the files are counted. The pattern match is case insensitive. See also the functions ``extra_file_names()``, ``extra_file_size()`` and ``extra_file_modtime()``. This function can be used only in the GUI. -* ``identifier_in_list(val, id_name [, found_val, not_found_val])`` -- treat ``val`` as a list of identifiers separated by commas. An identifier has the format ``id_name:value``. The ``id_name`` parameter is the id_name text to search for, either ``id_name`` or ``id_name:regexp``. The first case matches if there is any identifier matching that id_name. The second case matches if id_name matches an identifier and the regexp matches the identifier's value. If ``found_val`` and ``not_found_val`` are provided then if there is a match then return ``found_val``, otherwise return ``not_found_val``. If ``found_val`` and ``not_found_val`` are not provided then if there is a match then return the ``indentfier:value`` pair, otherwise the empty string (``''``). +* ``identifier_in_list(val, id_name [, found_val, not_found_val])`` -- treat ``val`` as a list of identifiers separated by commas. An identifier has the format ``id_name:value``. The ``id_name`` parameter is the id_name text to search for, either ``id_name`` or ``id_name:regexp``. The first case matches if there is any identifier matching that id_name. The second case matches if id_name matches an identifier and the regexp matches the identifier's value. If ``found_val`` and ``not_found_val`` are provided then if there is a match then return ``found_val``, otherwise return ``not_found_val``. If ``found_val`` and ``not_found_val`` are not provided then if there is a match then return the ``identifier:value`` pair, otherwise the empty string (``''``). * ``is_marked()`` -- check whether the book is `marked` in calibre. If it is then return the value of the mark, either ``'true'`` (lower case) or a comma-separated list of named marks. Returns ``''`` (the empty string) if the book is not marked. This function works only in the GUI. * ``language_codes(lang_strings)`` -- return the `language codes `_ for the language names passed in `lang_strings`. The strings must be in the language of the current locale. ``Lang_strings`` is a comma-separated list. * ``list_contains(value, separator, [ pattern, found_val, ]* not_found_val)`` -- (Alias of ``in_list``) Interpreting the value as a list of items separated by ``separator``, evaluate the ``pattern`` against each value in the list. If the ``pattern`` matches any value then return ``found_val``, otherwise return ``not_found_val``. The ``pattern`` and ``found_value`` can be repeated as many times as desired, permitting returning different values depending on the search. The patterns are checked in order. The first match is returned. Aliases: ``in_list()``, ``list_contains()`` diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 0bfd0cb000..b9811a170b 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -807,7 +807,7 @@ class BuiltinIdentifierInList(BuiltinFormatterFunction): 'matches the identifier\'s value. If found_val and not_found_val ' 'are provided then if there is a match then return found_val, otherwise ' 'return not_found_val. If found_val and not_found_val are not ' - 'provided then if there is a match then return the indentfier:value ' + 'provided then if there is a match then return the identifier:value ' 'pair, otherwise the empty string.') def evaluate(self, formatter, kwargs, mi, locals, val, ident, *args): From c5d4ece489ca3bd32c8d8649c2ee0d46fc380c94 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 30 May 2023 20:43:44 +0530 Subject: [PATCH 0779/2055] E-book viewer: Ensure CSS stylesheets are interpreted as UTF-8. Fixes #2021554 [The E-book viewer displays a CSS inserted em dash as an encoding error](https://bugs.launchpad.net/calibre/+bug/2021554) --- src/calibre/gui2/viewer/web_view.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/gui2/viewer/web_view.py b/src/calibre/gui2/viewer/web_view.py index fc077f1ea5..00b5a253d8 100644 --- a/src/calibre/gui2/viewer/web_view.py +++ b/src/calibre/gui2/viewer/web_view.py @@ -149,6 +149,8 @@ class UrlSchemeHandler(QWebEngineUrlSchemeHandler): 'application/x-font-truetype':'application/x-font-ttf', 'application/font-sfnt': 'application/x-font-ttf', }.get(mime_type, mime_type) + if mime_type == 'text/css': + mime_type += '; charset=utf-8' send_reply(rq, mime_type, data) except Exception: import traceback From 7ec6bc2167421a671083f9bf4496eebb15670a45 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 31 May 2023 19:14:18 +0100 Subject: [PATCH 0780/2055] Instead of returning the empty string, make advanced template search return a search with no comparison value so the user will see the error messages. --- src/calibre/gui2/dialogs/search.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/dialogs/search.py b/src/calibre/gui2/dialogs/search.py index 876ceffbf8..a6aa2d69a4 100644 --- a/src/calibre/gui2/dialogs/search.py +++ b/src/calibre/gui2/dialogs/search.py @@ -442,13 +442,11 @@ class SearchDialog(QDialog): def template_search_string(self): template = str(self.template_program_box.text()) value = str(self.template_value_box.text()) - if template and value: - cb = self.template_test_type_box - op = str(cb.itemData(cb.currentIndex())) - l = f'{template}#@#:{op}:{value}' - # Use docstring quoting (super-quoting) to avoid problems with escaping - return 'template:"""' + l + '"""' - return '' + cb = self.template_test_type_box + op = str(cb.itemData(cb.currentIndex())) + l = f'{template}#@#:{op}:{value}' + # Use docstring quoting (super-quoting) to avoid problems with escaping + return 'template:"""' + l + '"""' def date_search_string(self): field = str(self.date_field.itemData(self.date_field.currentIndex()) or '') From eb80c90221cdfeac03a7a7a77108c6949b5cbc3a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 1 Jun 2023 14:00:28 +0530 Subject: [PATCH 0781/2055] Ensure palmdoc decompress does nto write out of bounds. Fixes #2022035 [Private bug](https://bugs.launchpad.net/calibre/+bug/2022035) --- src/calibre/ebooks/compression/palmdoc.c | 43 ++++++++++++++---------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/calibre/ebooks/compression/palmdoc.c b/src/calibre/ebooks/compression/palmdoc.c index 45eac58fd2..81e3d16f0a 100644 --- a/src/calibre/ebooks/compression/palmdoc.c +++ b/src/calibre/ebooks/compression/palmdoc.c @@ -42,10 +42,19 @@ typedef struct { #define CHAR(x) (( (x) > 127 ) ? (x)-256 : (x)) +static bool +write_to_bytes_object(PyObject **output, Py_ssize_t pos, char ch) { + if (pos >= PyBytes_GET_SIZE(*output)) { + if (_PyBytes_Resize(output, 2 * pos) != 0) return false; + } + PyBytes_AS_STRING(*output)[pos] = ch; + return true; +} + static PyObject * cpalmdoc_decompress(PyObject *self, PyObject *args) { const char *_input = NULL; Py_ssize_t input_len = 0; - Byte *input; char *output; Byte c; PyObject *ans; + Byte *input; Byte c; PyObject *ans; Py_ssize_t i = 0, o = 0, j = 0, di, n; if (!PyArg_ParseTuple(args, "y#", &_input, &input_len)) return NULL; @@ -54,31 +63,29 @@ cpalmdoc_decompress(PyObject *self, PyObject *args) { // Map chars to bytes for (j = 0; j < input_len; j++) input[j] = (_input[j] < 0) ? _input[j]+256 : _input[j]; - output = (char *)PyMem_Malloc(sizeof(char)*(MAX(BUFFER, 8*input_len))); - if (output == NULL) return PyErr_NoMemory(); + ans = PyBytes_FromStringAndSize(NULL, 8*input_len); + if (ans == NULL) { PyMem_Free(input); return NULL; } +#define write(ch) if (!write_to_bytes_object(&ans, o++, ch)) { PyMem_Free(input); return NULL; } while (i < input_len) { c = input[i++]; - if (c >= 1 && c <= 8) // copy 'c' bytes - while (c--) output[o++] = (char)input[i++]; - - else if (c <= 0x7F) // 0, 09-7F = self - output[o++] = (char)c; - - else if (c >= 0xC0) { // space + ASCII char - output[o++] = ' '; - output[o++] = c ^ 0x80; - } - else { // 80-BF repeat sequences + if (c >= 1 && c <= 8) { // copy 'c' bytes + while (c--) { write(input[i++]); } + } else if (c <= 0x7F) { // 0, 09-7F = self + write((char)c); + } else if (c >= 0xC0) { // space + ASCII char + write(' '); write(c ^ 0x80); + } else { // 80-BF repeat sequences c = (c << 8) + input[i++]; di = (c & 0x3FFF) >> 3; - for ( n = (c & 7) + 3; n--; ++o ) - output[o] = output[o - di]; + for ( n = (c & 7) + 3; n--; ++o ) { + if (!write_to_bytes_object(&ans, o, PyBytes_AS_STRING(ans)[o-di])) { PyMem_Free(input); return NULL; } + } } } - ans = Py_BuildValue("y#", output, o); - if (output != NULL) PyMem_Free(output); +#undef write if (input != NULL) PyMem_Free(input); + if (_PyBytes_Resize(&ans, o) != 0) return NULL; return ans; } From 7be68fcebafbaf1a6702b46bb1a08729c0999113 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 1 Jun 2023 14:07:52 +0530 Subject: [PATCH 0782/2055] Also ensure there are no overreads on the input data --- src/calibre/ebooks/compression/palmdoc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calibre/ebooks/compression/palmdoc.c b/src/calibre/ebooks/compression/palmdoc.c index 81e3d16f0a..dc8206f574 100644 --- a/src/calibre/ebooks/compression/palmdoc.c +++ b/src/calibre/ebooks/compression/palmdoc.c @@ -70,12 +70,12 @@ cpalmdoc_decompress(PyObject *self, PyObject *args) { while (i < input_len) { c = input[i++]; if (c >= 1 && c <= 8) { // copy 'c' bytes - while (c--) { write(input[i++]); } + while (c-- && i < input_len) { write(input[i++]); } } else if (c <= 0x7F) { // 0, 09-7F = self write((char)c); } else if (c >= 0xC0) { // space + ASCII char write(' '); write(c ^ 0x80); - } else { // 80-BF repeat sequences + } else if (i < input_len) { // 80-BF repeat sequences c = (c << 8) + input[i++]; di = (c & 0x3FFF) >> 3; for ( n = (c & 7) + 3; n--; ++o ) { @@ -84,7 +84,7 @@ cpalmdoc_decompress(PyObject *self, PyObject *args) { } } #undef write - if (input != NULL) PyMem_Free(input); + PyMem_Free(input); if (_PyBytes_Resize(&ans, o) != 0) return NULL; return ans; } From 77add5a7b26f062b40b608934e0533c1a68534c8 Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Sat, 3 Jun 2023 19:46:30 +0530 Subject: [PATCH 0783/2055] removing defunct recipes. --- recipes/aachener_nachrichten.recipe | 113 ------------------------- recipes/acim_bilim_dergisi.recipe | 24 ------ recipes/aftenposten.recipe | 20 ----- recipes/agrogerila.recipe | 36 -------- recipes/air_force_times.recipe | 42 --------- recipes/icons/aachener_nachrichten.png | Bin 522 -> 0 bytes recipes/icons/aftenposten.png | Bin 96 -> 0 bytes recipes/icons/agrogerila.png | Bin 2497 -> 0 bytes recipes/icons/air_force_times.png | Bin 1335 -> 0 bytes 9 files changed, 235 deletions(-) delete mode 100644 recipes/aachener_nachrichten.recipe delete mode 100644 recipes/acim_bilim_dergisi.recipe delete mode 100644 recipes/aftenposten.recipe delete mode 100644 recipes/agrogerila.recipe delete mode 100644 recipes/air_force_times.recipe delete mode 100644 recipes/icons/aachener_nachrichten.png delete mode 100644 recipes/icons/aftenposten.png delete mode 100644 recipes/icons/agrogerila.png delete mode 100644 recipes/icons/air_force_times.png diff --git a/recipes/aachener_nachrichten.recipe b/recipes/aachener_nachrichten.recipe deleted file mode 100644 index 7424426f70..0000000000 --- a/recipes/aachener_nachrichten.recipe +++ /dev/null @@ -1,113 +0,0 @@ -from calibre.web.feeds.recipes import BasicNewsRecipe - - -class AdvancedUserRecipe(BasicNewsRecipe): - - title = u'Aachener Nachrichten' - __author__ = 'schuster' # AGE update 2012-11-28 - oldest_article = 1 - max_articles_per_feed = 100 - no_stylesheets = True - remove_javascript = True - remove_empty_feeds = True - language = 'de' - -# cover_url = 'http://www.aachener-nachrichten.de/img/logos/an_website_retina.png' - masthead_url = 'http://www.aachener-nachrichten.de/img/logos/an_website_retina.png' - - keep_only_tags = [ - dict(name='article', attrs={'class': ['single']}) - ] - - remove_tags = [ - dict(name='div', attrs={'class': ["clearfix navi-wrapper"]}), - dict(name='div', attrs={'id': ["article_actions"]}), - dict(name='style', attrs={'type': ["text/css"]}), - dict(name='aside'), - dict(name='a', attrs={'class': ["btn btn-action"]}) - ] - - feeds = [ - (u'Lokales - Euregio', - u'http://www.aachener-nachrichten.de/cmlink/euregio-rss-1.357285'), - (u'Lokales - Aachen', - u'http://www.aachener-nachrichten.de/cmlink/aachen-rss-1.357286'), - (u'Lokales - Nordkreis', - u'http://www.aachener-nachrichten.de/cmlink/nordkreis-rss-1.358150'), - (u'Lokales - Düren', - u'http://www.aachener-nachrichten.de/cmlink/dueren-rss-1.358626'), - (u'Lokales - Eiffel', - u'http://www.aachener-nachrichten.de/cmlink/eifel-rss-1.358978'), - (u'Lokales - Eschweiler', - u'http://www.aachener-nachrichten.de/cmlink/eschweiler-rss-1.359332'), - (u'Lokales - Geilenkirchen', - u'http://www.aachener-nachrichten.de/cmlink/geilenkirchen-rss-1.359643'), - (u'Lokales - Heinsberg', - u'http://www.aachener-nachrichten.de/cmlink/heinsberg-rss-1.359724'), - (u'Lokales - Jülich', - u'http://www.aachener-nachrichten.de/cmlink/juelich-rss-1.359725'), - (u'Lokales - Stolberg', - u'http://www.aachener-nachrichten.de/cmlink/stolberg-rss-1.359726'), - (u'News - Politik', - u'http://www.aachener-nachrichten.de/cmlink/politik-rss-1.359727'), - (u'News - Aus aller Welt', - u'http://www.aachener-nachrichten.de/cmlink/ausallerwelt-rss-1.453282'), - (u'News - Wirtschaft', - u'http://www.aachener-nachrichten.de/cmlink/wirtschaft-rss-1.359872'), - (u'News - Kultur', - u'http://www.aachener-nachrichten.de/cmlink/kultur-rss-1.365018'), - (u'News - Kino', u'http://www.aachener-nachrichten.de/cmlink/kino-rss-1.365019'), - (u'News - Digital', - u'http://www.aachener-nachrichten.de/cmlink/digital-rss-1.365020'), - (u'News - Wissenschaft', - u'http://www.aachener-nachrichten.de/cmlink/wissenschaft-rss-1.365021'), - (u'News - Hochschule', - u'http://www.aachener-nachrichten.de/cmlink/hochschule-rss-1.365022'), - (u'News - Auto', u'http://www.aachener-nachrichten.de/cmlink/auto-rss-1.365023'), - (u'News - Kurioses', - u'http://www.aachener-nachrichten.de/cmlink/kurioses-rss-1.365067'), - (u'News - Musik', - u'http://www.aachener-nachrichten.de/cmlink/musik-rss-1.365305'), - (u'News - Tagesthema', - u'http://www.aachener-nachrichten.de/cmlink/tagesthema-rss-1.365519'), - (u'News - Newsticker', - u'http://www.aachener-nachrichten.de/cmlink/newsticker-rss-1.451948'), - (u'Sport - Aktuell', - u'http://www.aachener-nachrichten.de/cmlink/aktuell-rss-1.366716'), - (u'Sport - Fußball', - u'http://www.aachener-nachrichten.de/cmlink/fussball-rss-1.367060'), - (u'Sport - Bundesliga', - u'http://www.aachener-nachrichten.de/cmlink/bundesliga-rss-1.453367'), - (u'Sport - Alemannia Aachen', - u'http://www.aachener-nachrichten.de/cmlink/alemanniaaachen-rss-1.366057'), - (u'Sport - Volleyball', - u'http://www.aachener-nachrichten.de/cmlink/volleyball-rss-1.453370'), - (u'Sport - Chio', - u'http://www.aachener-nachrichten.de/cmlink/chio-rss-1.453371'), - (u'Dossier - Kinderuni', - u'http://www.aachener-nachrichten.de/cmlink/kinderuni-rss-1.453375'), - (u'Dossier - Karlspreis', - u'http://www.aachener-nachrichten.de/cmlink/karlspreis-rss-1.453376'), - (u'Dossier - Ritterorden', - u'http://www.aachener-nachrichten.de/cmlink/ritterorden-rss-1.453377'), - (u'Dossier - ZAB-Aachen', - u'http://www.aachener-nachrichten.de/cmlink/zabaachen-rss-1.453380'), - (u'Dossier - Karneval', - u'http://www.aachener-nachrichten.de/cmlink/karneval-rss-1.453384'), - (u'Ratgeber - Geld', - u'http://www.aachener-nachrichten.de/cmlink/geld-rss-1.453385'), - (u'Ratgeber - Recht', - u'http://www.aachener-nachrichten.de/cmlink/recht-rss-1.453386'), - (u'Ratgeber - Gesundheit', - u'http://www.aachener-nachrichten.de/cmlink/gesundheit-rss-1.453387'), - (u'Ratgeber - Familie', - u'http://www.aachener-nachrichten.de/cmlink/familie-rss-1.453388'), - (u'Ratgeber - Livestyle', - u'http://www.aachener-nachrichten.de/cmlink/lifestyle-rss-1.453389'), - (u'Ratgeber - Reisen', - u'http://www.aachener-nachrichten.de/cmlink/reisen-rss-1.453390'), - (u'Ratgeber - Bauen und Wohnen', - u'http://www.aachener-nachrichten.de/cmlink/bauen-rss-1.453398'), - (u'Ratgeber - Bildung und Beruf', - u'http://www.aachener-nachrichten.de/cmlink/bildung-rss-1.453400'), - ] diff --git a/recipes/acim_bilim_dergisi.recipe b/recipes/acim_bilim_dergisi.recipe deleted file mode 100644 index 1d9746b127..0000000000 --- a/recipes/acim_bilim_dergisi.recipe +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- - -from calibre.web.feeds.news import BasicNewsRecipe - - -class AdvancedUserRecipe1334868409(BasicNewsRecipe): - title = u'AÇIK BİLİM DERGİSİ' - description = ' Aylık çevrimiçi bilim dergisi' - __author__ = u'thomass' - oldest_article = 30 - max_articles_per_feed = 300 - auto_cleanup = True - encoding = 'UTF-8' - publisher = 'açık bilim' - category = 'haber, bilim,TR,dergi' - language = 'tr' - publication_type = 'magazine ' - conversion_options = { - 'tags': category, 'language': language, 'publisher': publisher, 'linearize_tables': True - } - cover_img_url = 'http://www.acikbilim.com/wp-content/themes/Equilibrium/images/logodene.jpg' - masthead_url = 'http://www.acikbilim.com/wp-content/themes/Equilibrium/images/logodene.jpg' - - feeds = [(u'Tüm Yayınlar', u'http://www.acikbilim.com/feed')] diff --git a/recipes/aftenposten.recipe b/recipes/aftenposten.recipe deleted file mode 100644 index fea850fc00..0000000000 --- a/recipes/aftenposten.recipe +++ /dev/null @@ -1,20 +0,0 @@ -from calibre.web.feeds.news import BasicNewsRecipe - - -class Aftenposten(BasicNewsRecipe): - title = u'Aftenposten' - __author__ = 'davotibarna' - description = 'Norske nyheter' - language = 'no' - oldest_article = 5 - max_articles_per_feed = 100 - recipe_disabled = ('The recipe to download Aftenposten has been ' - 'temporarily disabled at the publisher\'s request, while ' - 'they finalize their digital strategy.') - no_stylesheets = True - encoding = 'ISO-8859-1' - - feeds = [(u'Aftenposten', u'http://www.aftenposten.no/eksport/rss-1_0/')] - - def print_version(self, url): - return url.replace('#xtor=RSS-3', '?service=print') diff --git a/recipes/agrogerila.recipe b/recipes/agrogerila.recipe deleted file mode 100644 index 70abbe0960..0000000000 --- a/recipes/agrogerila.recipe +++ /dev/null @@ -1,36 +0,0 @@ - -__license__ = 'GPL v3' -__copyright__ = '2010, Darko Miletic ' -''' -boljevac.blogspot.com -''' - -import re -from calibre.web.feeds.news import BasicNewsRecipe - - -class AgroGerila(BasicNewsRecipe): - title = 'Agro Gerila' - __author__ = 'Darko Miletic' - description = 'Politicki nekorektan blog.' - oldest_article = 45 - max_articles_per_feed = 100 - language = 'sr' - encoding = 'utf-8' - no_stylesheets = True - use_embedded_content = True - publication_type = 'blog' - extra_css = ' @font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} body{font-family: "Trebuchet MS",Trebuchet,Verdana,sans1,sans-serif} .article_description{font-family: sans1, sans-serif} img{margin-bottom: 0.8em; border: 1px solid #333333; padding: 4px } ' # noqa - - conversion_options = { - 'comment': description, 'tags': 'film, blog, srbija', 'publisher': 'Dry-Na-Nord', 'language': language - } - - preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] - - feeds = [(u'Posts', u'http://boljevac.blogspot.com/feeds/posts/default')] - - def preprocess_html(self, soup): - for item in soup.findAll(style=True): - del item['style'] - return self.adeify_images(soup) diff --git a/recipes/air_force_times.recipe b/recipes/air_force_times.recipe deleted file mode 100644 index 0d94fb1d72..0000000000 --- a/recipes/air_force_times.recipe +++ /dev/null @@ -1,42 +0,0 @@ -from calibre.web.feeds.news import BasicNewsRecipe - - -class AirForceTimes(BasicNewsRecipe): - title = 'Air Force Times' - __author__ = 'jde' - __date__ = '16 May 2012' - __version__ = '1.0' - description = 'News of the U.S. Air Force' - language = 'en' - publisher = 'AirForceTimes.com' - category = 'news, U.S. Air Force' - tags = 'news, U.S. Air Force' - cover_url = 'http://www.airforcetimes.com/images/logo_airforcetimes_alert.jpg' - masthead_url = 'http://www.airforcetimes.com/images/logo_airforcetimes_alert.jpg' - oldest_article = 7 # days - max_articles_per_feed = 25 - publication_type = 'newspaper' - no_stylesheets = True - use_embedded_content = False - encoding = None - recursions = 0 - needs_subscription = False - remove_javascript = True - remove_empty_feeds = True - auto_cleanup = True - - feeds = [ - ('Home','http://feeds.feedburner.com/rss/category/air-home?format=xml'), - ('Health Benefits','http://feeds.feedburner.com/rss/category/air-healthbenefits?format=xml'), - ('Retirement Benefits','http://feeds.feedburner.com/rss/category/air-retirementbenefits?format=xml'), - ('Veterans Benefits','http://feeds.feedburner.com/rss/category/air-VeteransBenefits?format=xml'), - ('Education Benefits','http://feeds.feedburner.com/rss/category/air-educationbenefits?format=xml'), - ('Adventure','http://feeds.feedburner.com/rss/category/air-adventure?format=xml'), - ('Entertainment','http://feeds.feedburner.com/rss/category/air-Entertainment?format=xml'), - ('Careers','http://feeds.feedburner.com/rss/category/air-careers?format=xml'), - ('Technology','http://feeds.feedburner.com/rss/category/air-technology?format=xml'), - ('Opinion','http://feeds.feedburner.com/rss/category/air-opinion?format=xml'), - ('Pay','http://feeds.feedburner.com/rss/category/air-pay?format=xml'), - ('Guard','http://feeds.feedburner.com/rss/category/air-guard?format=xml'), - ('Your Air Force','http://feeds.feedburner.com/rss/category/air-yourairforce?format=xml'), - ] diff --git a/recipes/icons/aachener_nachrichten.png b/recipes/icons/aachener_nachrichten.png deleted file mode 100644 index 8bc6e36174ff11fd309f977fa32df43147018e4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 522 zcmWlVT}V>_0EO=qag4^U)y$uuB!pX$u%rhiifYnky5?NdW)EgGQV5jE8Ws{hls2+G z7)T?^T2v$nQw&;SBK45l=q{GByUn{F9j>vGHm7s$bc64F=k;)gl#260L79Lc2%%gi zRdG%J4?mwXbxhPu5WFH~gSrk21RQyAMca?Av%cY5TYim4P)&QVx#yq zins;w=ZIU8c!Bi~ShwTv7bL$T<-~>y8*Zc*kzPXDi%kYwEVe?(tRSwR>KYk86(`N{e<=4^(_HSx=40sQc&Qmbs=MgY`m?Vd`i5vocv9g-)$czHVa=D%XlYZQx0xFA+J+N-=A$C_y;M1%mmiTn0pv241o;Is oI6S+N2IO#ix;Tb#$R;ZY0yzc@j9kw>M1U*?Pgg&ebxsLQ0FhA?m;e9( diff --git a/recipes/icons/agrogerila.png b/recipes/icons/agrogerila.png deleted file mode 100644 index a95cbfc2b661bf6887131482d5782ff32eb27d39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2497 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 LNkvXXu0mjf{FS>g diff --git a/recipes/icons/air_force_times.png b/recipes/icons/air_force_times.png deleted file mode 100644 index dcae18de1e9a15f17656c0ea441f485a5e220c74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1335 zcmV-71<3k|P)mwDUR>dS0p@Lfl zSE6fOh%T&97ycLBs%sy(DHYpVA9Wwy6jB3XZIWv9u(>9=@B2Mw<{TF(YQ+kI1yK*P z7-pFBo8RHg?>9I%HwSkz2<~wF5AaU_Kmn9H2Vf9URRM)`Z$)mQrSw~PLAYh>wk7%X zh*5+fY5~PNYk?TWd16vvRX|lz^;!Uc7qUqlyhlV4Awoe2UXclv7!Z*>Ko9{y0zpIq zF(7ISW}Of`*1lc}mHa;c2B zv2x|w@)-7ghWqOW)u)4Qzgn$&(a4xqyIUU}^=T*T^*o!Q+HhJFl67s?WpPFYovI|| zoTBxu?fS%2d+Ud2cq}zTl_)7|vD0kFqW9i4m9-nAhmVX5m2)Sdzzk`uk_^&8TmbKj z$4$7lcHPturcq)Ud3VEqyI@^LqKa&WCnf??$(U+vM(X1Q*uENXlgo>X;Ik@ecWWc` z`V;lZ)y>xR-IQySj!JzGOIKEtk;&!F-G#+(Zges;GLhLzUz5_vB(jZ-HHyFy*Qds} zwl^`$;^BC-HeNW2*hkK58SmMY(k~-#@e-XYprYmRq}pku)P`OZEbIrt!R<=cN$6F z%Q{U(_JhvO_gAViptM=Ew|mW<;l!rxpSFMeez0?+W-)eYbA35>ebj)*<{qt2O~3m3 z8)~dkyu7&h$o%|;Pd~qS@w3Qq1c5+;u=6fqRt3+-i;@Ha&!EH%XoITPaPt`|0RVxw zTCfVKs)4|H@0lkbe|Y9VB7-<@SA6GCEv{l-i$n$hzVd3oAvv0roR;$(8+}u2Q@}zT4fqt(gzetB+ zW@E^3Qc9fnfLOQ!0M4I3AII^=#>Ud+%k#(PUwH9FL|R!{dG@*IXJ=>k4Gr%!cNf3@ zI?Ho1=Fj9>RkJJu02M_<06-)FSY2K9-uHUFZodyiy+MC_cjw#hzWeH{FR!n!e|+Iw ztJ`_$z|F9D;_y^Yq08&*p97T5h`dYKq0)SStndf<{)e@0LqmgAmVUCL$t1P`ycL zZ=$N!+T7=a2mowM@ZNcEjS*FbKgJ+L4Bi`NR}^f_UUno5Avot$HH07{d)b|H%me_$ ztg1-J%m5HY(QOS!5EK9rl+a5+!wR4P#u#G^01%O?0)Y1(5g`Bqs^7CGWMEJTMfmrc t{%!kruPlEvz~7bhFEiL{*8l3y?q^5}(D#T(az+3E002ovPDHLkV1k5wdwT!? From 41e23a8b0c3d1a0050100117af3b081580e9c64a Mon Sep 17 00:00:00 2001 From: ping Date: Sun, 4 Jun 2023 12:05:34 +0800 Subject: [PATCH 0784/2055] Add Prospect Magazine UK (Free) recipe --- recipes/prospectmaguk_free.recipe | 128 ++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 recipes/prospectmaguk_free.recipe diff --git a/recipes/prospectmaguk_free.recipe b/recipes/prospectmaguk_free.recipe new file mode 100644 index 0000000000..c37513a90d --- /dev/null +++ b/recipes/prospectmaguk_free.recipe @@ -0,0 +1,128 @@ +# Copyright (c) 2023 https://github.com/ping/ +# +# This software is released under the GNU General Public License v3.0 +# https://opensource.org/licenses/GPL-3.0 + +from collections import OrderedDict +from urllib.parse import urljoin + +from calibre.web.feeds.news import BasicNewsRecipe, prefixed_classes + +_issue_url = "" + + +class ProspectMagazineUKFree(BasicNewsRecipe): + title = "Prospect Magazine (Free)" + __author__ = "ping" + description = ( + "Prospect is Britain’s leading current affairs monthly magazine. " + "It is an independent and eclectic forum for writing and thinking—in " + "print and online. Published every month with two double issues in " + "the summer and winter, it spans politics, science, foreign affairs, " + "economics, the environment, philosophy and the arts." + ) + language = "en_GB" + category = "news, UK" + publication_type = "magazine" + masthead_url = "https://media.prospectmagazine.co.uk/prod/images/gm_grid_thumbnail/358ffc17208c-f4c3cddcdeda-prospect-masthead.png" + encoding = "utf-8" + remove_javascript = True + no_stylesheets = True + ignore_duplicate_articles = {"url"} + INDEX = "https://www.prospectmagazine.co.uk/issues" + + keep_only_tags = [dict(class_="prop-book-article-panel_main")] + remove_tags = [ + dict( + class_=[ + "prop-book-review-header-wrapper_magazine", + "prop-mobile-social-share_header", + "prop-magazine-link-block", + "pros-article-body__img-credit", + "pros-article-topics__wrapper", + "pros-article-author__image-wrapper", + "prop-book-review-promo_details-buy-mobile", + ] + ), + dict(id=["disqus_thread", "newsletter_wrapper"]), + prefixed_classes("dfp-slot-"), + ] + + extra_css = """ + h1 { font-size: 1.8rem; margin-bottom: 0.4rem; } + .prop-book-review-header-wrapper_standfirst { font-size: 1.2rem; font-style: italic; font-weight: normal; margin-bottom: 0.5rem; } + .prop-book-review-header-wrapper_details { margin-top: 1rem; margin-bottom: 1rem; } + .prop-book-review-header-wrapper_details-byline { + display: inline-block; font-weight: bold; color: #444; margin-right: 0.5rem; } + .prop-book-review-header-wrapper_details-date { display: inline-block; } + .gd-picture img { display: block; max-width: 100%; height: auto; } + .pros-article-body__img-caption { + font-size: 0.8rem; display: block; margin-top: 0.2rem; + } + .pullquote, blockquote { text-align: center; margin-left: 0; margin-bottom: 0.4rem; font-size: 1.25rem; } + .prop-book-review-article_author { margin: 1.5rem 0; font-style: italic; } + .prop-book-review-promo { margin-bottom: 1rem; } + """ + + def preprocess_html(self, soup): + # re-position lede image + lede_img = soup.find("img", class_="prop-book-review-header-wrapper_image") + meta = soup.find("div", class_="prop-book-review-header-wrapper_details") + if lede_img and meta: + lede_img = lede_img.extract() + meta.insert_after(lede_img) + + for img in soup.find_all("img", attrs={"data-src": True}): + img["src"] = img["data-src"] + del img["data-src"] + + for byline_link in soup.find_all("a", attrs={"data-author-name": True}): + byline_link.unwrap() + for author_link in soup.find_all("a", class_="pros-article-author"): + author_link.unwrap() + + return soup + + def parse_index(self): + if not _issue_url: + issues_soup = self.index_to_soup(self.INDEX) + curr_issue_a_ele = issues_soup.find( + "a", class_="pros-collection-landing__item" + ) + curr_issue_url = urljoin(self.INDEX, curr_issue_a_ele["href"]) + else: + curr_issue_url = _issue_url + + soup = self.index_to_soup(curr_issue_url) + issue_name = ( + self.tag_to_string(soup.find(class_="magazine-lhc__issue-name")) + .replace(" issue", "") + .strip() + ) + self.timefmt = f" [{issue_name}]" + + self.cover_url = soup.find("img", class_="magazine-lhc__cover-image")[ + "data-src" + ].replace("portrait_small_fit", "portrait_large_fit") + + articles = OrderedDict() + sections = soup.find_all("div", class_="pro-magazine-section") + for section in sections: + section_name = self.tag_to_string( + section.find(class_="pro-magazine-section__name") + ) + for sect_article in section.find_all( + class_="pro-magazine-section__article" + ): + articles.setdefault(section_name, []).append( + { + "url": urljoin(self.INDEX, sect_article.find("a")["href"]), + "title": self.tag_to_string( + sect_article.find( + class_="pro-magazine-section__article-headline" + ) + ), + } + ) + + return articles.items() From 1317d395927a019f0b1f999e20ced4632a129144 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 4 Jun 2023 11:37:11 +0530 Subject: [PATCH 0785/2055] remove not working recipe --- recipes/prospectmaguk.recipe | 55 ------------------------------------ 1 file changed, 55 deletions(-) delete mode 100644 recipes/prospectmaguk.recipe diff --git a/recipes/prospectmaguk.recipe b/recipes/prospectmaguk.recipe deleted file mode 100644 index 550b034b35..0000000000 --- a/recipes/prospectmaguk.recipe +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python -__copyright__ = '2008, Kovid Goyal ' - -__license__ = 'GPL v3' - -''' -calibre recipe for prospectmagazine.co.uk (subscription) -''' - -from calibre.web.feeds.recipes import BasicNewsRecipe -from css_selectors import Select - - -class ProspectMagUK(BasicNewsRecipe): - title = u'Prospect Magazine' - description = 'A general-interest publication offering analysis and commentary about politics, news and business.' - __author__ = 'barty, duluoz' - timefmt = ' [%d %B %Y]' - no_stylesheets = True - publication_type = 'magazine' - category = 'news, UK' - language = 'en_GB' - max_articles_per_feed = 100 - needs_subscription = True - - INDEX = 'http://www.prospectmagazine.co.uk/issue/' - keep_only_tags = [dict(id='post_content')] - - def get_browser(self): - br = BasicNewsRecipe.get_browser(self) - if self.username is not None and self.password is not None: - br.open('http://www.prospectmagazine.co.uk/wp-login.php') - br.select_form(name='loginform') - br['log'] = self.username - br['pwd'] = self.password - br.submit() - return br - - def parse_index(self): - root = self.index_to_soup(self.INDEX, as_tree=True) - sel = Select(root) - for img in sel('.block_this_month .img_wrap img'): - self.cover_url = img.get('src').partition('?')[0] - feeds = [] - for h2 in sel('h2.block-title'): - current_section = self.tag_to_string(h2) - articles = [] - self.log('Found section:', current_section) - for div in sel('div.block_home_post', h2.getparent()): - for a in sel('div.title a[href]', div): - articles.append({'title':self.tag_to_string(a), 'url':a.get('href')}) - self.log('\tFound article:', articles[-1]['title']) - if articles: - feeds.append((current_section, articles)) - return feeds From c895bb59ce34d9933a71901a25cb97342dfeb807 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 4 Jun 2023 18:53:11 +0530 Subject: [PATCH 0786/2055] Fix #2022847 [ValueError](https://bugs.launchpad.net/calibre/+bug/2022847) --- src/calibre/ebooks/__init__.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/calibre/ebooks/__init__.py b/src/calibre/ebooks/__init__.py index 11b70e2c7a..cd4b5edcec 100644 --- a/src/calibre/ebooks/__init__.py +++ b/src/calibre/ebooks/__init__.py @@ -6,7 +6,12 @@ Code for the conversion of ebook formats and the reading of metadata from various formats. ''' -import os, re, numbers, sys +import numbers +import os +import re +import sys +from contextlib import suppress + from calibre import prints from calibre.ebooks.chardet import xml_to_unicode @@ -48,7 +53,7 @@ def return_raster_image(path): def extract_cover_from_embedded_svg(html, base, log): - from calibre.ebooks.oeb.base import XPath, SVG, XLINK + from calibre.ebooks.oeb.base import SVG, XLINK, XPath from calibre.utils.xml_parse import safe_xml_fromstring root = safe_xml_fromstring(html) @@ -114,7 +119,7 @@ def render_html_svg_workaround(path_to_html, log, width=590, height=750, root='' def render_html_data(path_to_html, width, height, root=''): from calibre.ptempfile import TemporaryDirectory - from calibre.utils.ipc.simple_worker import fork_job, WorkerError + from calibre.utils.ipc.simple_worker import WorkerError, fork_job result = {} def report_error(text=''): @@ -173,14 +178,15 @@ def unit_convert(value, base, font, dpi, body_font_size=12): ' Return value in pts' if isinstance(value, numbers.Number): return value - try: + with suppress(Exception): return float(value) * 72.0 / dpi - except: - pass result = value m = UNIT_RE.match(value) if m is not None and m.group(1): - value = float(m.group(1)) + try: + value = float(m.group(1)) + except ValueError: + value = 0 unit = m.group(2) if unit == '%': result = (value / 100.0) * base From b725d7feb66ef915548167c4c70c1dfbff85d253 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 6 Jun 2023 10:47:31 +0530 Subject: [PATCH 0787/2055] Move deletion of source files into context handler --- src/calibre/utils/copy_files.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index 856ff79704..a0aa55fcf7 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -21,7 +21,8 @@ WindowsFileId = Tuple[int, int, int] class UnixFileCopier: - def __init__(self): + def __init__(self, delete_all=False): + self.delete_all = delete_all self.copy_map: Dict[str, str] = {} def register(self, path: str, dest: str) -> None: @@ -30,8 +31,9 @@ class UnixFileCopier: def __enter__(self) -> None: pass - def __exit__(self, *a) -> None: - pass + def __exit__(self, exc_type, exc_val, exc_tb) -> None: + if self.delete_all and exc_val is None: + self.delete_all_source_files() def rename_all(self) -> None: for src_path, dest_path in self.copy_map.items(): @@ -57,7 +59,8 @@ class WindowsFileCopier: Locks all files before starting the copy, ensuring other processes cannot interfere ''' - def __init__(self): + def __init__(self, delete_all=False): + self.delete_all = delete_all self.path_to_fileid_map : Dict[str, WindowsFileId] = {} self.fileid_to_paths_map: Dict[WindowsFileId, Set[str]] = defaultdict(set) self.path_to_handle_map: Dict[str, 'winutil.Handle'] = {} @@ -98,9 +101,13 @@ class WindowsFileCopier: for src in self.copy_map: self.path_to_handle_map[src] = self._open_file(src) - def __exit__(self, *a) -> None: - for h in self.path_to_handle_map.values(): - h.close() + def __exit__(self, exc_type, exc_val, exc_tb) -> None: + try: + if self.delete_all and exc_val is None: + self.delete_all_source_files() + finally: + for h in self.path_to_handle_map.values(): + h.close() def copy_all(self) -> None: for src_path, dest_path in self.copy_map.items(): @@ -128,8 +135,8 @@ class WindowsFileCopier: winutil.delete_file(make_long_path_useable(src_path)) -def get_copier() -> Union[UnixFileCopier, WindowsFileCopier]: - return WindowsFileCopier() if iswindows else UnixFileCopier() +def get_copier(delete_all=False) -> Union[UnixFileCopier, WindowsFileCopier]: + return WindowsFileCopier(delete_all) if iswindows else UnixFileCopier(delete_all) def rename_files(src_to_dest_map: Dict[str, str]) -> None: @@ -142,14 +149,12 @@ def rename_files(src_to_dest_map: Dict[str, str]) -> None: def copy_files(src_to_dest_map: Dict[str, str], delete_source: bool = False) -> None: - copier = get_copier() + copier = get_copier(delete_source) for s, d in src_to_dest_map.items(): if not samefile(s, d): copier.register(s, d) with copier: copier.copy_all() - if delete_source: - copier.delete_all_source_files() def copy_tree( @@ -183,7 +188,7 @@ def copy_tree( return os.path.join(dest_dir, rel) - copier = get_copier() + copier = get_copier(delete_source) for (dirpath, dirnames, filenames) in os.walk(src, onerror=raise_error): for d in dirnames: path = os.path.join(dirpath, d) @@ -205,8 +210,6 @@ def copy_tree( with copier: copier.copy_all() - if delete_source: - copier.delete_all_source_files() if delete_source: try: From c2e9a78ddfb244f44e6c67d091f1a1d73d790be4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 6 Jun 2023 11:33:06 +0530 Subject: [PATCH 0788/2055] Windows: Make moving files in the calibre library folder more robust Now even folders are opened and locked at the start of the operation not just files. Also folders have their attributes reset to normal in case they are marked readonly, not just files. --- src/calibre/utils/copy_files.py | 32 +++++++++++++++++++++++----- src/calibre/utils/copy_files_test.py | 8 +++++++ 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index a0aa55fcf7..33d6a997ef 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -8,7 +8,7 @@ import stat import time from collections import defaultdict from contextlib import suppress -from typing import Callable, Dict, Set, Tuple, Union +from typing import Callable, Dict, Set, Tuple, Union, List from calibre.constants import filesystem_encoding, iswindows from calibre.utils.filenames import make_long_path_useable, samefile, windows_hardlink @@ -28,6 +28,9 @@ class UnixFileCopier: def register(self, path: str, dest: str) -> None: self.copy_map[path] = dest + def register_folder(self, path: str) -> None: + pass + def __enter__(self) -> None: pass @@ -64,6 +67,8 @@ class WindowsFileCopier: self.path_to_fileid_map : Dict[str, WindowsFileId] = {} self.fileid_to_paths_map: Dict[WindowsFileId, Set[str]] = defaultdict(set) self.path_to_handle_map: Dict[str, 'winutil.Handle'] = {} + self.folder_to_handle_map: Dict[str, 'winutil.Handle'] = {} + self.folders: List[str] = [] self.copy_map: Dict[str, str] = {} def register(self, path: str, dest: str) -> None: @@ -73,11 +78,18 @@ class WindowsFileCopier: self.path_to_fileid_map[path] = winutil.get_file_id(make_long_path_useable(path)) self.copy_map[path] = dest - def _open_file(self, path: str, retry_on_sharing_violation: bool = True) -> 'winutil.Handle': + def register_folder(self, path: str) -> None: + with suppress(OSError): + # Ensure the folder is not read-only + winutil.set_file_attributes(make_long_path_useable(path), winutil.FILE_ATTRIBUTE_NORMAL) + self.path_to_fileid_map[path] = winutil.get_file_id(make_long_path_useable(path)) + self.folders.append(path) + + def _open_file(self, path: str, retry_on_sharing_violation: bool = True, is_folder: bool = False) -> 'winutil.Handle': + flags = winutil.FILE_FLAG_BACKUP_SEMANTICS if is_folder else winutil.FILE_FLAG_SEQUENTIAL_SCAN try: return winutil.create_file(make_long_path_useable(path), winutil.GENERIC_READ, - winutil.FILE_SHARE_DELETE, - winutil.OPEN_EXISTING, winutil.FILE_FLAG_SEQUENTIAL_SCAN) + winutil.FILE_SHARE_DELETE, winutil.OPEN_EXISTING, flags) except OSError as e: if e.winerror == winutil.ERROR_SHARING_VIOLATION: # The file could be a hardlink to an already opened file, @@ -88,7 +100,7 @@ class WindowsFileCopier: return self.path_to_handle_map[other] if retry_on_sharing_violation: time.sleep(WINDOWS_SLEEP_FOR_RETRY_TIME) - return self._open_file(path, False) + return self._open_file(path, False, is_folder) err = IOError(errno.EACCES, _('File is open in another program')) err.filename = path @@ -100,6 +112,8 @@ class WindowsFileCopier: self.fileid_to_paths_map[file_id].add(path) for src in self.copy_map: self.path_to_handle_map[src] = self._open_file(src) + for path in self.folders: + self.folder_to_handle_map[path] = self._open_file(path, is_folder=True) def __exit__(self, exc_type, exc_val, exc_tb) -> None: try: @@ -108,6 +122,8 @@ class WindowsFileCopier: finally: for h in self.path_to_handle_map.values(): h.close() + for h in self.folder_to_handle_map.values(): + h.close() def copy_all(self) -> None: for src_path, dest_path in self.copy_map.items(): @@ -133,6 +149,8 @@ class WindowsFileCopier: def delete_all_source_files(self) -> None: for src_path in self.copy_map: winutil.delete_file(make_long_path_useable(src_path)) + for path in reversed(self.folders): + os.rmdir(make_long_path_useable(path)) def get_copier(delete_all=False) -> Union[UnixFileCopier, WindowsFileCopier]: @@ -189,12 +207,14 @@ def copy_tree( copier = get_copier(delete_source) + copier.register_folder(src) for (dirpath, dirnames, filenames) in os.walk(src, onerror=raise_error): for d in dirnames: path = os.path.join(dirpath, d) dest = dest_from_entry(dirpath, d) os.makedirs(make_long_path_useable(dest), exist_ok=True) shutil.copystat(make_long_path_useable(path), make_long_path_useable(dest), follow_symlinks=False) + copier.register_folder(path) for f in filenames: path = os.path.join(dirpath, f) dest = dest_from_entry(dirpath, f) @@ -214,6 +234,8 @@ def copy_tree( if delete_source: try: shutil.rmtree(make_long_path_useable(src)) + except FileNotFoundError: + pass except OSError: if iswindows: time.sleep(WINDOWS_SLEEP_FOR_RETRY_TIME) diff --git a/src/calibre/utils/copy_files_test.py b/src/calibre/utils/copy_files_test.py index 4fd17cf33f..a73a103cb4 100644 --- a/src/calibre/utils/copy_files_test.py +++ b/src/calibre/utils/copy_files_test.py @@ -6,12 +6,15 @@ import shutil import tempfile import time import unittest +from contextlib import closing from calibre import walk from calibre.constants import iswindows from .copy_files import copy_tree, rename_files from .filenames import nlinks_file +if iswindows: + from calibre_extensions import winutil class TestCopyFiles(unittest.TestCase): @@ -97,6 +100,11 @@ class TestCopyFiles(unittest.TestCase): self.assertRaises(IOError, copy_tree, src, dest) self.ae(os.listdir(self.d()), ['sub']) self.assertFalse(tuple(walk(self.d()))) + h = winutil.create_file(self.s('sub'), winutil.GENERIC_READ, 0, winutil.OPEN_EXISTING, winutil.FILE_FLAG_BACKUP_SEMANTICS) + with closing(h): + self.assertRaises(IOError, copy_tree, src, dest) + self.ae(os.listdir(self.d()), ['sub']) + self.assertFalse(tuple(walk(self.d()))) def find_tests(): return unittest.defaultTestLoader.loadTestsFromTestCase(TestCopyFiles) From c3da25d1120b42206fc2d96a6752ea9b04238d5c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 6 Jun 2023 13:24:21 +0530 Subject: [PATCH 0789/2055] Use delete on close semantics on windows for cleanup post copy --- src/calibre/utils/copy_files.py | 34 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index 33d6a997ef..d68d1a1f55 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -21,8 +21,9 @@ WindowsFileId = Tuple[int, int, int] class UnixFileCopier: - def __init__(self, delete_all=False): + def __init__(self, delete_all=False, allow_move=False): self.delete_all = delete_all + self.allow_move = allow_move self.copy_map: Dict[str, str] = {} def register(self, path: str, dest: str) -> None: @@ -62,8 +63,9 @@ class WindowsFileCopier: Locks all files before starting the copy, ensuring other processes cannot interfere ''' - def __init__(self, delete_all=False): + def __init__(self, delete_all=False, allow_move=False): self.delete_all = delete_all + self.allow_move = allow_move self.path_to_fileid_map : Dict[str, WindowsFileId] = {} self.fileid_to_paths_map: Dict[WindowsFileId, Set[str]] = defaultdict(set) self.path_to_handle_map: Dict[str, 'winutil.Handle'] = {} @@ -87,9 +89,11 @@ class WindowsFileCopier: def _open_file(self, path: str, retry_on_sharing_violation: bool = True, is_folder: bool = False) -> 'winutil.Handle': flags = winutil.FILE_FLAG_BACKUP_SEMANTICS if is_folder else winutil.FILE_FLAG_SEQUENTIAL_SCAN + if self.delete_all: + flags |= winutil.FILE_FLAG_DELETE_ON_CLOSE try: return winutil.create_file(make_long_path_useable(path), winutil.GENERIC_READ, - winutil.FILE_SHARE_DELETE, winutil.OPEN_EXISTING, flags) + winutil.FILE_SHARE_DELETE if self.allow_move else 0, winutil.OPEN_EXISTING, flags) except OSError as e: if e.winerror == winutil.ERROR_SHARING_VIOLATION: # The file could be a hardlink to an already opened file, @@ -116,14 +120,10 @@ class WindowsFileCopier: self.folder_to_handle_map[path] = self._open_file(path, is_folder=True) def __exit__(self, exc_type, exc_val, exc_tb) -> None: - try: - if self.delete_all and exc_val is None: - self.delete_all_source_files() - finally: - for h in self.path_to_handle_map.values(): - h.close() - for h in self.folder_to_handle_map.values(): - h.close() + for h in self.path_to_handle_map.values(): + h.close() + for h in reversed(self.folder_to_handle_map.values()): + h.close() def copy_all(self) -> None: for src_path, dest_path in self.copy_map.items(): @@ -146,20 +146,14 @@ class WindowsFileCopier: for src_path, dest_path in self.copy_map.items(): winutil.move_file(make_long_path_useable(src_path), make_long_path_useable(dest_path)) - def delete_all_source_files(self) -> None: - for src_path in self.copy_map: - winutil.delete_file(make_long_path_useable(src_path)) - for path in reversed(self.folders): - os.rmdir(make_long_path_useable(path)) - -def get_copier(delete_all=False) -> Union[UnixFileCopier, WindowsFileCopier]: - return WindowsFileCopier(delete_all) if iswindows else UnixFileCopier(delete_all) +def get_copier(delete_all=False, allow_move=False) -> Union[UnixFileCopier, WindowsFileCopier]: + return (WindowsFileCopier if iswindows else UnixFileCopier)(delete_all, allow_move) def rename_files(src_to_dest_map: Dict[str, str]) -> None: ' Rename a bunch of files. On Windows all files are locked before renaming so no other process can interfere. ' - copier = get_copier() + copier = get_copier(allow_move=True) for s, d in src_to_dest_map.items(): copier.register(s, d) with copier: From e20e7f1d514e0f9d9046b330a5809009927a8564 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 6 Jun 2023 15:08:26 +0530 Subject: [PATCH 0790/2055] ... --- src/calibre/utils/copy_files.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index d68d1a1f55..66948e92c6 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -225,11 +225,9 @@ def copy_tree( with copier: copier.copy_all() - if delete_source: + if delete_source and os.path.exists(make_long_path_useable(src)): try: shutil.rmtree(make_long_path_useable(src)) - except FileNotFoundError: - pass except OSError: if iswindows: time.sleep(WINDOWS_SLEEP_FOR_RETRY_TIME) From 289039b829128a208b8069495be888987ffd8f42 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 7 Jun 2023 17:03:27 +0530 Subject: [PATCH 0791/2055] PDF Output: Fix using column-count for body causing conversion to fail when header/footer is specified --- src/calibre/ebooks/pdf/html_writer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/ebooks/pdf/html_writer.py b/src/calibre/ebooks/pdf/html_writer.py index 99e7ec2771..f27fc5ca9e 100644 --- a/src/calibre/ebooks/pdf/html_writer.py +++ b/src/calibre/ebooks/pdf/html_writer.py @@ -898,7 +898,7 @@ def add_header_footer(manager, opts, pdf_doc, container, page_number_display_map report_progress(0.8, _('Adding headers and footers')) name = create_skeleton(container) root = container.parsed(name) - reset_css = 'margin: 0; padding: 0; border-width: 0; background-color: unset;' + reset_css = 'margin: 0; padding: 0; border-width: 0; background-color: unset; column-count: unset; column-width: unset;' root.set('style', reset_css) body = last_tag(root) body.attrib.pop('id', None) From f2b8bc9dd305c802fb1b9f277f479f4d8747d78b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 7 Jun 2023 17:20:23 +0530 Subject: [PATCH 0792/2055] Arch now has up-to-date podofo --- setup/arch-ci.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/setup/arch-ci.sh b/setup/arch-ci.sh index f0a2a5df2e..9c2b10cee1 100755 --- a/setup/arch-ci.sh +++ b/setup/arch-ci.sh @@ -5,11 +5,7 @@ set -xe -pacman -S --noconfirm --needed base-devel sudo git sip pyqt-builder cmake chmlib icu jxrlib hunspell libmtp libusb libwmf optipng python-apsw python-beautifulsoup4 python-cssselect python-css-parser python-dateutil python-jeepney python-dnspython python-feedparser python-html2text python-html5-parser python-lxml python-markdown python-mechanize python-msgpack python-netifaces python-unrardll python-pillow python-psutil python-pygments python-pyqt6 python-regex python-zeroconf python-pyqt6-webengine qt6-svg qt6-imageformats udisks2 hyphen python-pychm python-pycryptodome speech-dispatcher python-sphinx python-urllib3 python-py7zr python-pip python-fonttools uchardet libstemmer poppler tk - -curl -fSsL https://github.com/podofo/podofo/archive/refs/tags/0.10.0.tar.gz | tar xz -cd podofo* && mkdir build && cd build -cmake -DCMAKE_INSTALL_PREFIX=/usr .. && make -j4 && make install +pacman -S --noconfirm --needed base-devel sudo git sip pyqt-builder cmake chmlib icu jxrlib hunspell libmtp libusb libwmf optipng python-apsw python-beautifulsoup4 python-cssselect python-css-parser python-dateutil python-jeepney python-dnspython python-feedparser python-html2text python-html5-parser python-lxml python-markdown python-mechanize python-msgpack python-netifaces python-unrardll python-pillow python-psutil python-pygments python-pyqt6 python-regex python-zeroconf python-pyqt6-webengine qt6-svg qt6-imageformats udisks2 hyphen python-pychm python-pycryptodome speech-dispatcher python-sphinx python-urllib3 python-py7zr python-pip python-fonttools uchardet libstemmer poppler tk podofo useradd -m ci chown -R ci:users $GITHUB_WORKSPACE From 2c89d50fb8b71a7d0f6d2365c5408325b7ad2a86 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 7 Jun 2023 19:52:04 +0530 Subject: [PATCH 0793/2055] PDF Output: Fix error when input document contains multiple instances of a font some with vertical metric and some without. Fixes #2023041 [Private bug](https://bugs.launchpad.net/calibre/+bug/2023041) --- src/calibre/utils/fonts/sfnt/merge.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/calibre/utils/fonts/sfnt/merge.py b/src/calibre/utils/fonts/sfnt/merge.py index 8c24d6b5e9..72c21ab0d8 100644 --- a/src/calibre/utils/fonts/sfnt/merge.py +++ b/src/calibre/utils/fonts/sfnt/merge.py @@ -50,11 +50,17 @@ def merge_truetype_fonts_for_pdf(fonts, log=None): log(f'Size mismatch for glyph id: {glyph_id} prev_sz: {len(prev_glyph_data)} sz: {sz}') if hhea is not None: m = hhea.metrics_for(glyph_id) - if m != hmetrics_map[glyph_id]: + old_val = hmetrics_map.get(glyph_id) + if old_val is None: + hmetrics_map[glyph_id] = m + elif m != old_val: log(f'Metrics mismatch for glyph id: {glyph_id} prev: {hmetrics_map[glyph_id]} cur: {m}') if vhea is not None: m = vhea.metrics_for(glyph_id) - if m != vmetrics_map[glyph_id]: + old_val = vmetrics_map.get(glyph_id) + if old_val is None: + vmetrics_map[glyph_id] = m + elif m != vmetrics_map[glyph_id]: log(f'Metrics mismatch for glyph id: {glyph_id} prev: {vmetrics_map[glyph_id]} cur: {m}') glyf = ans[b'glyf'] @@ -71,9 +77,9 @@ def merge_truetype_fonts_for_pdf(fonts, log=None): head.update() maxp.num_glyphs = len(loca.offset_map) - 1 maxp.update() - if hmetrics_map: + if hmetrics_map and b'hhea' in ans: ans[b'hhea'].update(hmetrics_map, ans[b'hmtx']) - if vmetrics_map: + if vmetrics_map and b'vhea' in ans: ans[b'vhea'].update(vmetrics_map, ans[b'vmtx']) for name in 'hdmx GPOS GSUB'.split(): From 92d62bf9f1247abcb2a540e149957dca6565ba92 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 7 Jun 2023 20:04:15 +0530 Subject: [PATCH 0794/2055] ... --- src/calibre/utils/fonts/sfnt/merge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/fonts/sfnt/merge.py b/src/calibre/utils/fonts/sfnt/merge.py index 72c21ab0d8..3dfbf893b6 100644 --- a/src/calibre/utils/fonts/sfnt/merge.py +++ b/src/calibre/utils/fonts/sfnt/merge.py @@ -61,7 +61,7 @@ def merge_truetype_fonts_for_pdf(fonts, log=None): if old_val is None: vmetrics_map[glyph_id] = m elif m != vmetrics_map[glyph_id]: - log(f'Metrics mismatch for glyph id: {glyph_id} prev: {vmetrics_map[glyph_id]} cur: {m}') + log(f'Vertical metrics mismatch for glyph id: {glyph_id} prev: {vmetrics_map[glyph_id]} cur: {m}') glyf = ans[b'glyf'] head = ans[b'head'] From 812fecec5fd9163ea7a2b42072275bde67d7aa46 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 7 Jun 2023 20:30:22 +0530 Subject: [PATCH 0795/2055] Kindle output: Only re-encode JPEG images with EXIF metadata if the metadata contains actual transpose operations. Fixes #2023189 [[EPUB->MOBI] Calibre MOBI file Size too small](https://bugs.launchpad.net/calibre/+bug/2023189) --- src/calibre/ebooks/mobi/writer2/resources.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/calibre/ebooks/mobi/writer2/resources.py b/src/calibre/ebooks/mobi/writer2/resources.py index 710c510374..f75ba43aa4 100644 --- a/src/calibre/ebooks/mobi/writer2/resources.py +++ b/src/calibre/ebooks/mobi/writer2/resources.py @@ -27,9 +27,11 @@ def process_jpegs_for_amazon(data: bytes) -> bytes: # Amazon's MOBI renderer can't render JPEG images without JFIF metadata # and images with EXIF data dont get displayed on the cover screen changed = not img.info - if hasattr(img, '_getexif') and img._getexif(): - changed = True - img = ImageOps.exif_transpose(img) + if hasattr(img, 'getexif'): + exif = img.getexif() + if exif.get(0x0112) in (2,3,4,5,6,7,8): + changed = True + img = ImageOps.exif_transpose(img) if changed: out = BytesIO() img.save(out, 'JPEG') From c13777d1be45bc053d05cbe17094a0c8db050354 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 8 Jun 2023 10:57:20 +0530 Subject: [PATCH 0796/2055] Get books: Update Barnes and Noble store plugin for website changes. Fixes #2023046 [Get Books unable to find book from ebooks.com](https://bugs.launchpad.net/calibre/+bug/2023046) --- src/calibre/gui2/store/stores/bn_plugin.py | 109 +++++++++++++-------- 1 file changed, 67 insertions(+), 42 deletions(-) diff --git a/src/calibre/gui2/store/stores/bn_plugin.py b/src/calibre/gui2/store/stores/bn_plugin.py index a8f0a4e5f3..63369ab2bb 100644 --- a/src/calibre/gui2/store/stores/bn_plugin.py +++ b/src/calibre/gui2/store/stores/bn_plugin.py @@ -1,13 +1,12 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function, unicode_literals -store_version = 2 # Needed for dynamic plugin loading +store_version = 3 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' __docformat__ = 'restructuredtext en' -import re from contextlib import closing try: from urllib.parse import quote_plus @@ -26,10 +25,70 @@ from calibre.gui2.store.search_result import SearchResult from calibre.gui2.store.web_store_dialog import WebStoreDialog + +def search_bn(query, max_results=10, timeout=60, write_html_to=''): + url = 'https://www.barnesandnoble.com/s/%s?keyword=%s&store=ebook&view=list' % (query.replace(' ', '-'), quote_plus(query)) + url = 'file:///t/bn.html' + + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + raw = f.read() + if write_html_to: + with open(write_html_to, 'wb') as f: + f.write(raw) + doc = html.fromstring(raw) + for data in doc.xpath('//section[@id="gridView"]//div[contains(@class, "product-shelf-tile-book")]'): + if counter <= 0: + break + counter -= 1 + + cover_url = '' + cover_div = data.xpath('.//div[contains(@class, "product-shelf-image")]') + if cover_div: + cover_url = 'https:' + ''.join(cover_div[0].xpath('descendant::img/@src')) + + title_div = data.xpath('.//div[contains(@class, "product-shelf-title")]') + if not title_div: + continue + title = ''.join(title_div[0].xpath('descendant::a/text()')).strip() + if not title: + continue + item_url = ''.join(title_div[0].xpath('descendant::a/@href')).strip() + if not item_url: + continue + item_url = 'https://www.barnesandnoble.com' + item_url + + author = '' + author_div = data.xpath('.//div[contains(@class, "product-shelf-author")]') + if author_div: + author = ''.join(author_div[0].xpath('descendant::a/text()')).strip() + + price = '' + price_div = data.xpath('.//div[contains(@class, "product-shelf-pricing")]/div[contains(@class, "current")]') + if price_div: + spans = price_div[0].xpath('descendant::span') + if spans: + price = ''.join(spans[-1].xpath('descendant::text()')) + if '\n' in price: + price = price.split('\n')[1].split(',')[0] + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = item_url.strip() + s.drm = SearchResult.DRM_UNKNOWN + s.formats = 'Nook' + yield s + + class BNStore(BasicStoreConfig, StorePlugin): def open(self, parent=None, detail_item=None, external=False): - url = "http://bn.com" + url = "https://bn.com" if external or self.config.get('open_external', False): open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url))) @@ -40,44 +99,10 @@ class BNStore(BasicStoreConfig, StorePlugin): d.exec() def search(self, query, max_results=10, timeout=60): - url = 'http://www.barnesandnoble.com/s/%s?keyword=%s&store=ebook&view=list' % (query.decode('utf-8').replace(' ', '-'), quote_plus(query)) + yield from search_bn(query, max_results, timeout) - br = browser() - counter = max_results - with closing(br.open(url, timeout=timeout)) as f: - raw = f.read() - doc = html.fromstring(raw) - for data in doc.xpath('//ol[contains(@class, "result-set")]/li[contains(@class, "result")]'): - if counter <= 0: - break - - id = ''.join(data.xpath('.//div[contains(@class, "image-block")]/a/@href')) - if not id: - continue - - cover_url = '' - cover_id = ''.join(data.xpath('.//img[contains(@class, "product-image")]/@id')) - m = re.search(r"%s'.*?srcUrl: '(?P.*?)'.*?}" % cover_id, raw) - if m: - cover_url = m.group('iurl') - - title = ''.join(data.xpath('descendant::p[@class="title"]//span[@class="name"]//text()')).strip() - if not title: - continue - - author = ', '.join(data.xpath('.//ul[contains(@class, "contributors")]//a[contains(@class, "subtle")]//text()')).strip() - price = ''.join(data.xpath('.//a[contains(@class, "bn-price")]//text()')) - - counter -= 1 - - s = SearchResult() - s.cover_url = cover_url - s.title = title.strip() - s.author = author.strip() - s.price = price.strip() - s.detail_item = id.strip() - s.drm = SearchResult.DRM_UNKNOWN - s.formats = 'Nook' - - yield s +if __name__ == '__main__': + import sys + for result in search_bn(' '.join(sys.argv[1:]), write_html_to='/t/bn.html'): + print(result) From f33136e7e168fedd4cf2be7aef009453e43cf0ad Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 8 Jun 2023 13:47:11 +0530 Subject: [PATCH 0797/2055] ... --- src/calibre/ebooks/conversion/plugins/html_input.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/ebooks/conversion/plugins/html_input.py b/src/calibre/ebooks/conversion/plugins/html_input.py index b86348ce4e..ed2f385a77 100644 --- a/src/calibre/ebooks/conversion/plugins/html_input.py +++ b/src/calibre/ebooks/conversion/plugins/html_input.py @@ -131,6 +131,7 @@ class HTMLInput(InputFormatPlugin): ) from calibre.ebooks.oeb.transforms.metadata import meta_info_to_oeb_metadata from calibre.utils.localization import canonicalize_lang + self.opts = opts css_parser.log.setLevel(logging.WARN) self.OEB_STYLES = OEB_STYLES oeb = create_oebbook(log, None, opts, self, From c0f2581f55b18cfbfc9dae980cdfe2e8f3927aed Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 8 Jun 2023 13:55:45 +0530 Subject: [PATCH 0798/2055] Only show warning when file that is filtered exists --- src/calibre/ebooks/conversion/plugins/html_input.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/conversion/plugins/html_input.py b/src/calibre/ebooks/conversion/plugins/html_input.py index ed2f385a77..c1d85b2d37 100644 --- a/src/calibre/ebooks/conversion/plugins/html_input.py +++ b/src/calibre/ebooks/conversion/plugins/html_input.py @@ -269,7 +269,8 @@ class HTMLInput(InputFormatPlugin): q = os.path.normcase(get_long_path_name(link)) if not q.startswith(self.root_dir_of_input): if not self.opts.allow_local_files_outside_root: - self.log.warn('Not adding {} as it is outside the document root: {}'.format(q, self.root_dir_of_input)) + if os.path.exists(q): + self.log.warn('Not adding {} as it is outside the document root: {}'.format(q, self.root_dir_of_input)) return None, None return link, frag From faf096384bd20e465af6c1ced60e98ca69274bc9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 8 Jun 2023 14:56:25 +0530 Subject: [PATCH 0799/2055] Another bounds check for palmdoc decompress --- src/calibre/ebooks/compression/palmdoc.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/calibre/ebooks/compression/palmdoc.c b/src/calibre/ebooks/compression/palmdoc.c index dc8206f574..359b775dd6 100644 --- a/src/calibre/ebooks/compression/palmdoc.c +++ b/src/calibre/ebooks/compression/palmdoc.c @@ -43,7 +43,7 @@ typedef struct { #define CHAR(x) (( (x) > 127 ) ? (x)-256 : (x)) static bool -write_to_bytes_object(PyObject **output, Py_ssize_t pos, char ch) { +write_to_bytes_object(PyObject **output, size_t pos, char ch) { if (pos >= PyBytes_GET_SIZE(*output)) { if (_PyBytes_Resize(output, 2 * pos) != 0) return false; } @@ -54,15 +54,17 @@ write_to_bytes_object(PyObject **output, Py_ssize_t pos, char ch) { static PyObject * cpalmdoc_decompress(PyObject *self, PyObject *args) { const char *_input = NULL; Py_ssize_t input_len = 0; - Byte *input; Byte c; PyObject *ans; - Py_ssize_t i = 0, o = 0, j = 0, di, n; + Byte *input; Byte c; PyObject *ans = NULL; + Py_ssize_t i = 0, j = 0; + size_t o = 0, di = 0, n = 0; if (!PyArg_ParseTuple(args, "y#", &_input, &input_len)) return NULL; input = (Byte *) PyMem_Malloc(sizeof(Byte)*input_len); if (input == NULL) return PyErr_NoMemory(); // Map chars to bytes - for (j = 0; j < input_len; j++) + for (j = 0; j < input_len; j++) { input[j] = (_input[j] < 0) ? _input[j]+256 : _input[j]; + } ans = PyBytes_FromStringAndSize(NULL, 8*input_len); if (ans == NULL) { PyMem_Free(input); return NULL; } @@ -78,8 +80,10 @@ cpalmdoc_decompress(PyObject *self, PyObject *args) { } else if (i < input_len) { // 80-BF repeat sequences c = (c << 8) + input[i++]; di = (c & 0x3FFF) >> 3; - for ( n = (c & 7) + 3; n--; ++o ) { - if (!write_to_bytes_object(&ans, o, PyBytes_AS_STRING(ans)[o-di])) { PyMem_Free(input); return NULL; } + if (di <= o) { + for ( n = (c & 7) + 3; n--; ++o ) { + if (!write_to_bytes_object(&ans, o, PyBytes_AS_STRING(ans)[o-di])) { PyMem_Free(input); return NULL; } + } } } } From e0d2c5cfcfb8762263d79cd2e1aab6520a50cf88 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 9 Jun 2023 07:06:58 +0530 Subject: [PATCH 0800/2055] version 6.20.0 --- Changelog.txt | 27 +++++++++++++++++++++++++++ src/calibre/constants.py | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Changelog.txt b/Changelog.txt index 3d4ae4ab5e..defbde9c75 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -23,6 +23,33 @@ # - title by author # }}} +{{{ 6.20.0 2023-06-09 + +:: bug fixes + +- [2021413] CHM Input: Fix a regression in the previous release that broke conversion of CHM files + +- Windows: Make moving files in the calibre library folder more robust, locking folders in addition to files, before the start of the move + +- [2023046] Get books: Update Barnes and Noble store plugin for website changes + +- [2023189] Kindle output: Only re-encode JPEG images with EXIF metadata if the metadata contains actual transpose operations + +- [2023041] PDF Output: Fix error when input document contains multiple instances of a font some with vertical metrics and some without + +- PDF Output: Fix using CSS Multicolumns for body causing conversion to fail when header/footer is specified + +- [2022035] MOBI Input: Fix a crash when converting some corrupted palmdoc compressed MOBI files + +- [2021554] E-book viewer: Ensure CSS stylesheets are interpreted as UTF-8 + +:: improved recipes +- Foreign Affairs + +:: new recipes +- Prospect Magazine UK (Free) by ping +}}} + {{{ 6.19.1 2023-05-29 :: new features diff --git a/src/calibre/constants.py b/src/calibre/constants.py index c794f21b36..f9a1da7e9e 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -5,7 +5,7 @@ from functools import lru_cache import sys, locale, codecs, os, collections, collections.abc __appname__ = 'calibre' -numeric_version = (6, 19, 1) +numeric_version = (6, 20, 0) __version__ = '.'.join(map(str, numeric_version)) git_version = None __author__ = "Kovid Goyal " From 4c4cfb843c5114cf28adefc6d30cf8e86bb30941 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 9 Jun 2023 09:23:41 +0530 Subject: [PATCH 0801/2055] DOCX Input: Add support for SVG images --- src/calibre/ebooks/docx/images.py | 11 +++++++---- src/calibre/ebooks/docx/names.py | 5 ++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/calibre/ebooks/docx/images.py b/src/calibre/ebooks/docx/images.py index ac00d1d371..c8e1694b94 100644 --- a/src/calibre/ebooks/docx/images.py +++ b/src/calibre/ebooks/docx/images.py @@ -10,7 +10,7 @@ from lxml.html.builder import HR, IMG from calibre import sanitize_file_name from calibre.constants import iswindows -from calibre.ebooks.docx.names import barename +from calibre.ebooks.docx.names import SVG_BLIP_URI, barename from calibre.utils.filenames import ascii_filename from calibre.utils.img import image_to_data, resize_to_fit from calibre.utils.imghdr import what @@ -236,9 +236,12 @@ class Images: name = image_filename(name) alt = pr.get('descr') or alt for a in XPath('descendant::a:blip[@r:embed or @r:link]')(pic): - rid = get(a, 'r:embed') - if not rid: - rid = get(a, 'r:link') + rid = get(a, 'r:embed') or get(a, 'r:link') + for asvg in XPath(f'./a:extLst/a:ext[@uri="{SVG_BLIP_URI}"]/asvg:svgBlip[@r:embed or @r:link]')(a): + svg_rid = get(asvg, 'r:embed') or get(asvg, 'r:link') + if svg_rid and svg_rid in self.rid_map: + rid = svg_rid + break if rid and rid in self.rid_map: try: src = self.generate_filename(rid, name) diff --git a/src/calibre/ebooks/docx/names.py b/src/calibre/ebooks/docx/names.py index f045efcef6..6124eb3941 100644 --- a/src/calibre/ebooks/docx/names.py +++ b/src/calibre/ebooks/docx/names.py @@ -63,7 +63,9 @@ TRANSITIONAL_NAMESPACES = { 'pr': 'http://schemas.openxmlformats.org/package/2006/relationships', # Dublin Core document properties 'dcmitype': 'http://purl.org/dc/dcmitype/', - 'dcterms': 'http://purl.org/dc/terms/' + 'dcterms': 'http://purl.org/dc/terms/', + # SVG embeds + 'asvg': 'http://schemas.microsoft.com/office/drawing/2016/SVG/main', } STRICT_NAMESPACES = { @@ -73,6 +75,7 @@ STRICT_NAMESPACES = { 'http://schemas.openxmlformats.org/drawingml/2006', 'http://purl.oclc.org/ooxml/drawingml') for k, v in iteritems(TRANSITIONAL_NAMESPACES) } +SVG_BLIP_URI = '{96DAC541-7B7A-43D3-8B79-37D633B846F1}' # }}} From 41f7a01e350b92b92c3db3ee9863401dfba58f05 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 9 Jun 2023 14:48:42 +0530 Subject: [PATCH 0802/2055] DOCX Output: Add support for SVG images. Now the generated DOCX will contain both the rasterized version of the SVG image and the original SVG image, which is supported by modern versions of Word. --- src/calibre/ebooks/docx/names.py | 2 + src/calibre/ebooks/docx/writer/from_html.py | 6 +- src/calibre/ebooks/docx/writer/images.py | 39 ++++++-- src/calibre/ebooks/oeb/base.py | 12 ++- .../ebooks/oeb/transforms/rasterize.py | 95 ++++++++++++------- 5 files changed, 105 insertions(+), 49 deletions(-) diff --git a/src/calibre/ebooks/docx/names.py b/src/calibre/ebooks/docx/names.py index 6124eb3941..702177f2b4 100644 --- a/src/calibre/ebooks/docx/names.py +++ b/src/calibre/ebooks/docx/names.py @@ -46,6 +46,7 @@ TRANSITIONAL_NAMESPACES = { 'xml': 'http://www.w3.org/XML/1998/namespace', # Drawing 'a': 'http://schemas.openxmlformats.org/drawingml/2006/main', + 'a14': 'http://schemas.microsoft.com/office/drawing/2010/main', 'm': 'http://schemas.openxmlformats.org/officeDocument/2006/math', 'mv': 'urn:schemas-microsoft-com:mac:vml', 'pic': 'http://schemas.openxmlformats.org/drawingml/2006/picture', @@ -76,6 +77,7 @@ STRICT_NAMESPACES = { for k, v in iteritems(TRANSITIONAL_NAMESPACES) } SVG_BLIP_URI = '{96DAC541-7B7A-43D3-8B79-37D633B846F1}' +USE_LOCAL_DPI_URI = '{28A0092B-C50C-407E-A947-70E740481C1C}' # }}} diff --git a/src/calibre/ebooks/docx/writer/from_html.py b/src/calibre/ebooks/docx/writer/from_html.py index a7ed841b78..7bd5628d8d 100644 --- a/src/calibre/ebooks/docx/writer/from_html.py +++ b/src/calibre/ebooks/docx/writer/from_html.py @@ -440,7 +440,7 @@ class Convert: self.styles_manager = StylesManager(self.docx.namespace, self.log, self.mi.language) self.links_manager = LinksManager(self.docx.namespace, self.docx.document_relationships, self.log) - self.images_manager = ImagesManager(self.oeb, self.docx.document_relationships, self.opts) + self.images_manager = ImagesManager(self.oeb, self.docx.document_relationships, self.opts, self.svg_rasterizer) self.lists_manager = ListsManager(self.docx) self.fonts_manager = FontsManager(self.docx.namespace, self.oeb, self.opts) self.blocks = Blocks(self.docx.namespace, self.styles_manager, self.links_manager) @@ -481,9 +481,7 @@ class Convert: def process_item(self, item): self.current_item = item - stylizer = self.svg_rasterizer.stylizer_cache.get(item) - if stylizer is None: - stylizer = Stylizer(item.data, item.href, self.oeb, self.opts, profile=self.opts.output_profile, base_css=self.base_css) + stylizer = self.svg_rasterizer.stylizer(item) self.abshref = self.images_manager.abshref = item.abshref self.current_lang = lang_for_tag(item.data) or self.styles_manager.document_lang diff --git a/src/calibre/ebooks/docx/writer/images.py b/src/calibre/ebooks/docx/writer/images.py index 18bdf6142a..d29a0d8a41 100644 --- a/src/calibre/ebooks/docx/writer/images.py +++ b/src/calibre/ebooks/docx/writer/images.py @@ -12,11 +12,11 @@ from lxml import etree from calibre import fit_image from calibre.ebooks.docx.images import pt_to_emu +from calibre.ebooks.docx.names import USE_LOCAL_DPI_URI, SVG_BLIP_URI from calibre.ebooks.oeb.base import urlquote, urlunquote from calibre.utils.filenames import ascii_filename from calibre.utils.imghdr import identify from calibre.utils.resources import get_image_path as I -from polyglot.builtins import iteritems, itervalues Image = namedtuple('Image', 'rid fname width height fmt item') @@ -39,13 +39,26 @@ def get_image_margins(style): class ImagesManager: - def __init__(self, oeb, document_relationships, opts): + def __init__(self, oeb, document_relationships, opts, svg_rasterizer): self.oeb, self.log = oeb, oeb.log + self.svg_rasterizer = svg_rasterizer self.page_width, self.page_height = opts.output_profile.width_pts, opts.output_profile.height_pts self.images = {} self.seen_filenames = set() self.document_relationships = document_relationships self.count = 0 + self.svg_images = {} + + def read_svg(self, href): + if href not in self.svg_images: + item = self.oeb.manifest.hrefs.get(href) or self.oeb.manifest.hrefs.get(urlquote(href)) + if item is None: + self.log.warning('Failed to find image:', href) + return + image_fname = 'media/' + self.create_filename(href, 'svg') + image_rid = self.document_relationships.add_image(image_fname) + self.svg_images[href] = Image(image_rid, image_fname, -1, -1, 'svg', item) + return self.svg_images[href] def read_image(self, href): if href not in self.images: @@ -84,6 +97,12 @@ class ImagesManager: def create_image_markup(self, html_img, stylizer, href, as_block=False): # TODO: img inside a link (clickable image) + svg_rid = '' + svghref = self.svg_rasterizer.svg_originals.get(href) + if svghref: + si = self.read_svg(svghref) + if si: + svg_rid = si.rid style = stylizer.style(html_img) floating = style['float'] if floating not in {'left', 'right'}: @@ -134,7 +153,7 @@ class ImagesManager: if fake_margins: # DOCX does not support setting margins for inline images, so we # fake it by using effect extents to simulate margins - makeelement(parent, 'wp:effectExtent', **{k[-1].lower():v for k, v in iteritems(get_image_margins(style))}) + makeelement(parent, 'wp:effectExtent', **{k[-1].lower():v for k, v in get_image_margins(style).items()}) else: makeelement(parent, 'wp:effectExtent', l='0', r='0', t='0', b='0') if floating is not None: @@ -143,10 +162,10 @@ class ImagesManager: makeelement(parent, 'wp:wrapTopAndBottom') else: makeelement(parent, 'wp:wrapSquare', wrapText='bothSides') - self.create_docx_image_markup(parent, name, html_img.get('alt') or name, img.rid, width, height) + self.create_docx_image_markup(parent, name, html_img.get('alt') or name, img.rid, width, height, svg_rid=svg_rid) return ans - def create_docx_image_markup(self, parent, name, alt, img_rid, width, height): + def create_docx_image_markup(self, parent, name, alt, img_rid, width, height, svg_rid=''): makeelement, namespaces = self.document_relationships.namespace.makeelement, self.document_relationships.namespace.namespaces makeelement(parent, 'wp:docPr', id=str(self.count), name=name, descr=alt) makeelement(makeelement(parent, 'wp:cNvGraphicFramePr'), 'a:graphicFrameLocks', noChangeAspect="1") @@ -157,7 +176,11 @@ class ImagesManager: makeelement(nvPicPr, 'pic:cNvPr', id='0', name=name, descr=alt) makeelement(nvPicPr, 'pic:cNvPicPr') bf = makeelement(pic, 'pic:blipFill') - makeelement(bf, 'a:blip', r_embed=img_rid) + blip = makeelement(bf, 'a:blip', r_embed=img_rid) + if svg_rid: + ext_list = makeelement(blip, 'a:extLst') + makeelement(makeelement(ext_list, 'a:ext', uri=USE_LOCAL_DPI_URI), 'a14:useLocalDpi', val='0') + makeelement(makeelement(ext_list, 'a:ext', uri=SVG_BLIP_URI), 'asvg:svgBlip', r_embed=svg_rid) makeelement(makeelement(bf, 'a:stretch'), 'a:fillRect') spPr = makeelement(pic, 'pic:spPr') xfrm = makeelement(spPr, 'a:xfrm') @@ -178,8 +201,10 @@ class ImagesManager: return fname def serialize(self, images_map): - for img in itervalues(self.images): + for img in self.images.values(): images_map['word/' + img.fname] = partial(self.get_data, img.item) + for img in self.svg_images.values(): + images_map['word/' + img.fname] = lambda: img.item.data_as_bytes_or_none def get_data(self, item): try: diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index be1e855cca..74d17da6d3 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -15,6 +15,7 @@ from collections import defaultdict from itertools import count from lxml import etree, html from operator import attrgetter +from typing import Optional from calibre import as_unicode, force_unicode, get_types_map, isbytestring from calibre.constants import __version__, filesystem_encoding @@ -1017,6 +1018,12 @@ class Manifest: # }}} + @property + def data_as_bytes_or_none(self) -> Optional[bytes]: + if self._loader is None: + return None + return self._loader(getattr(self, 'html_input_href', self.href)) + @property def data(self): """Provides MIME type sensitive access to the manifest @@ -1033,10 +1040,7 @@ class Manifest: """ data = self._data if data is None: - if self._loader is None: - return None - data = self._loader(getattr(self, 'html_input_href', - self.href)) + data = self.data_as_bytes_or_none try: mt = self.media_type.lower() except Exception: diff --git a/src/calibre/ebooks/oeb/transforms/rasterize.py b/src/calibre/ebooks/oeb/transforms/rasterize.py index 95396703da..ae5459adb1 100644 --- a/src/calibre/ebooks/oeb/transforms/rasterize.py +++ b/src/calibre/ebooks/oeb/transforms/rasterize.py @@ -5,32 +5,44 @@ SVG rasterization transform. __license__ = 'GPL v3' __copyright__ = '2008, Marshall T. Vandegrift ' -import os, re +import os +import re +from base64 import standard_b64encode +from functools import lru_cache +from lxml import etree from qt.core import ( - Qt, QByteArray, QBuffer, QIODevice, QColor, QImage, QPainter, QSvgRenderer) -from calibre.ebooks.oeb.base import XHTML, XLINK -from calibre.ebooks.oeb.base import SVG_MIME, PNG_MIME -from calibre.ebooks.oeb.base import xml2str, xpath -from calibre.ebooks.oeb.base import urlnormalize + QBuffer, QByteArray, QColor, QImage, QIODevice, QPainter, QSvgRenderer, Qt, +) + +from calibre import guess_type +from calibre.ebooks.oeb.base import ( + PNG_MIME, SVG_MIME, XHTML, XLINK, urlnormalize, xml2str, xpath, +) from calibre.ebooks.oeb.stylizer import Stylizer -from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.imghdr import what from polyglot.urllib import urldefrag IMAGE_TAGS = {XHTML('img'), XHTML('object')} KEEP_ATTRS = {'class', 'style', 'width', 'height', 'align'} -TEST_SVG = b''' - - -''' + +def test_svg(): # {{{ + TEST_PNG_DATA_URI='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAWJQTFRFAAAAAAAAAAAAAAAAAAAAAQEAAgIBAwIBBgQCBwUCCAYDCggECwkEDgsFDwwFEA0GHRcKHxkLIBkLIxwMJR0NJx8OKCAOKCAPKSAPMScSPTAWQTQXQzUYSjsaSjsbSzsbUD8dUUAdVEMeWkggW0ggW0ghW0khXUohYk4ja1Umb1gocVoocVopclspc1spdV0qd18reF8riW0xjXEzl3g2mns3nn04nn45n345oIA6ooE7o4I7pII7pIM7pYQ7p4U8qYY8rYo+s45Bxp1Hx55Hy6FJy6JJzaRJz6RKz6RLz6VL0qdL1KpM1apM1qtN16xN2KxN2K1O2a1O2q1O265P3K9P3bBQ3rBP37FP37FQ37JQ4rNR47VR5LVR7LxV7bxV7r1V7r5W8L9W8MBW8b9V8b9W8cBW8cBX8sBW8sBX8sFW8sFX88BX88FW88FX88FY88JX88JY9MFX9MJX9MJY9MNYSw0rOAAAAAR0Uk5T2+rr8giKtGMAAAFDSURBVDjLhdNFUwNBEIbhJWkkuLu7u5PgHtwWl0CGnW34aJLl/3OgUlRlGfKepqafmstUW1Yw8E9By6IMWVn/z7OsQOpYNrE0H4lEwuFwZHmyLnUb+AUzIiLMItDgrWIfKH3mnz4RA6PX/8Im8xuEgVfxxG33g+rVi9OT46OdPQ0kDgv8gCg3FMrLphkNyCD9BYiIqEErraP5ZrDGDrw2MoIhsPACGUH5g2gVqzWDKQ/gETKCZmHwbo4ZbHhJ1q1kBMMJCKbJCCof35V+qjCDOUCrMTKCFkc8vU5GENpW8NwmMxhVccYsGUHVvWKOFhlBySJicV6u7+7s6Ozq6anxgT44Lwy4jlKK4br96WDl09GA/gA4zp7gLh2MM3MS+EgCGl+iD9JB4cDZzbV9ZV/atn1+frvfaPhuX4HMq0cZsjKt/zfXXmDab9zjGwAAAABJRU5ErkJggg==' + return f''' + + + + '''.encode() +# }}} class Unavailable(Exception): pass -def rasterize_svg(data=TEST_SVG, sizes=(), width=0, height=0, print=None, fmt='PNG', as_qimage=False): +def rasterize_svg(data=None, sizes=(), width=0, height=0, print=None, fmt='PNG', as_qimage=False): + if data is None: + data = test_svg() svg = QSvgRenderer(QByteArray(data)) size = svg.defaultSize() if size.width() == 100 and size.height() == 100 and sizes: @@ -54,10 +66,16 @@ def rasterize_svg(data=TEST_SVG, sizes=(), width=0, height=0, print=None, fmt='P return array.data() +@lru_cache(maxsize=128) +def data_url(mime_type: str, data: bytes) -> str: + return f'data:{mime_type};base64,' + standard_b64encode(data).decode('ascii') + + class SVGRasterizer: - def __init__(self, base_css=''): + def __init__(self, base_css='', save_svg_originals=False): self.base_css = base_css + self.save_svg_originals = save_svg_originals from calibre.gui2 import must_use_qt must_use_qt() @@ -71,20 +89,15 @@ class SVGRasterizer: def __call__(self, oeb, context): oeb.logger.info('Rasterizing SVG images...') - self.temp_files = [] self.stylizer_cache = {} self.oeb = oeb self.opts = context self.profile = context.dest self.images = {} - self.dataize_manifest() + self.svg_originals = {} + self.scan_for_linked_resources_in_manifest() self.rasterize_spine() self.rasterize_cover() - for pt in self.temp_files: - try: - os.remove(pt) - except: - pass def rasterize_svg(self, elem, width=0, height=0, format='PNG'): view_box = elem.get('viewBox', elem.get('viewbox', None)) @@ -110,38 +123,41 @@ class SVGRasterizer: return rasterize_svg(xml2str(elem, with_tail=False), sizes=sizes, width=width, height=height, print=logger.info, fmt=format) - def dataize_manifest(self): + def scan_for_linked_resources_in_manifest(self): for item in self.oeb.manifest.values(): if item.media_type == SVG_MIME and item.data is not None: - self.dataize_svg(item) + self.scan_for_linked_resources_in_svg(item) - def dataize_svg(self, item, svg=None): + def scan_for_linked_resources_in_svg(self, item, svg=None): if svg is None: svg = item.data hrefs = self.oeb.manifest.hrefs + ha = XLINK('href') for elem in xpath(svg, '//svg:*[@xl:href]'): - href = urlnormalize(elem.attrib[XLINK('href')]) + href = urlnormalize(elem.get(ha)) path = urldefrag(href)[0] if not path: continue abshref = item.abshref(path) - if abshref not in hrefs: + linkee = hrefs.get(abshref) + if linkee is None: continue - linkee = hrefs[abshref] data = linkee.bytes_representation - ext = what(None, data) or 'jpg' - with PersistentTemporaryFile(suffix='.'+ext) as pt: - pt.write(data) - self.temp_files.append(pt.name) - elem.attrib[XLINK('href')] = pt.name + ext = what(None, data) + if not ext: + continue + mt = guess_type('file.'+ext)[0] + if not mt or not mt.startswith('image/'): + continue + elem.set(ha, data_url(mt, data)) + return svg def stylizer(self, item): ans = self.stylizer_cache.get(item, None) if ans is None: - ans = Stylizer(item.data, item.href, self.oeb, self.opts, + ans = self.stylizer_cache[item] = Stylizer(item.data, item.href, self.oeb, self.opts, self.profile, base_css=self.base_css) - self.stylizer_cache[item] = ans return ans def rasterize_spine(self): @@ -172,13 +188,19 @@ class SVGRasterizer: height = style['height'] width = (width / 72) * self.profile.dpi height = (height / 72) * self.profile.dpi - elem = self.dataize_svg(item, elem) + self.scan_for_linked_resources_in_svg(item, elem) data = self.rasterize_svg(elem, width, height) manifest = self.oeb.manifest href = os.path.splitext(item.href)[0] + '.png' id, href = manifest.generate(item.id, href) manifest.add(id, href, PNG_MIME, data=data) img = elem.makeelement(XHTML('img'), src=item.relhref(href)) + if self.save_svg_originals: + svg_bytes = etree.tostring(elem, encoding='utf-8', xml_declaration=True, pretty_print=True, with_tail=False) + svg_id, svg_href = manifest.generate(item.id, 'inline.svg') + manifest.add(svg_id, svg_href, SVG_MIME, data=svg_bytes) + self.svg_originals[href] = svg_href + img.tail = elem.tail elem.getparent().replace(elem, img) for prop in ('width', 'height'): if prop in elem.attrib: @@ -215,6 +237,7 @@ class SVGRasterizer: id, href = manifest.generate(svgitem.id, href) manifest.add(id, href, PNG_MIME, data=data) self.images[key] = href + self.svg_originals[href] = svgitem.href elem.tag = XHTML('img') for attr in elem.attrib: if attr not in KEEP_ATTRS: @@ -244,3 +267,7 @@ class SVGRasterizer: id, href = self.oeb.manifest.generate(cover.id, href) self.oeb.manifest.add(id, href, PNG_MIME, data=data) covers[0].value = id + + +if __name__ == '__main__': + open('/t/test-svg-rasterization.png', 'wb').write(rasterize_svg()) From 0acd1a8182532b22c3068ad1ef602c7bf2bbe73d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 9 Jun 2023 15:28:02 +0530 Subject: [PATCH 0803/2055] better name for two finger tap gesture --- src/pyj/read_book/iframe.pyj | 2 ++ src/pyj/read_book/touch.pyj | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index dd627b823e..9eaf9f5ff4 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -317,6 +317,8 @@ class IframeBoss: def handle_gesture(self, gesture): if gesture.type is 'show-chrome': self.send_message('show_chrome') + elif gesture.type is 'two-finger-tap': + self.send_message('show_chrome') elif gesture.type is 'pinch': self.send_message('bump_font_size', increase=gesture.direction is 'out') elif gesture.type is 'long-tap': diff --git a/src/pyj/read_book/touch.pyj b/src/pyj/read_book/touch.pyj index 6b8612feac..be642b166e 100644 --- a/src/pyj/read_book/touch.pyj +++ b/src/pyj/read_book/touch.pyj @@ -82,7 +82,7 @@ def interpret_double_gesture(touch1, touch2, gesture_id): max_y_displacement1 = max_displacement(touch1.viewport_y) max_y_displacement2 = max_displacement(touch2.viewport_y) if max(max_x_displacement1, max_y_displacement1) < TAP_THRESHOLD and max(max_x_displacement2, max_y_displacement2) < TAP_THRESHOLD: - ans.type = 'double-tap' + ans.type = 'two-finger-tap' ans.viewport_x1 = touch1.viewport_x[0] ans.viewport_y1 = touch1.viewport_y[0] ans.viewport_x2 = touch2.viewport_x[0] @@ -288,10 +288,9 @@ class BookTouchHandler(TouchHandler): if gesture.type is 'pinch': if gesture.active: return - if gesture.type is 'double-tap': + if gesture.type is 'two-finger-tap': if gesture.active: return - gesture.type = 'show-chrome' if self.for_side_margin: ui_operations.forward_gesture(gesture) else: From a1b874398d33dac04a039d5ca0d770beb1be93c5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 9 Jun 2023 16:01:07 +0530 Subject: [PATCH 0804/2055] CHM Input: Yet another regression opening CHM files with missing internal files on windows. See #2023377 (Since the last update Calibre cannot open some of my ebooks.) [Since the last update Calibre cannot open some of my ebooks.](https://bugs.launchpad.net/calibre/+bug/2023377) --- src/calibre/utils/filenames.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/calibre/utils/filenames.py b/src/calibre/utils/filenames.py index 897b0a47f3..182fab9c1e 100644 --- a/src/calibre/utils/filenames.py +++ b/src/calibre/utils/filenames.py @@ -623,9 +623,13 @@ if iswindows: def get_long_path_name(path): from calibre_extensions.winutil import get_long_path_name - if os.path.isabs(path) and not path.startswith(long_path_prefix): - path = long_path_prefix + path - return get_long_path_name(path) + lpath = path + if os.path.isabs(lpath) and not lpath.startswith(long_path_prefix): + lpath = long_path_prefix + lpath + try: + return get_long_path_name(lpath) + except FileNotFoundError: + return path else: def make_long_path_useable(path): From 482922cddb8b3eb426517669a01aa765db14ba3a Mon Sep 17 00:00:00 2001 From: ping Date: Sat, 10 Jun 2023 11:29:00 +0800 Subject: [PATCH 0805/2055] Update Harper's Magazine Print recipe --- recipes/harpers_full.recipe | 224 +++++++++++++++++++++++------------- 1 file changed, 143 insertions(+), 81 deletions(-) diff --git a/recipes/harpers_full.recipe b/recipes/harpers_full.recipe index 5a8e91fb0d..159419d623 100644 --- a/recipes/harpers_full.recipe +++ b/recipes/harpers_full.recipe @@ -3,107 +3,169 @@ # vi: set fenc=utf-8 ft=python : # kate: encoding utf-8; syntax python; -__license__ = 'GPL v3' -__copyright__ = '2008-2019, Darko Miletic ' -''' -harpers.org - paid subscription/ printed issue articles +__license__ = "GPL v3" +__copyright__ = "2008-2019, Darko Miletic " +""" +harpers.org - printed issue articles This recipe only get's article's published in text format images and pdf's are ignored -If you have institutional subscription based on access IP you do not need to enter -anything in username/password fields -''' +""" -import time -try: - from urllib.parse import urlencode -except ImportError: - from urllib import urlencode +from urllib.parse import urljoin + +from calibre import browser from calibre.web.feeds.news import BasicNewsRecipe - -def classes(classes): - q = frozenset(classes.split(' ')) - return dict(attrs={ - 'class': lambda x: x and frozenset(x.split()).intersection(q)}) +# overwrite this with a custom issue url, e.g. https://harpers.org/archive/2023/01/ +_issue_url = "" class Harpers_full(BasicNewsRecipe): title = "Harper's Magazine - articles from printed edition" - __author__ = 'Darko Miletic' + __author__ = "Darko Miletic, updated by ping" description = "Harper's Magazine, the oldest general-interest monthly in America, explores the issues that drive our national conversation, through long-form narrative journalism and essays, and such celebrated features as the iconic Harper's Index." # noqa publisher = "Harpers's" - category = 'news, politics, USA' - oldest_article = 30 + category = "news, politics, USA" + oldest_article = 31 max_articles_per_feed = 100 no_stylesheets = True use_embedded_content = False - delay = 1 - language = 'en' - encoding = 'utf8' - needs_subscription = 'optional' - publication_type = 'magazine' - LOGIN = 'https://harpers.org/wp-admin/admin-ajax.php' + language = "en" + encoding = "utf8" + publication_type = "magazine" + requires_version = (5, 0, 0) # py3 + ignore_duplicate_articles = {"url"} + base_url = "https://harpers.org" + keep_only_tags = [ - classes('article-header-text entry-content'), + dict( + class_=[ + "article-content", + "template-index-archive", # harper's index + ] + ) ] remove_tags = [ - classes('related-issue-tout section-tags component-from-author component-share-buttons') + dict( + class_=[ + "component-newsletter-signup", + "sidebar", + "header-meta", + "component-from-author", + "from-issue", + "d-none", + "COA_roles_fix_space", + "section-tags", + "aria-font-adjusts", + "component-share-buttons", + "index-footer", + "index-prev-link", + "comma", + ] + ), + # for harper's index + dict( + class_=[ + "aria-font-adjusts", + "component-share-buttons", + "index-footer", + "index-prev-link", + ] + ), ] + remove_attributes = ["style", "width", "height"] - def get_browser(self): - br = BasicNewsRecipe.get_browser(self) - br.open('https://harpers.org/') - if self.username is not None and self.password is not None: - tt = time.localtime() * 1000 - data = urlencode({'action': 'cds_auth_user', 'm': self.username, 'p': self.password, 'rt': 'https://harpers.org/', 'tt': tt - }) - br.open(self.LOGIN, data) - return br + extra_css = """ + h1.article-title { font-size: x-large; margin-bottom: 0.4rem; } + .subheading, .post-subtitle { font-size: large; font-style: italic; margin-bottom: 1rem; } + .byline { margin-bottom: 1rem } + .article-hero-img img, .flex-section-image img, .wp-caption img { + display: block; margin-bottom: 0.3rem; max-width: 100%; height: auto; + box-sizing: border-box; + } + .wp-caption-text { font-size: small; margin-top: 0.3rem; } + + .author-bio { margin-top: 2.5rem; font-style: italic; } + .author-bio em { font-weight: bold; } + + .index-item { font-size: large; margin: 1rem 0; } + .index-statement > p { display: inline-block; margin: 0.5rem 0; } + .index-statement > span { display: inline-block; } + .index-statement .index-tooltip { font-size: small; } + """ + + # Send cookie-less requests to get full article + def get_browser(self, *args, **kwargs): + return self + + def clone_browser(self, *args, **kwargs): + return self.get_browser() + + def open_novisit(self, *args, **kwargs): + br = browser() + return br.open_novisit(*args, **kwargs) + + open = open_novisit + + def preprocess_html(self, soup): + # General UI tweaks + # move subheading to before byline (instead of where it is now, after) + subheading_ele = soup.find(class_="subheading") + byline_ele = soup.find(class_="byline") + if byline_ele and subheading_ele: + byline_ele.insert_before(subheading_ele.extract()) + + # strip extraneous stuff from author bio + for bio in soup.find_all(class_="author-bio"): + for dec_ele in bio.find_all("br"): + dec_ele.decompose() + for unwrap_ele in bio.find_all("p"): + unwrap_ele.unwrap() + + # remove extraneous hr + for hr in soup.select(".after-post-content hr"): + hr.decompose() + return soup def parse_index(self): - # find current issue - soup = self.index_to_soup('https://harpers.org/') - currentIssue_url = soup.find(attrs={'data-current-issue-url': True})['data-current-issue-url'] - self.log('Found issue at:', currentIssue_url) + if not _issue_url: + issues_soup = self.index_to_soup("https://harpers.org/issues/") + curr_issue_a_ele = issues_soup.select_one("div.issue-card a") + curr_issue_url = urljoin(self.base_url, curr_issue_a_ele["href"]) + else: + curr_issue_url = _issue_url - # go to the current issue - soup = self.index_to_soup(currentIssue_url) - self.timefmt = u' [%s]' % self.tag_to_string(soup.find('a', href=currentIssue_url)) + soup = self.index_to_soup(curr_issue_url) + self.timefmt = ( + f' [{self.tag_to_string(soup.find("h1", class_="issue-heading")).strip()}]' + ) + self.cover_url = soup.find("img", class_="cover-img")["src"] - # get cover - self.cover_url = soup.find(**classes('past-issue')).find('img')['src'] - self.log('Found cover at:', self.cover_url) - features = [] - - self.log('Features') - for item in soup.find(**classes('issue-features')).findAll(**classes('article-card')): - h = item.find(**classes('ac-title')) - a = h.parent - url = a['href'] - title = self.tag_to_string(h).strip() - h = item.find(**classes('ac-subtitle')) - if h is not None: - st = self.tag_to_string(h).strip() - if st: - title += ': ' + st - desc = '' - p = item.find(**classes('byline')) - if p is not None: - desc += self.tag_to_string(p) - self.log(' ', title, 'at', url) - features.append({'title': title, 'url': url, 'description': desc}) - - readings = [] - self.log('Readings') - for item in soup.find(**classes('issue-readings')).findAll(**classes('reading-item')): - a = item.find('a', **classes('ac-title')) - title = self.tag_to_string(a).strip() - url = a['href'] - desc = '' - a = item.find(**classes('ac-author')) - if a is not None: - desc = self.tag_to_string(a) - self.log(' ', title, 'at', url) - readings.append({'title': title, 'url': url, 'description': desc}) - - return [('Features', features), ('Readings', readings)] + articles = {} + for section_name in ("features", "readings", "articles"): + section = soup.find("section", class_=f"issue-{section_name}") + if not section: + continue + for card in section.find_all("div", class_="article-card"): + title_ele = card.find(class_="ac-title") + if not title_ele: + continue + article_url = card.find("a")["href"] + article_title = self.tag_to_string(title_ele) + article_description = ( + f'{self.tag_to_string(card.find(class_="ac-tax"))} ' + f'{self.tag_to_string(card.find(class_="ac-subtitle"))}' + ).strip() + byline = card.find(class_="byline") + if byline: + article_description += ( + f' {self.tag_to_string(byline).strip().strip(",")}' + ) + articles.setdefault(section_name.title(), []).append( + { + "url": article_url, + "title": article_title, + "description": article_description, + } + ) + return articles.items() From fdb68a16133ecc966add15af975a13f5792c81a8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 10 Jun 2023 12:15:02 +0530 Subject: [PATCH 0806/2055] CHM Input: resolve absolute links to resource files from the root of the CHM file. Fixes #2023431 [Private bug](https://bugs.launchpad.net/calibre/+bug/2023431) Previously they would have been ignored or caused an error on Windows. --- src/calibre/ebooks/conversion/plugins/chm_input.py | 1 + src/calibre/ebooks/conversion/plugins/html_input.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/calibre/ebooks/conversion/plugins/chm_input.py b/src/calibre/ebooks/conversion/plugins/chm_input.py index fbdfb48a52..d8a4428adc 100644 --- a/src/calibre/ebooks/conversion/plugins/chm_input.py +++ b/src/calibre/ebooks/conversion/plugins/chm_input.py @@ -104,6 +104,7 @@ class CHMInput(InputFormatPlugin): opts.breadth_first = True htmlinput = HTMLInput(None) htmlinput.set_root_dir_of_input(basedir) + htmlinput.root_dir_for_absolute_links = basedir oeb = htmlinput.create_oebbook(htmlpath, basedir, opts, log, mi) return oeb diff --git a/src/calibre/ebooks/conversion/plugins/html_input.py b/src/calibre/ebooks/conversion/plugins/html_input.py index c1d85b2d37..7957d0e230 100644 --- a/src/calibre/ebooks/conversion/plugins/html_input.py +++ b/src/calibre/ebooks/conversion/plugins/html_input.py @@ -37,6 +37,7 @@ class HTMLInput(InputFormatPlugin): description = _('Convert HTML and OPF files to an OEB') file_types = {'opf', 'html', 'htm', 'xhtml', 'xhtm', 'shtm', 'shtml'} commit_name = 'html_input' + root_dir_for_absolute_links = '' options = { OptionRecommendation(name='breadth_first', @@ -253,6 +254,9 @@ class HTMLInput(InputFormatPlugin): except: self.log.warn('Failed to decode link %r. Ignoring'%link_) return None, None + if self.root_dir_for_absolute_links and link_.startswith('/'): + link_ = link_.lstrip('/') + base = self.root_dir_for_absolute_links try: l = Link(link_, base if base else os.getcwd()) except: From dcfc477c286fdb590d91055a38e4237a0a22faea Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 11 Jun 2023 09:30:27 +0530 Subject: [PATCH 0807/2055] Clean up gesture handling All the gnarly direction detection for swipes is moved into touch.pyj and the handlers just need to check the event type (except for flow mode where we really want to match finger movement). --- src/pyj/read_book/flow_mode.pyj | 11 +++--- src/pyj/read_book/iframe.pyj | 13 ++++--- src/pyj/read_book/paged_mode.pyj | 24 ++++++------ src/pyj/read_book/touch.pyj | 63 +++++++++++++++++++++++++------- 4 files changed, 75 insertions(+), 36 deletions(-) diff --git a/src/pyj/read_book/flow_mode.pyj b/src/pyj/read_book/flow_mode.pyj index 1354c44b5e..ab955abbba 100644 --- a/src/pyj/read_book/flow_mode.pyj +++ b/src/pyj/read_book/flow_mode.pyj @@ -23,6 +23,7 @@ from __python__ import bound_methods, hash_literals from dom import set_css from range_utils import wrap_range, unwrap from read_book.cfi import scroll_to as cfi_scroll_to +from read_book.touch import GESTURE from read_book.globals import current_spine_item, get_boss, rtl_page_progression, ltr_page_progression from read_book.settings import opts from read_book.viewport import line_height, rem_size, scroll_viewport @@ -536,7 +537,9 @@ def start_drag_scroll(delta): def handle_gesture(gesture): flick_animator.stop() - if gesture.type is 'swipe': + if gesture.type is GESTURE.flick_block_forward or gesture.type is GESTURE.flick_block_backward: + flick_animator.start(gesture) + elif gesture.type.startsWith('swipe_') and gesture.type.endsWith('_in_progress'): if gesture.points.length > 1 and not gesture.is_held: delta = gesture.points[-2] - gesture.points[-1] if Math.abs(delta) >= 1: @@ -556,13 +559,11 @@ def handle_gesture(gesture): # In horizontal modes, just move by the delta. else: scroll_viewport.scroll_by(delta, 0) - if not gesture.active and not gesture.is_held: - flick_animator.start(gesture) - elif gesture.type is 'prev-page': + elif gesture.type is GESTURE.back_zone_tap: # should flip = False - previous is previous whether RTL or LTR. # flipping of this command is handled higher up scroll_by_page(-1, False) - elif gesture.type is 'next-page': + elif gesture.type is GESTURE.forward_zone_tap: # should flip = False - next is next whether RTL or LTR. # flipping of this command is handled higher up scroll_by_page(1, False) diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index 9eaf9f5ff4..f19ba47dfb 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -9,6 +9,7 @@ from range_utils import ( all_annots_in_selection, highlight_associated_with_selection, last_span_for_crw, reset_highlight_counter, select_crw, unwrap_all_crw, unwrap_crw, wrap_text_in_range ) +from read_book.touch import GESTURE from read_book.cfi import cfi_for_selection, range_from_cfi from read_book.extract import get_elements from read_book.find import ( @@ -315,13 +316,13 @@ class IframeBoss: self.auto_scroll_action('toggle') def handle_gesture(self, gesture): - if gesture.type is 'show-chrome': + if gesture.type is GESTURE.control_zone_tap or gesture.type is GESTURE.two_finger_tap: self.send_message('show_chrome') - elif gesture.type is 'two-finger-tap': - self.send_message('show_chrome') - elif gesture.type is 'pinch': - self.send_message('bump_font_size', increase=gesture.direction is 'out') - elif gesture.type is 'long-tap': + elif gesture.type is GESTURE.pinch_in: + self.send_message('bump_font_size', increase=False) + elif gesture.type is GESTURE.pinch_out: + self.send_message('bump_font_size', increase=True) + elif gesture.type is GESTURE.long_tap: self.handle_long_tap(gesture) else: self._handle_gesture(gesture) diff --git a/src/pyj/read_book/paged_mode.pyj b/src/pyj/read_book/paged_mode.pyj index 23ecb1ee65..42c816ded1 100644 --- a/src/pyj/read_book/paged_mode.pyj +++ b/src/pyj/read_book/paged_mode.pyj @@ -32,6 +32,7 @@ from read_book.cfi import ( scroll_to as cfi_scroll_to ) from read_book.globals import current_spine_item, get_boss, rtl_page_progression +from read_book.touch import GESTURE from read_book.settings import opts from read_book.viewport import scroll_viewport, line_height, rem_size, get_unit_size_in_pixels from utils import ( @@ -808,19 +809,20 @@ def handle_shortcut(sc_name, evt): def handle_gesture(gesture): - if gesture.type is 'swipe': - if gesture.axis is 'vertical': - if not gesture.active: - get_boss().send_message('next_section', forward=gesture.direction is 'up') - else: - if not gesture.active or gesture.is_held: - scroll_by_page(gesture.direction is 'right', True, flip_if_rtl_page_progression=True) # Gesture progression direction is determined in the gesture code, # don't set flip_if_rtl_page_progression=True here. - elif gesture.type is 'prev-page': - scroll_by_page(True, opts.paged_taps_scroll_by_screen, flip_if_rtl_page_progression=False) - elif gesture.type is 'next-page': - scroll_by_page(False, opts.paged_taps_scroll_by_screen, flip_if_rtl_page_progression=False) + if gesture.type is GESTURE.flick_block_forward: + get_boss().send_message('next_section', forward=True) + elif gesture.type is GESTURE.flick_block_backward: + get_boss().send_message('next_section', forward=False) + elif gesture.type is GESTURE.flick_inline_forward or gesture.type is GESTURE.swipe_inline_forward_hold: + scroll_by_page(False, True) + elif gesture.type is GESTURE.flick_inline_backward or gesture.type is GESTURE.swipe_inline_backward_hold: + scroll_by_page(True, True) + elif gesture.type is GESTURE.back_zone_tap: + scroll_by_page(True, opts.paged_taps_scroll_by_screen) + elif gesture.type is GESTURE.forward_zone_tap: + scroll_by_page(False, opts.paged_taps_scroll_by_screen) anchor_funcs = { diff --git a/src/pyj/read_book/touch.pyj b/src/pyj/read_book/touch.pyj index be642b166e..14952656dd 100644 --- a/src/pyj/read_book/touch.pyj +++ b/src/pyj/read_book/touch.pyj @@ -2,6 +2,7 @@ # License: GPL v3 Copyright: 2016, Kovid Goyal from __python__ import bound_methods, hash_literals +from gettext import gettext as _ from read_book.globals import get_boss, ltr_page_progression, ui_operations from read_book.settings import opts from read_book.viewport import get_unit_size_in_pixels, scroll_viewport @@ -11,6 +12,31 @@ TAP_THRESHOLD = 8 # pixels SWIPE_THRESHOLD = 64 # pixels TAP_LINK_THRESHOLD = 5 # pixels PINCH_THRESHOLD = 20 # pixels +GESTURE_NAMES = { + 'back_zone_tap': _('Tap on back zone'), + 'forward_zone_tap': _('Tap on forward zone'), + 'control_zone_tap': _('Tap in the controls zone'), + 'long_tap': _('Long tap'), + 'two_finger_tap': _('Two finger tap'), + 'pinch_in': _('Pinch in'), + 'pinch_out': _('Pinch out'), + 'flick_inline_backward': _('Flick in writing direction, backwards'), + 'flick_inline_forward': _('Flick in writing direction, forwards'), + 'flick_block_backward': _('Flick perpendicular to writing direction, backwards'), + 'flick_block_forward': _('Flick perpendicular to writing direction, forwards'), + 'swipe_inline_backward_in_progress': _('Swipe in writing direction, backwards, in-progress'), + 'swipe_inline_forward_in_progress': _('Swipe in writing direction, forwards, in-progress'), + 'swipe_block_backward_in_progress': _('Swipe perpendicular to writing direction, backwards, in-progress'), + 'swipe_block_forward_in_progress': _('Swipe perpendicular to writing direction, forwards, in-progress'), + 'swipe_inline_backward_hold': _('Swipe in writing direction, backwards and hold'), + 'swipe_inline_forward_hold': _('Swipe in writing direction, forwards and hold'), + 'swipe_block_backward_hold': _('Swipe perpendicular to writing direction, backwards and hold'), + 'swipe_block_forward_hold': _('Swipe perpendicular to writing direction, forwards and hold'), +} +GESTURE = {k:k for k in Object.keys(GESTURE_NAMES)} +GESTURE.tap = 'tap' +GESTURE.swipe = 'swipe' +GESTURE.pinch = 'pinch' gesture_id = 0 @@ -50,7 +76,7 @@ def interpret_single_gesture(touch, gesture_id): max_y_displacement = max_displacement(touch.viewport_y) ans = {'active':touch.active, 'is_held':touch.is_held, 'id':gesture_id, 'start_time': touch.ctime} if max(max_x_displacement, max_y_displacement) < TAP_THRESHOLD: - ans.type = 'tap' + ans.type = GESTURE.tap ans.viewport_x = touch.viewport_x[0] ans.viewport_y = touch.viewport_y[0] return ans @@ -60,7 +86,7 @@ def interpret_single_gesture(touch, gesture_id): delta_y = abs(touch.viewport_y[-1] - touch.viewport_y[0]) max_disp = max(delta_y, delta_x) if max_disp > SWIPE_THRESHOLD and min(delta_x, delta_y)/max_disp < 0.35: - ans.type = 'swipe' + ans.type = GESTURE.swipe ans.axis = 'vertical' if delta_y > delta_x else 'horizontal' ans.points = pts = touch.viewport_y if ans.axis is 'vertical' else touch.viewport_x ans.times = touch.mtimes @@ -82,7 +108,7 @@ def interpret_double_gesture(touch1, touch2, gesture_id): max_y_displacement1 = max_displacement(touch1.viewport_y) max_y_displacement2 = max_displacement(touch2.viewport_y) if max(max_x_displacement1, max_y_displacement1) < TAP_THRESHOLD and max(max_x_displacement2, max_y_displacement2) < TAP_THRESHOLD: - ans.type = 'two-finger-tap' + ans.type = GESTURE.two_finger_tap ans.viewport_x1 = touch1.viewport_x[0] ans.viewport_y1 = touch1.viewport_y[0] ans.viewport_x2 = touch2.viewport_x[0] @@ -92,7 +118,7 @@ def interpret_double_gesture(touch1, touch2, gesture_id): final_distance = Math.sqrt((touch1.viewport_x[-1] - touch2.viewport_x[-1])**2 + (touch1.viewport_y[-1] - touch2.viewport_y[-1])**2) distance = abs(final_distance - initial_distance) if distance > PINCH_THRESHOLD: - ans.type = 'pinch' + ans.type = GESTURE.pinch ans.direction = 'in' if final_distance < initial_distance else 'out' ans.distance = distance return ans @@ -255,42 +281,51 @@ class BookTouchHandler(TouchHandler): TouchHandler.__init__(self) def handle_gesture(self, gesture): - gesture.from_side_margin = self.for_side_margin - if gesture.type is 'tap': + if gesture.type is GESTURE.tap: if gesture.is_held: if not self.for_side_margin and not self.handled_tap_hold and window.performance.now() - gesture.start_time >= HOLD_THRESHOLD: self.handled_tap_hold = True - gesture.type = 'long-tap' + gesture.type = GESTURE.long_tap get_boss().handle_gesture(gesture) return if not gesture.active: if self.for_side_margin or not tap_on_link(gesture): inch = inch_in_pixels() if gesture.viewport_y < min(100, scroll_viewport.height() / 4): - gesture.type = 'show-chrome' + gesture.type = GESTURE.control_zone_tap else: limit = inch # default, books that go left to right. if ltr_page_progression() and not opts.reverse_page_turn_zones: if gesture.viewport_x < min(limit, scroll_viewport.width() / 4): - gesture.type = 'prev-page' + gesture.type = GESTURE.back_zone_tap else: - gesture.type = 'next-page' + gesture.type = GESTURE.forward_zone_tap # We swap the sizes in RTL mode, so that going to the next page is always the bigger touch region. else: # The "going back" area should not be more than limit units big, # even if 1/4 of the scroll viewport is more than limit units. # Checking against the larger of the width minus the limit units and 3/4 of the width will accomplish that. if gesture.viewport_x > max(scroll_viewport.width() - limit, scroll_viewport.width() * (3/4)): - gesture.type = 'prev-page' + gesture.type = GESTURE.back_zone_tap else: - gesture.type = 'next-page' - if gesture.type is 'pinch': + gesture.type = GESTURE.forward_zone_tap + elif gesture.type is GESTURE.pinch: if gesture.active: return - if gesture.type is 'two-finger-tap': + gesture.type = GESTURE.pinch_in if gesture.direction is 'in' else GESTURE.pinch_out + elif gesture.type is GESTURE.two_finger_tap: if gesture.active: return + elif gesture.type is 'swipe': + backward_dir = 'down' if gesture.axis is 'vertical' else ('right' if ltr_page_progression() else 'left') + direction = 'backward' if gesture.direction is backward_dir else 'forward' + inline_dir = 'vertical' if scroll_viewport.vertical_writing_mode else 'horizontal' + axis = 'inline' if gesture.axis is inline_dir else 'block' + if gesture.active: + gesture.type = GESTURE[f'swipe_{axis}_{direction}' + ('_hold' if gesture.is_held else '_in_progress')] + elif not gesture.is_held: + gesture.type = GESTURE[f'flick_{axis}_{direction}'] if self.for_side_margin: ui_operations.forward_gesture(gesture) else: From 80702e5a720a27f23dc9d51c011238944cba2f90 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 11 Jun 2023 14:40:38 +0530 Subject: [PATCH 0808/2055] Map gestures to configurable actions --- src/pyj/read_book/flow_mode.pyj | 9 +++-- src/pyj/read_book/gestures.pyj | 49 +++++++++++++++++++++++++++ src/pyj/read_book/iframe.pyj | 11 +++--- src/pyj/read_book/paged_mode.pyj | 17 +++++----- src/pyj/read_book/prefs/scrolling.pyj | 2 -- src/pyj/read_book/settings.pyj | 2 +- src/pyj/read_book/touch.pyj | 1 + src/pyj/read_book/view.pyj | 2 +- src/pyj/session.pyj | 2 +- 9 files changed, 71 insertions(+), 24 deletions(-) create mode 100644 src/pyj/read_book/gestures.pyj diff --git a/src/pyj/read_book/flow_mode.pyj b/src/pyj/read_book/flow_mode.pyj index ab955abbba..bd47f81e44 100644 --- a/src/pyj/read_book/flow_mode.pyj +++ b/src/pyj/read_book/flow_mode.pyj @@ -23,7 +23,6 @@ from __python__ import bound_methods, hash_literals from dom import set_css from range_utils import wrap_range, unwrap from read_book.cfi import scroll_to as cfi_scroll_to -from read_book.touch import GESTURE from read_book.globals import current_spine_item, get_boss, rtl_page_progression, ltr_page_progression from read_book.settings import opts from read_book.viewport import line_height, rem_size, scroll_viewport @@ -537,9 +536,9 @@ def start_drag_scroll(delta): def handle_gesture(gesture): flick_animator.stop() - if gesture.type is GESTURE.flick_block_forward or gesture.type is GESTURE.flick_block_backward: + if gesture.resolved_action is 'animated_scroll': flick_animator.start(gesture) - elif gesture.type.startsWith('swipe_') and gesture.type.endsWith('_in_progress'): + elif gesture.resolved_action is 'pan': if gesture.points.length > 1 and not gesture.is_held: delta = gesture.points[-2] - gesture.points[-1] if Math.abs(delta) >= 1: @@ -559,11 +558,11 @@ def handle_gesture(gesture): # In horizontal modes, just move by the delta. else: scroll_viewport.scroll_by(delta, 0) - elif gesture.type is GESTURE.back_zone_tap: + elif gesture.resolved_action is 'prev_page' or gesture.resolved_action is 'prev_screen': # should flip = False - previous is previous whether RTL or LTR. # flipping of this command is handled higher up scroll_by_page(-1, False) - elif gesture.type is GESTURE.forward_zone_tap: + elif gesture.resolved_action is 'next_page' or gesture.resolved_action is 'next_screen': # should flip = False - next is next whether RTL or LTR. # flipping of this command is handled higher up scroll_by_page(1, False) diff --git a/src/pyj/read_book/gestures.pyj b/src/pyj/read_book/gestures.pyj new file mode 100644 index 0000000000..116c1b6f2e --- /dev/null +++ b/src/pyj/read_book/gestures.pyj @@ -0,0 +1,49 @@ +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2023, Kovid Goyal +from __python__ import bound_methods, hash_literals + +from read_book.touch import GESTURE +from read_book.settings import opts + +default_actions_for_gesture = { + 'common': { + GESTURE.back_zone_tap: 'prev_page', + GESTURE.forward_zone_tap: 'next_page', + GESTURE.control_zone_tap: 'show_chrome', + GESTURE.two_finger_tap: 'show_chrome', + GESTURE.long_tap: 'highlight_or_inspect', + GESTURE.pinch_in: 'decrease_font_size', + GESTURE.pinch_out: 'increase_font_size', + }, + 'flow_mode': { + GESTURE.swipe_inline_backward_in_progress: 'pan', + GESTURE.swipe_inline_forward_in_progress: 'pan', + GESTURE.swipe_block_backward_in_progress: 'pan', + GESTURE.swipe_block_forward_in_progress: 'pan', + GESTURE.flick_block_forward: 'animated_scroll', + GESTURE.flick_block_backward: 'animated_scroll', + GESTURE.flick_inline_forward: 'animated_scroll', + GESTURE.flick_inline_backward: 'animated_scroll', + }, + 'paged_mode': { + GESTURE.flick_block_forward: 'next_section', + GESTURE.flick_block_backward: 'prev_section', + GESTURE.flick_inline_forward: 'next_screen', + GESTURE.flick_inline_backward: 'prev_screen', + GESTURE.swipe_inline_forward_hold: 'next_screen', + GESTURE.swipe_inline_backward_hold: 'prev_screen', + }, +} + +def action_for_gesture(gesture, in_flow_mode): + overrides = opts.gesture_overrides + mode = 'flow_mode' if in_flow_mode else 'paged_mode' + mode_overrides = overrides[mode] or {} + if mode_overrides[gesture.type]: + return mode_overrides[gesture.type] + common_overrides = overrides.common or {} + if common_overrides[gesture.type]: + return common_overrides[gesture.type] + if default_actions_for_gesture[mode][gesture.type]: + return default_actions_for_gesture[mode][gesture.type] + return default_actions_for_gesture.common[gesture.type] or 'none' diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index f19ba47dfb..5c06a3db58 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -9,7 +9,6 @@ from range_utils import ( all_annots_in_selection, highlight_associated_with_selection, last_span_for_crw, reset_highlight_counter, select_crw, unwrap_all_crw, unwrap_crw, wrap_text_in_range ) -from read_book.touch import GESTURE from read_book.cfi import cfi_for_selection, range_from_cfi from read_book.extract import get_elements from read_book.find import ( @@ -27,6 +26,7 @@ from read_book.flow_mode import ( start_drag_scroll as start_drag_scroll_flow ) from read_book.footnotes import is_footnote_link +from read_book.gestures import action_for_gesture from read_book.globals import ( annot_id_uuid_map, clear_annot_id_uuid_map, current_book, current_layout_mode, current_spine_item, runtime, set_boss, set_current_spine_item, set_layout_mode, @@ -316,13 +316,14 @@ class IframeBoss: self.auto_scroll_action('toggle') def handle_gesture(self, gesture): - if gesture.type is GESTURE.control_zone_tap or gesture.type is GESTURE.two_finger_tap: + gesture.resolved_action = action_for_gesture(gesture, current_layout_mode() is 'flow') + if gesture.resolved_action is 'show_chrome': self.send_message('show_chrome') - elif gesture.type is GESTURE.pinch_in: + elif gesture.resolved_action is 'decrease_font_size': self.send_message('bump_font_size', increase=False) - elif gesture.type is GESTURE.pinch_out: + elif gesture.resolved_action is 'increase_font_size': self.send_message('bump_font_size', increase=True) - elif gesture.type is GESTURE.long_tap: + elif gesture.resolved_action is 'highlight_or_inspect': self.handle_long_tap(gesture) else: self._handle_gesture(gesture) diff --git a/src/pyj/read_book/paged_mode.pyj b/src/pyj/read_book/paged_mode.pyj index 42c816ded1..343810263d 100644 --- a/src/pyj/read_book/paged_mode.pyj +++ b/src/pyj/read_book/paged_mode.pyj @@ -32,7 +32,6 @@ from read_book.cfi import ( scroll_to as cfi_scroll_to ) from read_book.globals import current_spine_item, get_boss, rtl_page_progression -from read_book.touch import GESTURE from read_book.settings import opts from read_book.viewport import scroll_viewport, line_height, rem_size, get_unit_size_in_pixels from utils import ( @@ -811,18 +810,18 @@ def handle_shortcut(sc_name, evt): def handle_gesture(gesture): # Gesture progression direction is determined in the gesture code, # don't set flip_if_rtl_page_progression=True here. - if gesture.type is GESTURE.flick_block_forward: + if gesture.resolved_action is 'next_section': get_boss().send_message('next_section', forward=True) - elif gesture.type is GESTURE.flick_block_backward: + elif gesture.resolved_action is 'prev_section': get_boss().send_message('next_section', forward=False) - elif gesture.type is GESTURE.flick_inline_forward or gesture.type is GESTURE.swipe_inline_forward_hold: + elif gesture.resolved_action is 'next_screen': scroll_by_page(False, True) - elif gesture.type is GESTURE.flick_inline_backward or gesture.type is GESTURE.swipe_inline_backward_hold: + elif gesture.resolved_action is 'prev_screen': scroll_by_page(True, True) - elif gesture.type is GESTURE.back_zone_tap: - scroll_by_page(True, opts.paged_taps_scroll_by_screen) - elif gesture.type is GESTURE.forward_zone_tap: - scroll_by_page(False, opts.paged_taps_scroll_by_screen) + elif gesture.resolved_action is 'prev_page': + scroll_by_page(True, False) + elif gesture.resolved_action is 'next_page': + scroll_by_page(False, False) anchor_funcs = { diff --git a/src/pyj/read_book/prefs/scrolling.pyj b/src/pyj/read_book/prefs/scrolling.pyj index 87204aa08f..f3ccf67be3 100644 --- a/src/pyj/read_book/prefs/scrolling.pyj +++ b/src/pyj/read_book/prefs/scrolling.pyj @@ -69,8 +69,6 @@ def create_scrolling_panel(container, apply_func, cancel_func): 'paged_wheel_scrolls_by_screen', _('Mouse wheel scrolls by screen fulls instead of pages'))) container.lastChild.appendChild(cb( 'paged_margin_clicks_scroll_by_screen', _('Clicking on the margins scrolls by screen fulls instead of pages'))) - container.lastChild.appendChild(cb( - 'paged_taps_scroll_by_screen', _('Tapping scrolls by screen fulls instead of pages'))) container.lastChild.appendChild( E.div(style='display:grid;margin-top:1ex;align-items:center;grid-template-columns:auto min-content;grid-gap:1ex; max-width: 30em', *spinner( diff --git a/src/pyj/read_book/settings.pyj b/src/pyj/read_book/settings.pyj index 4ce39e2f29..7b5cc894bb 100644 --- a/src/pyj/read_book/settings.pyj +++ b/src/pyj/read_book/settings.pyj @@ -27,11 +27,11 @@ def update_settings(settings): opts.override_book_colors = settings.override_book_colors opts.paged_wheel_scrolls_by_screen = v'!!settings.paged_wheel_scrolls_by_screen' opts.paged_pixel_scroll_threshold = settings.paged_pixel_scroll_threshold - opts.paged_taps_scroll_by_screen = v'!!settings.paged_taps_scroll_by_screen' opts.scroll_auto_boundary_delay = settings.scroll_auto_boundary_delay opts.scroll_stop_boundaries = v'!!settings.scroll_stop_boundaries' opts.reverse_page_turn_zones = v'!!settings.reverse_page_turn_zones' opts.user_stylesheet = settings.user_stylesheet + opts.gesture_overrides = settings.gesture_overrides update_settings() diff --git a/src/pyj/read_book/touch.pyj b/src/pyj/read_book/touch.pyj index 14952656dd..9c968931a2 100644 --- a/src/pyj/read_book/touch.pyj +++ b/src/pyj/read_book/touch.pyj @@ -20,6 +20,7 @@ GESTURE_NAMES = { 'two_finger_tap': _('Two finger tap'), 'pinch_in': _('Pinch in'), 'pinch_out': _('Pinch out'), + 'flick_inline_backward': _('Flick in writing direction, backwards'), 'flick_inline_forward': _('Flick in writing direction, forwards'), 'flick_block_backward': _('Flick perpendicular to writing direction, backwards'), diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index 1aabe9ea6e..b661ff0b66 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -1017,12 +1017,12 @@ class View: 'cover_preserve_aspect_ratio': sd.get('cover_preserve_aspect_ratio'), 'paged_wheel_scrolls_by_screen': sd.get('paged_wheel_scrolls_by_screen'), 'paged_pixel_scroll_threshold': sd.get('paged_pixel_scroll_threshold'), - 'paged_taps_scroll_by_screen': sd.get('paged_taps_scroll_by_screen'), 'lines_per_sec_auto': sd.get('lines_per_sec_auto'), 'lines_per_sec_smooth': sd.get('lines_per_sec_smooth'), 'scroll_auto_boundary_delay': sd.get('scroll_auto_boundary_delay'), 'scroll_stop_boundaries': sd.get('scroll_stop_boundaries'), 'reverse_page_turn_zones': sd.get('reverse_page_turn_zones'), + 'gesture_overrides': sd.get('gesture_overrides'), } def show_name(self, name, initial_position=None): diff --git a/src/pyj/session.pyj b/src/pyj/session.pyj index 0c1d9cd29e..0812a7c427 100644 --- a/src/pyj/session.pyj +++ b/src/pyj/session.pyj @@ -53,7 +53,6 @@ defaults = { 'override_book_colors': 'never', 'paged_margin_clicks_scroll_by_screen': True, 'paged_wheel_scrolls_by_screen': False, - 'paged_taps_scroll_by_screen': False, 'paged_pixel_scroll_threshold': 60, 'read_mode': 'paged', 'scroll_auto_boundary_delay': 5, @@ -78,6 +77,7 @@ defaults = { 'book_search_mode': 'contains', 'book_search_case_sensitive': False, 'reverse_page_turn_zones': False, + 'gesture_overrides': {}, } is_local_setting = { From 73ecd2a53d0c61575dbb5eb4ccd4c3e8b4a5d909 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 11 Jun 2023 15:14:13 +0530 Subject: [PATCH 0809/2055] Add descriptions for gesture actions --- src/pyj/read_book/gestures.pyj | 63 ++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/pyj/read_book/gestures.pyj b/src/pyj/read_book/gestures.pyj index 116c1b6f2e..642af79c73 100644 --- a/src/pyj/read_book/gestures.pyj +++ b/src/pyj/read_book/gestures.pyj @@ -4,6 +4,69 @@ from __python__ import bound_methods, hash_literals from read_book.touch import GESTURE from read_book.settings import opts +from gettext import gettext as _ + + +def get_action_descriptions(): + if not get_action_descriptions.ans: + get_action_descriptions.ans = { + 'prev_page': { + 'short': _('Previous page'), + 'long': _('Scroll to the previous page, if more than one page is displayed on the screen only a single page is scrolled') + }, + 'next_page': { + 'short': _('Next page'), + 'long': _('Scroll to the next page, if more than one page is displayed on the screen only a single page is scrolled') + }, + 'prev_screen': { + 'short': _('Previous screen'), + 'long': _('Scroll to the previous screen, if more than one page is displayed on the screen all pages are scrolled') + }, + 'next_screen': { + 'short': _('Next screen'), + 'long': _('Scroll to the next screen, if more than one page is displayed on the screen all pages are scrolled') + }, + 'show_chrome': { + 'short': _('Show controls'), + 'long': _('Show the controls for the viewer including Preferences, Go to, etc.') + }, + 'highlight_or_inspect': { + 'short': _('Highlight or inspect'), + 'long': _('Highlight the word under the tap point or') + }, + 'decrease_font_size': { + 'short': _('Make text smaller'), + 'long': _('Decrease the font size') + }, + 'increase_font_size': { + 'short': _('Make text bigger'), + 'long': _('Increase the font size') + }, + 'next_section': { + 'short': _('Next chapter'), + 'long': _('Scroll to the start of the next section or chapter in the book') + }, + 'prev_section': { + 'short': _('Previous chapter'), + 'long': _('Scroll to the start of the previous section or chapter in the book') + }, + 'pan': { + 'short': _('Pan the contents'), + 'long': _('Scroll the screen contents in tandem with finger movement') + }, + 'animated_scroll': { + 'short': _('Flick scroll'), + 'long': _('Scroll the screen contents with momentum based on how fast you flick your finger') + }, + 'none': { + 'short': _('No action'), + 'long': _('Ignore this gesture performing no action in response to it') + }, + } + return get_action_descriptions.ans + +only_flow_swipe_mode_actions = {'pan': True, 'animated_scroll': True} +only_tap_actions = {'highlight_or_inspect': True} default_actions_for_gesture = { 'common': { From 332d3aa944759286ff14fb8effefcba335be4b3b Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 11 Jun 2023 12:35:26 +0100 Subject: [PATCH 0810/2055] Bug 2023459: Duplicate user-category items after merging in tag category editor --- src/calibre/gui2/tag_browser/model.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/tag_browser/model.py b/src/calibre/gui2/tag_browser/model.py index de73096f13..622b276b10 100644 --- a/src/calibre/gui2/tag_browser/model.py +++ b/src/calibre/gui2/tag_browser/model.py @@ -1481,13 +1481,21 @@ class TagsModel(QAbstractItemModel): # {{{ ''' user_cats = self.db.new_api.pref('user_categories', {}) for k in user_cats.keys(): - new_contents = [] - for tup in user_cats[k]: - if tup[0] == item_name and tup[1] == item_category: - new_contents.append([new_name, item_category, 0]) - else: - new_contents.append(tup) - user_cats[k] = new_contents + ucat = {n:c for n,c,_ in user_cats[k]} + # Check if the new name with the same category already exists. If + # so, remove the old name because it would be a duplicate. This can + # happen if two items in the item_category were renamed to the same + # name. + if ucat.get(new_name, None) == item_category: + if ucat.pop(item_name, None) is not None: + # Only update the user_cats when something changes + user_cats[k] = list([(n, c, 0) for n, c in ucat.items()]) + elif ucat.get(item_name, None) == item_category: + # If the old name/item_category exists, rename it to the new + # name using del/add + del ucat[item_name] + ucat[new_name] = item_category + user_cats[k] = list([(n, c, 0) for n, c in ucat.items()]) self.db.new_api.set_pref('user_categories', user_cats) def delete_item_from_all_user_categories(self, item_name, item_category): From 205c8e1f95d18e38007015bdc74333dfa2ce60fa Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 11 Jun 2023 18:02:05 +0530 Subject: [PATCH 0811/2055] Update Guardian & Observer --- recipes/guardian.recipe | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/recipes/guardian.recipe b/recipes/guardian.recipe index c990354fac..bab2a67b86 100644 --- a/recipes/guardian.recipe +++ b/recipes/guardian.recipe @@ -102,9 +102,29 @@ class Guardian(BasicNewsRecipe): return cover - def parse_section(self, url, title_prefix=''): + def parse_observer_index(self, soup): + for section in soup.findAll('section'): + articles = [] + title = self.tag_to_string(section.find('h2')) + if not title: + continue + self.log('Found section:', title) + for li in section.findAll('li'): + a = li.find('a', attrs={'href': True, 'aria-label': True}) + if a: + url = a['href'] + if url.startswith('/'): + url = self.base_url.rpartition('/')[0] + url + self.log('\t', a['aria-label'], url) + articles.append({'title': a['aria-label'], 'url': url}) + if articles: + yield title, articles + + def parse_section(self, section_url, title_prefix=''): feeds = [] - soup = self.index_to_soup(url) + soup = self.index_to_soup(section_url) + if '/observer' in section_url: + return list(self.parse_observer_index(soup)) for section in soup.findAll('section'): title = title_prefix + self.tag_to_string(section.find( attrs={'class': 'fc-container__header__title'})).strip().capitalize() @@ -117,6 +137,8 @@ class Guardian(BasicNewsRecipe): for a in li.findAll('a', attrs={'data-link-name': 'article'}, href=True): title = self.tag_to_string(a).strip() url = a['href'] + if url.startswith('/'): + url = self.base_url.rpartition('/')[0] + url self.log(' ', title, url) feeds[-1][1].append({'title': title, 'url': url}) break From 97ad986acea5955d3f93bec8eec92489d05e4a59 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 11 Jun 2023 18:24:24 +0530 Subject: [PATCH 0812/2055] Fix #2023476 [Linux error in new trash management](https://bugs.launchpad.net/calibre/+bug/2023476) --- src/calibre/utils/copy_files.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index 66948e92c6..62282b8939 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -49,7 +49,8 @@ class UnixFileCopier: os.link(src_path, dest_path, follow_symlinks=False) shutil.copystat(src_path, dest_path, follow_symlinks=False) continue - shutil.copy2(src_path, dest_path, follow_symlinks=False) + with suppress(shutil.SameFileError): + shutil.copy2(src_path, dest_path, follow_symlinks=False) def delete_all_source_files(self) -> None: for src_path in self.copy_map: From 748ffc7fb344af9c61d2347ad75e897fee37fc7a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Jun 2023 08:00:19 +0530 Subject: [PATCH 0813/2055] E-book viewer: Allow configuring the actions triggered by touch gestures. Fixes #2023367 [Feature Request: Customizable Touchscreen Behaviors in Content Server's Web Viewer](https://bugs.launchpad.net/calibre/+bug/2023367) --- src/pyj/read_book/gestures.pyj | 39 ++++--- src/pyj/read_book/prefs/main.pyj | 9 ++ src/pyj/read_book/prefs/touch.pyj | 170 ++++++++++++++++++++++++++++++ src/pyj/read_book/touch.pyj | 26 ++--- 4 files changed, 220 insertions(+), 24 deletions(-) create mode 100644 src/pyj/read_book/prefs/touch.pyj diff --git a/src/pyj/read_book/gestures.pyj b/src/pyj/read_book/gestures.pyj index 642af79c73..6dd53be9d8 100644 --- a/src/pyj/read_book/gestures.pyj +++ b/src/pyj/read_book/gestures.pyj @@ -32,7 +32,7 @@ def get_action_descriptions(): }, 'highlight_or_inspect': { 'short': _('Highlight or inspect'), - 'long': _('Highlight the word under the tap point or') + 'long': _('Highlight the word under the tap point or view the image under the tap or select the highlight under the tap') }, 'decrease_font_size': { 'short': _('Make text smaller'), @@ -67,14 +67,14 @@ def get_action_descriptions(): only_flow_swipe_mode_actions = {'pan': True, 'animated_scroll': True} only_tap_actions = {'highlight_or_inspect': True} - default_actions_for_gesture = { 'common': { GESTURE.back_zone_tap: 'prev_page', GESTURE.forward_zone_tap: 'next_page', GESTURE.control_zone_tap: 'show_chrome', - GESTURE.two_finger_tap: 'show_chrome', GESTURE.long_tap: 'highlight_or_inspect', + + GESTURE.two_finger_tap: 'show_chrome', GESTURE.pinch_in: 'decrease_font_size', GESTURE.pinch_out: 'increase_font_size', }, @@ -98,15 +98,30 @@ default_actions_for_gesture = { }, } -def action_for_gesture(gesture, in_flow_mode): - overrides = opts.gesture_overrides +def current_action_for_gesture_type(overrides, gesture_type, in_flow_mode): mode = 'flow_mode' if in_flow_mode else 'paged_mode' mode_overrides = overrides[mode] or {} - if mode_overrides[gesture.type]: - return mode_overrides[gesture.type] + if mode_overrides[gesture_type]: + return mode_overrides[gesture_type] common_overrides = overrides.common or {} - if common_overrides[gesture.type]: - return common_overrides[gesture.type] - if default_actions_for_gesture[mode][gesture.type]: - return default_actions_for_gesture[mode][gesture.type] - return default_actions_for_gesture.common[gesture.type] or 'none' + if common_overrides[gesture_type]: + return common_overrides[gesture_type] + if default_actions_for_gesture[mode][gesture_type]: + return default_actions_for_gesture[mode][gesture_type] + return default_actions_for_gesture.common[gesture_type] or 'none' + +def action_for_gesture(gesture, in_flow_mode): + return current_action_for_gesture_type(opts.gesture_overrides, gesture.type, in_flow_mode) + +def allowed_actions_for_tap(): + return [ac for ac in Object.keys(get_action_descriptions()) if not only_flow_swipe_mode_actions[ac]] + +def allowed_actions_for_paged_mode_swipe(): + return [ac for ac in Object.keys(get_action_descriptions()) if not only_flow_swipe_mode_actions[ac] and not only_tap_actions[ac]] +allowed_actions_for_two_fingers = allowed_actions_for_paged_mode_swipe + +def allowed_actions_for_flow_mode_flick(): + return [ac for ac in Object.keys(get_action_descriptions()) if not only_tap_actions[ac]] + +def allowed_actions_for_flow_mode_drag(): + return ['pan', 'none'] diff --git a/src/pyj/read_book/prefs/main.pyj b/src/pyj/read_book/prefs/main.pyj index dd2947eab3..0f297f3283 100644 --- a/src/pyj/read_book/prefs/main.pyj +++ b/src/pyj/read_book/prefs/main.pyj @@ -17,6 +17,7 @@ from read_book.prefs.layout import commit_layout, create_layout_panel from read_book.prefs.misc import commit_misc, create_misc_panel from read_book.prefs.scrolling import commit_scrolling, create_scrolling_panel from read_book.prefs.selection import commit_selection, create_selection_panel +from read_book.prefs.touch import commit_touch, create_touch_panel from read_book.prefs.user_stylesheet import ( commit_user_stylesheet, create_user_stylesheet_panel ) @@ -106,6 +107,7 @@ class Prefs: ci(_('Headers and footers'), 'head_foot', _('Customize the headers and footers')) ci(_('Scrolling behavior'), 'scrolling', _('Control how the viewer scrolls')) ci(_('Selection behavior'), 'selection', _('Control how the viewer selects text')) + ci(_('Touch behavior'), 'touch', _('Customize what various touchscreen gestures do')) ci(_('Keyboard shortcuts'), 'keyboard', _('Customize the keyboard shortcuts')) if runtime.is_standalone_viewer: ci(_('Fonts'), 'fonts', _('Font choices')) @@ -166,6 +168,13 @@ class Prefs: def close_selection(self): commit_selection(self.onchange, self.container) + def display_touch(self, container): + self.create_panel(container, 'touch', create_touch_panel) + + def close_touch(self): + commit_touch(self.onchange, self.container) + + def create_prefs_panel(container, close_func, on_change): return Prefs(container, close_func, on_change) diff --git a/src/pyj/read_book/prefs/touch.pyj b/src/pyj/read_book/prefs/touch.pyj new file mode 100644 index 0000000000..61a3e8f7c0 --- /dev/null +++ b/src/pyj/read_book/prefs/touch.pyj @@ -0,0 +1,170 @@ +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2016, Kovid Goyal +from __python__ import bound_methods, hash_literals + +from elementmaker import E + +from book_list.globals import get_session_data +from dom import unique_id +from gettext import gettext as _ +from read_book.gestures import ( + allowed_actions_for_flow_mode_drag, allowed_actions_for_flow_mode_flick, + allowed_actions_for_paged_mode_swipe, allowed_actions_for_tap, + allowed_actions_for_two_fingers, current_action_for_gesture_type, + get_action_descriptions +) +from read_book.prefs.utils import create_button_box +from read_book.touch import GESTURE, GESTURE_NAMES + +CONTAINER = unique_id('touch-settings') + +def restore_defaults(): + apply_settings_to_ui({}) + +def get_container(): + return document.getElementById(CONTAINER) + + +def apply_settings_to_ui(overrides): + overrides = overrides or {} + for group in get_container().querySelectorAll('[data-group]'): + group_name = group.dataset.group + in_flow_mode = group_name.indexOf('flow') >= 0 + for select in group.querySelectorAll('select'): + allowed_actions = v'[]' + for option in select.querySelectorAll('option'): + allowed_actions.push(option.value) + gesture_type = select.name + current_action = current_action_for_gesture_type(overrides, gesture_type, in_flow_mode) + if allowed_actions.indexOf(current_action) < 0: + current_action = current_action_for_gesture_type({}, gesture_type, in_flow_mode) + if not current_action or allowed_actions.indexOf(current_action) < 0: + current_action = 'none' + select.value = current_action + +def get_overrides_from_ui(): + ans = {} + for group in get_container().querySelectorAll('[data-group]'): + group_name = group.dataset.group + in_flow_mode = group_name.indexOf('flow') >= 0 + if group_name is 'paged_swipe': + attr = 'paged_mode' + elif group_name is 'flow_swipe': + attr = 'flow_mode' + else: + attr = 'common' + if not ans[attr]: + ans[attr] = {} + for select in group.querySelectorAll('select'): + val = select.value + defval = current_action_for_gesture_type({}, select.name, in_flow_mode) + if val is not defval: + ans[attr][select.name] = val + for which in Object.keys(ans): + if Object.keys(ans[which]).length is 0: + v'delete ans[which]' + return ans + + +def create_touch_panel(container, apply_func, cancel_func): + container.appendChild(E.div(id=CONTAINER, style='margin: 1rem')) + container = container.lastChild + sd = get_session_data() + overrides = sd.get('gesture_overrides') + action_descriptions = get_action_descriptions() + in_flow_mode = False + + def on_select_change(ev): + select = ev.target + ad = action_descriptions[select.value] + span = select.parentNode.querySelector('span') + span.textContent = ad.long + + def make_setting(gesture_type, allowed_actions): + ans = E.div(style='margin-top: 1ex') + title = GESTURE_NAMES[gesture_type] + sid = unique_id(gesture_type) + ans.appendChild(E.h4(E.label(title, 'for'=sid))) + select = E.select(name=gesture_type, id=sid) + for action in allowed_actions: + ad = action_descriptions[action] + select.appendChild(E.option(ad.short, value=action)) + select.addEventListener('change', on_select_change) + ans.appendChild(E.div(select, '\xa0', E.span(style='font-size: smaller; font-style: italic'))) + on_select_change({'target': select}) + return ans + + container.appendChild(E.h2(_('Tapping'))) + container.appendChild(E.div(_( + 'There are three tap zones, depending on where on the screen you tap. When the tap is' + ' on a link, the link is followed, otherwise a configurable action based on the zone is performed.'))) + c = E.div('data-group'='tap') + container.appendChild(c) + aat = allowed_actions_for_tap() + c.appendChild(make_setting(GESTURE.control_zone_tap, aat)) + c.appendChild(make_setting(GESTURE.forward_zone_tap, aat)) + c.appendChild(make_setting(GESTURE.back_zone_tap, aat)) + c.appendChild(make_setting(GESTURE.long_tap, aat)) + container.appendChild(E.hr()) + + container.appendChild(E.h2(_('Two finger gestures'))) + c = E.div('data-group'='two_finger') + container.appendChild(c) + aat = allowed_actions_for_two_fingers() + c.appendChild(make_setting(GESTURE.two_finger_tap, aat)) + c.appendChild(make_setting(GESTURE.pinch_in, aat)) + c.appendChild(make_setting(GESTURE.pinch_out, aat)) + container.appendChild(E.hr()) + + + container.appendChild(E.h2(_('Swiping'))) + container.appendChild(E.div(_( + 'Swiping works differently in paged and flow mode, with different actions. For an English like language, swiping in' + ' the writing direction means swiping horizontally. For languages written vertically, it means swiping vertically.' + ' For languages written left-to-right "going forward" means swiping right-to-left, like turning a page with your finger.' + ))) + container.appendChild(E.h3(_('Swiping in paged mode'), style='padding-top: 1ex')) + c = E.div('data-group'='paged_swipe') + container.appendChild(c) + aap = allowed_actions_for_paged_mode_swipe() + c.appendChild(make_setting(GESTURE.flick_block_forward, aap)) + c.appendChild(make_setting(GESTURE.flick_block_backward, aap)) + c.appendChild(make_setting(GESTURE.flick_inline_forward, aap)) + c.appendChild(make_setting(GESTURE.flick_inline_backward, aap)) + c.appendChild(make_setting(GESTURE.swipe_inline_forward_hold, aap)) + c.appendChild(make_setting(GESTURE.swipe_inline_backward_hold, aap)) + c.appendChild(make_setting(GESTURE.swipe_block_forward_hold, aap)) + c.appendChild(make_setting(GESTURE.swipe_block_backward_hold, aap)) + container.appendChild(E.hr()) + container.appendChild(E.h3(_('Swiping in flow mode'), style='padding-top: 1ex')) + c = E.div('data-group'='flow_swipe') + container.appendChild(c) + in_flow_mode = True + in_flow_mode + aaf = allowed_actions_for_flow_mode_flick() + c.appendChild(make_setting(GESTURE.flick_block_forward, aaf)) + c.appendChild(make_setting(GESTURE.flick_block_backward, aaf)) + c.appendChild(make_setting(GESTURE.flick_inline_forward, aaf)) + c.appendChild(make_setting(GESTURE.flick_inline_backward, aaf)) + aaf = allowed_actions_for_flow_mode_drag() + c.appendChild(make_setting(GESTURE.swipe_inline_backward_in_progress, aaf)) + c.appendChild(make_setting(GESTURE.swipe_inline_forward_in_progress, aaf)) + c.appendChild(make_setting(GESTURE.swipe_block_backward_in_progress, aaf)) + c.appendChild(make_setting(GESTURE.swipe_block_forward_in_progress, aaf)) + + container.appendChild(E.hr()) + container.appendChild(create_button_box(restore_defaults, apply_func, cancel_func)) + apply_settings_to_ui(overrides) + + +develop = create_touch_panel + + +def commit_touch(onchange): + sd = get_session_data() + current_overrides = sd.get('gesture_overrides') + overrides = get_overrides_from_ui() + changed = overrides != current_overrides + if changed: + sd.set('gesture_overrides', overrides) + onchange() diff --git a/src/pyj/read_book/touch.pyj b/src/pyj/read_book/touch.pyj index 9c968931a2..a2dd1fd027 100644 --- a/src/pyj/read_book/touch.pyj +++ b/src/pyj/read_book/touch.pyj @@ -21,18 +21,20 @@ GESTURE_NAMES = { 'pinch_in': _('Pinch in'), 'pinch_out': _('Pinch out'), - 'flick_inline_backward': _('Flick in writing direction, backwards'), - 'flick_inline_forward': _('Flick in writing direction, forwards'), - 'flick_block_backward': _('Flick perpendicular to writing direction, backwards'), - 'flick_block_forward': _('Flick perpendicular to writing direction, forwards'), - 'swipe_inline_backward_in_progress': _('Swipe in writing direction, backwards, in-progress'), - 'swipe_inline_forward_in_progress': _('Swipe in writing direction, forwards, in-progress'), - 'swipe_block_backward_in_progress': _('Swipe perpendicular to writing direction, backwards, in-progress'), - 'swipe_block_forward_in_progress': _('Swipe perpendicular to writing direction, forwards, in-progress'), - 'swipe_inline_backward_hold': _('Swipe in writing direction, backwards and hold'), - 'swipe_inline_forward_hold': _('Swipe in writing direction, forwards and hold'), - 'swipe_block_backward_hold': _('Swipe perpendicular to writing direction, backwards and hold'), - 'swipe_block_forward_hold': _('Swipe perpendicular to writing direction, forwards and hold'), + 'flick_inline_backward': _('Flick in writing direction, to go back'), + 'flick_inline_forward': _('Flick in writing direction, to go forward'), + 'flick_block_backward': _('Flick perpendicular to writing direction, to go forward'), + 'flick_block_forward': _('Flick perpendicular to writing direction, to go back'), + + 'swipe_inline_backward_in_progress': _('Drag finger in writing direction, to go back'), + 'swipe_inline_forward_in_progress': _('Drag finger in writing direction, to go forward'), + 'swipe_block_backward_in_progress': _('Drag finger perpendicular to writing direction, to go back'), + 'swipe_block_forward_in_progress': _('Drag finger perpendicular to writing direction, to go forward'), + + 'swipe_inline_backward_hold': _('Drag and hold finger in writing direction, to go back'), + 'swipe_inline_forward_hold': _('Drag and hold finger in writing direction, to go forward'), + 'swipe_block_backward_hold': _('Drag and hold finger perpendicular to writing direction, to go back'), + 'swipe_block_forward_hold': _('Drag and hold finger perpendicular to writing direction, to go forward'), } GESTURE = {k:k for k in Object.keys(GESTURE_NAMES)} GESTURE.tap = 'tap' From 32acefedf2f7cf5c0e3b835b0afbd9aeb85f2ff2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Jun 2023 11:40:11 +0530 Subject: [PATCH 0814/2055] Windows: Fix a regression in the previous release that could cause files to be deleted if one of the files/folders was locked ina nother program while changing title/author in calibre --- src/calibre/utils/copy_files.py | 45 +++++++++++++++++++++------ src/calibre/utils/copy_files_test.py | 23 ++++++++++---- src/calibre/utils/windows/winutil.cpp | 16 ++++++++++ 3 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index 62282b8939..2025aeebcd 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -90,11 +90,12 @@ class WindowsFileCopier: def _open_file(self, path: str, retry_on_sharing_violation: bool = True, is_folder: bool = False) -> 'winutil.Handle': flags = winutil.FILE_FLAG_BACKUP_SEMANTICS if is_folder else winutil.FILE_FLAG_SEQUENTIAL_SCAN + access_flags = winutil.GENERIC_READ if self.delete_all: - flags |= winutil.FILE_FLAG_DELETE_ON_CLOSE + access_flags |= winutil.DELETE + share_flags = winutil.FILE_SHARE_DELETE if self.allow_move else 0 try: - return winutil.create_file(make_long_path_useable(path), winutil.GENERIC_READ, - winutil.FILE_SHARE_DELETE if self.allow_move else 0, winutil.OPEN_EXISTING, flags) + return winutil.create_file(make_long_path_useable(path), access_flags, share_flags, winutil.OPEN_EXISTING, flags) except OSError as e: if e.winerror == winutil.ERROR_SHARING_VIOLATION: # The file could be a hardlink to an already opened file, @@ -106,13 +107,12 @@ class WindowsFileCopier: if retry_on_sharing_violation: time.sleep(WINDOWS_SLEEP_FOR_RETRY_TIME) return self._open_file(path, False, is_folder) - err = IOError(errno.EACCES, - _('File is open in another program')) + err = IOError(errno.EACCES, _('File {} is open in another program').format(path)) err.filename = path raise err from e raise - def __enter__(self) -> None: + def open_all_handles(self) -> None: for path, file_id in self.path_to_fileid_map.items(): self.fileid_to_paths_map[file_id].add(path) for src in self.copy_map: @@ -120,11 +120,36 @@ class WindowsFileCopier: for path in self.folders: self.folder_to_handle_map[path] = self._open_file(path, is_folder=True) + def __enter__(self) -> None: + try: + self.open_all_handles() + except OSError: + self.close_all_handles() + raise + + def close_all_handles(self, delete_on_close: bool = False) -> None: + while self.path_to_handle_map: + path, h = next(iter(self.path_to_handle_map.items())) + if delete_on_close: + winutil.set_file_handle_delete_on_close(h, True) + h.close() + self.path_to_handle_map.pop(path) + while self.folder_to_handle_map: + path, h = next(reversed(self.folder_to_handle_map.items())) + if delete_on_close: + try: + winutil.set_file_handle_delete_on_close(h, True) + except OSError as err: + # Ignore dir not empty errors. Should never happen but we + # ignore it as the UNIX semantics are to no delete folders + # during __exit__ anyway and we dont want to leak the handle. + if err.winerror != winutil.ERROR_DIR_NOT_EMPTY: + raise + h.close() + self.folder_to_handle_map.pop(path) + def __exit__(self, exc_type, exc_val, exc_tb) -> None: - for h in self.path_to_handle_map.values(): - h.close() - for h in reversed(self.folder_to_handle_map.values()): - h.close() + self.close_all_handles(delete_on_close=self.delete_all and exc_val is None) def copy_all(self) -> None: for src_path, dest_path in self.copy_map.items(): diff --git a/src/calibre/utils/copy_files_test.py b/src/calibre/utils/copy_files_test.py index a73a103cb4..7b201eaca5 100644 --- a/src/calibre/utils/copy_files_test.py +++ b/src/calibre/utils/copy_files_test.py @@ -95,16 +95,27 @@ class TestCopyFiles(unittest.TestCase): self.reset() src, dest = self.s(), self.d() if iswindows: - with open(self.s('sub/a')) as locked: + os.mkdir(self.s('lockdir')) + open(self.s('lockdir/lockfile'), 'w').close() + before = frozenset(walk(src)) + with open(self.s('lockdir/lockfile')) as locked: locked - self.assertRaises(IOError, copy_tree, src, dest) - self.ae(os.listdir(self.d()), ['sub']) + self.assertRaises(IOError, copy_tree, src, dest, delete_source=True) + self.ae(set(os.listdir(self.d())), {'sub', 'lockdir'}) self.assertFalse(tuple(walk(self.d()))) - h = winutil.create_file(self.s('sub'), winutil.GENERIC_READ, 0, winutil.OPEN_EXISTING, winutil.FILE_FLAG_BACKUP_SEMANTICS) + self.ae(before, frozenset(walk(src)), 'Source files were deleted despite there being an error') + + shutil.rmtree(dest) + os.mkdir(dest) + h = winutil.create_file( + self.s('lockdir'), winutil.GENERIC_READ|winutil.GENERIC_WRITE|winutil.DELETE, + winutil.FILE_SHARE_READ|winutil.FILE_SHARE_WRITE|winutil.FILE_SHARE_DELETE, winutil.OPEN_EXISTING, + winutil.FILE_FLAG_BACKUP_SEMANTICS) with closing(h): - self.assertRaises(IOError, copy_tree, src, dest) - self.ae(os.listdir(self.d()), ['sub']) + self.assertRaises(IOError, copy_tree, src, dest, delete_source=True) + self.ae(set(os.listdir(self.d())), {'sub', 'lockdir'}) self.assertFalse(tuple(walk(self.d()))) + self.ae(before, frozenset(walk(src)), 'Source files were deleted despite there being an error') def find_tests(): return unittest.defaultTestLoader.loadTestsFromTestCase(TestCopyFiles) diff --git a/src/calibre/utils/windows/winutil.cpp b/src/calibre/utils/windows/winutil.cpp index 6f7a5dab4f..7295a2886c 100644 --- a/src/calibre/utils/windows/winutil.cpp +++ b/src/calibre/utils/windows/winutil.cpp @@ -490,6 +490,17 @@ winutil_set_file_pointer(PyObject *self, PyObject *args) { return PyLong_FromLongLong(ans.QuadPart); } +static PyObject* +winutil_set_file_handle_delete_on_close(PyObject *self, PyObject *args) { + HANDLE handle; + int delete_on_close; + if (!PyArg_ParseTuple(args, "O&p", convert_handle, &handle, &delete_on_close)) return NULL; + FILE_DISPOSITION_INFO di; + di.DeleteFile = delete_on_close; + if (!SetFileInformationByHandle(handle, FileDispositionInfo, &di, sizeof(FILE_DISPOSITION_INFO))) return set_error_from_handle(args); + Py_RETURN_NONE; +} + static PyObject* winutil_create_file(PyObject *self, PyObject *args) { wchar_raii path; @@ -1255,6 +1266,10 @@ static PyMethodDef winutil_methods[] = { "set_file_pointer(handle, pos, method=FILE_BEGIN)\n\nWrapper for SetFilePointer" }, + {"set_file_handle_delete_on_close", (PyCFunction)winutil_set_file_handle_delete_on_close, METH_VARARGS, + "set_file_handle_delete_on_close(handle, delete_on_close)\n\nSet the delete on close flag on the specified handle. Note only works if CreateFile() is called with DELETE generic access, and without FILE_FLAG_DELETE_ON_CLOSE." + }, + {"read_file", (PyCFunction)winutil_read_file, METH_VARARGS, "read_file(handle, chunk_size=16KB)\n\nWrapper for ReadFile" }, @@ -1467,6 +1482,7 @@ exec_module(PyObject *m) { A(ERROR_ALREADY_EXISTS); A(ERROR_BROKEN_PIPE); A(ERROR_PIPE_BUSY); + A(ERROR_DIR_NOT_EMPTY); A(NormalHandle); A(ModuleHandle); A(IconHandle); From 565b86b853d9978255d362ff4dff38b95e90e798 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Jun 2023 12:22:35 +0530 Subject: [PATCH 0815/2055] ... --- src/calibre/utils/copy_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index 2025aeebcd..9ef2047075 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -141,7 +141,7 @@ class WindowsFileCopier: winutil.set_file_handle_delete_on_close(h, True) except OSError as err: # Ignore dir not empty errors. Should never happen but we - # ignore it as the UNIX semantics are to no delete folders + # ignore it as the UNIX semantics are to not delete folders # during __exit__ anyway and we dont want to leak the handle. if err.winerror != winutil.ERROR_DIR_NOT_EMPTY: raise From fdf95531e089b952b288ffe65f4a63b130bd0dd4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Jun 2023 13:50:26 +0530 Subject: [PATCH 0816/2055] macOS: Fix extra dock icons visible when doing a job using Qt WebEngine such as converting to PDF or searching in Get books. Fixes #2023395 [Multiple Dock Icons When Searching for Books](https://bugs.launchpad.net/calibre/+bug/2023395) Apparently a recent macOS update changed some behavior. Now we need to have LSBackgroundOnly=1 in the Info.plist for calibre-parallel otherwise when using Qt we get a dock icon for the process. Sigh. --- bypy/macos/__main__.py | 11 ++++++++++- src/calibre/utils/ipc/launch.py | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/bypy/macos/__main__.py b/bypy/macos/__main__.py index 4ac6831aba..c3a8924d00 100644 --- a/bypy/macos/__main__.py +++ b/bypy/macos/__main__.py @@ -742,15 +742,24 @@ class Freeze: e = plist['CFBundleDocumentTypes'][0] e['CFBundleTypeExtensions'] = [x.lower() for x in formats] + def headless_plist(plist): + plist['CFBundleDisplayName'] = 'calibre worker process' + plist['CFBundleExecutable'] = 'calibre-parallel' + plist['CFBundleIdentifier'] = 'com.calibre-ebook.calibre-parallel' + plist['LSBackgroundOnly'] = '1' + plist.pop('CFBundleDocumentTypes') + self.create_app_clone('ebook-viewer.app', partial(specialise_plist, 'ebook-viewer', input_formats)) self.create_app_clone('ebook-edit.app', partial(specialise_plist, 'ebook-edit', edit_formats), base_dir=join(self.contents_dir, 'ebook-viewer.app', 'Contents')) + self.create_app_clone('headless.app', headless_plist, + base_dir=join(self.contents_dir, 'ebook-viewer.app', 'Contents', 'ebook-edit.app', 'Contents')) # We need to move the webengine resources into the deepest sub-app # because the sandbox gets set to the nearest enclosing app which # means that WebEngine will fail to access its resources when running # in the sub-apps unless they are present inside the sub app bundle # somewhere - base_dest = join(self.contents_dir, 'ebook-viewer.app', 'Contents', 'ebook-edit.app', 'Contents', 'SharedSupport') + base_dest = join(self.contents_dir, 'ebook-viewer.app', 'Contents', 'ebook-edit.app', 'Contents', 'headless.app', 'Contents', 'SharedSupport') os.mkdir(base_dest) base_src = os.path.realpath(join(self.frameworks_dir, 'QtWebEngineCore.framework/Resources')) items = [join(base_src, 'qtwebengine_locales')] + glob.glob(join(base_src, '*.pak')) + glob.glob(join(base_src, '*.dat')) diff --git a/src/calibre/utils/ipc/launch.py b/src/calibre/utils/ipc/launch.py index a48178f048..7fdf1889df 100644 --- a/src/calibre/utils/ipc/launch.py +++ b/src/calibre/utils/ipc/launch.py @@ -73,6 +73,9 @@ class Worker: @property def executable(self): + if ismacos and not hasattr(sys, 'running_from_setup'): + base = os.path.dirname(sys.executables_location) + return os.path.join(base, 'ebook-viewer.app/Contents/ebook-edit.app/Contents/headless.app/Contents/MacOS', self.exe_name) return exe_path(self.exe_name) @property From ab4f3c8fdf69911cb55629872e4d5c031869d7ef Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Jun 2023 13:57:20 +0530 Subject: [PATCH 0817/2055] Fix order of fake protocol registration in standalone ToC editor --- src/calibre/gui2/toc/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py index 476ea6512c..6c9734c4b8 100644 --- a/src/calibre/gui2/toc/main.py +++ b/src/calibre/gui2/toc/main.py @@ -1207,8 +1207,8 @@ def main(shm_name=None): override = 'calibre-gui' if islinux else None app = Application([], override_program_name=override) from calibre.utils.webengine import setup_default_profile, setup_fake_protocol - setup_default_profile() setup_fake_protocol() + setup_default_profile() d = TOCEditor(path, title=title, write_result_to=path + '.result') d.start() ok = 0 From f956f4a207451dcbab1aaf5324571c8cd8962f56 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Mon, 12 Jun 2023 11:25:06 +0100 Subject: [PATCH 0818/2055] Enhancement #2023514: Add columns: More descriptive tooltip for 'contains names' --- src/calibre/gui2/preferences/create_custom_column.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/preferences/create_custom_column.py b/src/calibre/gui2/preferences/create_custom_column.py index cc2cfe82a0..50a2b8aed1 100644 --- a/src/calibre/gui2/preferences/create_custom_column.py +++ b/src/calibre/gui2/preferences/create_custom_column.py @@ -326,7 +326,12 @@ class CreateCustomColumn(QDialog): "Everything else will show nothing.")) h.addWidget(ud) self.is_names = ins = QCheckBox(_("Contains names"), self) - ins.setToolTip(_("Check this box if this column contains names, like the authors column.")) + ins.setToolTip('

' + _('Check this box if this column contains names, ' + 'like the authors column. If checked, the item separator will be an ampersand ' + '(&) instead of a comma (,), sorting will be done using a computed value ' + 'that respects the author sort tweaks (for example converting "Firstname ' + 'Lastname" into "Lastname, Firstname"), and item order will be ' + 'preserved.')+'

') h.addWidget(ins) add_row(_("&Column type:"), h) From 2e49369657ced0144f990ca71f58bd5af7749152 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Jun 2023 16:47:43 +0530 Subject: [PATCH 0819/2055] Unix: Ignore failure to copy file metadata See #2023476 (Linux error in new trash management) --- src/calibre/utils/copy_files.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index 9ef2047075..7adcd12c3b 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -47,7 +47,12 @@ class UnixFileCopier: for src_path, dest_path in self.copy_map.items(): with suppress(OSError): os.link(src_path, dest_path, follow_symlinks=False) - shutil.copystat(src_path, dest_path, follow_symlinks=False) + try: + shutil.copystat(src_path, dest_path, follow_symlinks=False) + except OSError: + # Failure to copy metadata is not critical + import traceback + traceback.print_exc() continue with suppress(shutil.SameFileError): shutil.copy2(src_path, dest_path, follow_symlinks=False) From 15f68d97da2f818e01d4559a7af653e20fd53e6c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Jun 2023 17:08:45 +0530 Subject: [PATCH 0820/2055] ... --- src/calibre/utils/copy_files_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/utils/copy_files_test.py b/src/calibre/utils/copy_files_test.py index 7b201eaca5..9d884de31e 100644 --- a/src/calibre/utils/copy_files_test.py +++ b/src/calibre/utils/copy_files_test.py @@ -80,6 +80,7 @@ class TestCopyFiles(unittest.TestCase): copy_tree(src, dest, delete_source=True) self.ae(set(os.listdir(self.tdir)), {'dest', 'base'}) self.ae(nlinks_file(self.d('one')), 1) + self.assertFalse(os.path.exists(src)) def transform_destination_filename(src, dest): return dest + '.extra' From 20011f4b51e5649194d2c435a2e3e7a56f4ca2ea Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Mon, 12 Jun 2023 20:29:28 +0530 Subject: [PATCH 0821/2055] The India Forum recipe and fix for Live Mint. --- recipes/icons/theindiaforum.png | Bin 0 -> 1224 bytes recipes/livemint.recipe | 2 - recipes/theindiaforum.recipe | 71 ++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 recipes/icons/theindiaforum.png create mode 100644 recipes/theindiaforum.recipe diff --git a/recipes/icons/theindiaforum.png b/recipes/icons/theindiaforum.png new file mode 100644 index 0000000000000000000000000000000000000000..a977252549e207743aadf08e40f7ebc20dfcced9 GIT binary patch literal 1224 zcmV;(1ULJMP)w=BUHjv&!B8RF9ju(?Wl&4_%e8#@ol&=e5k= z08@?tRgbX7+&p`!08fi@oxrch+r`)BwaecNU6lY?k^oGJ1yztjeyadhkN`Y>roq{$ z!q=_E+or+TY?;0QM}`1VjR94V078QRLV~c!-K4c-gT#n|V@*yhIB=f~LQ$J*!0-08H-->t{n zuE*Q2$lS5X-LT2stj621$=tKb-KN3Uti;;L+3BOc)}+4GrN7p$#oMF5*2dT8#MkDs z$=$2Q+N;Ids>Rx>#@e*Y-pSkPs>Ru?#@epO+_B2tvdZ1I&EUh=<;vRW#n|S>*X7FF z=x&<7k+IE!sK$Vti{@;z}8ECtdg?MGH#*24ah41 z0004WQchCx>ukUySm!bB0uCNK#F1|TaLV6p`$oH7-dHhso# zU?yhu++nr_m{T-&9_BB|U$_X1O<+kEz%sy6E3j;NaR~}m6s}waMO74KwYmmta}=R< zz*j`b&L$BJ|R~_6{9U(>Oz|q}%qLIj6 zU|-bl_eb~t0gz)FDOQ8pI?lPK9iYZ>q54{TKr*XT1KliZz1e9d#Y*jg9%VK9U;=J? zAZP*&sK@|u6FAr_;OIJ1_v2$sN!SA#mr1b(gjEs9b#x@UJLgK+9%yVzrc#Hh590_+ z9!-&ez_F(0mg6V-Z1%#5lZnbx6tspg+kP^B3&bw~5E6#4o~aJU%rJT)KRv z9apbiFR=x(yC?33IY3mUn;o}0aQjYC@!ej4dnnJ8755)Jgcz_#c0keq_+u-8Cr^bx zdu|7$1rXvdP-Os;0la)g_WF(O9nwbQ-ge@h*|_(V`|wfx`D9pVlwaK7WGz64Gh_0000 timedelta(self.oldest_article): + continue + self.log('\t', title, '\n\t', desc, '\n\t\t', url) + ans.append({'title': title, 'url': url, 'description': desc}) + return ans + \ No newline at end of file From 68454456da880d3fa363841f446137e49e02f715 Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Mon, 12 Jun 2023 20:33:37 +0530 Subject: [PATCH 0822/2055] ... --- recipes/theindiaforum.recipe | 1 - 1 file changed, 1 deletion(-) diff --git a/recipes/theindiaforum.recipe b/recipes/theindiaforum.recipe index 96448b5414..e3d35e27cc 100644 --- a/recipes/theindiaforum.recipe +++ b/recipes/theindiaforum.recipe @@ -68,4 +68,3 @@ class mains(BasicNewsRecipe): self.log('\t', title, '\n\t', desc, '\n\t\t', url) ans.append({'title': title, 'url': url, 'description': desc}) return ans - \ No newline at end of file From ce8b82f8dc70e9edca4309abc523e08605254604 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Jun 2023 22:24:37 +0530 Subject: [PATCH 0823/2055] pep8 --- recipes/theindiaforum.recipe | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/recipes/theindiaforum.recipe b/recipes/theindiaforum.recipe index e3d35e27cc..de84ef46ac 100644 --- a/recipes/theindiaforum.recipe +++ b/recipes/theindiaforum.recipe @@ -13,7 +13,7 @@ class mains(BasicNewsRecipe): encoding = 'utf-8' ignore_duplicate_articles = {'url'} remove_attributes = ['height', 'width', 'style'] - no_stylesheets = True + no_stylesheets = True resolve_internal_links = True remove_empty_feeds = True use_embedded_content = False @@ -28,17 +28,17 @@ class mains(BasicNewsRecipe): def parse_index(self): soup = self.index_to_soup('https://www.theindiaforum.in/') - ul = soup.find('ul', attrs={'class':'float-left'}) - + ul = soup.find('ul', attrs={'class':'float-left'}) + section_list = [] - + for x in ul.findAll('a', href=True): if '/podcast' in x['href']: continue section_list.append( (self.tag_to_string(x).strip().replace('■','■ '), 'https://www.theindiaforum.in' + x['href']) ) - + feeds = [] for section in section_list: @@ -50,7 +50,7 @@ class mains(BasicNewsRecipe): if articles: feeds.append((section_title, articles)) return feeds - + def articles_from_soup(self, soup): ans = [] for art in soup.findAll('div', attrs={'class':lambda x: x and 'views-col' in x.split()}): From d93df6b175f96d7765de83823674b17f4ce8756f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Jun 2023 07:11:33 +0530 Subject: [PATCH 0824/2055] version 6.21.0 --- Changelog.txt | 36 ++++++++++++++++++++++++++++++++++++ src/calibre/constants.py | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Changelog.txt b/Changelog.txt index defbde9c75..4264943784 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -23,6 +23,42 @@ # - title by author # }}} +{{{ 6.21.0 2023-06-13 + +:: new features + +- DOCX Output: Add support for SVG images + + Now the generated DOCX will contain both the rasterized version of the SVG + image and the original SVG image as the preferred source, which is supported + by modern versions of Word. + +- [2023367] E-book viewer: Allow configuring the actions triggered by touch gestures + +- DOCX Input: Add support for SVG images + +:: bug fixes + +- Windows: Fix a regression in the previous release that could cause files to be deleted if one of the files/folders was open in another program while changing title/author in calibre + +- [2023395] macOS: Fix extra dock icons visible when doing a job using Qt WebEngine such as converting to PDF or searching in Get books + +- [2023476] macOS and Linux: Fix an error when changing metadata or deleting books whose files are owned by another user + +- [2023377] CHM Input: Yet another regression opening CHM files with missing internal files on windows + +- [2023431] CHM Input: Resolve absolute links to resource files from the root of the CHM file + +:: improved recipes +- Guardian & Observer +- Harper's Magazine Print recipe +- Live Mint + +:: new recipes +- The India Forum by unkn0wn + +}}} + {{{ 6.20.0 2023-06-09 :: bug fixes diff --git a/src/calibre/constants.py b/src/calibre/constants.py index f9a1da7e9e..3b009d9b59 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -5,7 +5,7 @@ from functools import lru_cache import sys, locale, codecs, os, collections, collections.abc __appname__ = 'calibre' -numeric_version = (6, 20, 0) +numeric_version = (6, 21, 0) __version__ = '.'.join(map(str, numeric_version)) git_version = None __author__ = "Kovid Goyal " From bd3ecf12e91625983f354e958a1e2731ffde353d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 13 Jun 2023 15:27:26 +0530 Subject: [PATCH 0825/2055] Update Harper's Magazine --- recipes/harpers.recipe | 67 +++++++++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/recipes/harpers.recipe b/recipes/harpers.recipe index 511a2a7316..059f4b5e08 100644 --- a/recipes/harpers.recipe +++ b/recipes/harpers.recipe @@ -17,22 +17,67 @@ class Harpers(BasicNewsRecipe): max_articles_per_feed = 100 no_stylesheets = True use_embedded_content = False - masthead_url = 'http://harpers.org/wp-content/themes/harpers/images/pheader.gif' conversion_options = { 'comment': description, 'tags': category, 'publisher': publisher, 'language': language } - extra_css = ''' - h1{ font-family:georgia ; color:#111111; font-size:large;} - .box-of-helpful{ font-family:arial ; font-size:x-small;} - p{font-family:georgia ;} - .caption{font-family:Verdana,sans-serif;font-size:x-small;color:#666666;} - ''' - keep_only_tags = [ - dict(name='div', attrs={'class': ['postdetailFull', 'articlePost']})] - remove_tags = [dict(name=['link', 'object', 'embed', 'meta', 'base'])] - remove_attributes = ['width', 'height'] + dict( + class_=[ + "article-content", + "template-index-archive", # harper's index + ] + ) + ] + remove_tags = [ + dict( + class_=[ + "component-newsletter-signup", + "sidebar", + "header-meta", + "component-from-author", + "from-issue", + "d-none", + "COA_roles_fix_space", + "section-tags", + "aria-font-adjusts", + "component-share-buttons", + "index-footer", + "index-prev-link", + "comma", + ] + ), + # for harper's index + dict( + class_=[ + "aria-font-adjusts", + "component-share-buttons", + "index-footer", + "index-prev-link", + ] + ), + ] + remove_attributes = ["style", "width", "height"] + + extra_css = """ + h1.article-title { font-size: x-large; margin-bottom: 0.4rem; } + .subheading, .post-subtitle { font-size: large; font-style: italic; margin-bottom: 1rem; } + .byline { margin-bottom: 1rem } + .article-hero-img img, .flex-section-image img, .wp-caption img { + display: block; margin-bottom: 0.3rem; max-width: 100%; height: auto; + box-sizing: border-box; + } + .wp-caption-text { font-size: small; margin-top: 0.3rem; } + + .author-bio { margin-top: 2.5rem; font-style: italic; } + .author-bio em { font-weight: bold; } + + .index-item { font-size: large; margin: 1rem 0; } + .index-statement > p { display: inline-block; margin: 0.5rem 0; } + .index-statement > span { display: inline-block; } + .index-statement .index-tooltip { font-size: small; } + """ + feeds = [(u"Harper's Magazine", u'https://harpers.org/feed/')] From dc695609aea7c2bfafd79cca9b75ef0d07459032 Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Tue, 13 Jun 2023 21:44:08 +0530 Subject: [PATCH 0826/2055] Update livemint.recipe --- recipes/livemint.recipe | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/recipes/livemint.recipe b/recipes/livemint.recipe index 4b990afd37..2e0f497808 100644 --- a/recipes/livemint.recipe +++ b/recipes/livemint.recipe @@ -6,7 +6,7 @@ from calibre.web.feeds.news import BasicNewsRecipe, classes is_saturday = date.today().weekday() == 5 class LiveMint(BasicNewsRecipe): - title = u'Live Mint' + title = 'Live Mint' description = 'Financial News from India.' language = 'en_IN' __author__ = 'Krittika Goyal, revised by unkn0wn' @@ -20,12 +20,18 @@ class LiveMint(BasicNewsRecipe): remove_empty_feeds = True resolve_internal_links = True + + def __init__(self, *args, **kwargs): + BasicNewsRecipe.__init__(self, *args, **kwargs) + if self.output_profile.short_name.startswith('kindle'): + self.title = 'Mint ' + date.today().strftime('%b %d, %Y') + if is_saturday: + self.title = 'Mint Lounge ' + date.today().strftime('%b %d, %Y') if is_saturday: def get_cover_url(self): soup = self.index_to_soup('https://lifestyle.livemint.com/') - self.title = 'Mint Lounge' if citem := soup.find('div', attrs={'class':'headLatestIss_cover'}): return citem.img['src'].replace('_tn.jpg', '_mr.jpg') @@ -75,11 +81,13 @@ class LiveMint(BasicNewsRecipe): extra_css = ''' img {display:block; margin:0 auto;} #img-cap {font-size:small; text-align:center;} - #auth-info {font-size:small; text-align:center;} - .highlights {font-style:italic;} - .summary{font-style:italic; color:#202020;} - .author-widget{font-size:small; font-style:italic; color:#404040; text-align:center;} + .summary, .highlights { + font-weight:normal !important; font-style:italic; color:#202020; + } + h2 {font-size:normal !important;} + .author-widget {font-size:small; font-style:italic; color:#404040;} em, blockquote {color:#202020;} + .moreAbout, .articleInfo {font-size:small;} ''' keep_only_tags = [ @@ -90,6 +98,7 @@ class LiveMint(BasicNewsRecipe): classes( 'trendingSimilarHeight moreNews mobAppDownload label msgError msgOk taboolaHeight' ' socialHolder imgbig disclamerText disqus-comment-count openinApp2 lastAdSlot' + ' datePublish sepStory' ) ] @@ -131,8 +140,7 @@ class LiveMint(BasicNewsRecipe): def preprocess_html(self, soup): for span in soup.findAll('figcaption'): span['id'] = 'img-cap' - for auth in soup.findAll('span', attrs={'class':['articleInfo pubtime','articleInfo author']}): - auth['id'] = 'auth-info' + for auth in soup.findAll('span', attrs={'class':lambda x: x and 'articleInfo' in x.split()}): auth.name = 'div' for span in soup.findAll('span', attrs={'class':'exclusive'}): span.extract() From 98f9263f805d5a84ad15a2f5086a2b8b4770ab26 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 14 Jun 2023 13:11:17 +0530 Subject: [PATCH 0827/2055] Add a utility function to get the process path for whichever process has a file open on Windows --- setup/extensions.json | 2 +- src/calibre/utils/windows/winutil.cpp | 54 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/setup/extensions.json b/setup/extensions.json index cf7d145b66..2648ce85c7 100644 --- a/setup/extensions.json +++ b/setup/extensions.json @@ -173,7 +173,7 @@ "only": "windows", "headers": "calibre/utils/cpp_binding.h calibre/utils/windows/common.h", "sources": "calibre/utils/windows/winutil.cpp", - "libraries": "shell32 wininet advapi32 gdi32", + "libraries": "shell32 wininet advapi32 gdi32 rstrtmgr", "cflags": "/X" }, { diff --git a/src/calibre/utils/windows/winutil.cpp b/src/calibre/utils/windows/winutil.cpp index 7295a2886c..8aff12ed50 100644 --- a/src/calibre/utils/windows/winutil.cpp +++ b/src/calibre/utils/windows/winutil.cpp @@ -7,6 +7,7 @@ #include "common.h" #include +#include #include #include #include @@ -20,6 +21,7 @@ #include #include // for CComPtr #include +#include // GUID {{{ typedef struct { @@ -470,6 +472,53 @@ winutil_read_directory_changes(PyObject *self, PyObject *args) { return ans; } +static void rm_end_session(DWORD sid) { RmEndSession(sid); } + +static PyObject* +winutil_get_processes_using_files(PyObject *self, PyObject *args) { + DWORD dwSession; + WCHAR szSessionKey[CCH_RM_SESSION_KEY+1] = { 0 }; + DWORD dwError = RmStartSession(&dwSession, 0, szSessionKey); + if (dwError != ERROR_SUCCESS) { PyErr_SetFromWindowsErr(dwError); return NULL; } + generic_raii sr(dwSession); + std::vector paths(PyTuple_GET_SIZE(args)); + for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(args); i++) { + if (!py_to_wchar_no_none(PyTuple_GET_ITEM(args, i), &paths[i])) return NULL; + } + std::vector array_of_paths(paths.size()); + for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(args); i++) { array_of_paths[i] = paths[i].ptr(); } + dwError = RmRegisterResources(dwSession, array_of_paths.size(), array_of_paths.data(), 0, NULL, 0, NULL); + if (dwError != ERROR_SUCCESS) { PyErr_SetFromWindowsErr(dwError); return NULL; } + DWORD dwReason; + UINT nProcInfoNeeded = 64, nProcInfo; + std::vector rgpi; + do { + nProcInfo = 2*nProcInfoNeeded; nProcInfoNeeded = 0; + rgpi.resize(nProcInfo); + dwError = RmGetList(dwSession, &nProcInfoNeeded, &nProcInfo, rgpi.data(), &dwReason); + } while (dwError == ERROR_MORE_DATA); + if (dwError != ERROR_SUCCESS) { PyErr_SetFromWindowsErr(dwError); return NULL; } + pyobject_raii ans(PyList_New(0)); + if (!ans) return NULL; + std::vector process_path(MAX_PATH*16); + for (UINT i = 0; i < nProcInfo; i++) { + handle_raii_null process(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, rgpi[i].Process.dwProcessId)); + if (process) { + FILETIME ftCreate, ftExit, ftKernel, ftUser; + if (GetProcessTimes(process.ptr(), &ftCreate, &ftExit, &ftKernel, &ftUser) && CompareFileTime( + &rgpi[i].Process.ProcessStartTime, &ftCreate) == 0) { + DWORD cch = process_path.size(); + if (QueryFullProcessImageNameW(process.ptr(), 0, process_path.data(), &cch) && cch < process_path.size()) { + pyobject_raii pp(Py_BuildValue("{su su# si}", "app_name", rgpi[i].strAppName, "path", process_path.data(), (Py_ssize_t)cch, "app_type", (int)rgpi[i].ApplicationType)); + if (!pp) return NULL; + if (PyList_Append(ans.ptr(), pp.ptr()) != 0) return NULL; + } + } + } + } + return ans.detach(); +} + static PyObject* winutil_get_file_size(PyObject *self, PyObject *args) { HANDLE handle; @@ -1302,6 +1351,11 @@ static PyMethodDef winutil_methods[] = { "read_directory_changes(handle, buffer, subtree, flags)\n\nWrapper for ReadDirectoryChangesW" }, + {"get_processes_using_files", (PyCFunction)winutil_get_processes_using_files, METH_VARARGS, + "get_processes_using_files(path1, path2, ...)\n\nGet information about processes that have the specified files open." + }, + + {NULL, NULL, 0, NULL} }; #undef M From 1e249c2d47238665f49bdeb124f7f8735133f039 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Jun 2023 08:06:15 +0530 Subject: [PATCH 0828/2055] MOBI Input: Ignore another form of corruption in trailing bytes. Fixes #2023943 [Private bug](https://bugs.launchpad.net/calibre/+bug/2023943) --- src/calibre/ebooks/mobi/reader/mobi6.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/mobi/reader/mobi6.py b/src/calibre/ebooks/mobi/reader/mobi6.py index f3bb969272..de1f42f102 100644 --- a/src/calibre/ebooks/mobi/reader/mobi6.py +++ b/src/calibre/ebooks/mobi/reader/mobi6.py @@ -788,7 +788,10 @@ class MobiReader: flags >>= 1 if self.book_header.extra_flags & 1: off = size - num - 1 - num += (ord(data[off:off+1]) & 0x3) + 1 + try: + num += (ord(data[off:off+1]) & 0x3) + 1 + except TypeError: + num += 1 return num def warn_about_trailing_entry_corruption(self): From 81c347777f0bbee54124317108057e97c812ee25 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Jun 2023 08:07:29 +0530 Subject: [PATCH 0829/2055] ... --- src/calibre/ebooks/mobi/reader/mobi6.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/ebooks/mobi/reader/mobi6.py b/src/calibre/ebooks/mobi/reader/mobi6.py index de1f42f102..75e43a1926 100644 --- a/src/calibre/ebooks/mobi/reader/mobi6.py +++ b/src/calibre/ebooks/mobi/reader/mobi6.py @@ -791,6 +791,7 @@ class MobiReader: try: num += (ord(data[off:off+1]) & 0x3) + 1 except TypeError: + self.log.warn('Invalid sizeof trailing entries') num += 1 return num From 9d9ee6dd5f2cc03adc7e9954d732582e020830b5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Jun 2023 09:38:54 +0530 Subject: [PATCH 0830/2055] Refactor for more clarity --- src/calibre/utils/copy_files.py | 64 ++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index 7adcd12c3b..c0973a996f 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -200,6 +200,40 @@ def copy_files(src_to_dest_map: Dict[str, str], delete_source: bool = False) -> copier.copy_all() +def register_folder_recursively( + src: str, copier: Union[UnixFileCopier, WindowsFileCopier], dest_dir: str, + transform_destination_filename: Callable[[str, str], str] = lambda src_path, dest_path : dest_path, +) -> None: + + def dest_from_entry(dirpath: str, x: str) -> str: + path = os.path.join(dirpath, x) + rel = os.path.relpath(path, src) + return os.path.join(dest_dir, rel) + + def raise_error(e: OSError) -> None: + raise e + + copier.register_folder(src) + for (dirpath, dirnames, filenames) in os.walk(src, onerror=raise_error): + for d in dirnames: + path = os.path.join(dirpath, d) + dest = dest_from_entry(dirpath, d) + os.makedirs(make_long_path_useable(dest), exist_ok=True) + shutil.copystat(make_long_path_useable(path), make_long_path_useable(dest), follow_symlinks=False) + copier.register_folder(path) + for f in filenames: + path = os.path.join(dirpath, f) + dest = dest_from_entry(dirpath, f) + dest = transform_destination_filename(path, dest) + if not iswindows: + s = os.stat(path, follow_symlinks=False) + if stat.S_ISLNK(s.st_mode): + link_dest = os.readlink(path) + os.symlink(link_dest, dest) + continue + copier.register(path, dest) + + def copy_tree( src: str, dest: str, transform_destination_filename: Callable[[str, str], str] = lambda src_path, dest_path : dest_path, @@ -222,36 +256,8 @@ def copy_tree( raise ValueError(f'Cannot copy tree if the source and destination are the same: {src!r} == {dest!r}') dest_dir = dest - def raise_error(e: OSError) -> None: - raise e - - def dest_from_entry(dirpath: str, x: str) -> str: - path = os.path.join(dirpath, x) - rel = os.path.relpath(path, src) - return os.path.join(dest_dir, rel) - - copier = get_copier(delete_source) - copier.register_folder(src) - for (dirpath, dirnames, filenames) in os.walk(src, onerror=raise_error): - for d in dirnames: - path = os.path.join(dirpath, d) - dest = dest_from_entry(dirpath, d) - os.makedirs(make_long_path_useable(dest), exist_ok=True) - shutil.copystat(make_long_path_useable(path), make_long_path_useable(dest), follow_symlinks=False) - copier.register_folder(path) - for f in filenames: - path = os.path.join(dirpath, f) - dest = dest_from_entry(dirpath, f) - dest = transform_destination_filename(path, dest) - if not iswindows: - s = os.stat(path, follow_symlinks=False) - if stat.S_ISLNK(s.st_mode): - link_dest = os.readlink(path) - os.symlink(link_dest, dest) - continue - copier.register(path, dest) - + register_folder_recursively(src, copier, dest_dir, transform_destination_filename) with copier: copier.copy_all() From c42af1f3dcfc50f01abf4773c8ce67d546c0bcea Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Jun 2023 09:58:08 +0530 Subject: [PATCH 0831/2055] Use the same code to check if folder is in use as to move the folder --- src/calibre/db/backend.py | 19 ++++++++----------- src/calibre/utils/copy_files.py | 17 ++++++++++++++--- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index c292c0b937..ff6380fce8 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -40,12 +40,14 @@ from calibre.library.field_metadata import FieldMetadata from calibre.ptempfile import PersistentTemporaryFile, TemporaryFile from calibre.utils import pickle_binary_string, unpickle_binary_string from calibre.utils.config import from_json, prefs, to_json, tweaks -from calibre.utils.copy_files import copy_files, copy_tree, rename_files +from calibre.utils.copy_files import ( + copy_files, copy_tree, rename_files, windows_check_if_files_in_use, +) from calibre.utils.date import EPOCH, parse_date, utcfromtimestamp, utcnow from calibre.utils.filenames import ( - WindowsAtomicFolderMove, ascii_filename, atomic_rename, copyfile_using_links, - copytree_using_links, hardlink_file, is_case_sensitive, is_fat_filesystem, - make_long_path_useable, remove_dir_if_empty, samefile, + ascii_filename, atomic_rename, copyfile_using_links, copytree_using_links, + hardlink_file, is_case_sensitive, is_fat_filesystem, make_long_path_useable, + remove_dir_if_empty, samefile, ) from calibre.utils.formatter_functions import ( compile_user_template_functions, formatter_functions, load_user_template_functions, @@ -1717,19 +1719,14 @@ class DB: def windows_check_if_files_in_use(self, paths): ''' - Raises an EACCES IOError if any of the files in the folder of book_id + Raises an EACCES IOError if any of the files in the specified folders are opened in another program on windows. ''' if iswindows: for path in paths: spath = os.path.join(self.library_path, *path.split('/')) - wam = None if os.path.exists(spath): - try: - wam = WindowsAtomicFolderMove(spath) - finally: - if wam is not None: - wam.close_handles() + windows_check_if_files_in_use(spath) def add_format(self, book_id, fmt, stream, title, author, path, current_name, mtime=None): fmt = ('.' + fmt.lower()) if fmt else '' diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index c0973a996f..84b226abd5 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -8,7 +8,7 @@ import stat import time from collections import defaultdict from contextlib import suppress -from typing import Callable, Dict, Set, Tuple, Union, List +from typing import Callable, Dict, List, Set, Tuple, Union from calibre.constants import filesystem_encoding, iswindows from calibre.utils.filenames import make_long_path_useable, samefile, windows_hardlink @@ -200,9 +200,13 @@ def copy_files(src_to_dest_map: Dict[str, str], delete_source: bool = False) -> copier.copy_all() +def identity_transform(src_path: str, dest_path: str) -> str: + return dest_path + + def register_folder_recursively( src: str, copier: Union[UnixFileCopier, WindowsFileCopier], dest_dir: str, - transform_destination_filename: Callable[[str, str], str] = lambda src_path, dest_path : dest_path, + transform_destination_filename: Callable[[str, str], str] = identity_transform, ) -> None: def dest_from_entry(dirpath: str, x: str) -> str: @@ -234,9 +238,16 @@ def register_folder_recursively( copier.register(path, dest) +def windows_check_if_files_in_use(src_folder: str) -> None: + copier = get_copier() + register_folder_recursively(src_folder, copier, os.getcwd()) + with copier: + pass + + def copy_tree( src: str, dest: str, - transform_destination_filename: Callable[[str, str], str] = lambda src_path, dest_path : dest_path, + transform_destination_filename: Callable[[str, str], str] = identity_transform, delete_source: bool = False ) -> None: ''' From 6e22fee0149bfca83784a99e5d038ccd0db997b1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Jun 2023 14:38:16 +0530 Subject: [PATCH 0832/2055] Fix #2023975 [[bug][convert] convert with embedded_fonts raise FileNotFoundError](https://bugs.launchpad.net/calibre/+bug/2023975) --- src/calibre/ebooks/oeb/transforms/subset.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/oeb/transforms/subset.py b/src/calibre/ebooks/oeb/transforms/subset.py index 604d8083f7..a10b1d901d 100644 --- a/src/calibre/ebooks/oeb/transforms/subset.py +++ b/src/calibre/ebooks/oeb/transforms/subset.py @@ -188,7 +188,10 @@ class SubsetFonts: ''' self.embedded_fonts = [] for item in self.oeb.manifest: - if not hasattr(item.data, 'cssRules'): + try: + if not hasattr(item.data, 'cssRules'): + continue + except FileNotFoundError: continue self.embedded_fonts.extend(find_font_face_rules(item, self.oeb)) From 056220d2a71dff89900d0c553104e472cade05ba Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Jun 2023 16:12:33 +0530 Subject: [PATCH 0833/2055] Windows: Nicer error message when file/folder is locked in another program --- src/calibre/db/backend.py | 15 ++-- src/calibre/gui2/__init__.py | 15 ++-- src/calibre/gui2/actions/catalog.py | 10 +-- src/calibre/gui2/actions/delete.py | 10 +-- src/calibre/gui2/convert/metadata.py | 11 +-- src/calibre/gui2/library/models.py | 27 ++++--- src/calibre/gui2/main_window.py | 82 +++++++++++++++++++++- src/calibre/gui2/metadata/basic_widgets.py | 16 +---- src/calibre/gui2/metadata/single.py | 33 +++------ src/calibre/utils/copy_files.py | 14 ++-- 10 files changed, 133 insertions(+), 100 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index ff6380fce8..38e61ab47e 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -41,7 +41,8 @@ from calibre.ptempfile import PersistentTemporaryFile, TemporaryFile from calibre.utils import pickle_binary_string, unpickle_binary_string from calibre.utils.config import from_json, prefs, to_json, tweaks from calibre.utils.copy_files import ( - copy_files, copy_tree, rename_files, windows_check_if_files_in_use, + copy_files, copy_tree, rename_files, + windows_check_if_files_in_use, ) from calibre.utils.date import EPOCH, parse_date, utcfromtimestamp, utcnow from calibre.utils.filenames import ( @@ -414,11 +415,10 @@ def rmtree_with_retry(path, sleep_time=1): try: shutil.rmtree(path) except OSError as e: - if not iswindows: - raise if e.errno == errno.ENOENT and not os.path.exists(path): return - time.sleep(sleep_time) # In case something has temporarily locked a file + if iswindows: + time.sleep(sleep_time) # In case something has temporarily locked a file shutil.rmtree(path) @@ -1577,12 +1577,7 @@ class DB: except OSError: if iswindows: time.sleep(0.2) - try: - f = open(path, 'rb') - except OSError as e: - # Ensure the path that caused this error is reported - raise Exception(f'Failed to open {path!r} with error: {e}') - + f = open(path, 'rb') with f: if hasattr(dest, 'write'): if report_file_size is not None: diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 89754a0245..1201565548 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -1099,6 +1099,7 @@ class Application(QApplication): if not args: args = sys.argv[:1] args = [args[0]] + sys.excepthook = simple_excepthook QNetworkProxyFactory.setUseSystemConfiguration(True) setup_to_run_webengine() if iswindows: @@ -1474,6 +1475,9 @@ def open_local_file(path): _ea_lock = Lock() +def simple_excepthook(t, v, tb): + return sys.__excepthook__(t, v, tb) + def ensure_app(headless=True): global _store_app @@ -1492,7 +1496,6 @@ def ensure_app(headless=True): set_image_allocation_limit() if headless and has_headless: _store_app.headless = True - import traceback # This is needed because as of PyQt 5.4 if sys.execpthook == # sys.__excepthook__ PyQt will abort the application on an @@ -1501,14 +1504,8 @@ def ensure_app(headless=True): # or running a headless browser, we circumvent this as I really # dont feel like going through all the code and making sure no # unhandled exceptions ever occur. All the actual GUI apps already - # override sys.except_hook with a proper error handler. - - def eh(t, v, tb): - try: - traceback.print_exception(t, v, tb, file=sys.stderr) - except: - pass - sys.excepthook = eh + # override sys.excepthook with a proper error handler. + sys.excepthook = simple_excepthook return _store_app diff --git a/src/calibre/gui2/actions/catalog.py b/src/calibre/gui2/actions/catalog.py index 499faf1a73..ba147c8039 100644 --- a/src/calibre/gui2/actions/catalog.py +++ b/src/calibre/gui2/actions/catalog.py @@ -5,7 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import re, os, shutil, errno +import re, os, shutil from qt.core import QModelIndex @@ -99,11 +99,5 @@ class GenerateCatalogAction(InterfaceAction): try: shutil.copyfile(job.catalog_file_path, destination) except OSError as err: - if getattr(err, 'errno', None) == errno.EACCES: # Permission denied - import traceback - error_dialog(self.gui, _('Permission denied'), - _('Could not open %s. Is it being used by another' - ' program?')%destination, det_msg=traceback.format_exc(), - show=True) - return + err.locking_violation_msg = _('Could not open the catalog output file.') raise diff --git a/src/calibre/gui2/actions/delete.py b/src/calibre/gui2/actions/delete.py index 724e6bf585..52af7717a5 100644 --- a/src/calibre/gui2/actions/delete.py +++ b/src/calibre/gui2/actions/delete.py @@ -5,8 +5,6 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import errno -import os from collections import Counter from functools import partial from qt.core import QDialog, QModelIndex, QObject, QTimer @@ -458,13 +456,7 @@ class DeleteAction(InterfaceAction): try: view.model().delete_books_by_id(to_delete_ids) except OSError as err: - if err.errno == errno.EACCES: - import traceback - fname = os.path.basename(getattr(err, 'filename', 'file') or 'file') - return error_dialog(self.gui, _('Permission denied'), - _('Could not access %s. Is it being used by another' - ' program? Click "Show details" for more information.')%fname, det_msg=traceback.format_exc(), - show=True) + err.locking_violation_msg = _('Could not change on-disk location of this book\'s files.') raise self.library_ids_deleted2(to_delete_ids, next_id=next_id, can_undo=True) else: diff --git a/src/calibre/gui2/convert/metadata.py b/src/calibre/gui2/convert/metadata.py index 8de7926d68..dd7caf624f 100644 --- a/src/calibre/gui2/convert/metadata.py +++ b/src/calibre/gui2/convert/metadata.py @@ -5,7 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, re, errno +import os, re from qt.core import QPixmap, QApplication @@ -243,13 +243,8 @@ class MetadataWidget(Widget, Ui_Form): if self.cover_changed and self.cover_data is not None: self.db.set_cover(self.book_id, self.cover_data) except OSError as err: - if getattr(err, 'errno', None) == errno.EACCES: # Permission denied - import traceback - fname = getattr(err, 'filename', None) or 'file' - error_dialog(self, _('Permission denied'), - _('Could not open %s. Is it being used by another' - ' program?')%fname, det_msg=traceback.format_exc(), show=True) - return False + err.locking_violation_msg = _('Failed to change on disk location of this book\'s files.') + raise publisher = self.publisher.text().strip() if publisher != db.field_for('publisher', self.book_id): db.set_field('publisher', {self.book_id:publisher}) diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index c7beff58b2..4f7a50af4b 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -5,11 +5,11 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import errno import functools import numbers import os import re +import sys import time import traceback from collections import defaultdict, namedtuple @@ -20,14 +20,13 @@ from qt.core import ( ) from calibre import ( - fit_image, force_unicode, human_readable, isbytestring, prepare_string_for_xml, - strftime, + fit_image, human_readable, isbytestring, prepare_string_for_xml, strftime, ) from calibre.constants import DEBUG, config_dir, dark_link_color, filesystem_encoding from calibre.db.search import CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH, _match from calibre.ebooks.metadata import authors_to_string, fmt_sidx, string_to_authors from calibre.ebooks.metadata.book.formatter import SafeFormat -from calibre.gui2 import error_dialog +from calibre.gui2 import error_dialog, simple_excepthook from calibre.gui2.library import DEFAULT_SORT from calibre.library.caches import force_to_bool from calibre.library.coloring import color_row_key @@ -641,10 +640,11 @@ class BooksModel(QAbstractTableModel): # {{{ return try: data = self.get_book_display_info(idx) - except Exception: - import traceback - error_dialog(None, _('Unhandled error'), _( - 'Failed to read book data from calibre library. Click "Show details" for more information'), det_msg=traceback.format_exc(), show=True) + except Exception as e: + if sys.excepthook is simple_excepthook or sys.excepthook is sys.__excepthook__: + return # ignore failures during startup/shutdown + e.locking_violation_msg = _('Failed to read cover file for this book from the calibre library.') + raise else: if emit_signal: self.new_bookdisplay_data.emit(data) @@ -1257,13 +1257,10 @@ class BooksModel(QAbstractTableModel): # {{{ return self._set_data(index, value) except OSError as err: import traceback - if getattr(err, 'errno', None) == errno.EACCES: # Permission denied - fname = getattr(err, 'filename', None) - p = 'Locked file: %s\n\n'%force_unicode(fname if fname else '') - error_dialog(get_gui(), _('Permission denied'), - _('Could not change the on disk location of this' - ' book. Is it open in another program?'), - det_msg=p+force_unicode(traceback.format_exc()), show=True) + traceback.print_exc() + det_msg = traceback.format_exc() + gui = get_gui() + if gui.show_possible_sharing_violation(err, det_msg): return False error_dialog(get_gui(), _('Failed to set data'), _('Could not set data, click "Show details" to see why.'), diff --git a/src/calibre/gui2/main_window.py b/src/calibre/gui2/main_window.py index a6d02665b5..b61b83fd90 100644 --- a/src/calibre/gui2/main_window.py +++ b/src/calibre/gui2/main_window.py @@ -2,7 +2,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import gc +import gc, os import sys import weakref from qt.core import ( @@ -11,6 +11,7 @@ from qt.core import ( ) from calibre import as_unicode, prepare_string_for_xml, prints +from calibre.constants import iswindows from calibre.gui2 import error_dialog from calibre.utils.config import OptionParser from polyglot.io import PolyglotStringIO @@ -133,6 +134,78 @@ class MainWindow(QMainWindow): def set_exception_handler(self): sys.excepthook = ExceptionHandler(self) + def show_possible_sharing_violation(self, e: Exception, det_msg: str = '') -> bool: + if not iswindows or not isinstance(e, OSError): + return False + from calibre_extensions import winutil + import errno + if not (e.winerror == winutil.ERROR_SHARING_VIOLATION or e.errno == errno.EACCES or isinstance(e, PermissionError)): + return False + msg = getattr(e, 'locking_violation_msg', '') + if msg: + msg = msg.strip() + ' ' + fname = e.filename + + def no_processes_found() -> bool: + is_folder = fname and os.path.isdir(fname) + w = _('folder') if is_folder else _('file') + if e.winerror == winutil.ERROR_SHARING_VIOLATION: + if fname: + dmsg = _('The {0} "{1}" is opened in another program, so calibre cannot access it.').format(w, fname) + else: + dmsg = _('A {} is open in another program so calibre cannot access it.').format(w) + if is_folder: + dmsg += _('This is usually caused by leaving Windows explorer or a similar file manager open' + ' to a folder in the calibre library. Close Windows explorer and retry.') + else: + dmsg += _('This is usually caused by software such as antivirus or file sync (aka DropBox and similar)' + ' accessing files in the calibre library folder at the same time as calibre. Try excluding' + ' the calibre library folder from such software.') + error_dialog(self, _('Cannot open file or folder as it is in use'), msg + dmsg, det_msg=det_msg, show=True) + return True + if msg: + if fname: + dmsg = _('Permission was denied by the operating system when calibre tried to access the file: "{0}".').format(fname) + else: + dmsg = _('Permission was denied by the operating system when calibre tried to access a file.') + dmsg += ' ' + _('This means either that the permissions on the file or its parent folder are incorrect or the file is' + ' open in another program.') + error_dialog(self, _('Cannot open file or folder'), msg + dmsg, det_msg=det_msg, show=True) + return True + return False + + if not hasattr(winutil, 'get_processes_using_files'): + return no_processes_found() # running from source + if not e.filename and not e.filename2: + return no_processes_found() + if e.filename and isinstance(e.filename, str): + if os.path.isdir(e.filename): + return no_processes_found() + try: + p = winutil.get_processes_using_files(e.filename) + except OSError: + return no_processes_found() + if not p and e.filename2 and isinstance(e.filename2, str): + if os.path.isdir(e.filename2): + return no_processes_found() + try: + p = winutil.get_processes_using_files(e.filename2) + except OSError: + return no_processes_found() + fname = e.filename2 + if not p: + return no_processes_found() + + path_map = {x['path']: x for x in p} + is_folder = fname and os.path.isdir(fname) + w = _('folder') if is_folder else _('file') + dmsg = _('Could not open the {0}: "{1}". It is already opened in the following programs:').format(w, fname) + '
' + for path, x in path_map.items(): + dmsg += '
' + prepare_string_for_xml(f'{x["app_name"]}: {path}') + msg = prepare_string_for_xml(msg) + error_dialog(self, _('Cannot open file or folder as it is in use'), '

' + msg + dmsg, det_msg=det_msg, show=True) + return True + def unhandled_exception(self, exc_type, value, tb): if exc_type is KeyboardInterrupt: return @@ -148,10 +221,15 @@ class MainWindow(QMainWindow): if getattr(value, 'locking_debug_msg', None): prints(value.locking_debug_msg, file=sio) fe = sio.getvalue() + prints(fe, file=sys.stderr) + try: + if self.show_possible_sharing_violation(value, det_msg=fe): + return + except Exception: + traceback.print_exc() msg = '%s:'%exc_type.__name__ + prepare_string_for_xml(as_unicode(value)) error_dialog(self, _('Unhandled exception'), msg, det_msg=fe, show=True) - prints(fe, file=sys.stderr) except BaseException: pass except: diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 43c06eca8a..9d85133ce3 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -54,16 +54,6 @@ from calibre.utils.localization import ngettext from polyglot.builtins import iteritems -def show_locked_file_error(parent, err): - import traceback - fname = getattr(err, 'filename', None) - p = 'Locked file: %s\n\n'%fname if fname else '' - error_dialog(parent, _('Permission denied'), - _('Could not change the on disk location of this' - ' book. Is it open in another program?'), - det_msg=p+traceback.format_exc(), show=True) - - def save_dialog(parent, title, msg, det_msg=''): d = QMessageBox(parent) d.setWindowTitle(title) @@ -388,9 +378,9 @@ class AuthorsEdit(EditWithComplete, ToMetadataMixin): if d == QMessageBox.StandardButton.Yes: try: self.commit(self.db, self.id_) - except PermissionError as err: - show_locked_file_error(self, err) - return + except OSError as e: + e.locking_violation_msg = _('Could not change on-disk location of this book\'s files.') + raise self.db.commit() self.original_val = self.current_val else: diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index 600cb37503..4d0fdd1cab 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -5,7 +5,6 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import errno import os from datetime import datetime from functools import partial @@ -26,7 +25,7 @@ from calibre.gui2.metadata.basic_widgets import ( AuthorsEdit, AuthorSortEdit, BuddyLabel, CommentsEdit, Cover, DateEdit, FormatsManager, IdentifiersEdit, LanguagesEdit, PubdateEdit, PublisherEdit, RatingEdit, RightClickButton, SeriesEdit, SeriesIndexEdit, TagsEdit, TitleEdit, - TitleSortEdit, show_locked_file_error, + TitleSortEdit ) from calibre.gui2.metadata.single_download import FullFetch from calibre.gui2.widgets2 import CenteredToolButton @@ -448,17 +447,9 @@ class MetadataSingleDialogBase(QDialog): if ext in ('pdf', 'cbz', 'cbr'): return self.choose_cover_from_pages(ext) try: - mi, ext = self.formats_manager.get_selected_format_metadata(self.db, - self.book_id) - except OSError as err: - if getattr(err, 'errno', None) == errno.EACCES: # Permission denied - import traceback - fname = err.filename if err.filename else 'file' - error_dialog(self, _('Permission denied'), - _('Could not open %s. Is it being used by another' - ' program?')%fname, det_msg=traceback.format_exc(), - show=True) - return + mi, ext = self.formats_manager.get_selected_format_metadata(self.db, self.book_id) + except OSError as e: + e.locking_violation_msg = _('Could not read from book file.') raise if mi is None: return @@ -608,18 +599,16 @@ class MetadataSingleDialogBase(QDialog): return True self.comments_edit_state_at_apply = {w:w.tab for w in self.comments_edit_state_at_apply} for widget in self.basic_metadata_widgets: + if hasattr(widget, 'validate_for_commit'): + title, msg, det_msg = widget.validate_for_commit() + if title is not None: + error_dialog(self, title, msg, det_msg=det_msg, show=True) + return False try: - if hasattr(widget, 'validate_for_commit'): - title, msg, det_msg = widget.validate_for_commit() - if title is not None: - error_dialog(self, title, msg, det_msg=det_msg, show=True) - return False widget.commit(self.db, self.book_id) self.books_to_refresh |= getattr(widget, 'books_to_refresh', set()) - except OSError as err: - if getattr(err, 'errno', None) == errno.EACCES: # Permission denied - show_locked_file_error(self, err) - return False + except OSError as e: + e.locking_violation_msg = _('Could not change on-disk location of this book\'s files.') raise for widget in getattr(self, 'custom_metadata_widgets', []): self.books_to_refresh |= widget.commit(self.book_id) diff --git a/src/calibre/utils/copy_files.py b/src/calibre/utils/copy_files.py index 84b226abd5..705b08db9e 100644 --- a/src/calibre/utils/copy_files.py +++ b/src/calibre/utils/copy_files.py @@ -1,7 +1,6 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2023, Kovid Goyal -import errno import os import shutil import stat @@ -63,6 +62,16 @@ class UnixFileCopier: os.unlink(src_path) +def windows_lock_path_and_callback(path: str, f: Callable) -> None: + is_folder = os.path.isdir(path) + flags = winutil.FILE_FLAG_BACKUP_SEMANTICS if is_folder else winutil.FILE_FLAG_SEQUENTIAL_SCAN + h = winutil.create_file(make_long_path_useable(path), winutil.GENERIC_READ, 0, winutil.OPEN_EXISTING, flags) + try: + f() + finally: + h.close() + + class WindowsFileCopier: ''' @@ -112,9 +121,6 @@ class WindowsFileCopier: if retry_on_sharing_violation: time.sleep(WINDOWS_SLEEP_FOR_RETRY_TIME) return self._open_file(path, False, is_folder) - err = IOError(errno.EACCES, _('File {} is open in another program').format(path)) - err.filename = path - raise err from e raise def open_all_handles(self) -> None: From 61ccbfc9954c6a9f8f67dfdf13d41d73572135c8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Jun 2023 16:39:27 +0530 Subject: [PATCH 0834/2055] Ignore yet more errors caused by corrupt MOBI files. Fixes #2023984 [[bug][convert] convert to epub raise indexerror](https://bugs.launchpad.net/calibre/+bug/2023984) --- src/calibre/ebooks/mobi/reader/markup.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/mobi/reader/markup.py b/src/calibre/ebooks/mobi/reader/markup.py index 979d7cf071..34c54d2ea4 100644 --- a/src/calibre/ebooks/mobi/reader/markup.py +++ b/src/calibre/ebooks/mobi/reader/markup.py @@ -274,7 +274,10 @@ def insert_images_into_markup(parts, resource_map, log): if tag.startswith(' Date: Thu, 15 Jun 2023 17:09:21 +0530 Subject: [PATCH 0835/2055] Add html to pdf render timings --- src/calibre/ebooks/pdf/html_writer.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/calibre/ebooks/pdf/html_writer.py b/src/calibre/ebooks/pdf/html_writer.py index f27fc5ca9e..a30c2cbd1e 100644 --- a/src/calibre/ebooks/pdf/html_writer.py +++ b/src/calibre/ebooks/pdf/html_writer.py @@ -321,6 +321,7 @@ class Renderer(QWebEnginePage): url = QUrl(f'{FAKE_PROTOCOL}://{FAKE_HOST}/') url.setPath(path) self.setUrl(url) + self.job_started_at = monotonic() class RequestInterceptor(QWebEngineUrlRequestInterceptor): @@ -393,9 +394,12 @@ class RenderManager(QObject): def convert_html_files(self, jobs, settle_time=0, wait_for_title=None, has_maths=None): self.has_maths = has_maths or {} + self.render_count = 0 + self.total_count = len(jobs) while len(self.workers) < min(len(jobs), self.max_workers): self.create_worker() self.pending = list(jobs) + self.log(f'Rendering {len(self.pending)} HTML files') self.results = {} self.settle_time = settle_time self.wait_for_title = wait_for_title @@ -436,6 +440,12 @@ class RenderManager(QObject): def work_done(self, worker, result): self.results[worker.result_key] = result + for w in self.workers: + if not w.working and w.job_started_at > 0: + time_taken = monotonic() - w.job_started_at + self.render_count += 1 + self.log.debug(f'Rendered: {worker.result_key} in {time_taken:.1f} seconds ({self.render_count}/{self.total_count})') + w.job_started_at = 0 if self.pending: self.assign_work() else: @@ -1124,6 +1134,7 @@ def convert(opf_path, opts, metadata=None, output_path=None, log=default_log, co results = manager.convert_html_files(jobs, settle_time=1, has_maths=has_maths) num_pages = 0 page_margins_map = [] + log(f'Merging {len(margin_files)} PDF render results, this could take a while...') for margin_file in margin_files: name = margin_file.name data = results[name] From bff8d6578b27ecea633109e7d3ede242c372bc59 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 15 Jun 2023 23:17:53 +0530 Subject: [PATCH 0836/2055] ... --- src/calibre/ebooks/pdf/html_writer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/ebooks/pdf/html_writer.py b/src/calibre/ebooks/pdf/html_writer.py index a30c2cbd1e..6fd6024375 100644 --- a/src/calibre/ebooks/pdf/html_writer.py +++ b/src/calibre/ebooks/pdf/html_writer.py @@ -1151,6 +1151,7 @@ def convert(opf_path, opts, metadata=None, output_path=None, log=default_log, co else: pdf_doc.append(doc) + log('Pages merged') page_number_display_map = get_page_number_display_map(manager, opts, num_pages, log) if has_toc: From 341f41b61d3a4a810fa63824766b89cfb18e6467 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 16 Jun 2023 09:07:37 +0530 Subject: [PATCH 0837/2055] Fix #2024121 [[bug][convert] convert mobi to epub raise keyerror](https://bugs.launchpad.net/calibre/+bug/2024121) --- src/calibre/ebooks/oeb/polish/upgrade.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/oeb/polish/upgrade.py b/src/calibre/ebooks/oeb/polish/upgrade.py index ecd85ee2fd..d520066551 100644 --- a/src/calibre/ebooks/oeb/polish/upgrade.py +++ b/src/calibre/ebooks/oeb/polish/upgrade.py @@ -74,7 +74,10 @@ def collect_properties(container): if mt.lower() not in OEB_DOCS: continue name = container.href_to_name(item.get('href'), container.opf_name) - root = container.parsed(name) + try: + root = container.parsed(name) + except KeyError: + continue root = ensure_namespace_prefixes(root, {'epub': EPUB_NS}) properties = set() container.replace(name, root) # Ensure entities are converted From 782517921bd3ccd5203984b339ddd0910c56f03d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 16 Jun 2023 17:23:37 +0530 Subject: [PATCH 0838/2055] Also output progress while merging PDF files --- src/calibre/ebooks/pdf/html_writer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/pdf/html_writer.py b/src/calibre/ebooks/pdf/html_writer.py index 6fd6024375..8fb2d08f84 100644 --- a/src/calibre/ebooks/pdf/html_writer.py +++ b/src/calibre/ebooks/pdf/html_writer.py @@ -1135,7 +1135,7 @@ def convert(opf_path, opts, metadata=None, output_path=None, log=default_log, co num_pages = 0 page_margins_map = [] log(f'Merging {len(margin_files)} PDF render results, this could take a while...') - for margin_file in margin_files: + for i, margin_file in enumerate(margin_files): name = margin_file.name data = results[name] if not isinstance(data, bytes): @@ -1150,8 +1150,8 @@ def convert(opf_path, opts, metadata=None, output_path=None, log=default_log, co pdf_doc = doc else: pdf_doc.append(doc) + log(f'Merged ({i}/{len(margin_files)-1})') - log('Pages merged') page_number_display_map = get_page_number_display_map(manager, opts, num_pages, log) if has_toc: From 82c7ce764b7e68019e027ba3e63a8ffbc37762a3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 16 Jun 2023 18:37:36 +0530 Subject: [PATCH 0839/2055] Output time to merge --- src/calibre/ebooks/pdf/html_writer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/pdf/html_writer.py b/src/calibre/ebooks/pdf/html_writer.py index 8fb2d08f84..08d203cd4b 100644 --- a/src/calibre/ebooks/pdf/html_writer.py +++ b/src/calibre/ebooks/pdf/html_writer.py @@ -1149,8 +1149,9 @@ def convert(opf_path, opts, metadata=None, output_path=None, log=default_log, co if pdf_doc is None: pdf_doc = doc else: + st = monotonic() pdf_doc.append(doc) - log(f'Merged ({i}/{len(margin_files)-1})') + log(f'Merged ({i}/{len(margin_files)-1}) in {monotonic()-st:.1f} seconds') page_number_display_map = get_page_number_display_map(manager, opts, num_pages, log) From 33bc00beb2931b0b9a1606d8b1222d9184a33fe4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 16 Jun 2023 18:37:47 +0530 Subject: [PATCH 0840/2055] Implement our own pdf append function the one in PoDoFo is awful --- src/calibre/utils/podofo/doc.cpp | 107 ++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 10 deletions(-) diff --git a/src/calibre/utils/podofo/doc.cpp b/src/calibre/utils/podofo/doc.cpp index 2177cd9766..6f1220c9b9 100644 --- a/src/calibre/utils/podofo/doc.cpp +++ b/src/calibre/utils/podofo/doc.cpp @@ -10,6 +10,7 @@ #include #include #include +#include using namespace pdf; @@ -330,23 +331,109 @@ PDFDoc_copy_page(PDFDoc *self, PyObject *args) { } // }}} // append() {{{ + +static void +fix_references(PdfObject &parent, const std::unordered_map &ref_map) { + switch(parent.GetDataType()) { + case PdfDataType::Dictionary: + for (auto& pair : parent.GetDictionary()) { + fix_references(pair.second, ref_map); + } + break; + case PdfDataType::Array: + for (auto& child : parent.GetArray()) fix_references(child, ref_map); + break; + case PdfDataType::Reference: + if (auto search = ref_map.find(parent.GetReference()); search != ref_map.end()) { + parent.SetReference(search->second->GetIndirectReference()); + } + break; + default: + break; + } +} + static PyObject * PDFDoc_append(PDFDoc *self, PyObject *args) { - PyObject *doc; - int typ; - - if (!PyArg_ParseTuple(args, "O", &doc)) return NULL; - - typ = PyObject_IsInstance(doc, (PyObject*)&PDFDocType); - if (typ == -1) return NULL; - if (typ == 0) { PyErr_SetString(PyExc_TypeError, "You must pass a PDFDoc instance to this method"); return NULL; } - PDFDoc *pdfdoc = (PDFDoc*)doc; + static const PdfName inheritableAttributes[] = { + PdfName("Resources"), + PdfName("MediaBox"), + PdfName("CropBox"), + PdfName("Rotate"), + PdfName::KeyNull + }; + PdfMemDocument *dest = self->doc; try { - self->doc->GetPages().AppendDocumentPages(*pdfdoc->doc); + for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(args); i++) { + PyObject *doc = PyTuple_GET_ITEM(args, i); + int typ = PyObject_IsInstance(doc, (PyObject*)&PDFDocType); + if (typ == -1) return NULL; + if (typ == 0) { PyErr_SetString(PyExc_TypeError, "You must pass a PDFDoc instance to this method"); return NULL; } + const PdfMemDocument *src = ((PDFDoc*)doc)->doc; + std::unordered_map ref_map; + std::unordered_map page_parent_map; + const unsigned initial_page_count = dest->GetPages().GetCount(); + // append pages first + for (unsigned i = 0; i < src->GetPages().GetCount(); i++) { + const auto& src_page = src->GetPages().GetPageAt(i); + auto& dest_page = dest->GetPages().CreatePage(src_page.GetRect()); + page_parent_map[dest_page.GetObject().GetIndirectReference()] = dest_page.GetDictionary().GetKeyAs("Parent"); + dest_page.GetObject() = src_page.GetObject(); + dest_page.GetDictionary().RemoveKey("Resource"); + dest_page.GetDictionary().RemoveKey("Parent"); + ref_map[src_page.GetObject().GetIndirectReference()] = &dest_page.GetObject(); + } + // append all remaining objects + for (const auto& obj : src->GetObjects()) { + if (obj->IsIndirect() && ref_map.find(obj->GetIndirectReference()) == ref_map.end()) { + auto copied_obj = &dest->GetObjects().CreateObject(*obj); + ref_map[obj->GetIndirectReference()] = copied_obj; + } + } + // fix references in appended objects + for (auto& elem : ref_map) fix_references(*elem.second, ref_map); + // fixup all pages + for (unsigned i = 0; i < src->GetPages().GetCount(); i++) { + auto& src_page = src->GetPages().GetPageAt(i); + auto& dest_page = dest->GetPages().GetPageAt(initial_page_count + i); + // Reset the parent to the correct value from the stored mapping + dest_page.GetDictionary().AddKey("Parent", page_parent_map[dest_page.GetObject().GetIndirectReference()]); + // Set the page contents + if (auto key = src_page.GetDictionary().GetKeyAs(PdfName::KeyContents); key.IsIndirect()) { + if (auto search = ref_map.find(key); search != ref_map.end()) { + dest_page.GetOrCreateContents().Reset(search->second); + } + } + // ensure the contents is not NULL to prevent segfaults in other code that assumes it + dest_page.GetOrCreateContents(); + + // Set the page resources + if (src_page.GetResources() != nullptr) { + const auto &src_resources = src_page.GetResources()->GetDictionary(); + dest_page.GetOrCreateResources().GetDictionary() = src_resources; + fix_references(dest_page.GetResources()->GetObject(), ref_map); + } else dest_page.GetOrCreateResources(); + + // Copy inherited properties + auto inherited = inheritableAttributes; + while (!inherited->IsNull()) { + auto attribute = src_page.GetDictionary().FindKeyParent(*inherited); + if (attribute != nullptr) { + PdfObject attributeCopy(*attribute); + fix_references(attributeCopy, ref_map); + dest_page.GetDictionary().AddKey(*inherited, attributeCopy); + } + inherited++; + } + } + } } catch (const PdfError & err) { podofo_set_exception(err); return NULL; + } catch (std::exception & err) { + PyErr_Format(PyExc_ValueError, "An error occurred while trying to append pages: %s", err.what()); + return NULL; } Py_RETURN_NONE; } // }}} From 29d31740445de71a4543e799f164cf99b4cc830a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 16 Jun 2023 22:12:52 +0530 Subject: [PATCH 0841/2055] Update fluter --- recipes/fluter_de.recipe | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/recipes/fluter_de.recipe b/recipes/fluter_de.recipe index 5a7912bc36..e1210c64cf 100644 --- a/recipes/fluter_de.recipe +++ b/recipes/fluter_de.recipe @@ -1,4 +1,9 @@ -__license__ = 'GPL v3' +## +## Written: 2013-02-05 +## Version: v4.1 +## Last update: 2013-02-05 V3, 2020-07-05 v4, 2023-06-16 v4.1 +## +__license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' ''' @@ -7,24 +12,33 @@ Fetch fluter.de from calibre.web.feeds.news import BasicNewsRecipe - class AdvancedUserRecipe1313693926(BasicNewsRecipe): - title = u'Fluter' + title = u' fluter. ' description = 'fluter.de Magazin der Bundeszentrale für politische Bildung/bpb' language = 'de' encoding = 'UTF-8' - __author__ = 'Armin Geller' # 2013-02-05 V3 + __author__ = 'Armin Geller' # 2013-02-05 V3 - oldest_article = 7 + oldest_article = 14 max_articles_per_feed = 50 + auto_cleanup = False + + feeds = [ + (u'Inhalt:', u'https://www.fluter.de/rss.xml') + ] + + keep_only_tags = [ + dict(name='article', attrs={'class':'node node-article block fullWidth stage'}) + ] + + remove_tags = [ + dict(name='h2', attrs={'class':'element-invisible'}) + ] + + extra_css = ''' + .field-group-format, .group_additional_info, .additional-info {display: inline-block; min-width: 8rem; text-align: center} + ''' - feeds = [ - (u'Inhalt:', u'http://www.fluter.de/de/?tpl=907'), - ] - - extra_css = '.cs_img {margin-right: 10pt;}' - - def print_version(self, url): - return url + '?tpl=1260' + \ No newline at end of file From 121bcfba185f9284f7a7f99f290608ad6e5ddcc6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 16 Jun 2023 22:14:42 +0530 Subject: [PATCH 0842/2055] Deutschland Funk by Armin Geller --- recipes/deutschland_funk.recipe | 82 +++++++++++++++++++++++++++++++++ recipes/fluter_de.recipe | 12 ++--- 2 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 recipes/deutschland_funk.recipe diff --git a/recipes/deutschland_funk.recipe b/recipes/deutschland_funk.recipe new file mode 100644 index 0000000000..aefe587276 --- /dev/null +++ b/recipes/deutschland_funk.recipe @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +#from __future__ import unicode_literals, division, absolute_import, print_function +from calibre.web.feeds.news import BasicNewsRecipe + +__license__ = 'GPL v3' +__copyright__ = '2014, 2023 Armin Geller' + +''' +Fetch Deutschlandfunk & Deutschlandfunk Kultur +''' +## +## Written: 2014-08-29 +## Last Edited: 2023-04-26 +## Version:1.6 +## New RSS source: https://www.deutschlandfunk.de/rss-angebot-102.html + +class AdvancedUserRecipe1432200863(BasicNewsRecipe): + + title = 'Deutschlandfunk & Deutschlandfunk Kultur' + __author__ = 'Armin Geller' + publisher = 'Deutschlandfunk' + category = 'Radio, News, Politics, Social, Culture, Nature, Environmental' + timefmt = ' [%a, %d %b %Y]' + language = 'de' + encoding = 'UTF-8' + publication_type = 'News feed' + oldest_article = 2 + max_articles_per_feed = 100 + auto_cleanup = False + + extra_css = ''' + h1, h2 {font-size: 1.6em; text-align: left} + .article-header-description {font-size: 1em; font-style: italic; font-weight: normal;margin-bottom: 1em} + .b-image-figure, .caption-figure.is-left, .b-image-credits {font-size: .75em; font-weight: normal;margin-bottom: .75em} + ''' + + + + feeds = [ + ('DLF Nachrichten', 'https://www.deutschlandfunk.de/nachrichten-100.rss'), + ('DLF Politikportal', 'https://www.deutschlandfunk.de/politikportal-100.rss'), + ('DLF Wirtschaft', 'https://www.deutschlandfunk.de/wirtschaft-106.rss'), + ('DLF Wissen', 'https://www.deutschlandfunk.de/wissen-106.rss'), + ('DLF Kulturportal', 'https://www.deutschlandfunk.de/kulturportal-100.rss'), + ('DLF Europa', 'https://www.deutschlandfunk.de/europa-112.rss'), + ('DLF Gesellschaft', 'https://www.deutschlandfunk.de/gesellschaft-106.rss'), + ('DLF Sportportal', 'https://www.deutschlandfunk.de/sportportal-100.rss'), + ('DLF-Kultur Politik', 'https://www.deutschlandfunkkultur.de/politik-114.rss'), + ('DLF-Kultur Bücher', 'https://www.deutschlandfunkkultur.de/buecher-108.rss'), + ('DLF-Kultur Musikportal', 'https://www.deutschlandfunkkultur.de/musikportal-100.rss'), + ('DLF-Kultur Wissenschaft', 'https://www.deutschlandfunkkultur.de/wissenschaft-108.rss'), + ('DLF-Kultur Meinung / Debatte', 'https://www.deutschlandfunkkultur.de/meinung-debatte-100.rss'), + ('DLF-Kultur Umwelt', 'https://www.deutschlandfunkkultur.de/umwelt-104.rss'), + ('DLF-Kultur Philosophie', 'https://www.deutschlandfunkkultur.de/philosophie-104.rss'), + ('DLF-Kultur Psychologie', 'https://www.deutschlandfunkkultur.de/psychologie-100.rss'), + ('DLF-Kultur Geschichte', 'https://www.deutschlandfunkkultur.de/geschichte-136.rss'), + ('DLF-Kultur Leben', 'https://www.deutschlandfunkkultur.de/leben-108.rss'), + ('DLF-Kultur Bühne', 'https://www.deutschlandfunkkultur.de/buehne-100.rss'), + ('DLF-Kultur Film / Serie', 'https://www.deutschlandfunkkultur.de/film-serie-100.rss'), + ] + keep_only_tags = [ + dict(name='nav', attrs={'class':'b-breadcrumbs'}), # DLF articles + dict(name='article', attrs={'class':'b-article'}), # DLF articles + dict(name='div', attrs={'class':[ + 'b-section-article-head-area', + 'b-section-editor-content', + ]}), # DLF Kultur articles + ] + + remove_tags = [ + dict(name='div', attrs={'class':[ + # 'article-header-actions', + 'b-article-extended-emphasis is-teaser-list u-space-bottom-xl', + 'article-extended-emphasis-teaser-group', + 'b-embed-opt-in js-embed-opt-in', + ]}), # DLF articles + dict(name='ul', attrs={'class':['b-social-icons']}), # DLF articles + + dict(name='ul', attrs={'class':['b-social-icons']}), # DLF Kultur articles + dict(name='div', attrs={'class':'b-footer-area-series'}), # DLF Kultur articles + dict(name='div', attrs={'id':'weekender'}) + ] diff --git a/recipes/fluter_de.recipe b/recipes/fluter_de.recipe index e1210c64cf..627437eeb4 100644 --- a/recipes/fluter_de.recipe +++ b/recipes/fluter_de.recipe @@ -21,24 +21,22 @@ class AdvancedUserRecipe1313693926(BasicNewsRecipe): __author__ = 'Armin Geller' # 2013-02-05 V3 - oldest_article = 14 + oldest_article = 14 max_articles_per_feed = 50 auto_cleanup = False - + feeds = [ (u'Inhalt:', u'https://www.fluter.de/rss.xml') ] - + keep_only_tags = [ dict(name='article', attrs={'class':'node node-article block fullWidth stage'}) ] - + remove_tags = [ dict(name='h2', attrs={'class':'element-invisible'}) ] - + extra_css = ''' .field-group-format, .group_additional_info, .additional-info {display: inline-block; min-width: 8rem; text-align: center} ''' - - \ No newline at end of file From a63abab58e287f3cdadf6a114941b12b44ba47b1 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Fri, 16 Jun 2023 21:11:39 +0100 Subject: [PATCH 0843/2055] Require a name when saving a multi-sort. --- src/calibre/gui2/dialogs/multisort.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/dialogs/multisort.py b/src/calibre/gui2/dialogs/multisort.py index 7a8a7479a7..c9cbbb3cb7 100644 --- a/src/calibre/gui2/dialogs/multisort.py +++ b/src/calibre/gui2/dialogs/multisort.py @@ -150,9 +150,13 @@ class ChooseMultiSort(Dialog): d.setLabelText(_('Choose a name for these settings')) if d.exec(): name = d.textValue() - q = self.saved_specs - q[name] = spec - self.saved_specs = q + if name: + q = self.saved_specs + q[name] = spec + self.saved_specs = q + else: + error_dialog(self, _('No name provided'), _( + 'You must provide a name for the settings'), show=True) def populate_load_menu(self): m = self.load_menu From 7469c920f22551d6a0b49b2224221be8c8991eed Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 17 Jun 2023 09:55:52 +0530 Subject: [PATCH 0844/2055] Append all at once --- src/calibre/ebooks/pdf/html_writer.py | 11 ++++------- src/calibre/utils/podofo/doc.cpp | 6 +++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/calibre/ebooks/pdf/html_writer.py b/src/calibre/ebooks/pdf/html_writer.py index 08d203cd4b..623e914898 100644 --- a/src/calibre/ebooks/pdf/html_writer.py +++ b/src/calibre/ebooks/pdf/html_writer.py @@ -1134,7 +1134,7 @@ def convert(opf_path, opts, metadata=None, output_path=None, log=default_log, co results = manager.convert_html_files(jobs, settle_time=1, has_maths=has_maths) num_pages = 0 page_margins_map = [] - log(f'Merging {len(margin_files)} PDF render results, this could take a while...') + all_docs = [] for i, margin_file in enumerate(margin_files): name = margin_file.name data = results[name] @@ -1145,13 +1145,10 @@ def convert(opf_path, opts, metadata=None, output_path=None, log=default_log, co doc_pages = doc.page_count() page_margins_map.extend(repeat(resolve_margins(margin_file.margins, page_layout), doc_pages)) num_pages += doc_pages + all_docs.append(doc) - if pdf_doc is None: - pdf_doc = doc - else: - st = monotonic() - pdf_doc.append(doc) - log(f'Merged ({i}/{len(margin_files)-1}) in {monotonic()-st:.1f} seconds') + pdf_doc = all_docs[0] + pdf_doc.append(*all_docs[1:]) page_number_display_map = get_page_number_display_map(manager, opts, num_pages, log) diff --git a/src/calibre/utils/podofo/doc.cpp b/src/calibre/utils/podofo/doc.cpp index 6f1220c9b9..66a6499bde 100644 --- a/src/calibre/utils/podofo/doc.cpp +++ b/src/calibre/utils/podofo/doc.cpp @@ -11,6 +11,7 @@ #include #include #include +#include using namespace pdf; @@ -365,12 +366,15 @@ PDFDoc_append(PDFDoc *self, PyObject *args) { PdfMemDocument *dest = self->doc; try { + std::vector docs(PyTuple_GET_SIZE(args)); for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(args); i++) { PyObject *doc = PyTuple_GET_ITEM(args, i); int typ = PyObject_IsInstance(doc, (PyObject*)&PDFDocType); if (typ == -1) return NULL; if (typ == 0) { PyErr_SetString(PyExc_TypeError, "You must pass a PDFDoc instance to this method"); return NULL; } - const PdfMemDocument *src = ((PDFDoc*)doc)->doc; + docs[i] = ((PDFDoc*)doc)->doc; + } + for (auto src : docs) { std::unordered_map ref_map; std::unordered_map page_parent_map; const unsigned initial_page_count = dest->GetPages().GetCount(); From 2acd1ee51836b2545b3de22f7a0e0647080f0150 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 17 Jun 2023 11:52:50 +0530 Subject: [PATCH 0845/2055] Release GIL while running append --- src/calibre/utils/podofo/doc.cpp | 97 ++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 41 deletions(-) diff --git a/src/calibre/utils/podofo/doc.cpp b/src/calibre/utils/podofo/doc.cpp index 66a6499bde..ffa2ea52fa 100644 --- a/src/calibre/utils/podofo/doc.cpp +++ b/src/calibre/utils/podofo/doc.cpp @@ -333,29 +333,42 @@ PDFDoc_copy_page(PDFDoc *self, PyObject *args) { // append() {{{ -static void -fix_references(PdfObject &parent, const std::unordered_map &ref_map) { - switch(parent.GetDataType()) { - case PdfDataType::Dictionary: - for (auto& pair : parent.GetDictionary()) { - fix_references(pair.second, ref_map); - } - break; - case PdfDataType::Array: - for (auto& child : parent.GetArray()) fix_references(child, ref_map); - break; - case PdfDataType::Reference: - if (auto search = ref_map.find(parent.GetReference()); search != ref_map.end()) { - parent.SetReference(search->second->GetIndirectReference()); - } - break; - default: - break; - } -} - static PyObject * PDFDoc_append(PDFDoc *self, PyObject *args) { + class AppendPagesData { + public: + const PdfPage *src_page; + PdfPage *dest_page; + PdfReference dest_page_parent; + AppendPagesData(const PdfPage &src, PdfPage &dest) { + src_page = &src; + dest_page = &dest; + dest_page_parent = dest.GetDictionary().GetKeyAs("Parent"); + } + }; + class MapReferences : public std::unordered_map { + public: + void apply(PdfObject &parent) { + switch(parent.GetDataType()) { + case PdfDataType::Dictionary: + for (auto& pair : parent.GetDictionary()) { + apply(pair.second ); + } + break; + case PdfDataType::Array: + for (auto& child : parent.GetArray()) apply(child); + break; + case PdfDataType::Reference: + if (auto search = find(parent.GetReference()); search != end()) { + parent.SetReference(search->second->GetIndirectReference()); + } + break; + default: + break; + } + } + }; + static const PdfName inheritableAttributes[] = { PdfName("Resources"), PdfName("MediaBox"), @@ -364,25 +377,25 @@ PDFDoc_append(PDFDoc *self, PyObject *args) { PdfName::KeyNull }; PdfMemDocument *dest = self->doc; + std::vector docs(PyTuple_GET_SIZE(args)); + for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(args); i++) { + PyObject *doc = PyTuple_GET_ITEM(args, i); + int typ = PyObject_IsInstance(doc, (PyObject*)&PDFDocType); + if (typ == -1) return NULL; + if (typ == 0) { PyErr_SetString(PyExc_TypeError, "You must pass a PDFDoc instance to this method"); return NULL; } + docs[i] = ((PDFDoc*)doc)->doc; + } + PyThreadState *_save; _save = PyEval_SaveThread(); try { - std::vector docs(PyTuple_GET_SIZE(args)); - for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(args); i++) { - PyObject *doc = PyTuple_GET_ITEM(args, i); - int typ = PyObject_IsInstance(doc, (PyObject*)&PDFDocType); - if (typ == -1) return NULL; - if (typ == 0) { PyErr_SetString(PyExc_TypeError, "You must pass a PDFDoc instance to this method"); return NULL; } - docs[i] = ((PDFDoc*)doc)->doc; - } for (auto src : docs) { - std::unordered_map ref_map; - std::unordered_map page_parent_map; - const unsigned initial_page_count = dest->GetPages().GetCount(); + MapReferences ref_map; + std::vector pages; // append pages first for (unsigned i = 0; i < src->GetPages().GetCount(); i++) { const auto& src_page = src->GetPages().GetPageAt(i); auto& dest_page = dest->GetPages().CreatePage(src_page.GetRect()); - page_parent_map[dest_page.GetObject().GetIndirectReference()] = dest_page.GetDictionary().GetKeyAs("Parent"); + pages.emplace_back(src_page, dest_page); dest_page.GetObject() = src_page.GetObject(); dest_page.GetDictionary().RemoveKey("Resource"); dest_page.GetDictionary().RemoveKey("Parent"); @@ -396,13 +409,12 @@ PDFDoc_append(PDFDoc *self, PyObject *args) { } } // fix references in appended objects - for (auto& elem : ref_map) fix_references(*elem.second, ref_map); + for (auto& elem : ref_map) ref_map.apply(*elem.second); // fixup all pages - for (unsigned i = 0; i < src->GetPages().GetCount(); i++) { - auto& src_page = src->GetPages().GetPageAt(i); - auto& dest_page = dest->GetPages().GetPageAt(initial_page_count + i); - // Reset the parent to the correct value from the stored mapping - dest_page.GetDictionary().AddKey("Parent", page_parent_map[dest_page.GetObject().GetIndirectReference()]); + for (auto& x : pages) { + auto& src_page = *x.src_page; + auto& dest_page = *x.dest_page; + dest_page.GetDictionary().AddKey("Parent", x.dest_page_parent); // Set the page contents if (auto key = src_page.GetDictionary().GetKeyAs(PdfName::KeyContents); key.IsIndirect()) { if (auto search = ref_map.find(key); search != ref_map.end()) { @@ -416,7 +428,7 @@ PDFDoc_append(PDFDoc *self, PyObject *args) { if (src_page.GetResources() != nullptr) { const auto &src_resources = src_page.GetResources()->GetDictionary(); dest_page.GetOrCreateResources().GetDictionary() = src_resources; - fix_references(dest_page.GetResources()->GetObject(), ref_map); + ref_map.apply(dest_page.GetResources()->GetObject()); } else dest_page.GetOrCreateResources(); // Copy inherited properties @@ -425,7 +437,7 @@ PDFDoc_append(PDFDoc *self, PyObject *args) { auto attribute = src_page.GetDictionary().FindKeyParent(*inherited); if (attribute != nullptr) { PdfObject attributeCopy(*attribute); - fix_references(attributeCopy, ref_map); + ref_map.apply(attributeCopy); dest_page.GetDictionary().AddKey(*inherited, attributeCopy); } inherited++; @@ -433,12 +445,15 @@ PDFDoc_append(PDFDoc *self, PyObject *args) { } } } catch (const PdfError & err) { + PyEval_RestoreThread(_save); podofo_set_exception(err); return NULL; } catch (std::exception & err) { + PyEval_RestoreThread(_save); PyErr_Format(PyExc_ValueError, "An error occurred while trying to append pages: %s", err.what()); return NULL; } + PyEval_RestoreThread(_save); Py_RETURN_NONE; } // }}} From 5e2dd561b66765230c9d1871b356261535816b55 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 17 Jun 2023 14:32:31 +0530 Subject: [PATCH 0846/2055] Use the much faster new bulk page creation API I contributed to PoDoFo See https://github.com/podofo/podofo/pull/86 --- src/calibre/utils/podofo/doc.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/calibre/utils/podofo/doc.cpp b/src/calibre/utils/podofo/doc.cpp index ffa2ea52fa..18479b6e0e 100644 --- a/src/calibre/utils/podofo/doc.cpp +++ b/src/calibre/utils/podofo/doc.cpp @@ -388,13 +388,21 @@ PDFDoc_append(PDFDoc *self, PyObject *args) { PyThreadState *_save; _save = PyEval_SaveThread(); try { + unsigned total_pages_to_append = 0; + for (auto src : docs) total_pages_to_append += src->GetPages().GetCount(); + unsigned base_page_index = dest->GetPages().GetCount(); +#if PODOFO_VERSION > PODOFO_MAKE_VERSION(0, 10, 0) + dest->GetPages().CreatePagesAt(base_page_index, Rect(), total_pages_to_append); +#else + while (total_pages_to_append--) dest->GetPages().CreatePage(Rect()); +#endif for (auto src : docs) { MapReferences ref_map; std::vector pages; // append pages first for (unsigned i = 0; i < src->GetPages().GetCount(); i++) { const auto& src_page = src->GetPages().GetPageAt(i); - auto& dest_page = dest->GetPages().CreatePage(src_page.GetRect()); + auto& dest_page = dest->GetPages().GetPageAt(base_page_index++); pages.emplace_back(src_page, dest_page); dest_page.GetObject() = src_page.GetObject(); dest_page.GetDictionary().RemoveKey("Resource"); From e0dee4fdefcfc22bf9c9a79aa85cd22366c972a4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 18 Jun 2023 09:18:11 +0530 Subject: [PATCH 0847/2055] E-book viewer: Fix searching for text next to hidden text not scrolling to the match --- src/pyj/range_utils.pyj | 44 ++++++++++++++++++++++++++++++++++++-- src/pyj/read_book/find.pyj | 11 ++++++---- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/pyj/range_utils.pyj b/src/pyj/range_utils.pyj index 7a5200e575..b408f796d9 100644 --- a/src/pyj/range_utils.pyj +++ b/src/pyj/range_utils.pyj @@ -8,7 +8,25 @@ def is_non_empty_text_node(node): return (node.nodeType is Node.TEXT_NODE or node.nodeType is Node.CDATA_SECTION_NODE) and node.nodeValue.length > 0 -def text_nodes_in_range(r): +def is_element_visible(elem): + s = window.getComputedStyle(elem) + return s.display is not 'none' and s.visibility is not 'hidden' + + +def is_node_visible(node): + if node.nodeType is not Node.ELEMENT_NODE: + node = node.parentElement + if not node: + return False + current = node + while current: + if not is_element_visible(current): + return False + current = current.parentElement + return True + + +def select_nodes_from_range(r, predicate): parent = r.commonAncestorContainer doc = parent.ownerDocument or document iterator = doc.createNodeIterator(parent) @@ -21,13 +39,35 @@ def text_nodes_in_range(r): if not in_range and node.isSameNode(r.startContainer): in_range = True if in_range: - if is_non_empty_text_node(node): + if predicate(node): ans.push(node) if node.isSameNode(r.endContainer): break return ans +def select_first_node_from_range(r, predicate): + parent = r.commonAncestorContainer + doc = parent.ownerDocument or document + iterator = doc.createNodeIterator(parent) + in_range = False + while True: + node = iterator.nextNode() + if not node: + break + if not in_range and node.isSameNode(r.startContainer): + in_range = True + if in_range: + if predicate(node): + return node + if node.isSameNode(r.endContainer): + break + + +def text_nodes_in_range(r): + return select_nodes_from_range(r, is_non_empty_text_node) + + def all_annots_in_range(r, annot_id_uuid_map, ans): parent = r.commonAncestorContainer doc = parent.ownerDocument or document diff --git a/src/pyj/read_book/find.pyj b/src/pyj/read_book/find.pyj index 688100632d..f6e34528c7 100644 --- a/src/pyj/read_book/find.pyj +++ b/src/pyj/read_book/find.pyj @@ -2,6 +2,7 @@ # License: GPL v3 Copyright: 2020, Kovid Goyal from __python__ import bound_methods, hash_literals +from range_utils import select_first_node_from_range, is_node_visible def build_text_map(): node_list = v'[]' @@ -27,9 +28,6 @@ def build_text_map(): tag = node.tagName.toLowerCase() if ignored_tags[tag]: return - style = window.getComputedStyle(node) - if style.display is 'none' or style.visibility is 'hidden': - return children = node.childNodes for i in range(children.length): process_node(v'children[i]') @@ -143,7 +141,12 @@ def select_find_result(match): sel.setBaseAndExtent(match.start_node, match.start_offset, match.end_node, match.end_offset) except: # if offset is outside node return False - return True + if not sel.rangeCount: + return False + for i in range(sel.rangeCount): + if select_first_node_from_range(sel.getRangeAt(i), is_node_visible): + return True + return False def select_search_result(sr): From 505980a60a5c80a3a88597a22cb81367656d46e7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 18 Jun 2023 12:14:35 +0530 Subject: [PATCH 0848/2055] CHM Input: Fix ToC entries that use fragments not supported. Fixes #2024139 [Private bug](https://bugs.launchpad.net/calibre/+bug/2024139) --- .../ebooks/conversion/plugins/chm_input.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/calibre/ebooks/conversion/plugins/chm_input.py b/src/calibre/ebooks/conversion/plugins/chm_input.py index d8a4428adc..61188b6502 100644 --- a/src/calibre/ebooks/conversion/plugins/chm_input.py +++ b/src/calibre/ebooks/conversion/plugins/chm_input.py @@ -133,17 +133,21 @@ class CHMInput(InputFormatPlugin): return _unquote(x).decode('utf-8') def unquote_path(x): - y = unquote(x) - if (not os.path.exists(os.path.join(base, x)) and os.path.exists(os.path.join(base, y))): - x = y - return x + x, _, frag = x.partition('#') + if frag: + frag = '#' + frag + if not os.path.exists(os.path.join(base, x)): + y = unquote(x) + if os.path.exists(os.path.join(base, y)): + x = y + return x, frag def donode(item, parent, base, subpath): for child in item: title = child.title if not title: continue - raw = unquote_path(child.href or '') + raw, frag = unquote_path(child.href or '') rsrcname = os.path.basename(raw) rsrcpath = os.path.join(subpath, rsrcname) if (not os.path.exists(os.path.join(base, rsrcpath)) and os.path.exists(os.path.join(base, raw))): @@ -153,7 +157,7 @@ class CHMInput(InputFormatPlugin): rsrcpath = urlquote(rsrcpath) if not raw: rsrcpath = '' - c = DIV(A(title, href=rsrcpath)) + c = DIV(A(title, href=rsrcpath + frag)) donode(child, c, base, subpath) parent.append(c) @@ -161,7 +165,7 @@ class CHMInput(InputFormatPlugin): if toc.count() > 1: from lxml.html.builder import HTML, BODY, DIV, A path0 = toc[0].href - path0 = unquote_path(path0) + path0 = unquote_path(path0)[0] subpath = os.path.dirname(path0) base = os.path.dirname(f.name) root = DIV() From 9ae6fdac9d8edc4db03eaa19be3af2139611b7fd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 18 Jun 2023 12:22:52 +0530 Subject: [PATCH 0849/2055] Fix changelog translations for a language not being used unless more than 50% of the changelog is translated into that language. Fixes #2024278 [[Enhancement] Untranslated text on the homepage](https://bugs.launchpad.net/calibre/+bug/2024278) --- setup/translations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup/translations.py b/setup/translations.py index c764ff4ad7..1ac70bf943 100644 --- a/setup/translations.py +++ b/setup/translations.py @@ -597,7 +597,7 @@ class Translations(POT): # {{{ self.compile_group(files, handle_stats=handle_stats) for locale, translated in iteritems(stats): - if translated >= 50: + if translated >= threshold: with open(os.path.join(tdir, locale + '.mo'), 'rb') as f: raw = f.read() zi = ZipInfo(os.path.basename(f.name)) @@ -627,7 +627,7 @@ class Translations(POT): # {{{ f.write(data) def compile_changelog_translations(self): - self._compile_website_translations('changelog', 0) + self._compile_website_translations('changelog', threshold=0) def compile_user_manual_translations(self): self.info('Compiling user manual translations...') From c349d94af414df4ec18d5aec12d3b0df91de32ec Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 18 Jun 2023 12:40:49 +0530 Subject: [PATCH 0850/2055] Trash bin: Allow setting removed books to be permanently deleted on on library close. Fixes #2023604 [Enhancement Request: Menu option to clear Calibre trash](https://bugs.launchpad.net/calibre/+bug/2023604) --- src/calibre/db/backend.py | 4 +++- src/calibre/gui2/trash.py | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 38e61ab47e..05d30ca5fc 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1261,6 +1261,8 @@ class DB: def close(self, force=False, unload_formatter_functions=True): if getattr(self, '_conn', None) is not None: + if self.prefs['expire_old_trash_after'] == 0: + self.expire_old_trash(0) if unload_formatter_functions: try: unload_user_template_functions(self.library_id) @@ -2021,7 +2023,7 @@ class DB: mtime = st.st_mtime except OSError: mtime = 0 - if mtime + expire_age_in_seconds <= now: + if mtime + expire_age_in_seconds <= now or expire_age_in_seconds <= 0: removals.append(x.path) for x in removals: rmtree_with_retry(x) diff --git a/src/calibre/gui2/trash.py b/src/calibre/gui2/trash.py index aa210d3b6b..a6f0c91645 100644 --- a/src/calibre/gui2/trash.py +++ b/src/calibre/gui2/trash.py @@ -134,11 +134,15 @@ class TrashView(Dialog): la = QLabel(_('&Permanently delete after:')) self.auto_delete = ad = QSpinBox(self) - ad.setMinimum(1) + ad.setMinimum(0) ad.setMaximum(365) + ad.setSpecialValueText(_('on close')) ad.setValue(int(self.db.pref('expire_old_trash_after', DEFAULT_TRASH_EXPIRY_TIME_SECONDS) / 86400)) ad.setSuffix(_(' days')) - ad.setToolTip(_('Deleted items are permanently deleted automatically after the specified number of days')) + ad.setToolTip(_( + 'Deleted items are permanently deleted automatically after the specified number of days. If set to "on close" they are' + ' deleted whenever the library is closed, that is when switching to another library or exiting calibre.' + )) ad.valueChanged.connect(self.trash_expiry_time_changed) h = QHBoxLayout() h.addWidget(la), h.addWidget(ad), h.addStretch(10) From 0ac205820ad26aeaf492d4f2cabf73ed8e355d8b Mon Sep 17 00:00:00 2001 From: un-pogaz <46523284+un-pogaz@users.noreply.github.com> Date: Sun, 18 Jun 2023 11:25:28 +0200 Subject: [PATCH 0851/2055] fix display --- src/calibre/gui2/trash.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/trash.py b/src/calibre/gui2/trash.py index a6f0c91645..272d336757 100644 --- a/src/calibre/gui2/trash.py +++ b/src/calibre/gui2/trash.py @@ -140,8 +140,8 @@ class TrashView(Dialog): ad.setValue(int(self.db.pref('expire_old_trash_after', DEFAULT_TRASH_EXPIRY_TIME_SECONDS) / 86400)) ad.setSuffix(_(' days')) ad.setToolTip(_( - 'Deleted items are permanently deleted automatically after the specified number of days. If set to "on close" they are' - ' deleted whenever the library is closed, that is when switching to another library or exiting calibre.' + 'Deleted items are permanently deleted automatically after the specified number of days.\n' + 'If set to "on close" they are deleted whenever the library is closed, that is when switching to another library or exiting calibre.' )) ad.valueChanged.connect(self.trash_expiry_time_changed) h = QHBoxLayout() From 1396035f9d719f027782610037f7126ff80ad3ed Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Sun, 18 Jun 2023 18:51:59 +0530 Subject: [PATCH 0852/2055] ... --- recipes/indian_express.recipe | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/indian_express.recipe b/recipes/indian_express.recipe index efbd434694..39a459427f 100644 --- a/recipes/indian_express.recipe +++ b/recipes/indian_express.recipe @@ -99,7 +99,7 @@ class IndianExpress(BasicNewsRecipe): div = soup.find('div', attrs={'class':['nation', 'o-opin']}) for art in div.findAll(attrs={'class':['articles', 'o-opin-article']}): for a in art.findAll('a', href=True): - if not a.find('img') and '/profile/' not in a['href']: + if not a.find('img') and not ('/profile/' in a['href'] or '/agency/' in a['href']):: url = a['href'] title = self.tag_to_string(a) desc = '' From 14ff72d60cc4ca9ccfafe8230bb1a46acfee069b Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Sun, 18 Jun 2023 19:21:55 +0530 Subject: [PATCH 0853/2055] Horizons Journal Update fix SSL certificate verification error. --- recipes/horizons.recipe | 3 +++ recipes/icons/horizons.png | Bin 0 -> 8639 bytes 2 files changed, 3 insertions(+) create mode 100644 recipes/icons/horizons.png diff --git a/recipes/horizons.recipe b/recipes/horizons.recipe index bae979a043..26b48b69b9 100644 --- a/recipes/horizons.recipe +++ b/recipes/horizons.recipe @@ -27,6 +27,9 @@ class horizons(BasicNewsRecipe): classes('back-link'), dict(name='div', attrs={'class':'single-post-footer'}) ] + + def get_browser(self): + return BasicNewsRecipe.get_browser(self, verify_ssl_certificates=False) def parse_index(self): soup = self.index_to_soup('https://www.cirsd.org/en/horizons') diff --git a/recipes/icons/horizons.png b/recipes/icons/horizons.png new file mode 100644 index 0000000000000000000000000000000000000000..5cf16612aca7a467dec92ab23e2c4478cabd0e34 GIT binary patch literal 8639 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 bn@jt^P{*zXr9d0pUN=Bn-9W7Z<`DjWCgu)p literal 0 HcmV?d00001 From a5a02d90fc4ef1ecb9f7ba3b1aed24086cfc287c Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Sun, 18 Jun 2023 19:47:15 +0530 Subject: [PATCH 0854/2055] DW en and de update --- recipes/deutsche_welle_de.recipe | 41 +++++++++--------- recipes/deutsche_welle_en.recipe | 72 +++++++++----------------------- 2 files changed, 39 insertions(+), 74 deletions(-) diff --git a/recipes/deutsche_welle_de.recipe b/recipes/deutsche_welle_de.recipe index 2d988f7c86..4ee5bd9c67 100644 --- a/recipes/deutsche_welle_de.recipe +++ b/recipes/deutsche_welle_de.recipe @@ -1,21 +1,12 @@ -from calibre.web.feeds.news import BasicNewsRecipe -# History: -# 1: Base Version -# 2: Added rules for wdr.de, ndr.de, br-online.de -# 3: Added rules for rbb-online.de, boerse.ard.de, sportschau.de -# 4: New design of tagesschau.de implemented. Simplified. -# 5: Taken out the pictures. - +from calibre.web.feeds.news import BasicNewsRecipe, classes class DeutscheWelle(BasicNewsRecipe): title = 'Deutsche Welle' description = 'Nachrichten der Deutschen Welle (DW)' publisher = 'DW - info@dw.com' language = 'de' - version = 1 - cover_url = 'https://pbs.twimg.com/profile_images/900269457976823808/nkod9w_m_400x400.jpg' - __author__ = 'VoHe' - oldest_article = 3 + __author__ = 'unkn0wn' + oldest_article = 2 max_articles_per_feed = 200 no_stylesheets = True remove_javascript = True @@ -23,26 +14,32 @@ class DeutscheWelle(BasicNewsRecipe): remove_javascript = True remove_empty_feeds = True ignore_duplicate_articles = {'title', 'url'} + remove_attributes = ['height', 'width', 'style'] - remove_tags_before = dict(name='h4', attrs={'class':'artikel'}) - - remove_tags_after = dict(name='div', attrs={'class':'col1 dim'}) + keep_only_tags = [ + dict(name='article') + ] remove_tags = [ - dict(name='div', attrs={'class':'footerSection'}), - dict(name='div', attrs={'class':'sharing-bar'}), - dict(name='div', attrs={'class':'coll dim'}), - dict(name='div', attrs={'class':'languageSection'}), + dict(name=['footer', 'source']), + dict(attrs={'data-tracking-name':'sharing-icons-inline'}), + classes('kicker advertisement vjs-wrapper') ] + # watch out https://www.dw.com/de/service/rss/s-9773 for description of possible rss feeds feeds = [ - ('Thema des Tages', 'http://rss.dw.com/xml/rss-de-top'), - # ('Nachrichten', 'http://rss.dw.com/xml/rss-de-news'), + ('Nachrichten', 'http://rss.dw.com/xml/rss-de-news'), ('Wissenschaft', 'http://rss.dw.com/xml/rss-de-wissenschaft'), - # ('Sport', 'http://rss.dw.com/xml/rss-de-sport'), + ('Sport', 'http://rss.dw.com/xml/rss-de-sport'), ('Deuschland entdecken', 'http://rss.dw.com/xml/rss-de-deutschlandentdecken'), ('Presse', 'http://rss.dw.com/xml/presse'), ('Politik', 'http://rss.dw.com/xml/rss_de_politik'), ('Wirtschaft', 'http://rss.dw.com/xml/rss-de-eco'), ('Kultur und Leben', 'http://rss.dw.com/xml/rss-de-cul'), + ('Thema des Tages', 'http://rss.dw.com/xml/rss-de-top'), ] + + def preprocess_html(self, soup): + for img in soup.findAll('img', srcset=True): + img['src'] = img['srcset'].split()[6] + return soup diff --git a/recipes/deutsche_welle_en.recipe b/recipes/deutsche_welle_en.recipe index 3cde7e7418..faa02a6183 100644 --- a/recipes/deutsche_welle_en.recipe +++ b/recipes/deutsche_welle_en.recipe @@ -1,34 +1,31 @@ -#!/usr/bin/env python -# vim:fileencoding=utf-8 -from __future__ import unicode_literals, division, absolute_import, print_function - -__license__ = 'GPL v3' -__copyright__ = '2010, Darko Miletic ' - -''' -Deutsche Welle (english) - dw.com/en -''' - -import re -from calibre.web.feeds.news import BasicNewsRecipe - +from calibre.web.feeds.news import BasicNewsRecipe, classes class DeutscheWelle_en(BasicNewsRecipe): title = 'Deutsche Welle' - __author__ = 'Darko Miletic' + __author__ = 'unkn0wn' description = 'News from Germany and the world' publisher = 'Deutsche Welle' language = 'en' - oldest_article = 1 + oldest_article = 2 max_articles_per_feed = 50 no_stylesheets = True remove_javascript = True remove_empty_feeds = True ignore_duplicate_articles = {'title', 'url'} - + remove_attributes = ['height', 'width', 'style'] + + keep_only_tags = [ + dict(name='article') + ] + + remove_tags = [ + dict(name=['footer', 'source']), + dict(attrs={'data-tracking-name':'sharing-icons-inline'}), + classes('kicker advertisement vjs-wrapper') + ] + feeds = [ - ('Top Stories', 'http://rss.dw-world.de/rdf/rss-en-top'), ('World', 'http://rss.dw.de/rdf/rss-en-world'), ('Germany', 'http://rss.dw.de/rdf/rss-en-ger'), ('Europe', 'http://rss.dw.de/rdf/rss-en-eu'), @@ -36,40 +33,11 @@ class DeutscheWelle_en(BasicNewsRecipe): ('Culture & Lifestyle', 'http://rss.dw.de/rdf/rss-en-cul'), ('Sports', 'http://rss.dw.de/rdf/rss-en-sports'), ('Visit Germany', 'http://rss.dw.de/rdf/rss-en-visitgermany'), - ('Asia', 'http://rss.dw.de/rdf/rss-en-asia') + ('Asia', 'http://rss.dw.de/rdf/rss-en-asia'), + ('Top Stories', 'http://rss.dw-world.de/rdf/rss-en-top'), ] - - keep_only_tags = [ - dict(name='div', attrs={'class': 'col3'}) - ] - - remove_tags_after = [ - dict(name='div', attrs={'class': 'group'}) - ] - - remove_tags = [ - dict(name='div', attrs={'class': 'col1'}), - dict(name='div', attrs={'class': re.compile('gallery')}), - dict(name='div', attrs={'class': re.compile('audio')}), - dict(name='div', attrs={'class': re.compile('video')}) - ] - - remove_attributes = ['height', 'width', - 'onclick', 'border', 'lang', 'link'] - - extra_css = ''' - h1 {font-size: 1.6em; margin-top: 0em} - .artikel {font-size: 1em; text-transform: uppercase; margin: 0em} - ''' - + def preprocess_html(self, soup): - # convert local hyperlinks - for a in soup.findAll('a', href=True): - if a['href'].startswith('/'): - a['href'] = 'http://www.dw.com' + a['href'] - elif a['href'].startswith('#'): - del a['href'] - # remove all style attributes with an effect on font size - for item in soup.findAll(attrs={'style': re.compile('font-size')}): - del item['style'] + for img in soup.findAll('img', srcset=True): + img['src'] = img['srcset'].split()[6] return soup From 0c8aacd740d8b0d369a5daafed8eec1b6a221207 Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Sun, 18 Jun 2023 20:07:41 +0530 Subject: [PATCH 0855/2055] DW (all languages) update. realized that they all require the same code. --- recipes/deutsche_welle_bs.recipe | 77 +++++++++----------------- recipes/deutsche_welle_es.recipe | 64 ++++++---------------- recipes/deutsche_welle_hr.recipe | 71 ++++++++---------------- recipes/deutsche_welle_pt.recipe | 66 ++++++++--------------- recipes/deutsche_welle_ru.recipe | 34 ++++++++---- recipes/deutsche_welle_sr.recipe | 93 +++++++++++--------------------- 6 files changed, 138 insertions(+), 267 deletions(-) diff --git a/recipes/deutsche_welle_bs.recipe b/recipes/deutsche_welle_bs.recipe index 0b852c94c8..65ccd31cb9 100644 --- a/recipes/deutsche_welle_bs.recipe +++ b/recipes/deutsche_welle_bs.recipe @@ -1,73 +1,44 @@ -__license__ = 'GPL v3' -__copyright__ = '2010, Darko Miletic ' -''' -dw-world.de -''' - -import re -from calibre.web.feeds.news import BasicNewsRecipe - +from calibre.web.feeds.news import BasicNewsRecipe, classes class DeutscheWelle_bs(BasicNewsRecipe): title = 'Deutsche Welle' - __author__ = 'Darko Miletic' + __author__ = 'unkn0wn' description = 'Vijesti iz Njemacke i svijeta' publisher = 'Deutsche Welle' category = 'news, politics, Germany' - oldest_article = 1 + oldest_article = 2 max_articles_per_feed = 100 use_embedded_content = False no_stylesheets = True language = 'bs' publication_type = 'newsportal' remove_empty_feeds = True + remove_javascript = True masthead_url = 'http://www.dw-world.de/skins/std/channel1/pics/dw_logo1024.gif' - extra_css = """ - @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} - body{font-family: Arial,sans1,sans-serif} - img{margin-top: 0.5em; margin-bottom: 0.2em; display: block} - .caption{font-size: x-small; display: block; margin-bottom: 0.4em} - """ - preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] - - conversion_options = { - 'comment': description, 'tags': category, 'publisher': publisher, 'language': language - } - + + ignore_duplicate_articles = {'title', 'url'} + remove_attributes = ['height', 'width', 'style'] + + keep_only_tags = [ + dict(name='article') + ] + remove_tags = [ - dict(name=['iframe', 'embed', 'object', 'form', 'base', 'meta', 'link']), dict( - attrs={'class': 'actionFooter'}) + dict(name=['footer', 'source']), + dict(attrs={'data-tracking-name':'sharing-icons-inline'}), + classes('kicker advertisement vjs-wrapper') ] - keep_only_tags = [dict(attrs={'class': 'ArticleDetail detail'})] - remove_attributes = ['height', 'width', 'onclick', 'border', 'lang'] - + feeds = [ - - (u'Politika', u'http://rss.dw-world.de/rdf/rss-bos-pol'), - (u'Evropa', u'http://rss.dw-world.de/rdf/rss-bos-eu'), - (u'Kiosk', u'http://rss.dw-world.de/rdf/rss-bos-eu'), - (u'Ekonomija i Nuka', u'http://rss.dw-world.de/rdf/rss-bos-eco'), - (u'Kultura', u'http://rss.dw-world.de/rdf/rss-bos-cul'), - (u'Sport', u'http://rss.dw-world.de/rdf/rss-bos-sp') + (u'Politika', u'http://rss.dw-world.de/rdf/rss-bos-pol'), + (u'Evropa', u'http://rss.dw-world.de/rdf/rss-bos-eu'), + (u'Kiosk', u'http://rss.dw-world.de/rdf/rss-bos-eu'), + (u'Ekonomija i Nuka', u'http://rss.dw-world.de/rdf/rss-bos-eco'), + (u'Kultura', u'http://rss.dw-world.de/rdf/rss-bos-cul'), + (u'Sport', u'http://rss.dw-world.de/rdf/rss-bos-sp') ] - def print_version(self, url): - artl = url.rpartition('/')[2] - return 'http://www.dw-world.de/popups/popup_printcontent/' + artl - def preprocess_html(self, soup): - for item in soup.findAll('a'): - limg = item.find('img') - if item.string is not None: - str = item.string - item.replaceWith(str) - else: - if limg: - item.name = 'div' - del item['href'] - item['target'] = '' - del item['target'] - else: - str = self.tag_to_string(item) - item.replaceWith(str) + for img in soup.findAll('img', srcset=True): + img['src'] = img['srcset'].split()[6] return soup diff --git a/recipes/deutsche_welle_es.recipe b/recipes/deutsche_welle_es.recipe index 1300fea96d..a036b2a96b 100644 --- a/recipes/deutsche_welle_es.recipe +++ b/recipes/deutsche_welle_es.recipe @@ -1,21 +1,8 @@ -#!/usr/bin/env python -# vim:fileencoding=utf-8 -from __future__ import unicode_literals, division, absolute_import, print_function - -__license__ = 'GPL v3' -__copyright__ = '2010, Darko Miletic ' - -''' -Deutsche Welle (español) - dw.com/es -''' - -import re -from calibre.web.feeds.news import BasicNewsRecipe - +from calibre.web.feeds.news import BasicNewsRecipe, classes class DeutscheWelle_es(BasicNewsRecipe): title = 'Deutsche Welle' - __author__ = 'Darko Miletic' + __author__ = 'unkn0wn' description = 'Noticias desde Alemania y mundo' publisher = 'Deutsche Welle' language = 'es' @@ -27,6 +14,18 @@ class DeutscheWelle_es(BasicNewsRecipe): remove_empty_feeds = True ignore_duplicate_articles = {'title', 'url'} + remove_attributes = ['height', 'width', 'style'] + + keep_only_tags = [ + dict(name='article') + ] + + remove_tags = [ + dict(name=['footer', 'source']), + dict(attrs={'data-tracking-name':'sharing-icons-inline'}), + classes('kicker advertisement vjs-wrapper') + ] + feeds = [ ('Titulares', 'http://rss.dw-world.de/rdf/rss-sp-top'), ('Noticias de Alemania', 'http://rss.dw-world.de/rdf/rss-sp-ale'), @@ -40,37 +39,8 @@ class DeutscheWelle_es(BasicNewsRecipe): ('Conozca Alemania', 'http://rss.dw-world.de/rdf/rss-sp-con') ] - keep_only_tags = [ - dict(name='div', attrs={'class': 'col3'}) - ] - - remove_tags_after = [ - dict(name='div', attrs={'class': 'group'}) - ] - - remove_tags = [ - dict(name='div', attrs={'class': 'col1'}), - dict(name='div', attrs={'class': re.compile('gallery')}), - dict(name='div', attrs={'class': re.compile('audio')}), - dict(name='div', attrs={'class': re.compile('video')}) - ] - - remove_attributes = ['height', 'width', - 'onclick', 'border', 'lang', 'link'] - - extra_css = ''' - h1 {font-size: 1.6em; margin-top: 0em} - .artikel {font-size: 1em; text-transform: uppercase; margin: 0em} - ''' - + def preprocess_html(self, soup): - # convert local hyperlinks - for a in soup.findAll('a', href=True): - if a['href'].startswith('/'): - a['href'] = 'http://www.dw.com' + a['href'] - elif a['href'].startswith('#'): - del a['href'] - # remove all style attributes with an effect on font size - for item in soup.findAll(attrs={'style': re.compile('font-size')}): - del item['style'] + for img in soup.findAll('img', srcset=True): + img['src'] = img['srcset'].split()[6] return soup diff --git a/recipes/deutsche_welle_hr.recipe b/recipes/deutsche_welle_hr.recipe index 906e4a1d39..73264fc635 100644 --- a/recipes/deutsche_welle_hr.recipe +++ b/recipes/deutsche_welle_hr.recipe @@ -1,20 +1,12 @@ -__license__ = 'GPL v3' -__copyright__ = '2010, Darko Miletic ' -''' -dw-world.de -''' - -import re -from calibre.web.feeds.news import BasicNewsRecipe - +from calibre.web.feeds.news import BasicNewsRecipe, classes class DeutscheWelle_hr(BasicNewsRecipe): title = 'Deutsche Welle' - __author__ = 'Darko Miletic' + __author__ = 'unkn0wn' description = 'Vesti iz Njemacke i svijeta' publisher = 'Deutsche Welle' category = 'news, politics, Germany' - oldest_article = 1 + oldest_article = 2 max_articles_per_feed = 100 use_embedded_content = False no_stylesheets = True @@ -22,50 +14,29 @@ class DeutscheWelle_hr(BasicNewsRecipe): publication_type = 'newsportal' remove_empty_feeds = True masthead_url = 'http://www.dw-world.de/skins/std/channel1/pics/dw_logo1024.gif' - extra_css = """ - @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} - body{font-family: Arial,sans1,sans-serif} - img{margin-top: 0.5em; margin-bottom: 0.2em; display: block} - .caption{font-size: x-small; display: block; margin-bottom: 0.4em} - """ - preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] - - conversion_options = { - 'comment': description, 'tags': category, 'publisher': publisher, 'language': language - } + remove_javascript = True + + ignore_duplicate_articles = {'title', 'url'} + remove_attributes = ['height', 'width', 'style'] + keep_only_tags = [ + dict(name='article') + ] + remove_tags = [ - dict(name=['iframe', 'embed', 'object', 'form', 'base', 'meta', 'link']), dict( - attrs={'class': 'actionFooter'}) + dict(name=['footer', 'source']), + dict(attrs={'data-tracking-name':'sharing-icons-inline'}), + classes('kicker advertisement vjs-wrapper') ] - keep_only_tags = [dict(attrs={'class': 'ArticleDetail detail'})] - remove_attributes = ['height', 'width', 'onclick', 'border', 'lang'] - + feeds = [ - - (u'Svijet', u'http://rss.dw-world.de/rdf/rss-cro-svijet'), - (u'Europa', u'http://rss.dw-world.de/rdf/rss-cro-eu'), - (u'Njemacka', u'http://rss.dw-world.de/rdf/rss-cro-ger'), - (u'Vijesti', u'http://rss.dw-world.de/rdf/rss-cro-all') + (u'Svijet', u'http://rss.dw-world.de/rdf/rss-cro-svijet'), + (u'Europa', u'http://rss.dw-world.de/rdf/rss-cro-eu'), + (u'Njemacka', u'http://rss.dw-world.de/rdf/rss-cro-ger'), + (u'Vijesti', u'http://rss.dw-world.de/rdf/rss-cro-all') ] - def print_version(self, url): - artl = url.rpartition('/')[2] - return 'http://www.dw-world.de/popups/popup_printcontent/' + artl - def preprocess_html(self, soup): - for item in soup.findAll('a'): - limg = item.find('img') - if item.string is not None: - str = item.string - item.replaceWith(str) - else: - if limg: - item.name = 'div' - del item['href'] - item['target'] = '' - del item['target'] - else: - str = self.tag_to_string(item) - item.replaceWith(str) + for img in soup.findAll('img', srcset=True): + img['src'] = img['srcset'].split()[6] return soup diff --git a/recipes/deutsche_welle_pt.recipe b/recipes/deutsche_welle_pt.recipe index 4b9a9ea9dc..aff42efd0a 100644 --- a/recipes/deutsche_welle_pt.recipe +++ b/recipes/deutsche_welle_pt.recipe @@ -1,19 +1,12 @@ -__license__ = 'GPL v3' -__copyright__ = '2010, Darko Miletic ' -''' -dw-world.de -''' - -from calibre.web.feeds.news import BasicNewsRecipe - +from calibre.web.feeds.news import BasicNewsRecipe, classes class DeutscheWelle_pt(BasicNewsRecipe): title = 'Deutsche Welle' - __author__ = 'Darko Miletic' + __author__ = 'unkn0wn' description = 'Noticias desde Alemania y mundo' publisher = 'Deutsche Welle' category = 'news, politics, Germany' - oldest_article = 1 + oldest_article = 2 max_articles_per_feed = 100 use_embedded_content = False no_stylesheets = True @@ -21,42 +14,25 @@ class DeutscheWelle_pt(BasicNewsRecipe): publication_type = 'newsportal' remove_empty_feeds = True masthead_url = 'http://www.dw-world.de/skins/std/channel1/pics/dw_logo1024.gif' - extra_css = """ - body{font-family: Arial,sans-serif} - img{margin-top: 0.5em; margin-bottom: 0.2em; display: block} - .caption{font-size: x-small; display: block; margin-bottom: 0.4em} - """ + + + remove_javascript = True + ignore_duplicate_articles = {'title', 'url'} + remove_attributes = ['height', 'width', 'style'] + + def preprocess_html(self, soup): + for img in soup.findAll('img', srcset=True): + img['src'] = img['srcset'].split()[6] + return soup - conversion_options = { - 'comment': description, 'tags': category, 'publisher': publisher, 'language': language - } - - remove_tags = [ - dict(name=['iframe', 'embed', 'object', 'form', 'base', 'meta', 'link']), dict( - attrs={'class': 'actionFooter'}) + keep_only_tags = [ + dict(name='article') + ] + + remove_tags = [ + dict(name=['footer', 'source']), + dict(attrs={'data-tracking-name':'sharing-icons-inline'}), + classes('kicker advertisement vjs-wrapper') ] - keep_only_tags = [dict(attrs={'class': 'ArticleDetail detail'})] - remove_attributes = ['height', 'width', 'onclick', 'border', 'lang'] feeds = [(u'Noticias', u'http://rss.dw-world.de/rdf/rss-br-all')] - - def print_version(self, url): - artl = url.rpartition('/')[2] - return 'http://www.dw-world.de/popups/popup_printcontent/' + artl - - def preprocess_html(self, soup): - for item in soup.findAll('a'): - limg = item.find('img') - if item.string is not None: - str = item.string - item.replaceWith(str) - else: - if limg: - item.name = 'div' - del item['href'] - item['target'] = '' - del item['target'] - else: - str = self.tag_to_string(item) - item.replaceWith(str) - return soup diff --git a/recipes/deutsche_welle_ru.recipe b/recipes/deutsche_welle_ru.recipe index 0fbbcc6327..ec4e838af0 100644 --- a/recipes/deutsche_welle_ru.recipe +++ b/recipes/deutsche_welle_ru.recipe @@ -1,24 +1,36 @@ -#!/usr/bin/env python -# vim:fileencoding=utf-8 - -from calibre.web.feeds.news import BasicNewsRecipe +from calibre.web.feeds.news import BasicNewsRecipe, classes class DeutscheWelle(BasicNewsRecipe): title = u'Deutsche Welle \u043D\u0430 \u0440\u0443\u0441\u0441\u043A\u043E\u043C' description = u'\u0420\u0443\u0441\u0441\u043A\u0430\u044F \u0440\u0435\u0434\u0430\u043A\u0446\u0438\u044F Deutsche Welle: \u043D\u043E\u0432\u043E\u0441\u0442\u0438, \u0430\u043D\u0430\u043B\u0438\u0442\u0438\u043A\u0430, \u043A\u043E\u043C\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0438 \u0438 \u0440\u0435\u043F\u043E\u0440\u0442\u0430\u0436\u0438 \u0438\u0437 \u0413\u0435\u0440\u043C\u0430\u043D\u0438\u0438 \u0438 \u0415\u0432\u0440\u043E\u043F\u044B, \u043D\u0435\u043C\u0435\u0446\u043A\u0438\u0439 \u0438 \u0435\u0432\u0440\u043E\u043F\u0435\u0439\u0441\u043A\u0438\u0439 \u0432\u0437\u0433\u043B\u044F\u0434 \u043D\u0430 \u0441\u043E\u0431\u044B\u0442\u0438\u044F \u0432 \u0420\u043E\u0441\u0441\u0438\u0438 \u0438 \u043C\u0438\u0440\u0435, \u043F\u0440\u0430\u043A\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0435 \u0441\u043E\u0432\u0435\u0442\u044B \u0434\u043B\u044F \u0442\u0443\u0440\u0438\u0441\u0442\u043E\u0432 \u0438 \u0442\u0435\u0445, \u043A\u0442\u043E \u0436\u0435\u043B\u0430\u0435\u0442 \u0443\u0447\u0438\u0442\u044C\u0441\u044F \u0438\u043B\u0438 \u0440\u0430\u0431\u043E\u0442\u0430\u0442\u044C \u0432 \u0413\u0435\u0440\u043C\u0430\u043D\u0438\u0438 \u0438 \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u0442\u0440\u0430\u043D\u0430\u0445 \u0415\u0432\u0440\u043E\u0441\u043E\u044E\u0437\u0430.' # noqa - __author__ = 'bugmen00t' + __author__ = 'bugmen00t, unkn0wn' publication_type = 'newspaper' - oldest_article = 14 + oldest_article = 2 max_articles_per_feed = 100 language = 'ru' - cover_url = 'https://www.dw.com/cssi/dwlogo-print.gif' - auto_cleanup = False - no_stylesheets = False + # cover_url = 'https://www.dw.com/cssi/dwlogo-print.gif' - remove_tags_before = dict(name='h1') + remove_javascript = True + no_stylesheets = True + remove_empty_feeds = True + ignore_duplicate_articles = {'title', 'url'} + remove_attributes = ['height', 'width', 'style'] + + def preprocess_html(self, soup): + for img in soup.findAll('img', srcset=True): + img['src'] = img['srcset'].split()[6] + return soup - remove_tags_after = dict(name='div', attrs={'class': 'longText'}) + keep_only_tags = [ + dict(name='article') + ] + + remove_tags = [ + dict(name=['footer', 'source']), + dict(attrs={'data-tracking-name':'sharing-icons-inline'}), + classes('kicker advertisement vjs-wrapper') + ] feeds = [ ( diff --git a/recipes/deutsche_welle_sr.recipe b/recipes/deutsche_welle_sr.recipe index b9c67e4976..7f1b1717c0 100644 --- a/recipes/deutsche_welle_sr.recipe +++ b/recipes/deutsche_welle_sr.recipe @@ -1,20 +1,12 @@ -__license__ = 'GPL v3' -__copyright__ = '2010, Darko Miletic ' -''' -dw-world.de -''' - -import re -from calibre.web.feeds.news import BasicNewsRecipe - +from calibre.web.feeds.news import BasicNewsRecipe, classes class DeutscheWelle_sr(BasicNewsRecipe): title = 'Deutsche Welle' - __author__ = 'Darko Miletic' + __author__ = 'unkn0wn' description = 'Vesti iz Nemacke i sveta' publisher = 'Deutsche Welle' category = 'news, politics, Germany' - oldest_article = 1 + oldest_article = 2 max_articles_per_feed = 100 use_embedded_content = False no_stylesheets = True @@ -22,55 +14,34 @@ class DeutscheWelle_sr(BasicNewsRecipe): publication_type = 'newsportal' remove_empty_feeds = True masthead_url = 'http://www.dw-world.de/skins/std/channel1/pics/dw_logo1024.gif' - extra_css = """ - @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} - body{font-family: Arial,sans1,sans-serif} - img{margin-top: 0.5em; margin-bottom: 0.2em; display: block} - .caption{font-size: x-small; display: block; margin-bottom: 0.4em} - """ - preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] - - conversion_options = { - 'comment': description, 'tags': category, 'publisher': publisher, 'language': language - } - - remove_tags = [ - dict(name=['iframe', 'embed', 'object', 'form', 'base', 'meta', 'link']), dict( - attrs={'class': 'actionFooter'}) - ] - keep_only_tags = [dict(attrs={'class': 'ArticleDetail detail'})] - remove_attributes = ['height', 'width', 'onclick', 'border', 'lang'] - - feeds = [ - - (u'Politika', u'http://rss.dw-world.de/rdf/rss-ser-pol'), - (u'Srbija', u'http://rss.dw-world.de/rdf/rss-ser-pol-ser'), - (u'Region', u'http://rss.dw-world.de/rdf/rss-ser-pol-region'), - (u'Evropa', u'http://rss.dw-world.de/rdf/rss-ser-pol-eu'), - (u'Nemacka', u'http://rss.dw-world.de/rdf/rss-ser-pol-ger'), - (u'Svet', u'http://rss.dw-world.de/rdf/rss-ser-pol-ger'), - (u'Pregled stampe', u'http://rss.dw-world.de/rdf/rss-ser-pol-ger'), - (u'Nauka Tehnika Medicina', u'http://rss.dw-world.de/rdf/rss-ser-science'), - (u'Kultura', u'feed:http://rss.dw-world.de/rdf/rss-ser-cul') - ] - - def print_version(self, url): - artl = url.rpartition('/')[2] - return 'http://www.dw-world.de/popups/popup_printcontent/' + artl - + remove_javascript = True + ignore_duplicate_articles = {'title', 'url'} + remove_attributes = ['height', 'width', 'style'] + def preprocess_html(self, soup): - for item in soup.findAll('a'): - limg = item.find('img') - if item.string is not None: - str = item.string - item.replaceWith(str) - else: - if limg: - item.name = 'div' - del item['href'] - item['target'] = '' - del item['target'] - else: - str = self.tag_to_string(item) - item.replaceWith(str) + for img in soup.findAll('img', srcset=True): + img['src'] = img['srcset'].split()[6] return soup + + keep_only_tags = [ + dict(name='article') + ] + + remove_tags = [ + dict(name=['footer', 'source']), + dict(attrs={'data-tracking-name':'sharing-icons-inline'}), + classes('kicker advertisement vjs-wrapper') + ] + + feeds = [ + (u'Politika', u'http://rss.dw-world.de/rdf/rss-ser-pol'), + (u'Srbija', u'http://rss.dw-world.de/rdf/rss-ser-pol-ser'), + (u'Region', u'http://rss.dw-world.de/rdf/rss-ser-pol-region'), + (u'Evropa', u'http://rss.dw-world.de/rdf/rss-ser-pol-eu'), + (u'Nemacka', u'http://rss.dw-world.de/rdf/rss-ser-pol-ger'), + (u'Svet', u'http://rss.dw-world.de/rdf/rss-ser-pol-ger'), + (u'Pregled stampe', u'http://rss.dw-world.de/rdf/rss-ser-pol-ger'), + (u'Nauka Tehnika Medicina', u'http://rss.dw-world.de/rdf/rss-ser-science'), + (u'Kultura', u'feed:http://rss.dw-world.de/rdf/rss-ser-cul') + ] + From 2ac8638b9896cd1d9ceb2ff8a12ab0f03bc57488 Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Sun, 18 Jun 2023 20:20:39 +0530 Subject: [PATCH 0856/2055] ... --- recipes/icons/deutsche_welle_bs.png | Bin 187 -> 926 bytes recipes/icons/deutsche_welle_de.png | Bin 1261 -> 926 bytes recipes/icons/deutsche_welle_en.png | Bin 187 -> 926 bytes recipes/icons/deutsche_welle_es.png | Bin 187 -> 926 bytes recipes/icons/deutsche_welle_hr.png | Bin 187 -> 926 bytes recipes/icons/deutsche_welle_pt.png | Bin 187 -> 926 bytes recipes/icons/deutsche_welle_sr.png | Bin 187 -> 926 bytes 7 files changed, 0 insertions(+), 0 deletions(-) diff --git a/recipes/icons/deutsche_welle_bs.png b/recipes/icons/deutsche_welle_bs.png index 034074e83de519fb0b0c300a4529d8e5dcda2a91..c0c5b8c0eb722ea657c3df60b9c714022273da36 100644 GIT binary patch literal 926 zcmY+BdrVUY6vjWKqZ7tpOGN5OrrC6E1iiaTbsc8lTD2Jv#+vy^#$?(}H?cCxCUtH~ zoPsi11)LAY3Kg@hN?=pNbabML((rx=LM;?6L~GmI+UM;p?e5}2vOmuGa!$TqzLT8t zob2_n(F>vh#ELfvb6Ji5x1Nn;`_h!S4_M)RmXVbKv?ybO`OmQP!fhLJvjD{s;J_gu zz^)EF1d0j3vK0`V0_N|jz50F{5HXUIDG_1EpZNS5cC|rv9fdbhbPHc~U~eZBJ=oue zZwGO37>7pj{TRx|acmO1bl80za@MAYq8s0^o&g*-pv;IL9zZ#T;tuTZ$9ID$9foQe zCmx~dG4^%i;2j*fhw=#=GofMU%6kn{UGI*WRHPoixU&#!q(H z4K~{94PG|7mz}rbloPd{#G^w7t1qu07rShrpHmy>DYVIKJ5T;Mecn-uDZ1@3JqewZJ)(wS+aGuZKnN&VfI^qc<=6YXI_ zGokQ+m08#7!?_UDG#Y|Y=a{*T3`fJnGzQQZ4)AFn`}>Gb-ditR8$Ytf%?@0-I7`Hx z<|ah(=OvFQ)7h0uEX@7B$Ey}xJBO-9y5bR+}|KkTpKv|cbPzetYC zF51Y8BsaeF4zsw4)bTCm<`)+#iZdJ9Dy3mi_}qox869 delta 170 zcmV;b09F5<2fG1~8Gi-<001BJ|6u?C0D?(GK~#9!V;Hnx6c{!AN5c1kI04FbfYRrI zxB-aYgA@w@aR5|cKbm3%5F09YABx{289bi>CIF@Tku=SR^8Z5(?MD*754Qy59GJ79 zwt~d=BjE<9m;#6oRZ{@O>wy^V=Kz?=P}~m{QvfN#hcN(H6C)x_<|DacJ(NyBHDD9~ Y0IBb4e9?j`PXGV_07*qoM6N<$f)9d0s{jB1 diff --git a/recipes/icons/deutsche_welle_de.png b/recipes/icons/deutsche_welle_de.png index 0cea86328ccb8900af80398f329b361554d02631..c0c5b8c0eb722ea657c3df60b9c714022273da36 100644 GIT binary patch literal 926 zcmY+BdrVUY6vjWKqZ7tpOGN5OrrC6E1iiaTbsc8lTD2Jv#+vy^#$?(}H?cCxCUtH~ zoPsi11)LAY3Kg@hN?=pNbabML((rx=LM;?6L~GmI+UM;p?e5}2vOmuGa!$TqzLT8t zob2_n(F>vh#ELfvb6Ji5x1Nn;`_h!S4_M)RmXVbKv?ybO`OmQP!fhLJvjD{s;J_gu zz^)EF1d0j3vK0`V0_N|jz50F{5HXUIDG_1EpZNS5cC|rv9fdbhbPHc~U~eZBJ=oue zZwGO37>7pj{TRx|acmO1bl80za@MAYq8s0^o&g*-pv;IL9zZ#T;tuTZ$9ID$9foQe zCmx~dG4^%i;2j*fhw=#=GofMU%6kn{UGI*WRHPoixU&#!q(H z4K~{94PG|7mz}rbloPd{#G^w7t1qu07rShrpHmy>DYVIKJ5T;Mecn-uDZ1@3JqewZJ)(wS+aGuZKnN&VfI^qc<=6YXI_ zGokQ+m08#7!?_UDG#Y|Y=a{*T3`fJnGzQQZ4)AFn`}>Gb-ditR8$Ytf%?@0-I7`Hx z<|ah(=OvFQ)7h0uEX@7B$Ey}xJBO-9y5bR+}|KkTpKv|cbPzetYC zF51Y8BsaeF4zsw4)bTCm<`)+#iZdJ9Dy3mi_}qox869 literal 1261 zcmVPx(r%6OXR7i>KR%>h&RTTct-PxJlcDuWMNx{a@(xMe94}(I2V)_FWT7m@;6%ect zZBhsj0UwDNqejFuASnvf)|73cQQANRwV{NNS`|vLKm}TxEwp8UzP8=AyE{8G{@Cu! z>;p{H{&RAF%{}*g-#OZsfp~43WMnIt8 z0*L*00Oi9WzoCV2n12@*;lL1B$Z60Wk>;A|BKwB`pABflnF_;924q`VikSfF6LG~2 zufkLkCWia1Na6vY$A{J-c;!0*l=W(SL#t@2@sqi{jVDo7d=e~1@<~c*i@fu98N_zZ zV4HIw7_^w&er!F5FWO@Q?7IyBj6neyBjDSG@r9X2N2^$R(SM;Ieubta(3a`O;vDXj zO9rqYEw%&>U&r#(h-fRq9+#eb$#m1+$KqF=7n+LA76^booFnX*$?s|w%^a0H$klX6 zm)*)6xm;bh+;B^IIh&;l$gEXdK1~#a-Pc2nM)U6#PCuJG?NDz^zwB6;uyU%<=9ZHL zN-?vePXCf0N0J2AyVknAZnVZ3=zKALcCFXtMZ;p#wx+=OKIQgG-ptX4M(JQ1RFxBU zIn_KYn`*9nGSOD%l0lBA?D?63?eiW+RJYWYm(FKY^*e`@j@9-CSKx*E(ZUSl;SLF) zrIWcM*Q8|`+&6gwfZZ1Zub-7w(u9xN0RjLBRgDlWqUaIG*YY zlOIhprdRZKjVPTjC0cow`enZllxkv+PvX9FN+9|=&$MES5x}lyaeI@b%98Lw>sTu> znm5Vx^Qwt6D!ZF|0@aI?pU;UO^vUr?GBB$8V|2|=?g?=Q*ZNcdOTHgG+97VAWiFU( zoL}oPaddmF&0-_~>rQzqTLWWt1rgr63>K6fS$OIy$vECVvhrxp>S>nQ6L=7YMKs>< zA9MN?(4Hr(8)sPnG~E&#y2a&L!X%5~z$LF;pcPpHfI+{KSv}P2huZvvcU!bcK(sd3 zT2YX;sjg>llUrpG92lD)PWX6%-N5c9({(32l~?@w0YiA_0uY+j1Q32Cw)Zu&iROGkMkEKY9gFW*=LfN9U zGz$knQ4}H?@JjDD4wRl51Yye@`_5?Znxc z6BzkFR+jWz0AbUqFmTO9IBUkD6cX`NhzUc4lKK$AD5qnEiaM#cS6?JS%1pNl0%L^Y zx-bf$*uma0Mf&?Z)EQ&(1y%Iw4>3-vq@qHOjTEuP^hMrqOegm6%&Llx6jGG^ox}YL XF5z;Y|GGYS00000NkvXXu0mjf$#Gd9 diff --git a/recipes/icons/deutsche_welle_en.png b/recipes/icons/deutsche_welle_en.png index 034074e83de519fb0b0c300a4529d8e5dcda2a91..c0c5b8c0eb722ea657c3df60b9c714022273da36 100644 GIT binary patch literal 926 zcmY+BdrVUY6vjWKqZ7tpOGN5OrrC6E1iiaTbsc8lTD2Jv#+vy^#$?(}H?cCxCUtH~ zoPsi11)LAY3Kg@hN?=pNbabML((rx=LM;?6L~GmI+UM;p?e5}2vOmuGa!$TqzLT8t zob2_n(F>vh#ELfvb6Ji5x1Nn;`_h!S4_M)RmXVbKv?ybO`OmQP!fhLJvjD{s;J_gu zz^)EF1d0j3vK0`V0_N|jz50F{5HXUIDG_1EpZNS5cC|rv9fdbhbPHc~U~eZBJ=oue zZwGO37>7pj{TRx|acmO1bl80za@MAYq8s0^o&g*-pv;IL9zZ#T;tuTZ$9ID$9foQe zCmx~dG4^%i;2j*fhw=#=GofMU%6kn{UGI*WRHPoixU&#!q(H z4K~{94PG|7mz}rbloPd{#G^w7t1qu07rShrpHmy>DYVIKJ5T;Mecn-uDZ1@3JqewZJ)(wS+aGuZKnN&VfI^qc<=6YXI_ zGokQ+m08#7!?_UDG#Y|Y=a{*T3`fJnGzQQZ4)AFn`}>Gb-ditR8$Ytf%?@0-I7`Hx z<|ah(=OvFQ)7h0uEX@7B$Ey}xJBO-9y5bR+}|KkTpKv|cbPzetYC zF51Y8BsaeF4zsw4)bTCm<`)+#iZdJ9Dy3mi_}qox869 delta 170 zcmV;b09F5<2fG1~8Gi-<001BJ|6u?C0D?(GK~#9!V;Hnx6c{!AN5c1kI04FbfYRrI zxB-aYgA@w@aR5|cKbm3%5F09YABx{289bi>CIF@Tku=SR^8Z5(?MD*754Qy59GJ79 zwt~d=BjE<9m;#6oRZ{@O>wy^V=Kz?=P}~m{QvfN#hcN(H6C)x_<|DacJ(NyBHDD9~ Y0IBb4e9?j`PXGV_07*qoM6N<$f)9d0s{jB1 diff --git a/recipes/icons/deutsche_welle_es.png b/recipes/icons/deutsche_welle_es.png index 034074e83de519fb0b0c300a4529d8e5dcda2a91..c0c5b8c0eb722ea657c3df60b9c714022273da36 100644 GIT binary patch literal 926 zcmY+BdrVUY6vjWKqZ7tpOGN5OrrC6E1iiaTbsc8lTD2Jv#+vy^#$?(}H?cCxCUtH~ zoPsi11)LAY3Kg@hN?=pNbabML((rx=LM;?6L~GmI+UM;p?e5}2vOmuGa!$TqzLT8t zob2_n(F>vh#ELfvb6Ji5x1Nn;`_h!S4_M)RmXVbKv?ybO`OmQP!fhLJvjD{s;J_gu zz^)EF1d0j3vK0`V0_N|jz50F{5HXUIDG_1EpZNS5cC|rv9fdbhbPHc~U~eZBJ=oue zZwGO37>7pj{TRx|acmO1bl80za@MAYq8s0^o&g*-pv;IL9zZ#T;tuTZ$9ID$9foQe zCmx~dG4^%i;2j*fhw=#=GofMU%6kn{UGI*WRHPoixU&#!q(H z4K~{94PG|7mz}rbloPd{#G^w7t1qu07rShrpHmy>DYVIKJ5T;Mecn-uDZ1@3JqewZJ)(wS+aGuZKnN&VfI^qc<=6YXI_ zGokQ+m08#7!?_UDG#Y|Y=a{*T3`fJnGzQQZ4)AFn`}>Gb-ditR8$Ytf%?@0-I7`Hx z<|ah(=OvFQ)7h0uEX@7B$Ey}xJBO-9y5bR+}|KkTpKv|cbPzetYC zF51Y8BsaeF4zsw4)bTCm<`)+#iZdJ9Dy3mi_}qox869 delta 170 zcmV;b09F5<2fG1~8Gi-<001BJ|6u?C0D?(GK~#9!V;Hnx6c{!AN5c1kI04FbfYRrI zxB-aYgA@w@aR5|cKbm3%5F09YABx{289bi>CIF@Tku=SR^8Z5(?MD*754Qy59GJ79 zwt~d=BjE<9m;#6oRZ{@O>wy^V=Kz?=P}~m{QvfN#hcN(H6C)x_<|DacJ(NyBHDD9~ Y0IBb4e9?j`PXGV_07*qoM6N<$f)9d0s{jB1 diff --git a/recipes/icons/deutsche_welle_hr.png b/recipes/icons/deutsche_welle_hr.png index 034074e83de519fb0b0c300a4529d8e5dcda2a91..c0c5b8c0eb722ea657c3df60b9c714022273da36 100644 GIT binary patch literal 926 zcmY+BdrVUY6vjWKqZ7tpOGN5OrrC6E1iiaTbsc8lTD2Jv#+vy^#$?(}H?cCxCUtH~ zoPsi11)LAY3Kg@hN?=pNbabML((rx=LM;?6L~GmI+UM;p?e5}2vOmuGa!$TqzLT8t zob2_n(F>vh#ELfvb6Ji5x1Nn;`_h!S4_M)RmXVbKv?ybO`OmQP!fhLJvjD{s;J_gu zz^)EF1d0j3vK0`V0_N|jz50F{5HXUIDG_1EpZNS5cC|rv9fdbhbPHc~U~eZBJ=oue zZwGO37>7pj{TRx|acmO1bl80za@MAYq8s0^o&g*-pv;IL9zZ#T;tuTZ$9ID$9foQe zCmx~dG4^%i;2j*fhw=#=GofMU%6kn{UGI*WRHPoixU&#!q(H z4K~{94PG|7mz}rbloPd{#G^w7t1qu07rShrpHmy>DYVIKJ5T;Mecn-uDZ1@3JqewZJ)(wS+aGuZKnN&VfI^qc<=6YXI_ zGokQ+m08#7!?_UDG#Y|Y=a{*T3`fJnGzQQZ4)AFn`}>Gb-ditR8$Ytf%?@0-I7`Hx z<|ah(=OvFQ)7h0uEX@7B$Ey}xJBO-9y5bR+}|KkTpKv|cbPzetYC zF51Y8BsaeF4zsw4)bTCm<`)+#iZdJ9Dy3mi_}qox869 delta 170 zcmV;b09F5<2fG1~8Gi-<001BJ|6u?C0D?(GK~#9!V;Hnx6c{!AN5c1kI04FbfYRrI zxB-aYgA@w@aR5|cKbm3%5F09YABx{289bi>CIF@Tku=SR^8Z5(?MD*754Qy59GJ79 zwt~d=BjE<9m;#6oRZ{@O>wy^V=Kz?=P}~m{QvfN#hcN(H6C)x_<|DacJ(NyBHDD9~ Y0IBb4e9?j`PXGV_07*qoM6N<$f)9d0s{jB1 diff --git a/recipes/icons/deutsche_welle_pt.png b/recipes/icons/deutsche_welle_pt.png index 034074e83de519fb0b0c300a4529d8e5dcda2a91..c0c5b8c0eb722ea657c3df60b9c714022273da36 100644 GIT binary patch literal 926 zcmY+BdrVUY6vjWKqZ7tpOGN5OrrC6E1iiaTbsc8lTD2Jv#+vy^#$?(}H?cCxCUtH~ zoPsi11)LAY3Kg@hN?=pNbabML((rx=LM;?6L~GmI+UM;p?e5}2vOmuGa!$TqzLT8t zob2_n(F>vh#ELfvb6Ji5x1Nn;`_h!S4_M)RmXVbKv?ybO`OmQP!fhLJvjD{s;J_gu zz^)EF1d0j3vK0`V0_N|jz50F{5HXUIDG_1EpZNS5cC|rv9fdbhbPHc~U~eZBJ=oue zZwGO37>7pj{TRx|acmO1bl80za@MAYq8s0^o&g*-pv;IL9zZ#T;tuTZ$9ID$9foQe zCmx~dG4^%i;2j*fhw=#=GofMU%6kn{UGI*WRHPoixU&#!q(H z4K~{94PG|7mz}rbloPd{#G^w7t1qu07rShrpHmy>DYVIKJ5T;Mecn-uDZ1@3JqewZJ)(wS+aGuZKnN&VfI^qc<=6YXI_ zGokQ+m08#7!?_UDG#Y|Y=a{*T3`fJnGzQQZ4)AFn`}>Gb-ditR8$Ytf%?@0-I7`Hx z<|ah(=OvFQ)7h0uEX@7B$Ey}xJBO-9y5bR+}|KkTpKv|cbPzetYC zF51Y8BsaeF4zsw4)bTCm<`)+#iZdJ9Dy3mi_}qox869 delta 170 zcmV;b09F5<2fG1~8Gi-<001BJ|6u?C0D?(GK~#9!V;Hnx6c{!AN5c1kI04FbfYRrI zxB-aYgA@w@aR5|cKbm3%5F09YABx{289bi>CIF@Tku=SR^8Z5(?MD*754Qy59GJ79 zwt~d=BjE<9m;#6oRZ{@O>wy^V=Kz?=P}~m{QvfN#hcN(H6C)x_<|DacJ(NyBHDD9~ Y0IBb4e9?j`PXGV_07*qoM6N<$f)9d0s{jB1 diff --git a/recipes/icons/deutsche_welle_sr.png b/recipes/icons/deutsche_welle_sr.png index 034074e83de519fb0b0c300a4529d8e5dcda2a91..c0c5b8c0eb722ea657c3df60b9c714022273da36 100644 GIT binary patch literal 926 zcmY+BdrVUY6vjWKqZ7tpOGN5OrrC6E1iiaTbsc8lTD2Jv#+vy^#$?(}H?cCxCUtH~ zoPsi11)LAY3Kg@hN?=pNbabML((rx=LM;?6L~GmI+UM;p?e5}2vOmuGa!$TqzLT8t zob2_n(F>vh#ELfvb6Ji5x1Nn;`_h!S4_M)RmXVbKv?ybO`OmQP!fhLJvjD{s;J_gu zz^)EF1d0j3vK0`V0_N|jz50F{5HXUIDG_1EpZNS5cC|rv9fdbhbPHc~U~eZBJ=oue zZwGO37>7pj{TRx|acmO1bl80za@MAYq8s0^o&g*-pv;IL9zZ#T;tuTZ$9ID$9foQe zCmx~dG4^%i;2j*fhw=#=GofMU%6kn{UGI*WRHPoixU&#!q(H z4K~{94PG|7mz}rbloPd{#G^w7t1qu07rShrpHmy>DYVIKJ5T;Mecn-uDZ1@3JqewZJ)(wS+aGuZKnN&VfI^qc<=6YXI_ zGokQ+m08#7!?_UDG#Y|Y=a{*T3`fJnGzQQZ4)AFn`}>Gb-ditR8$Ytf%?@0-I7`Hx z<|ah(=OvFQ)7h0uEX@7B$Ey}xJBO-9y5bR+}|KkTpKv|cbPzetYC zF51Y8BsaeF4zsw4)bTCm<`)+#iZdJ9Dy3mi_}qox869 delta 170 zcmV;b09F5<2fG1~8Gi-<001BJ|6u?C0D?(GK~#9!V;Hnx6c{!AN5c1kI04FbfYRrI zxB-aYgA@w@aR5|cKbm3%5F09YABx{289bi>CIF@Tku=SR^8Z5(?MD*754Qy59GJ79 zwt~d=BjE<9m;#6oRZ{@O>wy^V=Kz?=P}~m{QvfN#hcN(H6C)x_<|DacJ(NyBHDD9~ Y0IBb4e9?j`PXGV_07*qoM6N<$f)9d0s{jB1 From 65ff07bf20e13c481c499d8ddc2849cb2d16e060 Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Sun, 18 Jun 2023 20:24:03 +0530 Subject: [PATCH 0857/2055] fix error --- recipes/indian_express.recipe | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/indian_express.recipe b/recipes/indian_express.recipe index 39a459427f..36cf1da7aa 100644 --- a/recipes/indian_express.recipe +++ b/recipes/indian_express.recipe @@ -99,7 +99,7 @@ class IndianExpress(BasicNewsRecipe): div = soup.find('div', attrs={'class':['nation', 'o-opin']}) for art in div.findAll(attrs={'class':['articles', 'o-opin-article']}): for a in art.findAll('a', href=True): - if not a.find('img') and not ('/profile/' in a['href'] or '/agency/' in a['href']):: + if not a.find('img') and not ('/profile/' in a['href'] or '/agency/' in a['href']): url = a['href'] title = self.tag_to_string(a) desc = '' From 190af1b84666d6ac58fd913895f1ffb2694a2aaf Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Sun, 18 Jun 2023 20:46:14 +0530 Subject: [PATCH 0858/2055] Update psych.recipe --- recipes/psych.recipe | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/recipes/psych.recipe b/recipes/psych.recipe index 235489565c..757621f108 100644 --- a/recipes/psych.recipe +++ b/recipes/psych.recipe @@ -43,8 +43,8 @@ class PsychologyToday(BasicNewsRecipe): soup = self.index_to_soup(absurl(a['href'])) articles = [] for article in soup.findAll('div', attrs={'class':'article-text'}): - title = self.tag_to_string(article.find(['h2','h3'])).strip() - url = absurl(article.find(['h2','h3']).a['href']) + title = self.tag_to_string(article.find(attrs={'class':['h2','h3']})).strip() + url = absurl(article.find(attrs={'class':['h2','h3']}).a['href']) self.log('\n', title, 'at', url) desc = self.tag_to_string(article.find('p',**classes('description'))).strip() author = self.tag_to_string(article.find('p',**classes('byline')).a).strip() From 4a2396d90319bb7b2cda5f6f584a067fe9f17d45 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 18 Jun 2023 21:12:58 +0530 Subject: [PATCH 0859/2055] string changes --- src/calibre/gui2/main_window.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/main_window.py b/src/calibre/gui2/main_window.py index b61b83fd90..0a02091482 100644 --- a/src/calibre/gui2/main_window.py +++ b/src/calibre/gui2/main_window.py @@ -148,12 +148,17 @@ class MainWindow(QMainWindow): def no_processes_found() -> bool: is_folder = fname and os.path.isdir(fname) - w = _('folder') if is_folder else _('file') if e.winerror == winutil.ERROR_SHARING_VIOLATION: if fname: - dmsg = _('The {0} "{1}" is opened in another program, so calibre cannot access it.').format(w, fname) + if is_folder: + dmsg = _('The folder "{}" is opened in another program, so calibre cannot access it.').format(fname) + else: + dmsg = _('The file "{}" is opened in another program, so calibre cannot access it.').format(fname) else: - dmsg = _('A {} is open in another program so calibre cannot access it.').format(w) + if is_folder: + dmsg = _('A folder is open in another program so calibre cannot access it.') + else: + dmsg = _('A file is open in another program so calibre cannot access it.') if is_folder: dmsg += _('This is usually caused by leaving Windows explorer or a similar file manager open' ' to a folder in the calibre library. Close Windows explorer and retry.') From ec9e64437cbd378c50dc0fc9f8261a958781ef8e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 18 Jun 2023 21:28:46 +0530 Subject: [PATCH 0860/2055] optimize images --- recipes/icons/bar_and_bench.png | Bin 1497 -> 1024 bytes recipes/icons/horizons.png | Bin 8639 -> 8552 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/recipes/icons/bar_and_bench.png b/recipes/icons/bar_and_bench.png index abd3c4c13d151b7859e9e587e72d6c669f240f61..2885709e48120d5810c7a80308cd726a5f205be5 100644 GIT binary patch literal 1024 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0V7wdP6XN=xbdaKyo}!$ds*;hW znwhSam9CzZu920Yk)5HLovD?RrJb9t14Mb*x_P+<1$l;r`9?+g#zh6j#RVqCMP{YN z=A|W;WhIv7rB>yo))i$o73H>-m5w#lE_F37b+vBw^`4FOo=r`@EzSO|&HimIKorp4 z9@^Ov*3}u_-4)r>7e8TQ@|39=)2C(v(Tr(XGiKz>nwdL$R>7Rvg>&Z=&6`s^Z*Ix_ z`DF{|moHpUzGz|PqQx~!m((s>Qnzer{qkjvE0#B{T+y;>B@nf)UIj#Lt5EoeFCV;m_3+i}N3UN$e)Hz(+jr03 zy?^ok{mT!Z-hBG>_S5HgpTE5S^5w&quOGgC{rL6Ur*GdsfB*6I$Iov+fB*db=hvS< zzyJLG^A`yI{rv|7|NnvFb;5fGX<&Q{mIV0)GcYoXNhqkOX&9K8Im}(XX4lRjo&~^E zaM07mF~s6@aza8v+ELC3iH3%9Ii54Cy!gI<84^X=GeyHC)vA>1@Ekhy_gv z8rDk6%9lBgXv{RvpT!$;fqBINZrcX0XTDonHok4-S|G;F&B?{(Bzk7{gT^Ha9H;zV zzI?f{No$7rbupzEd?6DW3Q`({IvyIauTbJwlHd&q=m~hhl32mIYVI`5D=S+U7&-ZY6O+$@H)qdv3Ktj{F)*~U W3OEH{y*v{b(+r-jelF{r5}E+?sTLCe delta 1490 zcmV;@1ugo32-ype8Gi-<0047(dh`GQ1)xbpK~z`?y}=Jq*7Y60@%Q%unSf2)g|$D4 z0%x}%pRYrM+icy>9_>;zZYyuzwo?f8>5EAmf3D2cFNK=LX($&pi*w>GgUk5n<3oAb*B$MlWy)A0;CA{29n$ z1YWO~rc>u>Jkdy7QyZ?%9t0ugmS}FB6wl<;NhI8zKtyB&*I_rXNQp=?P{%OZ|9zR- zidv4mbCj!>yBJPvTr4x^&tUcvgWIRv#t3FA5z&B22yT~~w_kpj+Sm6p;2B^vED;uF zJ@ONNmY+q;_FMg>W{4U)2K&|(q~4c85JQQG222KBIMdALU%W_9R}VMSm~V~YH#=98G(C|3 zh7u7CnE1F|Zk~JSd0HD=2{u0d+gKtjgwxmKp}Vu2;eW(U9M6Wm>lim-9D@ucA{sF9 zsVc1I!}1z}EnK~T%$4&PNq1*AU+h20-?sgoOARfAl9828PSH{Z8A?PnV78xcXZ-`u zFyI*=n8RsD+pJu#zMei_d9;MOWAy}6O0n+kwcPQ;AK+ss5z&BIRani35st%YN87CU zU2eX@OTYRf?dRJGl$ke=g{u}Yr1k4I*4Z`?&caohWaiB`l!)kEh1KT1@*2XKn>&Xo z(Gl2>!^GQxi!mq!tpOWRJeEG>&gp-k#PEOHMLy3qkUsGWodh0MZ zqJN+=pX9V8TvuI`EGy;gXAKNT&#~gzW;c|G=<=s3%tLz)6OM?0aE-Dkf)!Nelbn`> zpR3bF<)$kB@z*1Sqvu$0Y_l6mM0DkIRc1{^E#YJrEg|)%DGYHs%BVknmOurS`6Q<$ zagD&1Wu=@vaFS5cAG(J}U(7a?i0FOgHGk%=!fL{CIPGYg6~BKS{F3dfci`%D5ol)S z404NeP)af6&b6?9?lXk4vg|Rev!@wKMD&1X!2E4S0j{eqLUB0lXqy$kwx%{}cYp53 z7g1xeEr$;e74P{Zfvc;S)F1dEJ{r1(e?W5T?`?mdU^5;}WBHa`d<-Qb8Za-`Uu5I_O?bUtf;pUaw9U$Be0BIVTXMJ3-`7vD zHC0cNI6V;`Ly3q6OnfTV?&M(gA%B7`TKh1z?E4vsh;ZR_GapwSiWYH__43K_H98!uRj|9u|v*a6Ek;u3Y>Nz5necoY=Tnez$iW zaks@W$WS7p0h2*z%?65p{W2c6hnp!PGJ?GA4pQ#DivWfa5e=9GI8plrWq*&qipTBY zW{8N4Abax??pZLCAchhV4VVO}|FoX6Cw9=;*2#^Cjf*9>IER#b?;-?4iHHVFLg>2O zMb-1WIr{D~M1;}Ob2N)LEMmg#69~glA~F>?%`loyHSxh~`!L`CoW9;Zh7%bTiFNig zY}xmd^rJ*Zkgh}opWVRE8B2lN<>t)MIvP(j()x89ZkHRssOTu-@3@tOyA!xGeJatn sL~|WJ03K8#!l0=@4Bw1i;4~or16!S4&0_PncmMzZ07*qoM6N<$f=$iYRsaA1 diff --git a/recipes/icons/horizons.png b/recipes/icons/horizons.png index 5cf16612aca7a467dec92ab23e2c4478cabd0e34..abb8189dff94cf26b3c853b528a5a478c6a68f05 100644 GIT binary patch delta 5833 zcmWkyc{~*V8+RX5SPZ!{M1!1>NRF}YYu8Z`3acFDDSv$9$SA z+>w^^LZVaqKJP`(wn3#T#ok5Oy2F;xx=VBir~%%~#@UzG+3vIQfA7jw!;`L3%WxMJ zvO?Ej$sN|VIg)9TN(`b!_99I^B~Aq3+QAx>{$J{&M@w)2XLSDCmxht6hUeou?O|O>VKa+;OMio%C&ZN^1p=oww1t7-L>iE9$U93tV?BVl}j$&j1|4- z@j^dw`k?ss?(Kk?WbaBNx5S~Tho{5k^9KaDKeqR`2UJx%%652p-`9;os^2?ontVOa z?8pAFRv5KGD((?vrKP8D=VI^L@rB!5q&$@-YQ9LSy4 z?#rmnnwFXHn!9<#HuOqcq;y2pj{E_;&}ZHKMkCYeBcpseb2k)zoXBsE_YRd}n+8`E zzf>A%%$cUIZGCOmK6B&59`(;3oC7wv*@7?3eIudpybwOk0Sa(8O6pcW2+AN8w^sCZxoQ)5a z_bce=?@2)Vnu|t??;{(fsSmb-!y>o%480KOz}dhVAv|>8*(-8n4JYcDMWEXKJlE3O zX7)ENm~=H0zFt%37t0r<=)IKh#YMFB|5$kHuC8f3GQc%xtnQs)trogMial=hvL$E4 zw<{ogP{StO?$XULJRMi~<#eMk6*=?iP5SHcB88mh+kf(g3rFrFjFjoydmi2lmn7I0 z2eEAb+U)GLIC~JWVZ_~SnN45Mu z30!_);;Lhs9$-YrRf^oTfK-1CU*ynn&nw~QcsUR^EnH&RKANHT*VxebNsR%iaYr1I zGS0Z0N`on~;~~@Uwr!Uf`(XUu4C8K|q#4KYpO%*r+v>;xUTSISq308JzJKq)6o*N5 z+(7>AmsK3_gzoJfdo%16iVAULMM~U!Qv~gqoauhnGw8BWdC>h2}7%qHvH3Oe=Oof6sN|NJRzE0jC zzc#ybI`GTXJ}h}-LtH)5r|z5bf#ccrFMShlM$7tFU%2a|Z(RtUHVvZZ-E zyXtg+ONHI~A4JhR({3fVBwr@lYMcXesd_k8ds2n@>F*@wxunt6kn#Qa=A1FVM}Q(a(k)=ntoe3m5@jbX)8pN)vje$MTX%ylTQkI2A+ae0g3q#Qr;pj>jdvG-+q^@JG^b>Q&y9&o@Qohjzh(~Y5F=a4KF~vF zyk&Q$pWNUD}S(2$o~}XC<7mp%^@2HT*didoepIu7x=_>r4;v; zI$&f=MShDG;}#O}behE8mLxvZg!hGLoNw2tm`ywqT~b>YB%0nU%A|EsMiiN}XgtjG zzpG&u&|c?(+(lEB6Mxsqv=4t}6gSy4&n_*YqzVu$KKq}jWot`(P?rjm7L11{Xj7I5 zidZY9SJf@NOKJKzj~9Mlb6m@9mpzE|hptokP<;$&cx0>M&fxmtJ9+%TMGp(e$ArML zev{~E0ex0UykyE)aB(U&;sClgBg?fFm)Od@-2(IR+J*bx}fR^zN zIg7aJK2`+)BU;aTX~i1ZUHso6Eg;KLLIzND!Q{TsY>DOovZ3+RfnIpl+1 zrax(_a+*S-OyLf&;(W>i40ou0H8FVeL2WRy;jk`_!d7DM$b zY{BvGMf31R={Vz^3LbJ`T$zx=`rog!#;u9Y`}qhnRSNY+sK^En9@=pYi82QDy5ox) z=`=&&3D>-+yY=Al4gNmaeP0Q-9!W%jyW|i&lw0RbWN|A5l$y4Iex-j$FT~Pm`>jF@ zB>+z+3Sul?jC)NetQypl0Mvw|8crZkk8kvW-47V`y%+Ind}6x|+{rL0!B(Qw8p_@Z zwgn^9zo}n-Ga`1qu)^MR_HxPYWMHi`1*tHADX-0j38O|)lRxV&=WwDoKfm7C6)wVY zMrNpP#wH|E#T+_I<1=*6UMJFNgnvhI%|Es?iMAiskFKz*H+?x^+b>E%ASUhQfIX_C?--D%C7OEltmX8YJ>FKj*;Pc zD&iPWnl}9%8>EsNx0!vorDaxHF)fh-Q%3lb#WzYn@L7KMB(Nfq=L@vNpM>t?9hxce zJ3BI#g5=`l4K|M~xj4qU2ok72imLZ4yIhgkX+aPmTXEK_E3D3qJqV7gn4z~U3ynV( zI|StCW}CT}Dj1jwCAGp7`ryOZ!~+uHl|nir(PO2P*3jR>1Xe1B4{KppO(UwVj_a!# z03Uhr(2P^V-TL0(jqZ(N+zsF8cp}SDX}gwhwkTH$eX2G?O7$FlYTl%&=e}Cz^21~9 zkOTQ*e@6n+Pbj=ju23)t+6Lp0gk`U9kW1E%lli>kCPj$PW&%cZ44S(ctOSm`w)za| z(;H&0ACmSl2VLDn2rPaIOnLCEwwuIWyqyg@us zfXw=y^V!U4RzgX}IcyIe(&8O@OduCl46=<+3vRn>MLqF3(CB|?@ECy=gMfNyk6EEr ze`rHp0x}b6cHw58wNdoz1CkzvMw@@m{lYRVdAmnF=%Yn>guW(1RUi)sGRXRzriiR^z;mlG8_`;*+))t!X2 z8U|(bXL~As5~R6{3-{VM2@-5uYzZvBeu}&t5S%ik@AxScQ8EcqOyK`LZ4zh=9i_I( zTO}UPJ=Pd|sF=pi41HxIqyp$ho2}xZm-d&tO8nergk;8xHh4BQ1ktpG1(zZv1e(LQ z$NizmUjQGn{Czy3Mc?2(f_l**2R%ObixtyEUIhQr<3hm;pVYroc^1m@ov28O z4TiJWFs)JKSAC-^1&FM2dHZagt(S2~BkZ%Aba+&D;l2orFKm65ghNd*>nG5*=>Ej_ z3I8H9FRPBpYygh0%E+L#X*{T0<|ex-Q~2t?adRi{-74J6*nf-@=DD0WK*wc0{(Rd` zg@H2)T|dm)`?}3+F5bJck`Il<{$4}S?N#g2^L=nfWnQ7ys7r2KnFZ8!m_XGEl}<&h z`S6e%m4E?5%59>?CYzV?>_@KTA(K~!r=oET+8-r6bQ*>!r#pRr#v?~~5J*x^0_J)T*gn3{eu_}l!1$TUBUJX zOY>Ybw_=)GqRmY$iNs)QNVPVh-k4@SU*`MRCvkUQ^?_l5CvEgJ|t`&SK#nyN4!D{`itiZA05 z3FAvEys=Imelc&oO99olQZUE@!3b>#klw`!5G|voe7g!f6)Re=1ao+H%?NGm4YOW- zf6hz)@nOLSITnHhR)sk{QD;k_&J-1QI(rhRY@ttj@y8|B#_!4ue@tj17SpVZ08S=u z`GXn3|-8_UP1PAMX4fcX06rKhR(;*SR8w9;^Nrudrm3u@f{xyx)v*39^>OL^P$f-efLa2%^_TDuzhCq@zYc>5fv_l1n9R?Qwc z0@Z+TZoEiRFAWg}$KQDU4xatb64JmVYo#!}XJveHrYKh&t=-jJG{k7y8=+Yz$>vAP zY=jJeU`uSz+?po*8mWd!_c$>&7t}8&DQ@>q<|&v{kuX6Dl3|sGSc@`fUJT=37*H{A z&6X>;IsG#*ezKH2S}kG(2%h-YDVI0d@qA#hs<`(Kb0}zAUjwG>itFg>nisIpZc^p6 zjH(zt!IGr!zhdB|qazVU38SBsIepO;_@XRA-Y#hWGifhQZ-*`GEl z;f_u-k4u_=9>1cD4b*LHg-H(9MAm>99akz_J6?o%C9G(6{d%n|1^}Mp@ko+7 z`E2>OKpZ->i)D5fyO7Xq6>j(?v}WjGjo)x@oh7hFTML~qEF)m}ON`l3*sT=2~FO=Y+kmK8eNsqUl`S>Uf zp4hx~qi7-#F&@N%%}PMZkSKCp-#hgB!2&6irKzfT_U|^h93$~xtK3e9zJC8j<|!}e zxVUgRktNp76}Ai1d(NoZZeek4b?m(Fm5BJCnf+sVrdzS2<6)D&o!%m>(>sw zHq*>$E3dm*L#MsM=rOjHFdVM5?Vfk_i)ACL!SP_npVIhrr^6vJWFxPA;=)I*s7SxN zUbsrLGEko5*y?+-(a3*OX89D zIqg%Q9Ev_1BNJH#u^0hHNn{Hp*~dOjVJcO^6H=l;zMB%r^@RZwZ6m`!jFtT=41bam z)IL@(0gitys(?v>Y6NP4hfnXZTm0m2fd`(*p*;-Tf^gu?@MDR$g?2Ud=FqFQSFq*8 zh&b@@)5#!i2HE}^;{FV^8%y~##l@tZ#<(J|jGQ5c_QMy`nlKu%d$iv#5|b=fT|m7S zE=<4cvL^P8-B_dJjwdglIP$dDYG|Ov5CbTj0NVe{<)W{$82W({)+fd6eG4H_7FAWw z72{$Gkzx*+G8&_)g2`{~{Y6yt8gq*|ZUN|8?Me$Z*hdi*bK_h!rMEG@d$^PLm39#Y zf(IgG06Vt-nd@ziGV zhAbn;EFk`CL>3i8Dze5M*(f`rt;&3)h)ojmB}2!ZzaSh&(my+-BkH|V1()Ou>aY2v z186^vj+-G22WaK2F~oFcc}q2P0Z(zivTh2Jmb2E-+bcVSBicB`Bi8(QEPP{(YYbK1 zqzfk_-$W(CX9)rxob}hq_6Npv42@-&m-N13%G9eftaIfmJ#r*5h$MF1H&5trJ#v+zwK5^srd;J%>&mf6IU}na zE6N=?B4;g6B3E+!=K20+{+oHv%=`8Jyx#BEYd+tgHlvzCfS2{kB~xRDqyJ7)Yw6?1 zB;W*I_`^kGL)+UE|K{Euup5nt%YWwzpcH>(qD|6Oz_ej;kzPX4t8aE*3GgD-sd-EE;RCG5}=U&B9r)vUC&eh*-EW4 z(n@R{bI-h$|FZx0cDB=LnH<#e>P*9@*Lgc`ZTNtD3&KR=Au*77#>AJ>v(A@FsddQ1hrhSX^wkUt*Dy|$qOwEZ? zWA|ps)FypYrB&aS%t^8`dlkim&4Q&~@%U;R^L?h}r%bEneRfh>Y<*iop85BVaGu8p zURJPIshY6muhH`$47@qEbB@-KDxaoJUc|~*Y4YSX-ihN z3@RjC7Jk&XndPTfGpgYb)j|{*W_%o-`@qpEA<62RJ=w2nym{rZw`qz24--D;w1ZXa zqGT9f4$~)w`=^)aY%1?>&onU44KF6_v`v&x2A^gBq2fs?G)Q6ac+saRaJ%Pb72b%d zLUQSH^&*U^tR3++>Ydbg6BEm~Cnt(bUFHDOwA|U$ZW^N_=C!COVKCT%2_ElyMm0#? z-Hh_|4I^Gp8E0iq)dh?hc^M8Qaq;h+a({fd5-A2(hUBaSNvd7z(`3EfIo@?1m+Ssa zwy`3a(skww>J48sG}N_5sF1+n&AyCtCG{j&@hTqf<7Ok*);RAd?PkOX0ol(w zDH2%PeJVq%T_E#SQxb7uoY`W>0A0T>cJh-B@?2P}}?QF6BX=LL#q9S!q6J z0dXg~r|a$>JJKOuFY#ZY_3z}$;f5FjkRfN^a0_&`$#p-#(yUZG4u4TU?-c};S7Qkn zZ=sVNg(tk-EP2j=$P!q3rul@4SI;KL&OV7bUq(!LJ*&X*k5ksCoQ3-xF^thY!eFgb z*4L*e`wh`+%r;>k8;lZ_c;V2Fyf>OalW9B7D9F6M;(ehSUY$Sx-izLNo^Q-k*FO86 zdHso^Ln3Cnu6#nE-DKmQJuzFv0A5LnqEfQYgC|cApxQ2|yZ`o08U1Ng&crsk*ASgc zJBJhS+CS6qo_aa$KGn{A|)4nLwEP1F5{O~ZL7;ttlO{2e5sT!q{^>+Ua&dO55@-T9K`-bhA zdUL}^Q>qNIY8dy+U<_gm*(oa_*tf?AwNw} z!-XAg;g0->^Vi21E8TYT8Tq8sQK}GG<)rW7;r3v(tZljfiFGIk7>rq$#uFXqo+UuS z&PegPC-uX4{H)6}D4D5GPw3|2BhRlG zHrJ9sFj(%p5V;9y})=sXK>KrQP1= zMiK>?mNjs+3Th`IWHCUq*R}em4vBAh_!bYKQ|_6UmSEL-#R9K%zu>J&so>N_aN}|1 zrS3dTkxjS90n@U1pr&bZI#mRo%DWG#Z5W_`sbIVlgI{F@#anq*WSsXf_hxc^YJiqv z&_aFmA`>O4b%c;yKrb(RfZ-lJ1g>t!EEviQ=HKbe%Gli`)IO|Y)hmmNUzm8d7j^0Rmtz|%tHvz6QSjz3eK9rM<{e{;4Hz zg6_6cUD@9j;yx)#FF~&xzVmCkJ3Q>kV~0+Z!O~O{TpZf#%aCULUrW@V*s;w$mVc%k z@7ol3@-OP#UUtjI@3S)MK_c??e}Tcde*yeIYeVi488O~3YR98BmF$-NPKL_XoMji% zv<^j)h_?hnRJ+yMbCTxcbl@;+Zki@AEZ`lhTz=3 zG&7cV0%ZhaX|h$?Z`=Jo|9)6_0X>P=@K@bSzYzKsvmhg>C5NM7(cqlxJ!H*wg;Z=$ zx*hFByC3*6uHSfM*Q?KJ)XD{>`Tm1@5R1P&87z;Zxj~qP=xy{P2yMgMc?R2kZEMs& z&b)-~nG*9T4P#96`;7&F?_c8~+w1jjeF(%0XfIhT?H4O>>=*$$^FWV==K^ar(T$2v zTqB)FP(ND5PIweMQ1n2J(*(%1C8NLpl&CIClDf>!i3BK67(y?h0;}0>#8XNM(DsR@ z-4O|K0w-)8rY`rl>Q9rzvDySkt~DbwF+mLluX0`Hxke(c%Nw9&!?sCpnkhEHWzC}C zl}n=$KDQAdT9pSQcxb}%cgE>}i~C-OvP`h6Y!al8VSpuYL8VB83}b05|J7r)d~H%~ zJ-X}Cxt5HEM)xmD%s{qGD8WSlZYg-K$mUB^C~_mGVp7=X_z zv5SHLocgU-+w_+-LcUgDFUM4ZK&37v(~wr~M`on+TYqyO+qs#@)^2 zJCWs5dj~O?g_{miI#yutz{`I}!pAr#o5;$ifF>*-oTSVLtMHqT78ej*^mfa5fr#uH z&M7VPgdb@g9>jnei3iY~x>LZTBr6I=?wXnhsBxbB@5m40rE#aDpZ^$>h^h3UKqW*) z`S8FobUY+y6Bgm#YmF)TGO{?y0nX8VGDVof6NTN3Gv>s;QZ4{+-$3!)5XM+TUsV8> z=xw5l{>qP?wA^~1wPG=={?L3kQwZj|Xo$96|IX@Z%h8gd-az7eP4VAH{Ycg5XoA( zGFi4@9rh1JGcu7awk0D9d4e^o>=?IsuNL$y@j_Kp zoI2p7_G&bTV_&1FrIZmPcPzh3ILXS3>wzmaz5^8);?H#9u8Npt1Ksuf=JMu^;#JIo z+UUM*ZqsOoD3?wlA7i!8FP4DU)BrKhH*@c-o+U#HNS#Za8Q^^Z?bAV3y_s|uz!uJL?~;P>RfQYJmb_u zDN(_6Sd4adlrGr|O62R3h)_!g;bJ|Xbg4-EEj$pi@lqKSfFX@}l0X4pKIhby$-6q? zwQhGZLX^-@MsOoT8u(mU%`}2PC}IO{mO1O|1P#`3uW;bSM0xjX4*&FQ%v%R&BB|sv zOZkOIK3x*4OaFaBQT3hR*t725-DJ7s{GBJx#~8guAJ-re?+Umi=Tj>4DQ&5lZpVR}`i9I# z$?no+lG4Sx60HAdavPvC(^+7Dc6yB3VC+m?={CG)R;mxVCi&(%bK zuGP$^LK$~n<)&RgyF|?o1MqWXSWHX%Jex28H|oEr-s6vk=*j`=Ye}*5_96MwK(-46 z)|&Fcjkm_vcWPZ^mc^Pm zj|O9aY-c1{*;q(!9zQ&Lybf_sD&Kr|Zcl$z`fj-MfDdST9gA7;W`_OQs!|Nl6##@5 zXR!%8CDQo8T{^-&+(vSKcqiKMYH9k+1u{`YuwrtL9n?@j!CZn6IyHj@o=P)7r>)-z zN}T|i`&u4F#MF0{qSLt%dU=m-7zRX)Qj&4;-bzkEsdyy9B7Inmy5y7jNjU6z5``tF zV78}&zTF&LIb-!ZE^k@})A3v3jV$oUY*Z*2f?8Lt=Z8hx-8`%j(}pk*`J_n+eFz(0 zkvYxB3#MIL$rsK~NqYe(3&W1*hV>erUPi<@&93)`dw3P5V-+RfwUVD?6;iDmOCpZp zIUzJ}lLAFN2`bVJLgw#BcJRtJ)2Z%D555+1!z`Vpr!WilLfa_B58aRUsS<(D;b?2- z51~B9k7LoU$T0NNV~TXvD#@nv=O2!2l^bCIorYM9+%IyCVpygD+N6lyXHTC;WSArQ z*=f#`FDESz-s(tA8G{-~Z*Jn%L}6EiHUg`4tI<%AT9ERr_*ZfTHa?cpW?ZheYoD89 zbxrOLA?sb!&6h@#WpZt=sUD%7g~Tremk%n;@Dwt=1Cx2u9*X6skhffzOcCc3Z#ppict~Co1#|PZ}(LY-= zYSvZS-axmWAeR&n-3jYu0gfR9cLv8fBAes&1r&e?T|`yO8s^m!Aid@HM*pKZ&(z1{ zb?e>lzNZH9b$q~QbP}ZI#|X}?UV1zf{{Bp*&bKGKq1ngWJJ|yp&zSU3^nI-o15KY}pqxYwYxAC*Ih%amb2vUY zvPl{YF6zK|Kji~-)a**7_976cbIkKq{V>f4t#CdpJnA{tY+a;L9%p6|c_$eKXLYjV zhZ&&T*KfQg5#cA@y+4T2nQr||HepLa9tD=hXhT+`h*ASYbS0T#i&O)2q6-7uaU1_R zd0kj%5;Z4F&{qRADP?HRE%Vy=z7M|v7tqypNG!>#sfu$&qM!yD!Mnl@%A}W{t=f0(j@RNS zBo=kXCQ^=eIH%pWL~5jC<%_2}^_Zqi?uKbUxq$9PsR#Wb5hIS*AGQfx+T}dmf>y#5 zCDk9ElH|Dz3JBu!OCnTx!Fa1*^yT{7q1s#zt;FTEf2mz{!qWT=L5&8g?KC&Ee#80q z?{6K3A4}i;eLK>*X1tGbRdMlqP-96frb9(ODRZ`gdQ&3V7o1-Xvny?uaqELYx5U{WCh+InnSF zf9;ERyB)@u2iC5}X7I#GwzqzLuhz_p6kd`5*#mZ}&sdER<79&@NXpEECqe1Xp~x6F zg|YG8|G^6t4NE#JgKYCmjR}*LMd8&1V>It8#Q8oK&(0|~N)rXsmWhWAs_M-GlY1UA z@sNUWEMysPMMz~M6I=NG@k5Rm237eW^vhUX33D>>@hi+j1BCO<1&oqBfBrJ&HJ8Gxmx|5|t0pDQxq6(O%nE~J7sxST)s~(h}7Xx)= z@WCCns;S8KNJ(eMNXfa$G{ADG7s=RSOmVTcnC^-*dve;)1*yL%ylG{KE)Z$78hoqH z1+%B8Efx?zqu&O)45mxK{_201SA}AVijCQFvKy(AUMn;24A6gKakOOBXvkjPa)jFi zO=%oDAbDdJ(gjNBIl2;b+xbry3W@fh+e|!V$-cGxVgZqd5tPX-zdNw3a1V#da*G+( z6oo5Q|HhYpd;nEytt$`qa0aHmF;yl&fhUmEi|GDHr#z_LR);@`(ZSIRTuv`NGQidthv@^`4|@tMTW zBTN`?lesvVT^6^5swSBLA;eDTquxk)ox?jD9Hmp|%sVSZSW_wvcR;q0o9UzE>cCM4Y^&J8o=0S~7e96U Se55M_a$GdAG_Ha?WBw05NcQyr From 779598eec08109780467b9ebc1e0bf28272f0bad Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 19 Jun 2023 08:59:39 +0530 Subject: [PATCH 0861/2055] Radio Canada by quatorze --- recipes/radio_canada.recipe | 80 +++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 recipes/radio_canada.recipe diff --git a/recipes/radio_canada.recipe b/recipes/radio_canada.recipe new file mode 100644 index 0000000000..1c63df5fbe --- /dev/null +++ b/recipes/radio_canada.recipe @@ -0,0 +1,80 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 + +__license__ = 'GPL v3' +__author__ = 'quatorze' +__copyright__ = '2023, quatorze' +__version__ = 'v1.0' +__date__ = '18 June 2023' +__description__ = 'Radio-Canada ' + +''' +https://ici.radio-canada.ca/rss/ +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +def classes(classes): + q = frozenset(classes.split(' ')) + return dict(attrs={ + 'class': lambda x: x and frozenset(x.split()).intersection(q)}) + +class RadioCanada(BasicNewsRecipe): + title = 'Radio Canada' + __author__ = 'quatorze' + timefmt = ' %Y-%m-%d' + language = 'fr' + encoding = 'utf-8' + publisher = 'ici.radiocanada.ca' + publication_type = 'newspaper' + category = 'News, finance, economy, politics' + ignore_duplicate_articles = {'title', 'url'} + oldest_article = 1.00 + + no_stylesheets = True + extra_css = ''' + .blockquote, q, span.signature-name { font-style: italic } + .footer, p.fQwTrv, p.xWUSX { font-size: 80%; } + ol { list-style: none; } + ol li { display: inline; } + ol li+li:before { padding: 16px; content: ">" } + ul { list-style-type: none; } + ''' + + keep_only_tags = [ + classes('document-simple-header-container main-multimedia-item signature-container-top ' + ' lead-container e-p picture-attachment-container blockquote framed signature-name'), + dict(id='picture') + ] + + remove_tags = [ + classes('signature-link comment-text'), + dict(name='aside') + ] + + feeds = [ + ('Politique', 'https://ici.radio-canada.ca/rss/4175'), + ('International', 'https://ici.radio-canada.ca/rss/96'), + ('Montréal', 'https://ici.radio-canada.ca/rss/4201'), + ('Société', 'https://ici.radio-canada.ca/rss/7110'), + ('Justice', 'https://ici.radio-canada.ca/rss/92411'), + ('Science', 'https://ici.radio-canada.ca/rss/4165'), + ('Santé', 'https://ici.radio-canada.ca/rss/4171'), + ('Économie', 'https://ici.radio-canada.ca/rss/5717'), + ('Techno', 'https://ici.radio-canada.ca/rss/4169'), + ('Environnement', 'https://ici.radio-canada.ca/rss/92408'), + ('Le reste', 'https://ici.radio-canada.ca/rss/4159') + ] + + # The following was copied and adapted as per the following post: + # https://www.mobileread.com/forums/showpost.php?p=1165462&postcount=6 + # Credit goes to user Starson17 + def parse_feeds (self): + feeds = BasicNewsRecipe.parse_feeds(self) + for feed in feeds: + for article in feed.articles[:]: + if ('VIDEO' in article.title.upper() or 'OHDIO' in article.title.upper() or + '/emissions/' in article.url or '/segments/' in article.url or '/entrevue/' in article.url or '/ohdio/' in article.url + ): + feed.articles.remove(article) + return feeds From cb41907196697e44d535a525057164e88f20e184 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 19 Jun 2023 10:33:50 +0530 Subject: [PATCH 0862/2055] Let the browser engine tell us if the selection is hidden or not --- src/pyj/read_book/find.pyj | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/pyj/read_book/find.pyj b/src/pyj/read_book/find.pyj index f6e34528c7..b25c9b83a1 100644 --- a/src/pyj/read_book/find.pyj +++ b/src/pyj/read_book/find.pyj @@ -2,8 +2,6 @@ # License: GPL v3 Copyright: 2020, Kovid Goyal from __python__ import bound_methods, hash_literals -from range_utils import select_first_node_from_range, is_node_visible - def build_text_map(): node_list = v'[]' flat_text = '' @@ -141,12 +139,7 @@ def select_find_result(match): sel.setBaseAndExtent(match.start_node, match.start_offset, match.end_node, match.end_offset) except: # if offset is outside node return False - if not sel.rangeCount: - return False - for i in range(sel.rangeCount): - if select_first_node_from_range(sel.getRangeAt(i), is_node_visible): - return True - return False + return bool(sel.rangeCount and sel.toString()) def select_search_result(sr): From 48b6b6f14556f3ec6557a84dec751931e562c646 Mon Sep 17 00:00:00 2001 From: un-pogaz <46523284+un-pogaz@users.noreply.github.com> Date: Mon, 19 Jun 2023 11:16:29 +0200 Subject: [PATCH 0863/2055] optimize images --- recipes/icons/automatiseringgids.png | Bin 9917 -> 3627 bytes recipes/icons/horizons.png | Bin 8552 -> 5509 bytes recipes/icons/journalofaccountancy.png | Bin 12074 -> 4512 bytes recipes/icons/le_peuple_breton.png | Bin 6934 -> 3565 bytes recipes/icons/nasa.png | Bin 9120 -> 4227 bytes recipes/icons/tagespost.png | Bin 24875 -> 3073 bytes recipes/icons/the_friday_times.png | Bin 15463 -> 302 bytes 7 files changed, 0 insertions(+), 0 deletions(-) diff --git a/recipes/icons/automatiseringgids.png b/recipes/icons/automatiseringgids.png index 9375f9fbc509abff63643dd780254c763c19c665..7998cacc896020f92ad0cd158fd8c01f8f38eaa9 100644 GIT binary patch delta 3593 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 P00000NkvXXu0mjfls)p7 literal 9917 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 diff --git a/recipes/icons/horizons.png b/recipes/icons/horizons.png index abb8189dff94cf26b3c853b528a5a478c6a68f05..32f87997b3687962759de61b012497fdecaad83f 100644 GIT binary patch literal 5509 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 B?;ijF literal 8552 zcmWkz1yoc`6kZw@fd!ThSp-}+V@-*@LFqfGP|z}#RE2*jX|M3|B4LQ*NBr6%3s zO|&7Tit@geu@(r__#geb6BP&qR`Sr+M){lR0Rs9u+KRHuii(nQQnDZrGPy7}*us2{ zJ#oBSsT=hel9D~)>25}jWavg_N{PwRK@9qEAVEchg)ah~SwSlYUy3Qy&TJG+!IGgH zwqgHzwr+IgA?Di2vA% z+urK@^>69QEInzNj$9mTv?m>MJt8jg0s0A=Ll0LQxgtyyLGZ`(Qa}97UA-h-9>eA0 zB)^4%mXPo6?oCNse;8*SVHzC!U@OHURGGvjVrQO^p2eEFJU(u`mOA)BhEXZ~+KTL_ zd@dW_Wk1lp@pBt!s5e)Wpz5ca$~R@aCB1v#)6w?lPahu#*nJCS#1X;pL<~yhylXbitp)_AH|6$S7|N+`PrGzg?f^t zvvm+76-EL^xvh5ytVvY)K!WB;alr}G+l%LO9l6P$Pnaiit;x$_+?rARAh;N_0A0AN z_{44`{g58T>1gU?eqQ09@wkgft9GLc0=rOjhKn9TuAK!v_OsyJmRw!{q@zi(smZfW z!P-aps*kxH#I!^~t_4q`eB>y*6bHqTAp|&}F|8$>*>QD_*Q=-lOPJ;36@57aVqKQx zZ_-G`jyO`zlbOjd7R89}fj?+MFjxYF%`7oMK%2nZgtf-;tWr>EGoyI3X@#``BsrVr zR*6{^Z%x7`{@(hXb$9@GCx}72tpx4Pa>f=$b4`Y>qTluwDeT%9pjB>ov<) z$Zi_b;KAO=FSirj2&O1njf`R#oHdQE9)kqLoFws&Cv#HHuOnHTSgwe=*LW-iXLNIUG_Vq%+1;K8CtarMuOnz!MMqdFRkz&~5^S4gW`_=`NV1Z*IJB^Pk=J024?#Ra{;Fnic!x}`8$jEZTa*P@Y$Qyz$XU+m zDqARPhc^4A?o)l&gGvl0t!OW!Q{aE#_u(!Um>N?z^N#7jY28hm&6^C`H!7$m%sk3l z9=gZ3h_a=m6dJS`6d9zyf5axrhv%83Sl>95J99bVT)SdoXePcRunmFB79bCOg)bz;nz<91c_&IsA_E!L-cg6_s&a=nLiEHyS z7==;!Yw}j^J4D`zXEReX`^5KCHB%~OM;1@(xABEF-P(UJzn?Q&4jb#0G;6OtA9G0n zzs<@Vt8zV*tq4#E{2M6TdDI!-c_VBn3>}6I+YUQEq&t)NEA*G|tozKIT7?=$O+{Tt z-Nx`MLE7(W5hzt%75$SmSH<3881mM*(FhI z!(}~q$Gds6?abZ#VWH20d*pC}GpoC~JJIE!wk}_C_SMku&vyHEiygIVLF17=_kD)j zzPJPviymBc?(OfHy@-!5iznbmou(%HWG5SEeo?0mfnB`s6$S&Up~`hZDnAZq=K5`Z zMtnNu;HWSxFiagO3mrN*IpAJP@7V0{v)i&S_qo!BXzTVd3$_o=2#^0m!4%00b=WI3PkU~si9S9tD`BwJr=}INXnZ$gDuUfx4{^s(T;5VJnR4IuW z>t{VDCoF@)&xG|@CX;28C6jlR@z1Eo4yR(uTMaAQy>_H_Sc_;L(mZ^;`(Ap@RH#EF z=lU&!Amgt^YWXWgW|{jIee4$WMU`ijGvz%d^u}L`e~aD{>ApqrBs1@k1(Laf?vGsS zjRU#F<`2y&&Cf@kJ=wNX6b7>>xM|O&Hgyfk&y7#V3?P~u+Q+GcwTN2$^nW9A0;CyW;a~S7=wm&tIR2w<|5J zY>#~!9Ct^+H}kt8UqN8?_v@5t~F2Ihrv#Dp?@u zR_9y$JaFIfP5;PZNHlR_$gB2YW!n>e@!#gT<&Qa2!rK{J{Oa5xEp5Z)mEXcgc8{+TZ{7B{`yMh!_a?cNwbh6tVN6$ZXEyA2>p6=-bg>jk>TKK|Fd(|x7xois^lBZ zpQ|d=eAKFmyosICs`fivMM@DXDSvW^R^1InUF^QwQ-t%(`D8SogcG)AieD zb!%<@KI8K?{v45Qt3^}YDSrx%d7|Y1Hrj6r+*04XrLyPPAFt{%-*{ca?eZ|{^*Zd- zX%I7pIJovjZ&)Pqy0nJFNyxeWOjds7poaLR-JcJubHTkK=W;S^stOl&hv&qGr+2G* zKV44kxa|gfI{a(J)o{<@$!m19|B~dE_hnaJFzbzu-Ns#Ujl8J!Q_jO*JC$js#ZeT; zn1esVldAtDrGN|4%oqd;69Iu@Fd)zwsVQb11PYV^fi@jMAeB52h{N|;Ct8j4g3cFd z;|~H!UHezaLdrFQNR2cB`o=mmOOzZCDzJ;~&@QQo+SyPK0lNHmm2_8Tfk5m*`UowH zkk8wN^l3SRY{SwGRG~Kgvx0R3llySp0VJxAgP9&G4tQ-_iuT_NSg)zUd z9F&jWxXF5X_0nZC(XLk6Jbr$?>Ao*t{tPX}+?(k)E_L-rJOj3N4YGki{YS%dwQv4q zBf0(QPM@W(tQuC(0Adn4`j_^y!Hmro`xXB0%bK}`q0z!@sAO^NI)M5kH+d3_)wQEnsr)cY8` zy8Ef;u{6KUjdp&cr&o04m}3E!REOr%fS(GO4VOXdFJWhuto~)<8sLs6)kDq#iTOP) zHnqudfS2vyw1`_ByU~GJ&u$bZx)X7B5iEXWJr>s=h(jeApI3y`I@L)TZN+->ITZ{{ zPs9OJJ;jTq4ZvAt>dOlcpMVQ$C0hWd=zYAz2!y09wlJs{U^-lI{{7v(4 zjmm`o*VyV5M(r+OcaU#Ojz&7q72YeCRgVQ`$Y)i=?BaAqJ&#<&?kErS<}5mlxcJVB z>!s_5=lLK?AcbG=cQPgcUY|dtzh5cl&*>@p^JJlLu>ruRY!nGK<@vIskOjH^MzIK3 z4SVG&>Sv%L@t`m8WL!OQ*p>{hVtM)Fmjg}w)=NP1S|7iNy!jhEJXWEFDYGyL( z=`PA8kXoj4Ettr!g;0=;#ghse(9lfG24jWxo23l7zbZ;9?Treqo%At4$_lOk;S z*&_Pu83z2tg+F5f*I$L6X%t{tk5pOVS`_ElVY19CRwMtcY!@}9&H(6s2kh`LSP=T= zhH8S?A~6kw6?&tzgIwy&C|t#~Z%;TEiniC? zD2DojBz$#MM|&W`6C*o^LZS3BCtjWADf>3eMf7@$Zssaz zqt=9N;X^$^P_wPlelGsSSY|kaO=<6onFjPVXx$U~xFRv8_uKc!(eE{$-*@}EE$Gyqb1kb=yM*ux#>Oq55W5Nz^t%I2iH$Bg)&6$SCre--k`(^WE8aI zdERino%BL`0zt_DZuGcL{K$TJ7~{O2ej_r|u>t+&)TE0ngQ}jWX-C}?Qwm8e8xsAo znFOS??GD#nP3eRWROryDS=?7Vl;GrpU^R(7wIuuK0MLCXk&Du1q*VqSUN|QIi5dHR z!@S}t-#PIzy9@r>0=^mv2RFV@u=wdrdIpL7o73!u0WUhqL|RzXNp`tdJ-cb^YqtJB z=g8elj6fsBA3<|?u_3OKstY}_@aI);jj|peq87dj7A|D(2iC_&tB8h`eN$3We$pAQ&3@!K9|_m0&H;9 zSl+B~EbRk+NiZ4rQwHn{)07JjyssoNi_U&#-_CGmUwmq|H`XAN$vZ#|d*`{|2vd`W zpClW0Z(+l4)%I1{J=qovQw-48_K+UDtTe+#mma;j>SCtA3HAvP?2|0RRT5J&_|OEp z<>*$;df}@I!#Rb0Y}k2kE!Y#~5*!ybv`LceRUYW1vd3m*F_k5MkDr$Xy!bW{&CB zm-8#MN^v^}muSM4z9@mPKaJPV%`F>3?nyEwvoNO4x9TSCnxhG1=Cw4MU#2^zcxW>^ z1i|ga*AOsikv&wQI2cc`{hC#vd3I!Lm0DbD9N;8FTHB*>`b^JiJ%*cq=Nn_5a$}8< z?m*z5oP`uQ&(K1rO0V~ZFyZ3|PA|x!uYt8%j0?pYg(~yJORNYBrCnL(f^n?iN5`hE zc$Iz))1<{^`BG}_ikeQJ()BOO=(1_D%LkdKuqRV!JjC|{TMS?_Xg9aTky(nfwHXoY zi43IUzM=rdoxGg#>_E@Gb&Pp|3+0EqKC=kK`aG@Cf|sOEk%vFnShko|kgiO*@_|>5gZV*c1bg05TLo zZ_1V)+(+Y~{NNe?^K0O_L`|5UA*9CNLuTNbu_^rSWi2Pgg&0^FnFRc1E`{vHBT8^= z*LxGFv1(L<%rOolODuY^Ku|?Xt2=Z0By$;V1hXhmh6_T#DW668Xu+cWt(Sk)@(}dY04olj*>WtTcKy~;4w8lSW z4-Ov?`KG4u;gjfWK!x`d(}5BbR1U|9jo=4id0h$$NPepVohqiq#$?&+bZ-MR?ym{> z*?mR#UnW=JuAFExC1LQ~W6X$c1pqU7S5J<8y*A-_%Kp|i-niP;L6-xB^%4PK52RJa zpl=kU@eCjTnuOuXlA3b+-_>hEA8U&B$}=ROs$Hevt|}~8`5Gj2WvzGzepb1H9~VZz z4Dpexyl8^caHDxw3DuX|WvQH4J0J--qeQ^ylYu}LsN7g=ac2om3EWP;!)&2Dd*_U1 zibvub3Tc^urY(^#YeWtiHt);(GY(%SkQeuS>!#j=0AWp-H;!7# zBKs6wf6vwcS&KYJRiA$$N1*1;~Z)DV*OvFf|G@ zr9&nCEUp(iG>zoGqJ^T>A!`%kKi*z`=@C=2S<-vRu=0vU6`Y@&tzl8gub|G5&KGlj^F<^flN1`@7&O!q&2wgGjv!9nQ=?M8xmCIfeYG%%aBG6#q zuFY^N_{Av|4V3(ytvW@AN1p3ztpjt+M&`=m$i^_5Pazgf8%@WgU0O&5w%aRqI;+lFqX;1YR1Q0#QK&QR+0hyYYzAlq=RMI?4tlT1 zZdrIcpcwze-fFq%qlf;T+PBU_XxiY3F6Pt9+FN0g$CfDCh&LJG(yC7y%_|zKI(ZJ+ zG%s6B3~e{*5@kzq&Ou(JxT*-4Zg0&A^xWE=8W~Ff`jp%=rn7AXKGWeWSQ#hvjOkFw zUL=Y@J&obx1$(5-%Nu?61O(OG1>$IaZ>YKH!mN_|`E=s%DT+=oe8&z!wX|qLHJ?GY z%*9X)&UwL5XRm2~w zNZ&3_Xa275;H?CFp!P|j)RJj#OqCXF^D9 zh5AXuz+4<;V6=ANy2D6;jX+IbqzA}mWRv0Sx{vPh$49pEuWr%3%+aDl5o$EytF1^> z(q?h#u!#*SiOBF-F7^)F$x1o*!dzSzx)i6Q3??gs9DWi7fys``$y_Dlv*-dY?*_jP z+S7#jKQH@1lG`NwD%FHjsuh#@PXF868kJFN3qEi)DJd1jgb0A=iXK7bzLNB*e6aoPvGq_J zCQcHkwTy6~qm}QQ#ks857LK0cd3@J(Kgm{hA`P}mk3j+Kp-oC9FE&uW7VF`zFUUyr zO($RNDsVad{RzKj65vCJ0Wx&b09|H0&K9roi-b>jNRRdrWLw?~j`&`=RL`Ugrn~#k zNg~dPvDDB~5mq~TO_=+oyf_@g5i>9~vO}vS-6cY%9ayuZYGDtJdm!W5MP-J^(Xc_~ zhA6NSRUqgJaiqT4-9h_%u}?zFk={wA;p9d2;IIvkEO~xp78w_`SXzUW$85ISP6@O#$_*cUR+lqhU$4V_5df)belI{CX0Ka1S6k<<}QS zO+-BI2jxX|Jxg@<+X_-n*=Ikk_2HmIvHgrR_2;u%_`tA+Ww_*@S4VfhPt06ay2*C- z!Ceh?(S#Y65=bRa%Stg|8+Rl*^6ULI7BJXmB^F2!^RE8k2EopA5H!k3#|@Hwj2*ui zPLwQl5oZ>CTPVU3FSZ0t@M3~Gdv0k=hnJDiry-;Sr%qzKiSAql_^S0Pe89pAZM>49 zg8>+IN%U1F!VWFvNj>x52(CcD9^AR4OL%qBZEr6ydzbX%uXr^cP?;sIH7gFN0s>p7 z#;OZQsJ_oA;6s?<>4XUhG z0J_3jQ-**YQ}3Jp$~nPQyW%M>a^e8>Bu;FA@V;r}=d&!!k66Z&SsJL{HDZw_+*Kqq zx~6vEDrR1u(XlyKo8~-_B==z8{3Nvp1wY`FJ=*BoA9LQUe>N=sWQ`^igpnvOkS&`b z!658)N||6Se3g?KoNq1&CI51MGPEWlQ|`Imq(-=L>WEP^x4s!0v~OV3|Mqy^_NpV zualSI?vwbzttx{BI}Hy_>mo6Se)A2d0Fpq7GKI?Zl9P10EMZk*`SD2!?S!l}NY*p~X)BG7WXO#)AEX@tNW*n;eA!u&g8zUNaK;V$c{(m^!{QD2Xm{)WDfZ zSgtd-6!a8~J!v`WP4ci8fU=mA9jll0r2l++cm8v)6z9M%;Xl$c~k{ zp;O`jbS0~GewgG!pY)-F2CUK3vmX&9r-msfFyPMO66FRA9e zvCA~tFhsShB2wCjm{UClT8u9+x7g@>}Ju*b)g*7Ok~lG|6Z}5(%KhpYsIj zqmjZp0Ze)cz}E~J3z`C7IFSCuFdYz^TrUXcRcK4LPX}YCAtjJa)Pjpt&Iz7HW{av) zTo!D@>Qps`0n&0#IwmK1<{@Z3!&pF<20>c93W;8ngg>M+CIUa0*?hN9w3cLT|3w=p zF3TvXaF>P4eIsdXl9T>d$yn?9q{=M4z6=RVVaA@gN?K8%kX;5!?I}kaFX#2;Q|Bzm z;I8?Hc+JHU(qUsm=510A^u2grH(p&*#E;a_A6Nx}zL^|XPT@i2yt{A4l;-2S3a8|& z8A+FNDEmfzu!d~PeaC&}yY3VF_`fhJaOO<-KwCe`CtyygQh($ diff --git a/recipes/icons/journalofaccountancy.png b/recipes/icons/journalofaccountancy.png index ddc65c8c5e8bf9c0a9cc67a515c749c9b4a7f54e..27c245eba23ee8809e8468b6b7664d8cf828085a 100644 GIT binary patch delta 4502 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 literal 12074 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 literal 6934 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 diff --git a/recipes/icons/nasa.png b/recipes/icons/nasa.png index 665acf2cfcb81e8833cdc5a86fed6a55eaaf7815..4df9d1a5a127addd9f91fc35f4b2531130f57b3c 100644 GIT binary patch literal 4227 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 literal 9120 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 LNkvXXu0mjfit?*A literal 24875 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 diff --git a/recipes/icons/the_friday_times.png b/recipes/icons/the_friday_times.png index 5e6a875d9da0f1d8868351f0f96f43d662385258..ee683209f2b97bb8f1faa67b966b65a0120bee71 100644 GIT binary patch delta 267 zcmV+m0rdXoc&-AF7zqdl0001UdV2JcAs2rD;7LS5R9Fe^mhtI>Fc5`}8>Az&Be+6W zXh(2`tYAlQ1y^tdJ3@XV*b!bI%@=MFx&aPQI&s-OeV)U1}6fC-p@37A0L z0`P&=6Oe_>>ZAIVEXv)KN!gK1I*-aM+L?6+)Tg~Z{cbKWkS;regzP+Ly)Vo=G8A{R zfuNMz3i#BQ1@`C4C|efT&fDy-cs0e^pQ|Qi4&3$oLl~TMk*w^mHQs^;fD1XXf7X5t z@Bb#?ob##wb`Ie0=uTQp?Wuj4dC3kWc^Fo}2>!99UT{t>HW8fA;Rd-Kg?E2{F zhb90o95JgK-bVSPrrWI~H0&Bm_F8S+8-V=LUK{G?k;m0g!?X&C->-j};7p^CIGrww z<@O@lG?%v>w7$Js)wj>h4Q%mv_%I&&vvS|-7JPxRvFBiTU_+3D$5-? zZCZxib)U@(q)W8kQ8ZM79x7r_l@}FW&Qv8u6LXrJdrnMiq8JMj_9SAIaqSz>gR?{# z=}X434YR&AQf^+veFDW1?6lG@|m0{^OA&AzE%@cJj&(_ zMXw_@tw@7Jv0!6~UUE!)F2QE3k3Sa!CsPb5g|e#7r%)=(D=Ed`bM>^ri)pp4W(+m0 z8*q?fLYT2s%chH~X)9J`xYmM`XC1vf+By)LW^jVEq2nTdV-^yFojE*mh9`>e6Krq| z>i*eN)cx{BMqU_cjfOS!?7Nxc%Q+i@Fn-K^dXDZUvS{TWh6;L358~Q&uL~V?tbr?J zmYo?!9d#qa=LP?)ZkqqWLb&k4zgvdccGdr48KNrIH=)%)Mo}0P@StI~l?&H%bY~5A z>-bL0M<+`a^}LgC3pws+DFi$9IBblz`r{1?=b!D=C|{6k6}C{ZdZL;aCJE$>{iH-c zf7BBL1OgdganY3h zigG_4(H!&gpFXsJ&^mrlM#W4zol55gvYGU#m_5|oD4F;_()G{!6wVa*kitaAGt(Sn zLfneqhGW>ca40eQak8|6v~Fk7z~@GAa>^B_V*&O@@okhfoSMWVq;j2$kU?fY42bi_V8o87=|{-DJ4vdSM zWVq;j2$kU?fY42bi_V8o87=|{-DJ4vdSMq~e;3UVlXve&@A|Uv7Pkd*(8J zF_zPp*UA8FKMlae*8#Y9AK!lkpd$kC+c^NV_W(F#zkBxU6Syy5Iayu0e;;?5^}4BD zyNdfh>aDG=gU1u8Q}ynf?|*!K=g!@4Z~s)oN`0ktta@Z!KR3V1S>Ju|=~G{v?cVtD zM#uVd{px~rEA_^{cW%$`d_Moj{LZD_yBBsn_Jpx_PQ08h?D=`$BMZCse|GcxYX>X) zu1L*=LzRmUgBH-QUIZK5FLz4Zm9K7c_wLT$s_X|pynXl;>D8T{Kkk*K)zT-&&t3it Dgy*nq From 76d89154a2491ee24005073a9644a3fc185594a4 Mon Sep 17 00:00:00 2001 From: un-pogaz <46523284+un-pogaz@users.noreply.github.com> Date: Mon, 19 Jun 2023 11:44:22 +0200 Subject: [PATCH 0864/2055] vectorize 'plugin_upgrade_ok' --- imgsrc/plugins/plugin_upgrade_ok.svg | 1 + .../images/plugins/plugin_upgrade_ok.png | Bin 10611 -> 4429 bytes 2 files changed, 1 insertion(+) create mode 100644 imgsrc/plugins/plugin_upgrade_ok.svg diff --git a/imgsrc/plugins/plugin_upgrade_ok.svg b/imgsrc/plugins/plugin_upgrade_ok.svg new file mode 100644 index 0000000000..88747145d5 --- /dev/null +++ b/imgsrc/plugins/plugin_upgrade_ok.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/images/plugins/plugin_upgrade_ok.png b/resources/images/plugins/plugin_upgrade_ok.png index 8e952984be12274a1c6b8854f2aa90831c5707c5..076917f782c2d1ab46dab88639a87fbfc72d7528 100644 GIT binary patch literal 4429 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 Tdu;yU00000NkvXXu0mjfn3VYr literal 10611 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 Date: Mon, 19 Jun 2023 20:48:10 +0000 Subject: [PATCH 0865/2055] [ImgBot] Optimize images *Total -- 76.60kb -> 60.63kb (20.84%) /recipes/icons/taz.png -- 3.74kb -> 0.71kb (80.93%) /recipes/icons/sn_dk.png -- 3.65kb -> 1.28kb (64.93%) /recipes/icons/prekshaa.png -- 3.55kb -> 1.33kb (62.6%) /recipes/icons/taipei.png -- 3.94kb -> 1.57kb (60.11%) /recipes/icons/sfbg.png -- 2.08kb -> 1.43kb (31.24%) /manual/images/python_template_example.png -- 13.47kb -> 10.50kb (22.02%) /recipes/icons/piratske_noviny.png -- 1.10kb -> 0.86kb (21.98%) /recipes/icons/thecodelesscode.png -- 1.06kb -> 0.87kb (17.97%) /recipes/icons/id_pixel.png -- 0.37kb -> 0.32kb (13.79%) /recipes/icons/the_insider.png -- 0.41kb -> 0.36kb (12.5%) /recipes/icons/tjournal.png -- 0.43kb -> 0.38kb (11.9%) /recipes/icons/nature.png -- 0.81kb -> 0.72kb (11.66%) /recipes/icons/unian_net.png -- 0.44kb -> 0.39kb (11.61%) /recipes/icons/pravda_uk.png -- 0.44kb -> 0.39kb (11.61%) /recipes/icons/pravda_ukraine_ru.png -- 0.44kb -> 0.39kb (11.61%) /recipes/icons/pravda_ukraine.png -- 0.44kb -> 0.39kb (11.61%) /recipes/icons/snopes.png -- 2.89kb -> 2.57kb (11.15%) /recipes/icons/old_games.png -- 0.44kb -> 0.39kb (10.51%) /recipes/icons/monbiot.png -- 0.73kb -> 0.65kb (10.5%) /recipes/icons/ukraiyns_tizhdien.png -- 0.56kb -> 0.51kb (8.6%) /recipes/icons/habr.png -- 0.69kb -> 0.64kb (7.38%) /recipes/icons/habr_ru.png -- 0.69kb -> 0.64kb (7.38%) /recipes/icons/kleinezeitung.png -- 2.09kb -> 1.96kb (6.4%) /imgsrc/srv/fts.svg -- 2.98kb -> 2.79kb (6.35%) /recipes/icons/n_plus_one.png -- 0.63kb -> 0.60kb (4.98%) /recipes/icons/the_clinic_online.png -- 0.61kb -> 0.58kb (4.97%) /recipes/icons/piratska_strana.png -- 0.84kb -> 0.81kb (4.39%) /imgsrc/folder_saved_search.svg -- 2.26kb -> 2.18kb (3.88%) /recipes/icons/7x7.png -- 1.43kb -> 1.38kb (3.54%) /recipes/icons/quanta_magazine.png -- 1.38kb -> 1.34kb (3.12%) /recipes/icons/unperiodico.png -- 1.22kb -> 1.18kb (3.05%) /recipes/icons/smith.png -- 0.56kb -> 0.55kb (2.27%) /recipes/icons/newz_dk.png -- 1.88kb -> 1.84kb (2.23%) /recipes/icons/thairath.png -- 1.30kb -> 1.28kb (2.03%) /recipes/icons/novaya_gazeta_europe.png -- 2.62kb -> 2.57kb (1.94%) /recipes/icons/novaya_gazeta_europe_en.png -- 2.62kb -> 2.57kb (1.94%) /imgsrc/context_menu.svg -- 1.61kb -> 1.58kb (1.52%) /imgsrc/external-link.svg -- 0.43kb -> 0.43kb (0.91%) /recipes/icons/skeptical_enquirer.png -- 2.73kb -> 2.72kb (0.47%) /imgsrc/srv/fit-to-screen.svg -- 0.29kb -> 0.28kb (0.34%) /imgsrc/srv/external-link.svg -- 0.42kb -> 0.41kb (0.24%) /imgsrc/external-link-for-dark-theme.svg -- 0.43kb -> 0.43kb (0.23%) /recipes/icons/spectator_magazine.png -- 1.46kb -> 1.45kb (0.2%) /recipes/icons/tagespost.png -- 3.00kb -> 3.00kb (0.16%) /imgsrc/width.svg -- 1.46kb -> 1.46kb (0.07%) Signed-off-by: ImgBotApp --- imgsrc/context_menu.svg | 12 +--------- imgsrc/external-link-for-dark-theme.svg | 2 +- imgsrc/external-link.svg | 2 +- imgsrc/folder_saved_search.svg | 9 +------- imgsrc/srv/external-link.svg | 2 +- imgsrc/srv/fit-to-screen.svg | 2 +- imgsrc/srv/fts.svg | 27 +--------------------- imgsrc/width.svg | 2 +- manual/images/python_template_example.png | Bin 13795 -> 10757 bytes recipes/icons/7x7.png | Bin 1467 -> 1415 bytes recipes/icons/habr.png | Bin 705 -> 653 bytes recipes/icons/habr_ru.png | Bin 705 -> 653 bytes recipes/icons/id_pixel.png | Bin 377 -> 325 bytes recipes/icons/kleinezeitung.png | Bin 2139 -> 2002 bytes recipes/icons/monbiot.png | Bin 743 -> 665 bytes recipes/icons/n_plus_one.png | Bin 643 -> 611 bytes recipes/icons/nature.png | Bin 832 -> 735 bytes recipes/icons/newz_dk.png | Bin 1927 -> 1884 bytes recipes/icons/novaya_gazeta_europe.png | Bin 2686 -> 2634 bytes recipes/icons/novaya_gazeta_europe_en.png | Bin 2686 -> 2634 bytes recipes/icons/old_games.png | Bin 447 -> 400 bytes recipes/icons/piratska_strana.png | Bin 865 -> 827 bytes recipes/icons/piratske_noviny.png | Bin 1124 -> 877 bytes recipes/icons/pravda_uk.png | Bin 448 -> 396 bytes recipes/icons/pravda_ukraine.png | Bin 448 -> 396 bytes recipes/icons/pravda_ukraine_ru.png | Bin 448 -> 396 bytes recipes/icons/prekshaa.png | Bin 3636 -> 1360 bytes recipes/icons/quanta_magazine.png | Bin 1412 -> 1368 bytes recipes/icons/sfbg.png | Bin 2132 -> 1466 bytes recipes/icons/skeptical_enquirer.png | Bin 2794 -> 2781 bytes recipes/icons/smith.png | Bin 573 -> 560 bytes recipes/icons/sn_dk.png | Bin 3738 -> 1311 bytes recipes/icons/snopes.png | Bin 2960 -> 2630 bytes recipes/icons/spectator_magazine.png | Bin 1490 -> 1487 bytes recipes/icons/tagespost.png | Bin 3073 -> 3068 bytes recipes/icons/taipei.png | Bin 4036 -> 1610 bytes recipes/icons/taz.png | Bin 3834 -> 731 bytes recipes/icons/thairath.png | Bin 1333 -> 1306 bytes recipes/icons/the_clinic_online.png | Bin 624 -> 593 bytes recipes/icons/the_insider.png | Bin 416 -> 364 bytes recipes/icons/thecodelesscode.png | Bin 1085 -> 890 bytes recipes/icons/tjournal.png | Bin 437 -> 385 bytes recipes/icons/ukraiyns_tizhdien.png | Bin 570 -> 521 bytes recipes/icons/unian_net.png | Bin 448 -> 396 bytes recipes/icons/unperiodico.png | Bin 1245 -> 1207 bytes 45 files changed, 8 insertions(+), 50 deletions(-) diff --git a/imgsrc/context_menu.svg b/imgsrc/context_menu.svg index 7dc82b32c9..d555595801 100644 --- a/imgsrc/context_menu.svg +++ b/imgsrc/context_menu.svg @@ -1,11 +1 @@ - - - - - - - - - - - + \ No newline at end of file diff --git a/imgsrc/external-link-for-dark-theme.svg b/imgsrc/external-link-for-dark-theme.svg index 433c2264dc..eb3baed22b 100644 --- a/imgsrc/external-link-for-dark-theme.svg +++ b/imgsrc/external-link-for-dark-theme.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/imgsrc/external-link.svg b/imgsrc/external-link.svg index 6ffb2e7475..df1f6fa494 100644 --- a/imgsrc/external-link.svg +++ b/imgsrc/external-link.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/imgsrc/folder_saved_search.svg b/imgsrc/folder_saved_search.svg index dbe20e41a9..d7d1a34b6e 100644 --- a/imgsrc/folder_saved_search.svg +++ b/imgsrc/folder_saved_search.svg @@ -1,8 +1 @@ - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/imgsrc/srv/external-link.svg b/imgsrc/srv/external-link.svg index 7072d735b9..0ce4f1bb35 100644 --- a/imgsrc/srv/external-link.svg +++ b/imgsrc/srv/external-link.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/imgsrc/srv/fit-to-screen.svg b/imgsrc/srv/fit-to-screen.svg index 9236e069df..16522c4228 100644 --- a/imgsrc/srv/fit-to-screen.svg +++ b/imgsrc/srv/fit-to-screen.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/imgsrc/srv/fts.svg b/imgsrc/srv/fts.svg index 39f49bdc71..7bf2900ce9 100644 --- a/imgsrc/srv/fts.svg +++ b/imgsrc/srv/fts.svg @@ -1,26 +1 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/imgsrc/width.svg b/imgsrc/width.svg index e82af93610..12aed74c37 100644 --- a/imgsrc/width.svg +++ b/imgsrc/width.svg @@ -1 +1 @@ - + \ No newline at end of file diff --git a/manual/images/python_template_example.png b/manual/images/python_template_example.png index 90aaf337624f2161387b8f54bcd0446559746ffe..d525ac42b6d50fa0abe7d93bcfd1cf04067246ca 100644 GIT binary patch literal 10757 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 literal 13795 zcmZ{LWl$VJ`{zQC1%f*ScPCho5Zo4D2<|K{Ay^0wf#9%s@Zj$5?j!_v2<{L(_)Xsb zdskOibswg7YIe4_r)TErpLLj;iX0|789E3A!c>r#hJru{LBPfcMg)!=V>p=tA81bU zx-K9PR^Pu3A(0J>90Z~UDM(9bdZZs`=)>9NK7W6%I1pZ$D$L)fi$0OlrcdB8LFy=~ zstyi7ex)P$+H~_!7Vj_$(~Dx&txwke-SL-e(<$GxuT%oFSZ43n6@QD^m~sygC1DCM zaH7~Fu_#ibQ7C{{Z1aRfrwR*NZfFkUW(?o^q+}MK>_1}!|5S3=NQ=|)|=P1C=&elJnHhg6HEITKyrzoI|7{-feU3>`~nEQ^3Ea;E9*tL1?#9l7daW9w1kacd07YL^$qGSzU97GMQTbVw3#^R5yR5RwEANC{}K4E!w;2T2z# zweu==B|MpHp=R}ZK;w;p(4q1h;N1Rilf0f5A3UCuQ3mnR1F6%-E=?CtDBtykQV+#` zarxH((*HETO-cib<7z$1-ayAy*Avx6cLq@7;w%`ac#JQxAE+Qs#j| z4~*qltRB9}@9>0m_VA0hXHd&OT}TdNr9dRP=C@(G6Ex&~vGjBq5zP6w_I$Hzv1N0I z&5{9ljs~o{9b$P%Q=;r|ZUzN8POc0XCtnpTeIlKzv#%%!ST#EER|TiJ znl5W`ninOJe{z`A+^90r9vVJ}?D^yCL%iBh=|$R&2LCD){BUPnmCChB&?1i@k_Zr@sLiYeq;`9oSBsew_Ex$&aM1Kt-h-0~e+Gi#B zOn!;~-8yvMAX!7ds&1L6tjZfbNP32Fa_V;34Ea`mleiYsY%L%sFyFkP=mcy5X8d`qO&u7!}g>$ zv;v#BOHI>luGE!_CC4$YK20BL%n%KWD7JTW5J9BW-0v(C5|e55C1qfX)NBq(e-Mxh z|Dv)}pwZ-ma6bAC?rPw@3S<`}B{i?ptyM?6bMpU7wbB9wi3`nH1i7fhP5x#6X;Ts4 zak;}oGqZRdA-)hUEt^DwhX~=wZly_i9q@zwL^b2=b?ti_5{NDwgG8dB>JmmUZg{9m zs?DtPmOQrDFQ>4f>#^%burO#3A*0p(LRwD65s`d1Gxko$od3hK9~yC~WCuo*pAi-Z9;Rh2O_{*SabS%hHA<=%p1I z39WojTzu?LnH~11Uu+BdiY|^j5C%m~3HvNmekW0CDvi zV<`!uCLL9lWo?g5By^*<#Vs3BI54TI4|kcZm7Ui!+D5+0R;8^M z=ZZ0JuYXOAzx{Y${-NsF-i!8M(RjuG1N-T+GJmbDjr`$uuuYEAYmd(Fs#WZ2)!3eo zj9>OE^^j~Vd9>l;@#E3G@BU)x2k*UgKVM+0U}FRd8vLx)rckBc_n^IV-qAtyk17>; z)YtkxplYB%o$Q{+u1kIAeQrKw`t77xjZ^uF1DC+qKThhiMj^Hgod2{%H~0!OkeXbP zlQc^9Ol7uQ>wY%P`2Hbcd=?iEs8XTGpx*Ug&l_uxr)#%V&lhi=M^7JCEBzj0xng8< zd6%qiP`xl3O7d*P^sNIXODg9DJ@MQ=%q+oYhPf(V6ZeeWQZj}RRm>q&qQF59B-?R=v`!LhMoM?6N6n<$>vTlQT@G$S$iJjLK&xc6||f6`JtEU_PFyQ zP0ahX$7%!d{p~hE4J14`X{0jS*}7PM2YsU-kFh@-`UTHXZ;-A>`9OZwoK=@{&!vB7 zSPlw#T)js=e0;Uz9vt43#eP;up~}^AG^M#n>cSaDacZ{FqR{Tx!%gGspnI6 zG5+>R)ws#8?DE3+DWtN5%T?95MLxb@a_r_@aXXPOs(OK{Nx}R`XT0dB3!gDbSxs_C zb4;3<0a*ZvAs=$6Hl7>SE(0+yrdm(m@m3uWs=4_gZ(iftUIU|0xa)W$uZ1F;aQbm0 zv}tOcyOF_S@R(r)VaBYQsnIT#GSR--aq-85A&q|1k`j@-sXdE>Bz9!{HOqV$V%;AC z^H{-@=o)tV31#n&jH|?e3X$35&bx)0xr(=5KQ=JgSn{CsbKSA*g=I-&Kc2haL{&Er zj9|Z2Fw=d}^H^q=lc(9oH`d?Jstu91Z+JCjLByD2P|13zrOx$dPa5M4mxHD^?5g5x zzq&4@J{Y~-WN;U^DCtnR>(dt#ra!3EEgwDODUyY%rH4=P7fgFlLYo>@sf7gm&?Uvu z%&lc5$4^}l^9xC0`oh~?32DNzcTbVJqg6So+d3$Dh-YiZMGdbPF?Stx^M?Q8=g_;X z7>z4?u`QY;fX&?sZI*2l$jpEAguhUY(`oQ;>Sgx#!v9i`L(i*QfP`-gtp++jt++~g z@Yyxh7@{CIQFZXB*+6?wiH!#G=g=Yln<+;J0^vDR_DkX|juSY-zCF&6YU^66eVEH7 za%uE(lpCrO(eMp7p#Prt;?iGp8WrRe2)nNztDLH?nhtpS~fxQaE7 zZFBOsEY7hYW($10_rcQl@31A@myMDY(swH`510Ah5O(|-m}|eqv6}*4T_(f|U{hom zf8_0}NpRtLq9j`2h~i;b&SbM`KHBGo2V@=97Vz7H8WO~9czU1>fxj@6vdb%us*I9{ zLni}vk8fAsDMT(p7J@Y2ovG3u9N+n7l?dRw-?E&?`j=l4y~w{p0D({MyDQ`*YEBd$ zkoB<3PQ7}ug?^mL+^$R|ckEw@Rp~^WwK2!HY{sQcgUss^skGj|KJIcO7*#Pfd(j+d zq$sSAprzmv3lqjo>}WP0420T-JEz0J3H(ICrYnsJJOc(6tiJp5U{ODD8)QpBkQRojy1fs|TRhWDD4Af7_D2SN@Ho!s z{1e~l+`mwY@V(aUZgB1KqO$#1IpU2%(+3YzLWhzjSp=c zADjH0tU?l_Q;$y;TSj#4Wp1A4j>G*1D%B5jFAW!B(4J2z3rv5S2Vr{Y>PlX2;Z`n) z_T;K~QkB&m`K5QKDuVyZx9<|FBW8ErHouX>VoupZLh#RQXH;oPRA z)#ABh!yd*UJUqzw(7hx|+9`f0znST^IaAfU?)2eV@=&vN;nkGhaj6sMs14o{g@Ys!9Z+!lL&EjX8*J|_7vraU z=J1Z)R=+>(r*}fj)BpnBq*0XZUH>!wbZ*qnBA+|qadFc@!3$v8L0JK%`{m@PcPp8D zaVHJFD0mV$TOOhD6RS_bo%e$=`S~kTOA!J()mTzBq;r1Mq`~71R5dsfTCswpKHJ&j z$H&IQX6j-O~h|L3~|LDH1_L4ogQUr+Cz80tUhg`QJI;1@?2QUc(jy~6(& z-&@LmZIEJsdj4!^jK7T~TJhd=&^G?O!b7Ir8uLU{w$4kfp6@xP{t`VsxR`w>77T{j&oHA$tkZ;dMDqx#u;Yn7{vI z0ssjjWe|xhYN1FnA6XPrruQZG)6uhZ@80sv8>7#+^=^Xzc0s|tiL@VmCkTGtsPq#f zlLbYkeP}8_=>j+uJ3ELZN*0vs_cS;D?s=mYL*(*++-PB$_t< zaX7(lOxO%0a$h?|v$P(TJc*M)5mKsGF=4|aSr?p^V3jgz+Fk+Qr?Kvr)k-L{s+-|7 z`sN-f-wjnL-w$T5Fkq=j93xmQ*pFyd$|IFm*okJ6;wYpKD*MKy4hiw}K?nW*M|d7o(*dTG*V?jN7@f9Fr_Y@>c;(Pt6%t%Vt2gBa$*HriPK@^Now>G*7~IIQ9BYDsmjg3mlDl{X zk9F(q$uKaQQj38;hqe`-U@R-SR98C%^2W?vr*ApC8Q-nF)gbEbzSK=GUhl zR(aG~VQlJ0|#@B zVu+ZCeFn>%JcTJGfACCbvR0G}itSF56flG&dHZW0I;P@rC|qw!cscee=!pxkII|&;Ncw z_e}NhK}U3-AIJdRgRut9-YRSykPiaK!e*@dhKt-v3_o!LZz?AdXfN^Zcvfqr8z|<@ zdoY-8(o7GaEy(-y6%1+=E=IeQ?C1b5l1Uj#S9f>!e!1gCQ>K6<4pK@VqCfX5dt*-& zCpqA)I_M2VF_>Qxt@UwNz*MoeT2VOFrGeDwBGspTEet--!WW4jwF`JRw3^Z-VlBZWB ztzzQMT=9UjE`xzo6V>wy`$ccX?A=VRRG4Gs93jq*7Dj0j9Cufb98aB$Wg&fk%Fw_>gKx~+*{%Y+aua>$?;NTI zU#x#Qx0{aVH$=E!Rl2Lg(h{>2mb_wrU8i0K-%e*#ko^#& zk*2jBvXanh)Bg|A#W!2ygB2XBQ(VS^3l??NI2zT+6tI{->O6dp@BPNRq$wPj zU}orc%MVOSuhx6#g4DQTTZud@%p-hj6mdcRdT+#zUgLfAOAg&)Y!7`1yVvj>G zYPZ!mc1>_ixcz2v4HNHsG^`MAhk-&xVyQ#&&ZLeSeJ>qOsiI`*{3pOBNb$T2MHw+xkU7l|90v0YjP2T}!v&@%OpNnkRD1ya)SDofBm?^1U3CpoQ1QQ=p`FN$;ltjarmEIfUbRE8M5j@r z&)*KkEOjxU6Vpd~glt!=32N8j_C)boWFQ?!E(L{#vY4xap~!y+sb^u@)@Wg&PGTQ2 z5~0eMy6B!%kO4dM6|)zx4lDX#I}-cBxs`%Ldl%7Kp0wk61k2A9iZ1HFFDZvt>Jq>8 zCebRqfP~xR=nj7y((+;*cD^0`v&4vpUsSWex}J&}nmWkG>?I=*^T6$(TP%MQ@bU{d z<5<;YIX672j&LBiZ$8Pbd%#ETjBODQUPPzp&~Vml?d)5dzc~2y-|qsKxo%?LhKPnd z(Vd0jsNjpa%hV>&1CLw^{B}g)jDp0~eJgdULQ<;+J@B(&3#56rvBA{%qX2sZDDSO!l`o#EIfg%vDcC{@Vw zEhX-9H7*S7#(7q-QR!2Bn))}olmjJ?xu;a>T-84jvsbs;lmZePayZsDKfgY{6xwl4 z<5=$)uZ^F3YZX+Z4SQkpaCa)kA2=@{GWm01!aBjbRN|ODkXr7?-x41H5$looIU>8! zf&-@m-={lsV*?1Mjb1c&`AalgwE*yal-YUqAc>MR@Z>Pb)e2w&HC_ec4ai2+gNkSP z^Z!1T)W`SK$b~Jh;mhdZu2%!glX6qtNyaIrp^zXw`-uV8J&VzA+9LW|on>us1qp}x zZTHyT#JyD2q+P1lmDcF=ifqZvY*B-=n=-s)o619pxlXp}zs|G#6QfD5E8QE)e06V;`U4l8M~|Jcv$BhUNg#h% z@@;Pri|#q9ZherFDzQ2l!v__`qF)jG?6^1AVfGB7$2NnO>JAEcL|jSitz$LcMk^Vl z+`qkSZ_9Cc`_<+{CG3`SHT~zt!@Y)kRK!E*DDxgEzwU|bc0$2JQsL&W_~8kaItZHmsiubXf`}qcM-4(|%+YKHsAm@INL zvj7T%WA?lpeNc%&m*o!^G0Hgg4>^-92f*!D5YgB1YjF0*lQ_07)bCm66|hhec#kd6 z_>TwqJWepcz(Q!6GP0Cn0-P|cD~RZZMY1l@#id;=M|RDVz3$74j`j=T2rD7?YO0T< z8bl+UtD@oCG7y&h=^$o~*|zifMG3)Om<`H&{q%o_{ps$|?!A7O5zjc{cD(U0Jwc

nvhy1LMiFRa%e2drGhKdr6)*U2qVNQ$}#+7g7-UO7lNLe1hu{sV1K4{M%;mFU3=6Y=P!_6G7dU z4g*F0Vuebpqdwv41E*Iq#4OX91D_X?|9b5HnsR4Q-TeqEI%FZ5YeAoHpK9G>Nr4TI z%hoK5+D_LbH?cC0x~#GI?<@0;E~IXmQ$0=*_0{4+7S;k@JeoyW1Pw*;hp64mAJxcq zy;}qd!`01oX>UWh-<9jQrvO7|6p?@hH}#z=k0aUf@xs2?WEOERrre@4#EOIE(h30_ zW%{B7xoUX%4DV&tVNNoKxzVw`?&G<;qFcXEms8;#kW)>bd3B`4j{Au5g1 z@kVJis3K37WGaTk>S#Xa-*{f?@$tI7DE&z;)n4s;xGWY;TKQ3za%nD&s0A5SH9XP?;43}FuMMEr=S?AI6cYueD@AGAXso$T@F<4BWq)u7*W5%c85 zc2Zu>s`arB&qroO*Wkae*QzK^j6}1j6L2As3+MRu!(26rLq`!|Yx&-nS*F+$3>zer zsvNTC>W9^Wos|+8a`Uk#>ZbXJ=xbT63h9?sUse~Fysiw*=U%eP-2(dd-o`c&@`blf zesHsh-@TS}?wfjL78ti@zL=FFg|&6)`yy&mY%+|PFM@h7I5tvt|2Z0*pNA9Kj7;`0 z&nvC8FtiaqpM(FtPAj{3=3{^DIVZleY3C`T8rM>jW%vO5>`yDobXH7c3MV}5J*NRO zU5aSCZw2&+%@t}v&f{4}YD)8S(Ff8Ol1)^9DR&caL@T#4{l`DpdtMivc&}gL)Zpo; zEs*PJ$tnC7;lZ5?jGy+yb&B0FIGfOZHfn3@?|Jy_pil`nRB#=$ON&NISh#z2x$*u<3op*uyDKQXf? zrygC|p@prHW-y;XYgD~7`vsOml07#YtH3p%&v^XZC#2%AKp5Z1V=X;EuNF$(yHt+N za89gHKX0a`GjJhfXTC(t`iwb14W(ZPUt7Z^8Y0Tn4DzCB`Z zte=r{x5O2q8#<`!!LZm2NThV`ziA>`^K)n}FQ=}V;=Sp@30W*ekciI8f${0Z22%%l zHNVEo&TRA$vjk3|tUGmKUViaHVm8qJTha+~P9sXdVu%eu*YMchDgh=^{18*_3`r)S zh6LzX+@f9pe$1f4B<~1^68SSRAdwYShx1|;&;VdE`3&tyeR%F7JI|C=-=1{Gc}}1H zP-DlM%a?Of@IjE+Ps!HG%K4|Q-6Cr_R06J>Qls`99&Zc+5r+NC0=U!A^J)dNun_6( zb!eKxAxUFD+eY|5Ve*@@O20ke{#0^2hTFqPABWg(J#?pDB{F2{63;?E#nxOypl10g zDoO$nG4MI;XGv4EG16k>A%vAq{I%*Sty*YrI1|~|sK`E1MiJk53Yl;9$#8I1^E+6@ zIkW~Jn&i{bY#GTXXndlTfj5W6Pk@tFB%o0FeEEtm8jhcoA+Fe8BM+mn3WJrMahYn_ z$2kUZ7FmbmrfGD?`OWd|*)>Rs>zn$tAWT!-o+81j+KT%)@4j#O=MW<4O4VdeI9o<^ z86fYpv}dM0Jyuq=7VItaH+Z<=a+H1cnBT~%mXC+T{7l{3j_xDPH(8ODr`k-kZc<7{j7?H{=n!{k^s!IjBp2z7~1lA021<*7q zQHce1VKD!Ib**J?&0+Z5OzNnec^bzq-^7A5^ncnCqRWzl*uTHbO>iYZi)Th5NS{gQ zqLC+!#}?(>rlFG*6}89{%agN`OD_qI+O62^zgzs)V8R#jX_J$)ngI|wI8}|JaRiDQ z?3O)yZR}y~L&_#;diB9B>fXc|3M(J82ZV2JTkTjvCVEO}2OTOP0j=&>W@+W}0up$V zMF(|m^mzJ!_}>27OH9R_&aVG_u7JUJ&b3oo9z|i>eU62L^LudAMDtL3{%gn6#1=aZ zz|>9bPjbL6mipaKdX0mp&!87~l`62A18QW7*@?yWaL&-nrGXJiM84GD z7Lva$jZ#O2G@QKplU>q?C4^$iP^gC#RHQOG!w?C30&fzBV16no0 zpN|eO%&MPakAM3k?!SFedFjHkfCF=@!pix=l;zuY!VP~*PivxFG|jqY%kV=<9q1am zsau1J-{Hd>5L2X9xMXpMEf4XHw3G__5Aw>^tKmYbUbI|7x{cKi*H+FWM)Cl`F$cEA zo86S?Sx7@OmIS=Ty5sKa66N0`rmwlCx#~Ii61D*^A54ZkS2E?%|2o12Bk7ljV2vKV zIm<}yd&pmUQ(JMjbgWmMPLj0UKXDp(*Y{e_^Zp{>y2luyXei~<7tx-}T&b^qA%MPi3`Eey`_=0tp7QoqSCTb#jH zo}s0E1ikr0XD5W+{?*7Kal{%JQ=#Bh{N48R!@fJ~v{|7WEOL&DwQ^<|x2A31s> z-5~4Uvh-bbWm4S=d#92BR!c`-YjtUmu#L~bF@ftIkiJlPm~OI#fLTc6%KAz!-z-Q5 zOH}1@@50A0l9j-NDcJ{r%nTjgLk%2;L=&IsDOw^k1l>z^@OOrLlQh)#yHODLd&V~_YO zFAFw2Qp0!}8tTyxbu1PL^DW)%Chsj0^Y~zsh@p9|;NusdY?`WqjaaC=1zuRBiiYN7 zfU`~mA^H4eNI*+b9mM2e`JdQ@a zVNydrqSA4KoX?G5Eh@x$$kPn?Wfu1$Nl_y{QmgmmvMg;J`LZqqfhNBb$inQdehPY} zX2xPeYFGt_%Atz}^J(j&Ovb|2vQp7w6PXT45w0UuxNG7a_H>%Az(U{qOoty?hK1vf zE=qLM5ts%F>}zeYJ~8Nv^$7Be{H4KAJ-*!1Wt{o-p>_%MR)MdvW@_J=b=36`bgM<+Ear zg`sN>l-(EDsLDg>dqTM{KqTwjn7Sy;zEVWFXgFe*9ysen6772#Q4Prx!-uLCqLw``^=InbZW+O@mnN}rAs51cLQ3AQIE zi)VCTjF}f8*XDlrA>E^US%%0P3|O}@ky`HTRrD*S{ZyUX=wpoS)*4Hn=I0SjW}G)M z49x7C3+nR4A%*8Sc_{24Yx*y>kKL&* z4OkZxov=uxS@OQLB|&mi?_truRO>dh>`yp42HS%Zw?1;7iYt#Ts0-V7O+;AV5ah9y zhJD<24lP%wNrgjuqve$zb;8t@gz@UW({1>kTo~IJv)M#_C5Qt}Chg9aC^KvrF=GhJ zZEI`l=#S`#8>8&!z1xUsdI-gY z7$E!6)um3*e;o1CwnLxfjOMma`xb7fUqO2^$2G-WNqFazG23MQ5Pd~i+kFz{5dK0f z2NdQtZA4UMa%Fsw{gN+>$CH`IS%S*}$ES`9V5>bTf+IPLn<_%!IG#wNmTo!Qss&%)9Qt(T`1tMp^wx&JT{@3)rO?li`y}DDZ)NBFBpdSUi zO-;}9jg;Z^`NDu}-COv<2?!f6SdqAok?U=IXZr>hQ zVq(iSz!YM)HjpYXMd;cu7o78ccoKUj*!6=DzWrr@AI3@qGMJoM(eii-{@{1ltvXAF z?aV{d=9h4&0*?ox2+3DJ3)cL0x>Y+u&49dUGzA9eXU75SX!r-euc*XSe+Oe`$o?De z(m(!5XjQ2Y@$}y3DL;ew00T_|u1tS|6RW=pCz4UWObnqcW1)lz{bogl9RsF(n$ff6 zl4C*sn#aQ-fBzS2AmLIQ+)5B{niA(=^s-zo`-1+{z~{^?zQ*B<+zbmt;u_cnQk~Df z#oF^g>%05l`>RH-&5}n#@h}bvrYxrPTlInWfq#ls-}anyI&iGf(w~s>lSMVQZ=aM_ zHmUfvUO=3(pCLZ#G~DrSWnB!G7>#V2ZSs=$nYGW~UW6eDf6z@+9MVW_^%}VB|aB=dAB$<(36HgXj!aXB#ga zJQ?5m^8b@6(?h65Do8wAevzYpLSWD@HYgwYt`*J!+d>tQP~p*cN7ru()1xLSGAQ7* zw|YJE3W}X{t9)~rPxX*ueE(bZc@fq3LG0;ot1s4E@r-i4o4xyR%D;@#>fg|q>o4C` zO;s3{B%J|QICUX_Hr~#ER3(;eoX=+an7trX>L6R_x_zd~vE_-sJ}8ht9OYF*%JLZ_ zrLoosVpaT1xx!UWnl;28Z9YaU+_g(Vn@XmA{-h`YnLhmtVgLL>>ne1RYkE9mn$PKa`OXXhuhTHC~cb=Zo{PdD?iOllaIw;P8w_)W= z=zXh;K3}wHyVnHE^xr_=EooEb%D`z1QYR=k76jQmaXzq~@YoCg_Ul0kftjMs`0TX7 zt|);;AqiI&(ZK7VaCTfJSv&DCH~W??r7(`{a~HOqTA>n__tG|eS#ifhTeefG*`u3( zc$D=%i$s*{z=ek}r!J3q(;JOM(aX&(;%Whg73!#Z>|zn4+=0_^IN_)#*-oMIz3;u2 zY_WlZz?Vh#1fx__xd=gQ(~&L*8esVGqr6T;u|eHGF|zK)?|+qt>U>`bK5ld!(Y;YS&e8i8$gkHbiS%c3Sum1FDnRb}(7 zn|VIKF)pI!R_@N(Hu0B+K1#M{=z#E!kc^lYN>V<(qOq5xWwCtkRQ5n<@h#hP+T&H* z%Nb`cilOKOeD57El``4hyWyLs5D_D2^FX5Sv!yjUI-PjLvhvV(SZ<};JdB`JD z?76`}1teof#)4&YggibhkLE^NSPY1@uhn&%d6b3mBu|2+g`5M@&5S1OV+rim!(%8~ z5B4paO5_XeWb5)Q=5$_=cp8CgB;|r5r;z+-i?3DNLfQ}IRy)1cw}IHGYKARBS1?9p zy0e~JNXQm7JtWxl9dan~+NZo(ya0}QfK4#_;~6&2ot2E>2av{yC%3}c6B>hGK7OYX zyxfIHo7)! zpdG{YwT486Y?98i-H95}<-lM`#oqS1SX} z3x4qr7b|qN9pqG53G`YN{-P+__<;+zfWjlMQN8@`_wPCP9gY1?W(KKn;>>(0UvBV8 z|8?x5gJ)3cUn12uErfpzszNw|y%z<6LX&bd21FRN`ON8&^#tEZmDOgHo`c_g&&eea zUofSo{jH=~l^9;t$|thAmeW+q_ zoxZx7FZ00jgtGZ~r8sZBlQ?ah$ZXpIL%N;T5W)|}O?=IG4yzZf$~4K5 zIm~x`g-@5polm!R6)jIc-?Y~kz~MG|R7?OI&J_zF8{Vi+19G(2LL6LTL3GLxRO@et z8(WT1KZYY?32fJa_FU~YwcGzTpEFOSPO?icFQHEGJBkR8ak$&lTJVY8cF6$)@}i5a zwYzui?|}=~TbG5DQNPruLQ8zASRDoHwcMrQGPylupM_CboCL`Ss4q8E)965++}t3g zKu=P3nF>GACRbna{M}f&P8vtta&V!Zphly>D2{z}sX#%aBv)t}2jNg`0M$#~$|Xhb z4`+fccg%g?5gOMVe4h;~KX0y_KBP=#D2_=cl$O5$)aUOe(1J2=OSP1*uP}aCPF1rX zPM)NsRdilO1F=saN0sAF^jdwA(N*o=dX&Q3wdaeCKTsTt!^`;QdG+vXu83~2LYw6gl};D)SZ4M}^JiYR>J&?t`#X!J zq+X`H_xOoJ{~~@u`1Q;YP4JH|fWmv$+>d@9%Q#z$+znt{MLe7Dl$0`qD#c n!0pJC#X)i#^rr?RK2vc8p|WWr#BBo{1f(FNB3&VA68OIWy&hn> diff --git a/recipes/icons/7x7.png b/recipes/icons/7x7.png index 9d8bbd02cf06e1125d7b20769e813894b86a5295..de3b51b3568ae782cb4e90ee4bd6139105c917cf 100644 GIT binary patch delta 111 zcmdnZ-Ojzinwjf4o3MzUf};I8=FP6muNlLQbq&mP4J|{A46IB{tPISw4GgRd3{GaM u2Qx4*sFt`!l%yncVb`$3Wd3`g1_n=8KbLh*2~7ZOHyx}1 delta 164 zcmZqY-p#$inwjevyBNEe>_#uo=bK%bUo+Ml=^B{p8X1Kcm|GbdSs7Sp8yHv_7;Nag z7SF)IpjzS@QIe8al4_NkpOTqY$zWt)gk8hID|wHB8Z_V4a{^6Ekle9tV~R-49v9+46F1ov delta 144 zcmeBWJ;=JjgOTeQyA+2od%@K`v6F)spGX<$8kp-EnS~fwSQ%PanObTa7+4t??5e%> zWpV(Mcoan1AjHtv%FxWp#26wyCB;gLfq_Az#5JNMC9x#csw6WvwYVfPw}8ROzzEq$ ZODh9&h@NbX*a<*A44$rjF6*2UngE6!CrJPR diff --git a/recipes/icons/habr_ru.png b/recipes/icons/habr_ru.png index ad1f85a4e8dfba88a926ffe84dc10831031bef81..147223c629f447de448feae7b16eceec62440bad 100644 GIT binary patch delta 92 zcmX@e+RM7ZgOTewo3MzU@zTckcP9riK9MrkH89gPvbP0l+XkK&WRYI delta 144 zcmeBWJ;=JjgOTeQyA+2od%@K`v6F)spGX<$8kp-EnS~fwSQ%PanObTa7+4t??5e%> zWpV(Mcoan1AjHtv%FxWp#26wyCB;gLfq_Az#5JNMC9x#csw6WvwYVfPw}8ROzzEq$ ZODh9&h@NbX*a<*A44$rjF6*2UngE6!CrJPR diff --git a/recipes/icons/id_pixel.png b/recipes/icons/id_pixel.png index e2de47e6663b500ba62cd6f5fad6f7acb43f037e..d2e560b37b5579ee6d85203258c3e4ffe446f3d0 100644 GIT binary patch delta 108 zcmey#bd+hr6t3rN!XkQN?^>;AO`I1HZmeryrfX;!Vq{=tVq#@ru5DmoWnhpmr}LbF rfkCyzHKHUXu_V<8c~vxSdwa$o1c=IR>@#wU}T_cV4-VZ9Aao_Wnf}uU<%RT_UM2(P=f|sLrG?C jYH>+oZUIOSswKuI5ItGqw<>smnixD?{an^LB{Ts5ZTBo4 diff --git a/recipes/icons/kleinezeitung.png b/recipes/icons/kleinezeitung.png index 57c02bd4f77751ea5f782c4d8a06f7c5bd9b3be7..bd1be9497abe32fd9b44b45342f8b0fa2826d2bc 100644 GIT binary patch delta 1930 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 ju_Vhx&Y6_M z6*t~(xq0xvjJ<5box?pmc|F#WcgvgJJT*ytaL;qzP;ihSw%*y7s{uL7-BEpd$~ zNl7e8wMs5Z1yT$~21Z7@2Ijg(CLsm}R>o#lMn>8|hJk_R&L7z*8glbfGSe!78VpT! z4GqB>%&ZJ8t&EHz8f2z9-U4dSfZI@#nVVW%l9*e-V5EU=i?Ja@)2GeV+(1nXp00i_ I>zopr0EIkfy#N3J diff --git a/recipes/icons/nature.png b/recipes/icons/nature.png index 2c1b06e715db0116337978cf2d1dda62c047e7c8..0ce0e3d71c74872171e9df0c05f724648cb80192 100644 GIT binary patch literal 735 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKpuOE zr>`sfb2ecSUCX@6P@o9ob59q?5Rc<)r|!=dPLw#l|C!#(Mc(GQ?tvKsjte~lmj|)` zXLX!=MV0lbm4!w}cR_QvlXc{s+Xq*P%F09)YHR4lO)qX+J4+*MO`F-)HM7*7WG|>(mF0N*miWre>vMHiXsws8 z^;!5T_R6{7@D&^5zI0u?`o`OR#mdsqO)oBYu}!+IeyQa9{r~gtOke1Pg})11F2_9PT6BU{?o*SioPp=TsT`phoIaV`d8mk>jSg z)a$3j{4+n9-Fap|S)cgb*z0DdTH&1hXL}B=NSuDt_)VmnvE^HxbL!XcKAe)ceNtV` z@rUcm{CKBKzN4t1kT9+Au%4m2!vPudN$I70QyP_hncbrJoK4#|&O4lRGI8tE4Ka7W z%BjA#eYN-Af5v0Cj;C)e7M==>bJY^ph?11Vl2ohYqEsNoU}RuqtZQJVYiJo_WME}t sVr5{iZD3$!V304T^BhG(ZhlH;S|x4`a?Vqtff^V*UHx3vIVCg!03~cK8UO$Q literal 832 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0V2lay32_B-X#^M<8JU=vn3tIk~yHd3bmP1qCG}BqSvzrKP2nm6cUhRn^qgw6wHzbaeFe z^b8FRjf{*;O-;?s&8@7gtgWqWY;0UyTwGmUJv}|6qM~ABW8>oD;^X5}Q&ZE@($dq@ zb8~YG3JMAf3yX`3OG-*ADk^GfYHDk18yXs#nwr|%+q=5D`uh4NPo6ww%9MHY=FOi! zf8oM~8#iv;v}x1k&6~Gu*|K%()@|FiZQs6q=gytGcJ11?Z{Lw4M~)pkcJ}Pq3l}b2 zym;~Q<;yp2+<5Zj$%_{+UcGwt`t|F#Z{NOq_wN1s_a8od`1tYT_wV2T{{8#!-@pI= z|JST6l>r8>Y)Oz`Far}aiEak-ak@1)J^YY^fK8R>iiNEOUcyK39k_Qf;r*hdqfN0>{{NpRT_n!=Y0poSnR8?G zHt*&LoS`$vT)}IS#D^j=;jGq+@AzB|9ZjBWlm1$2EPv3-rJ}g_?z8@fVn_aLyT2>= zK(2zt*6+8k9dtZr@cT~qdYKe+ZGvsE?KYFrr?c>E~W0q@uWHYch#MS%!`uWvg z@4lP6-ReQ6pJ|J%K*MUs$G^Q2^M9|tcc6Cus{+Z1rYkO; zeNU#ZZK#{>|L};^oKJfljGj(tk1-5oJM77HV@6T0s#3MUS%ttwZ4aNTH8&jj&#N@) zpSed4^Jn2R-k0wC%xkQf#bWk}DaZASkTix$~Q&NR>Nh8QU9&Y_Uvpxp2O9d`!_{w<>SC-u7irO9-9z)X9Fs%0)Wj;>NcQ z{E@W0V^s8JVQRl^b>WTG$K&EQ{c8fEyubAfQ|%|+lH2<6C@3Fzy85}Sb4q9e0AG1o Ad;kCd diff --git a/recipes/icons/newz_dk.png b/recipes/icons/newz_dk.png index 5e5dded4bd8a74c0f2c719ffa10a8ba38066a138..c5f8a9c5f207c8dba5ef936b70adf849b3972d68 100644 GIT binary patch delta 1834 zcmV+_2i5q858MurB!2{FK}|sb0I`n?{9y$E001CkNK#Dz0D2|>0Dy!50Qvv`0D$NK z0Cg|`0P0`>06Lfe02gqax=}m;00DzgOjJe1z^}!?u*t->!@jP=zplf-uf)Hu#=)@1 z!?eP^t;oZ&%EhzC!m+@+tGKhHwy~kWysO5-v%$Qqzq+cDj(>uUiGGfXfP{f|s->2_ zxTlqogouTDcXV!SYGj9lc%7Mxz`Corv!rfpXnc8gf`5EiR#VQ%x2&g`aBgabgMWyH zfr*BKUtL;(essdTti->uzPYKlvZHr&Z;6G1jERL}UtOi4l!}LghlGHOh=gfpW5d3$ zx3Z#XW?pr2ZhvE7U73}Kzq+Y_eR*?nZFzTdVqjUlxTw6hrnIl0sHBv;wx+YLpj}#5 zXJufzwWYJJp0BE#xwNFQtekdqajT}6gn@prtDLN;nY_2Dwy~lqC?pRL4lOJwW@KBj zteqYm8UO$QURzVJtDCZ|ovx{xt*Dx{u%NoMq_wc3vVX0gTv}48q?KD)QK+Mor=pam zp_8tuoTHzRRZ>cRdT*tmlBJ-LqMnbcrkJ9gk5GxcP>8%Vh|x5O(ldzBABf!`h~5AH z|FXJENdN!<0b)x>M1WArZfY+}0=S5C%O%x$$MmAUnO-#atbSJPN2E=8*(t)5ii zyVy#)_~zJH#5Zk44POKl!6s(u(m2V@7ukFZ@XD@2^$3h*r@g3A=@-O z+y|3q7_9BFLmxXhl|^ew#6hn6Sdzj`5?Am^VwlA{V=aq_ipSs^RvGpTd&igY&cehM zAD0cTjCYFd9|pgt$6#Ty6;@UIkQ&o4MtX}YMv@KbbRQ2lhZncO*az|&Ebttt$A$Z*FmI;D&hp@qF3VW~(07I=!!zNL0gGOMY ztB$HoC#18EYPsovic!?7j z{7%5=E4o0=Um;%uOQU&7yr7rSzXo*tz&xs!4#nQ=pv_$x`c|8w^_4&TLz1~HS)x!l$^Gcnji@yP+G%tMTd4tn`Z-#6(CKcbE0007S zNklHW&8mU z1cv!+9!?O1*Z0Uz2FS-gA2vE6CV?V|;MDZYh*=sYn9v*>4u{$K1;8kZpe7*_p`wei z5%I+Q6E2yIFQs^XDfTo?J&Qyl&p{A^%PTKZCaW)JUMV=APbhTlHo_Ju1^I?l7B3wKiV;A38EO4lUo+?^~Sy^ip<=$9rGbZJb-31S~)zj zIVP6_&8Gj86OX0~fdgo@_N31zHiylYN%4ylPv)YnWstv~Dz0!GkWH#cm>10gh8>B!8T%9LI@2kWk^y zp06yW#N8W@xuV z>v8!DnG76vB&miB0000bbVXQnWMOn=I%9HWVRU5xGB7eTEig7MF*#H+Fgi3eIxsgY zFfckWFmE0#=KufzC3HntbYx+4WjbwdWNBu305UK#Gc7PSEfq02R5CC+G&DLeH!CnO YIxsLELzG*QAtwp|07*qoM6N<$f_tlHZvX%Q literal 1927 zcmaJ>X;c$e6i)GIMX|WF($b1!P>VGrlK=ryqXJ0;OK402B8rk_2mvw^laYjolqIZI zM2jL6WD83OgoG_DQ8p1Rbpe-Ft$IAR(AFxb$1bSHPE>4vl+HOb@7;I4@4NTi`<=<* zglt+c&v_mWhg%R7$lzitV0-7z!oCb$?rCiK0A+@wJR}-b2^0{{PmBm5AV?;VKwL;5 zPTYMKT8G2U!b|z#XgE8VCPHLHfek}c%j6gvhg-KnEf`HJxy)&uL*G)zDSp#nfHi-(moH68y_mxk?a$0R)P z5`xCj@vn*sXLEo6L;(R_M9^IXQbB-9CXzioy}i8&00kt2B#=TPQ{2hkG!F_51cBEN z9?PZ>N7J|r*6UnYM90UVsGLS3sZ=VW%7cg~BqXx0udhvmLUG3s?#e_M6{y`|rR%f; z15%0Q#t-v1O<@)P9{i3=%_Jqi&vxM){fDmg7c0SQnR88%D%*gx}vJG zd~IrirMWUbR%~u4S)P5f#aQ0kWjJz3>$1$5lWjfOsh7jCJ0iF$6mF<3N=-(a>c6h9 zIaXa!&~I(%>8cOqZf-Fi_sky2&d^9DqMC}Y2766?)`sodw&dle)K?$vYSm??f1%eM zX)~4ARTVVW6_*z6??2I4rzkVb7goK&z<1*WlqL7gDutNhxaI8I6lUxFFD=cVzuZKRZ2(4cXBe) zL{X7BnQ5LmqXQ?6*%_$=J;sifsxt%4xmoEaI%~x>0=t!0@YFlk(fpWL3x7%Q8u4*$?_F4_QA|r+dTALe7PxY9SHHZX?NVs!fKk)4oU=IN1~}-p2FC-lgF*&dw+MBlkTU8T55=x&~=s^m?(G4oeJ6#Iv9Vs zp}y|gkBJmRU0B%dw}#ei9vr*oI|Qx0O^e{|<>}qV>@9Dv$=eS%5{?CrrYq^5oBm8% zFH|O$>$p2&9n<>wBB$V~!E+bHK0oXtd^U==9?-eWN$>k3{qTa*uxsJe2y^Msdl#QvS}oXBtPXW4JE%(^4q=T0g_pXu z9%0U*_qdUYFJm8`l@e$Fl||y5d>*RR*_pdJOU$XSM>xEA; z1yetyF1x#8*^Mm!ibdfYTCZI-7H9a6Tb4L(rJUOr(-y7YtD@e^IO_Mnuw-J)xpD2n zG15}jyto;qAK{C)!t*Wq~Kq4sQ;!C zpXI`kz)4Nk&Jj_sW}QbsQ(wM-{yR(fb7|_G>kG}9n#&g_$CuZ+tPBkD<#e7; a=H(Js!!9l7JSCa|2s~Z=T-G@yGywpQRu`K9 delta 143 zcmX>l@=s(!1=llnF?MNF!@&6g8=DF_rHph9%yo^7LJZ8U42`S|EVK;_tPBh`bY6>} z%*!PnggTe~DWM4fnnWoF diff --git a/recipes/icons/novaya_gazeta_europe_en.png b/recipes/icons/novaya_gazeta_europe_en.png index 946319432b5247cee7c4f2fc9f2c49f33eb0e56d..a9f3cf8d04f8dda0d7d187a0209d266b9a693705 100644 GIT binary patch delta 91 zcmew-a!O=E1=n*nVG%tQwV*7ijZKA|QpUOlX1a!!Aw~vPCMH$}=Gq1ZRt5(7ayri^ Z^Kyx+VV9P3o)XOf1fH&bF6*2UngCnM7V7{2 delta 143 zcmX>l@=s(!1=llnF?MNF!@&6g8=DF_rHph9%yo^7LJZ8U42`S|EVK;_tPBh`bY6>} z%*!PnggTe~DWM4fnnWoF diff --git a/recipes/icons/old_games.png b/recipes/icons/old_games.png index 6fd3fe9e9b6eafe32fb15945920bd2a283e9c49e..f176d5bcbb2fdf9f59376488ca719e53447b528f 100644 GIT binary patch delta 241 zcmdnbJb`(F1SbnK0|Ud`yN`kTchokZP^_nmV+hCfrip&%B;4P0){69gtG zKH``r98@A2l)`LuqmfCePr+5rscW8|f@|C*CHD0VB8*252J!UNHR%a>yTr&W-1dvl zIhS=eGeb@&=j}s})~%i_%P1~otZQJVYiJo_WME}tVr5{iZD3$!V304T^L(-&qqrJ& VX*uU9(Li@Hc)I$ztaD0e0szA$L<0Z- delta 325 zcmbQhyq|f31SbbG0|SGGE`RVuMSDi$iHRol|NsAIJ*{{hD9o7T?d~ElbrY`+ki%Z$ z>Fdh=j9rXf)W~p~dM!{W)YHW=MB;L6zqeR}0SB{Z@BjOM^H&E<2@os#Oxt8 zrKxZ7#oF5|EqgwHkmgn>Ze9E_gTe~DWM4fZS`wj diff --git a/recipes/icons/piratska_strana.png b/recipes/icons/piratska_strana.png index 692825ca491803927e79f1a2ec848f6f54fd6a61..1a9a8ae83b5799a296f334a1a64a7e00c5a0e161 100644 GIT binary patch delta 815 zcmaFJwwrB&WIYQ51H;YYP4z&ECEd~2k%3`jKlh(RRv=#?*(1o8fuTx`fuW&=f#DZW zsNn?zL#Y7+!>a@a2CEqi4B`cIb_Lo1C76=D-CY>|xA&jf59G0zc>21sKW7sb(UblCfmjr;`tQUTwM*@}i(+d*6!y z#a^9U4Po~B^DFZM9yRQ+O5kPwI<>oLea3{PN2jd6^C+6<=oH;e84IR7pOSFx{qqjDs2Zg$(Q z>PY1g`Ef4bY5(5Ut&OifAB)VMCqFkfar$mWuELr6+r(5;EPd;9m(`xKmh!0(^EBLI zJ<09t%`3fHb`xcoB#M=r9_`$_zRAsK>7o}$1P-LJ9CX!TxWys5v`un`mA<2XlS9q6 z<(FM}S4MMux#_(@-t)qy@bJKJsW4%VG9k`Y85+J>oGkiE57d8VuD^CcW#R*CaV3s< zyR6e$HiqALp2bnqq%L=|UOPiV`H}j)B0C1Vfv%Fem;cjwzCMZQ8r^&2g2dsZ(v9 z-+lPhX~*9=vEi$v-<2FxF7J7CI$wj)yZY#T)(;z&{`4uGF3bM^7!T)B1a{|aK~wr9Vq;QTB9@gKjL7Q@*~ zzA2z|t6Jh3QIe8al4_M)lnSI6j0}v7bq&mP4J|{A46IB{tPISx4GgRd4D#i4o}*~U e%}>cpt7I_Jz@Xt2AgoK0`85sox1fD*9nw*@>!NI}F$yrubcJScALx&DIIyzpwc=6Y-Uw{7m zadL7>NJ!Yab?e{1e|PWRotT)|+}ylx-#!Heh2Y>|9v&WEUS2*vJ{A_1>({T}ym|A< zlP5AVG8ZmfP*YR8bm`LN%a=7ZHFb4$@7%d__wHR26B8yTrsvO}zj*QD)vH&pU%zH% zW_ELP^YHNS_xBG93Ys-*)~Qpc&YU^J%F5c+)y2lfCM+ymQ&W?jot=}DBPuG|(b1uz zqH^rmvE#>&U%7H+-n@CXwzl^6_Wb<(j~_pdjEpQSEG#N2ijIzsjg6I+m3{N(&D*zc zzkmNOCnxv&_wV!P&s$kp_4W15m@z}r)J+x`F2W^2e!&b}0%{sYQN8_h=byd$`t8^6 zzyB`uipv28{aQ~K$B>G+*2$sGs|^I)GIx7NFW6EdwCCNvckk-1-i`gYUq9ugz^(S* zd*=zy@JwqlC^Xbv{d14j@}oNp=1OY1&zTmeC#~E5WXViU8SgCBy|);qxrORyu9P@0 z+;z9CvHSM+sV*ndji-6W@4fky=hjA}%@uZ=xAAFk*gqz#XLviNf1 z&h3kTF4<;3KfGT#E#g$Htmf{=A9p#rnQ?wPV8O<4*>BUoEq1I4`y=etAJ^PvT9AA8 zeVEox0gelQC8X{%-kJ2|ZoD76$by~y$J7}v6c;+Gg!(mH*Rc7+8lbpnLYdT_0QDKZ z4Q-Rfo``)o%gEtoT3;M={0A6RAx{5^W=v!PwZ znXK!f`%c^s?`DkqQN*lcIy1lccKWyT-3LFgFJ5!@!=ku~59b$@J24*C<68eq|K<)p zg=G_Z+xts@mWZl75>x$lC%5dwo5(4fo~Id4-Qp`>v(8-+aL?<#y-Y|KtSu za#P;CRP0~bncUy){PWlC9Tq#Pjyk38sha8(=zVP2G;?X4{Il_KHvjH_-OqaSUO1n{ SorPtfjN<9)=d#Wzp$PyEuzGa> diff --git a/recipes/icons/piratske_noviny.png b/recipes/icons/piratske_noviny.png index d991b2a5a4d104c9c9278d0637f7261f8c7e6025..9037c40e23827aa4671b6817dbe4f3c07fd9fddb 100644 GIT binary patch delta 855 zcmaFD@s@3Zay$rVsn9-dOTemLo80$PQLFQ%qVjFy#2=Qwr^AUndMm= zCmd)ubaARNSkgXGLsC<0$xbbXK<2k(YO3*X`E*e%SYY z>8_=hZ_asl+;(}pcbx7@hIkK}=Oc+-;7!p;v1lkHXJi z8Lk{F7yf+SoL2wIt%EmEFTt>F=Z)kGDq9lNmv302d?q%Lw~wdt!MzL0J7#%gtlheC z56?-57hR{8mYFb>1g~aq02d2G0QNM<%bIsxK0i zMQVO3nm^C?e8R9)XhK7bTJya-6Fe?ft&cm3YDL*{d27SHtJv6ot-q@gzWUgV zS|<@^jSG(LZs|3*BNk|BPr9vS3m%lKxcYSgC&7((OZXJ@ooj%X#z@~%s zpK^S+KTNsD|6#HElb6vy3_su9_v7pqPY(x=YYQ@OEJ@e7{j2Wrb&j`ZBqZ8mzs(MJ zUM@MuT5R^s*CmxPVV7%W{OGtXdp~&xbJ_PTtV&9pElf&jB0zLeJ~Y{Z)w^?YE-<~S zmbgZgq$HN4S|t~y0x1R~10!Qy12bJi%YYCg11l2~D+6^kiclZDR delta 1085 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 D@izW) diff --git a/recipes/icons/sfbg.png b/recipes/icons/sfbg.png index e52d33f3ca39711bdd8b84b4b8667555760fa302..284540bad6da5c44bf73f15bd569720936417637 100644 GIT binary patch delta 1430 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 o1_o9J2KjP2&rviOT3T9TV`FD$ zXK86^YHDh2ZEbIFZ+CZhe}8|1f`W&Khlq%XiHV7dii(Ymjem}gj*yU$larH_l$4c~ zm6w;7nwpxMo13Abp`)Xtsi~>1uCBDSw6?akxVX5wy1Ki&yS%)-y}iA_z`()5!NbGD z#KgqK#l_6b%+1Zs&(F`%(b3e@)YjJ4*x1uz*?d|RE z?(XmJ@A2{R@_+L3^z`)h_V)Mp_xbtx`uh6&`}_X>{{R2~QzQ^T00002bW%=J@bgBS zk0Ss80OCnRK~y-)#na0|f>00!U>MLS(=J{S%d#jXFDWWWrCF91mjC-djSGp@IPP** zv-lQg=AWY|s#uDml341$gUX}h&0ag1Ex>s8lSEWo;(wS2FN*{b5fzbVK^gt1b4?cX z(22UV%>oa@_%$pTV3ifeyaYaS;vRh9x1kF_>jIe7q)nFI0gO+DEVR2X;HN5ub?5>x z#Wb&1_|(O?>wv|8vl!?hS`26&#UenP0rw9vc!JFUEBreX?K?bRz&R$Hd!Z}baAO)1 xObbLr7bx@FEsHvFMsLBuby9o%I2_pZ{nu%0AO*&X>s$Z;002ovPDHLkV1gOi8X5oq diff --git a/recipes/icons/sn_dk.png b/recipes/icons/sn_dk.png index 7840cdeff1dfbf95b82cd159b28317d33da73516..d32b874ca22483432d307dd5e5888aecdd1d81e0 100644 GIT binary patch literal 1311 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 w`!Vr0Dy!50Qvv`0D$NK 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 LNkvXXu0mjf=u^e! literal 2960 zcmcguX;c$g77ohZomO!H5t|Tj0g_aA_VP+3Ny6-7}I6lGJ8(P2|STUnfz5eIB(P;n}NOgqOp(`SB6oqAQTUVZO=_ulW; zJ(c3?y&7+7X^O+)@Sc2kKkQD>4P!&>ipIpe!EPo>{st6>`)ams=;5-mEpRx4B56R7 zI!Gws!U`D$5-UUqMI%#UXdG_2i$)2-p@^CwLV~4o9&zAW1(6^X^N8y~Ayue!Lqeqd z7!~3l;~fCUgu)y#(Z!jtT*Ji-$PhI|(8$8%C|AQHj@#v8bKNpPB#cAUp*-RW9U&n| z=u2=@s1O31LMOviDwV+IP(T(75y7B{Ng~jxbUHu<0V;<~b>h-NE(j7P4L$X3|9xpS{J(|+ z=(@;+P{6-yIR5T;9S4hkd>)gqn?kfU57oVBg3-DwYiu8PuSU>yq3Z?2+i_1l zILaEz$2qM=ollB2i3TBTW?y|1{NvrViFMDPF?$PJNJo;5IwRkgDi(9MWjDOfPwY!H zJjt_i-RcBq+Z$32PTM;7g#uU=7k06ztdJ(P?LL-bQl-4K=P?+x&@yGzH~#BA1trFt z&i~1r5%-hnl?G)FaI-SWtfSmFHR-f{^4<#;EAzdqc~+P2AH)s+xx2$;u+G@9E!Tw6 z@bu`uO@8dSv?PNezcgOBUG?G`kt%Tcpe!YMFUyFK0Xly(3~1}3XX&3`ZNNWnS5}lX zvS??_amziSc|>WzS?K|Oy~V<$UFSGyMRS_2Tcif>z-c@;ma9vHx{5Q4k+1Q{sBLYm zXiiv*{%urYZFnnOJpc9ZP}mkm8<1m00H+=IJQUmR`m~@r0Uy{PL`wE6e|x!vR@v>g zzN};9>c>O7djHPJUn*c}jh_33kHjrp^X4n(=dcDKFLM3X(5IVa63^m1JlxaO7|j=c z`>G;5org~gYRxwsBUW-el2ejHJA6pD#vT>SYG5!*ltUio#V)3Qdt}P*{8jJ^Iqhm- z6)K00;FNW^v#qwvWMc~s%!c*S%ln6HBKjSL9?!dfjm;%9(8n?as1&52i$AHhSV9A`3yR}a+=jU*1lnVLSa8*9CuxC9kAKB+4Et< z3fzT`EOJPD`t$9zwO1^nAEb0q6bYN`vb_e)P9g(>5$9M>*2l5b(ahO{u{+Dw(u$i8 zIHo09Q64m_b_-4HAbI3+`T3zb|Ggh>=CXVA^^vS!KC{`(`&Gd-$N(u|ykkx$&NwH^Vr`H3g0_(v1ZZhy1x z*^$n&pR{LQw2Uk$_O9fm+Ya|u#`bdfykm)vLwQ@hljhfa@15LOGe&6CtFX>WS_GW2 zbU6f#Pv*hNlZ+j9$ zL@mW2ya~AHrnh^a_}+|k_27nm5!=(MmO41(k7Nk?h94i!j!3`vcpb`%ul>;vBx>e0 zyvz$FhWGro?ESWq^k^5b%h;87a6yFuXX``%jFTXvYev)c-0I8t(O$X3lD6em@lB`r zz~aHk*P0ueF$80?8~v)n4L#A= qx;dw_zFD!#M~1h~wyX_!FIu@}$3FqX=#fPL diff --git a/recipes/icons/spectator_magazine.png b/recipes/icons/spectator_magazine.png index acb2a2884247d506c8fa51aafb5e9d5a2b81e4f6..24e9988c082a6d2290b7ec4f44830a363617bcbd 100644 GIT binary patch delta 1200 zcmV;h1W)_Y3(pIXUkL&_P)t-s0001ykyeqQ1%Lkj{qpnrWY--eTe6Khvjm9;aO+gKTzLMVCQ*-^0&SI{QUXg;^b;~G#mfBd@c#Ju z`{(KGmY>xcF3|xO+&@po^F80gN*)2cYJWc!N=>7Bb<7af+ zJAX^l5hdI}Q0<a!4Ic`Ui#tW`{?TY z@bdic@X-Yu+dE9|q^$eu?En1z@1?EM2OQD{9NRfc>4}r~)Yty{`~La)>W!Aw87$Tz zG~!`y@wB@A@$>SzzuiSt(+(rj2OZN8C4b&bTl2lb|NZ^^?(gugw&GxI)*>|Qn4$O9 z+1fTo(h45c7c26%y#D+9{r30z=IH5$kkuJ2(FGdz(A4Xao9&yU_}bm|$IIVQU*TG4 z)DtK4!pHT=&e<+N_}1F)qpQ*iAN0h@^~%rH8ZGasu=2XV*Cjal-r@Dj&*g4=?|-MR z;aqCe6e!s+LHgw8<708u6e;t(!qN#I>yMe%9WdctYt$7f)f_MGp{e`p?d_YS+d50? ziNdkdHyha-!UE|7E8lMYm1H!i00001VoOIv0sjIm-jQ7% zf9D1h6fgkJ5U~^h00Ik1L_t(I%VS`G0!Aig7FITP4i0uURu*O^Mg~;C$i&IT%_9c| zJltHIC`uR^dHML|Pyjz4FC!yDUOBfqEU##*V+OI`&fWp6 zKu$~)sDM$N4gLc_u%pu&;bQLr$TVPJ(af1|x) z>|^8N!R!QOLzrQ#3~VrFqE}L~UrK6PI!chUF|fm!8JSsO_F+lcIk|aA3fLKB;cUbF zg2I@jqL|{6(lWT9ERq5_x$=t2D*I&n7^iAOgaURHE7NNKhPNz#Q4w1Z6b0 zwA#1nHNhPT^JaSoOp|=4S5kHtf83j}fa>mnMTJ~bZ(CnKJfL7fJz?S`xJxEa>6;1< zYFK1Vn?3`sV5Z-!+3?7MMfaS(xo`#ZlIE+zqZ^iB<}6sa2ejM5YBppGcUgE4*;WUo4YT!$aRw(1vX4FGc7PSEipM%GB7$cG&(Rh zD=;uRFfeZ(Eaw0K03~!qSaf7zbY(hiZ)9m^c>ppnGBYhOHZ3tZR5CC+G&DLeH!CnO OIxsLELzG*So&_{ksBjAa delta 1195 zcmV;c1XTOa3(^aaUz6Md917nE1`jb4a~Kg8kzHbc(E%6H1sc%>8_@>1YT;dL;$Us! zU~b}JZsTKd<7af_YIfvmcjRq(WY-=iyMf1lAG(4o$Qu>pX`{S?VF?Ro22cYr|zMt?xU;jq^$3y zt?#F<@2RlxueR~Dy7IQX^0&S6xW4kazw)}k^Sr|Iy~6at#`MC+^ux*Y#L4u<%Jjy{ z^~cNg$<6i2&h^U9^~%rn%g^@9(DuyH_R!S#($@FX*Z0=h_}1F^*xdNq-TB_(`QPGy z`QYOE;pF<{=KAF4`sL^P=IHz8==g@aL?fmZV{O<7l@9_NZ@%-@e{PFVr z@$>!i^ZoPm{r30$_xJwz`2P6${`vX-`uhI*`~Lg;{`~y^{Qdv^{r~>{|NsB9Y7-j( z0016zQchC<00IL98XGDrHa9pzZElKxn6ARZ+1lUW>FV?Q{r)W2G_3#t00Cl4M?}3n z)zWzY00It4L_t(I%VS`G0!9{gUO`bQSy?GjL0)ziMg~;C$igikF1;5Dq{RieQIs$; zvhqvrMFEohtc;8Zc}`(ju<)Ms3+60by$!^a73PGgU}WSG1xs&k%T7&62=)wrN@~~% zRB5+_Gg!k(r#wdXRt!7gz-&n-Exjflu&Ci2a4C?qCIbh1h@! z7@7DX!fED{pblJU=LS)*m!FA|fstDR%wBBgz5%LWPqYhEfdn@r1G4~x-KFET9_Gg~ zPZ(2xnSnzL!fe#E=!Gfh_g@QtWr=Yx@Ik|GvXNrIJgD%}gf*}*IsBJ#omn}^1AKtfPw{epZyHDODZ&UJK;eM zi>wKzHE;#Znzpmykp+wHiMsJ{1=Y%-+u+d+OE44l9OlD36s6ySNHDM@*RQLV4^>cW zmAMm<{(vwoC663XrhHB`%RsRz7gqp8F9(|kf?wqdql?Bvvy{8 z$JFHzX{3xpIO`G4y!f&|0OfPls5ZFzPm>u1HcT-!EigDOFg8>%H99moIx#sbFfuwY zFjN;?g#Z8mC3HntbYx+4WjbwdWNBu305UK!H7zhWEig7zF*Q0gIXW>pD=;!TFfb)b J<2jR?1vCP9d^Z39 diff --git a/recipes/icons/tagespost.png b/recipes/icons/tagespost.png index f54d0eca69e70a0d45703e79b049e3ba847861a1..2532a64eaf41a4d9bccf01646a60b0edf8db9468 100644 GIT binary patch delta 3025 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%0DWNOkYZ@M8qbov?cC9H-d7w>Xg-an zmyxjQ4D*t&7|;71wq4Gm^ypsF8!j+&>2aiJLXLMJa)cr;v8%Td&$mO{5Ep6k5*`Vk z6Gg(ULXI-x8_kYmbC|d4nAsnWG?uiQ*VufzlO_38NM1=)lHd_? zks!|y20VjF!aAYrR*fYswz(T~3Sh|>Liw>Dqj5b2_QPVn$1xbg^d>Mf>j@8Al zQu|CB$>mL^RijL38)k$!FiWC_#1fYzoCoc=yL9UI9W1oAB~zQ~7;C9v+Y z#wVOXg$r4nSClEkWD{*CBgG?49a6r?*CYM%MErj=K8axx!*q}6AsuBSCnDUTV36zo z0O5~105UK#Gc7PSEipM%GB7$cG&(RhD=;uR zFfeZ(Eaw0K03~!qSaf7zbY(hiZ)9m^c>ppnGBYhOHZ3tZR5CC+G&DLeH!CnOIxsLE TLzE3$00000NkvXXu0mjfpB~D% delta 3032 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 delta 4018 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 YAK5}0AVIL1hyVZp07*qoM6N<$f;A|tEdT%j diff --git a/recipes/icons/taz.png b/recipes/icons/taz.png index 7c1156bc9a0d831905f8aedbaf2817cc4b9ad24c..a4663a060ee21224df3e6ad0a1e4b6c59d90c79d 100644 GIT binary patch delta 632 zcmew*dz*EF%H%0r*7Xc~1AIbUud!-a};dRsGAD3_ae)j5tUG%5T`@ZZy zdDl4LmXyu^fB&vAsoygRdR0I1?UcnYiras`eDh)6np+AEx0PM~|M_!6$oNfY+suD| z{``La`Z~MzRc7^H&tATtz4H6Ht54%f@0kVv{qX7gnJd2@J^y<6%#(=x>ulQhZNjcH zt6pQ%{PX_P`x(n`N?HEA^YEwN!E+1@42((M?k-Ge+%+i^6~*fr*h@TpUD=z`zr;5F%z8(V|Vq-o(c%h>~%cB&*kn!GGx z>6-b~&%@_#Tvqx#e@E)7dSCVuQ-(F$vX)xqGH+0RduZM&epXYS(`W0L4H7C8PJZK8 zm1QiD`Fda;!yK>lLMBb=(iKMYo&9(1SaYZ3Oh~xvhgn%!Z1I!S%(rii6kt9O`}F?t z1I^*7FVtN#f|&XlKP)-&|GWvqyx6AcQT&Ib7>?D=kF$NCo3|wV?s9?nrew_L`h0wNvc(HQ7VvP zFfuSQ)-^EGHM9&dGO#i+u`)2%HZZUy delta 3776 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 N002ovPDHLkV1g83N16Zt literal 1333 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m^Cs(B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk#8koDQqR!L z*u>ION5ROz&_dt9Lf_C>*U-$$#K6kPNC66zfVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$DAX(!G<*}2 zGxI=#nqXbNzE+-j#U+V($*G<$wn{*A^fEJ3tXxe@O-$X4EGz*Q}aq-dQ%X39dYUfC5YStpv^9+MVV!(DQ-pixe8#9TV-N# zi?gAjfs3iJ5l-`wR zJzHSLco^Ul;>xgvfpH5X^Hye-tt{-@*g3az^6uaj-X$!uOGIL~gw!4>`MvT=`;?UT zDXZ*PQQfbqaX>@wkeo-~q{A3py6`04kjAAf)R{QL9wzu$lU{rUUv@Bjb*mrm?X2YPm! zr;B5VgyhwOufrG#tX{stY3i~WM;Go|T+#Bb`H|2ngTe~ HDWM4fGI;wP diff --git a/recipes/icons/the_clinic_online.png b/recipes/icons/the_clinic_online.png index b21f3b82b5f864783ba7f6604fd7304afe2de240..bcb0ed53c597beff87ad9274192a2642936155bc 100644 GIT binary patch delta 559 zcmeysa*<_%WDpAj1H;YYP4z&ECEd~2k%3`jKlh(RRv=#?*(1o8fuTx`fuW&=f#DZW zsNn?zL#Y7+!>a@a2CEqi4B`cIb_Lo1C76=D-CY>|xA&jfKT%P#Ih(ME zu4P_jC{P8Xi>HfYh{y4(SND1`JBqYD?62T`8Fcf;(hcH!c=H_&nlvqNGZ*M!vEIrd zX2!@WD8g;%!PUFX(Q)Z$_f749ALs)7GwWAAR(F9XfgJz7{-a zR4KhD@LWvevTKJ_Z@~iz$%MO1-(wVy6p199X1dJg@%bR9(~=#0>|A0qQdkwm8a7o{ z3Gk`tUN~*GAu&t;mZbzsU&EmS_Dq2lO_eieIPgZ4FzUrN8vl6v*p*$Y{=h2k4a`$w z8kjb`XY%=QwsocXV}?bZ!ivXMxH0GbP`SGO-qUW)BXUPI4rrV|5EGw1r@`GXb5akJ z%%w#~?>gkq( VXpnQB5)IVA;OXk;vd$@?2>@ME%6z`)26;1l8sl*A82MMY&~ zWR#SY)YaAX^z@93jBIUf9UL4YBO}w&($dq@v$C?Xv$G2d3W|%1%gV~i%gZY&Dypig z>Z_}(Yieqmo10r&TH4y$IyyS~`}-$Mm@skT#HmxKPM$Yv%cI?=(YuB!Q`}XbMzyH920|yTtJap*L>C>mr zo;`c++_{SvFJ8KI>H78SH*em&b?esc+qduEzyIjbqsNaQKdpcI^x3m#&!0bk@#4kH zmoHzxe*Nann|JTtegFRb$B!RBfByXS>(}q!zyJLC^Y`!H|Ns9NoU+{l3<|E2AirQH z=E|DdhG)-TznOi@%m*m?!_&nv#NzbU%aP{Hjsk5Dr`@$y{1$Opd76N=pp)>y4#%F2 zU;qDC*L$+gc&hZi)mF>rt*(C`qB-@{#2GbFD@BrwwA7oMl05zilpHePIc#*G=zU?? zS;oM}&-VUW))*9`{k@0zovm>3y2fAHTdepjjvZN({oox}Sj>jj&Y2ejepxg=+R9wx zb0&mUELYZ|prO5!@y0gk2DuB)@-mV33s=}>-0I?qD)VHUe)z!5t40|&LM7is-x-H#g5Q8wlFYVVlRzxBKqx`E9JzrxjL*EK}H%SN4RxS;=uzan6cmpk(6d>gTe~DWM4f{_HH~ diff --git a/recipes/icons/the_insider.png b/recipes/icons/the_insider.png index 78151d8f54f334104d262a33d2a6c8c77552abef..08fa048687e1d23582712f2837a98fd706fb0e4e 100644 GIT binary patch delta 90 zcmZ3${Dx^lC)aZ}VG%vCcdgd5CQfdWGS)RP(>1gVF*2|+F|jf**ETS)GBC)O(|JBw ZpHW;5yR@A1lxPMZ@O1TaS?83{1OS;y7wrH5 delta 142 zcmaFEw19a+C)YD}F?K2as~X|i6DK!G8R;6B>lzt_7?@ib8d(`wXd4(<85nHnycR!M zpHVytyY#^;d5;+w7&J;;BT7;dOH!>$GILXlOA>Pn7>o>zAbJc!3{0&|O{`2TAx6GC Sesexh4}+(xpUXO@geCx)vnhlC diff --git a/recipes/icons/thecodelesscode.png b/recipes/icons/thecodelesscode.png index 470ac285f3139068928667048c348a626b1a0d54..6e40d1181f157a84b2c67407ac610727986d86c0 100644 GIT binary patch delta 858 zcmdnX@r!MOWDpAj1H;YYP4z&ECEd~2k%3`jKlh(RRv=#?*(1o8fuTx`fuW&=f#DZW zsNn?zL#Y7+!>a@a2CEqi4B`cIb_Lo1C76=D-CY>|xA&jfKT%P#Ih(ME zo@jYbmI6>kuBVG*h{y4zSI*`|yGk7Ucwc7zin3tk<+@^eE}kM>9+|(U>bzh*5W0bp zb6xZ67pw*B8=PJ#1zq)OKn4WFzLl+$+Oq%=at_p zSMx~;oaVVSXrjiFAV-y{^*?XyQ8nMXH8j-J)OU8-+0x&94l=VjI$iv=CN1W7O1d{a z?d*=neWs-=kNln%v+=>Y-L(hIm1c%0O`QCBS##y)Z#G{%&h~{w-dx1Ge(jI5m+c;8 z*De*ZoVd8^MR@-ehnuF|VrwD-!{0OKB|EKItt@mh#kpZw`oCkUqULu$%quOe_s@CV zps;vB!u@#)CzkYGo}_wu+S=cz{p)OG_&4oqo_BA7^&17DpC`7w&htoJwQ3%pQDAire;*yZQ1fT*WYNx;PYdUTmip@VpJ`{G8GQ9}aBy(=^$QYZ z1-C11UAnaC?>6RSi;kl~CpSt;aev6KO*yVpFUMW5X!qnz8!``1n`T?Be0|Q0*3i}e z0=6bsZ)pyUoDo!XZjqi_Hp>F31rW(IA0{C#4>ocHcW((YSU##|88wqCzPLtRqq z=fsBE+)81+9Xe(&_bgh}H1E36$q&g=OH04AxUQ5Gjg^g!j>`JCCA(baz^+C7A!jTe z_jr6bNgUjuLKXx8@R?%>zQQ>{3{DhyUHg%nDe%trcwO=FO zOD=YI5UXL6hlF^5!P17l*qM7G+6p)e$FcU95=7&+W$;5LbkmqcD!)y zT7b3AyI@A?=50$6ciK{6%`g178n>985tQG8X6oN9Dg1j9v~nfAt50nBO@dv zBqb#!CMG5)CnqQX=!O{YHDk1Yiw+6ZEbCCZfVZo}Ztep`oFoqN1atqokyy zrKP2&rlzN-r>Ll?s;a81tE;T6tgWrBu&}VPv9YqUva_?ZwY9aoySu!+yuZJ{z`(%5 z!otMF#DB-f$H>UY$;rve%F4^j%goHo&CSiu&d$}<)i!;mQul!Dh^R14O}z9o3Rc~bj&0kAhz3eFW}b)mX2+TB}GDrm>v<@XN2UIGwY zp;*PZYMVeo(`uKWtscP6kz#>Qsg!qMG_p}C>Ww|8-=6vaJ3~hGJwl|wI>km)a|Tu$ z0Dl+|B80^104Ob2bX~N}wuu!80${xdRUSOk?i&|ZU+!l14$)A0SPlpM(R?+t*Vs-d zes#>D_XnKE05Eaf>H5N1Y|WV9e><`Gdl5=0@h>1Yr>z!_?M{|n1VmeOTgEk_V6#O& zn>o$oemD`Z*`iu~-q^qAev5$3;kMT)?SC9ST`ZPC;LSLX0oN_$w6d0R-1&UyBMt!j z6UmB7F$lxSjoJ5RF)$eH6%P&>qht|$m>~`T9*~rE4N|X_9vOBL2uZs09`coT@Ng9W z1E6MA1fHrYAe1D6TpIo?on{gw5lnu9oxa`89C<_2O#;(yy7*WpWWKP?X%e_;0WAAD tO2&>g_L6{Y1Nl6qBfB?564=gv!Edi3;G{EV5TyVB002ovPDHLkV1geS!leKJ diff --git a/recipes/icons/tjournal.png b/recipes/icons/tjournal.png index 48c652575fba3f0f7f8f74f11e73c1328c3d05e6..33520be1a887eaddac0a2f1b741b478aa48999d4 100644 GIT binary patch delta 108 zcmdnW+{irP1lMymVG%v`{n6G7CSHmNH`X;U(>1gVF*2|+F|jf**ETS)GBC)O(|OLo rz@S><8c~vxSdwa$o1c=IR>@#wV1!+Rob!}upaup{S3j3^P6w delta 161 zcmZo<-pV}T1lKcm5mr;?m_&JoiI*bkjdTsnb&U){3{0&|jjT+KwG9lc3=GzK=Vvo8 zFsPQeMwFx^mZVzc=BH$)RWcYE7=bkyT80>!Ss9vG8ACO2&SEVGYS4gdD9OxCEiOsS fEr9ERT4HQv0M(t@hz)JYlE7CTTP=< zSHr*m*CTH%Nj(<4?$Mc?ABWQ(HOik6e;5>&D6)7`3GYEQ$8N=L#p$}MZbWX8_@=tz z@W+yE4Pu|GcY1_AVEX#?Srh-+yRFlX3ybdxzsk(h+avZMwr<(diWvjVMV;EJ?LWE=mPb3`Pb<#<~V(x`vh^Mg~?UCRPUK+6D$z1_t?ZI?quwM zYeY#(Vo9o1a#1RfVlXl=GSW3L*EKQG_o?V&^9ozGBDWCc`Y7ALvDUbW?Cg~ z4F|8}JqBvffZI@#nVVW%l9*edh0tUWVqj`zYH4L?4zY##nYtKI6N9I#pUXO@geCw> Cylip+ diff --git a/recipes/icons/unian_net.png b/recipes/icons/unian_net.png index 8421cf4959d75f65665452caa47d5c472072696a..f9788060c937293828fc5956d6c3d3592d0eef9e 100644 GIT binary patch delta 91 zcmX@W+`~NK7}s+)VG&*P&Vv=~6ECDn8S5ID=^9#w7#UcZm{=K@Ya19?85rct={%q8 a&nTvDgk4_Fc}g?`5O})!xvX$GILXlOA>Pn7>o>z ckj*r+GBkqdi8eeM2-L&i>FVdQ&MBb@06{A$A^-pY diff --git a/recipes/icons/unperiodico.png b/recipes/icons/unperiodico.png index a89aafe8b287ea369c818cd7c89bd3312716b6da..b7dca0d57a02ae27a9409ecfe41dba580795d50a 100644 GIT binary patch delta 1178 zcmV;L1ZDf(3AYK58BzoQ007x@vVQ;o00d`2O+f$vv5yP zfP?@5`Tzg`fam}Kbua(`>RI+y?e7jT@qQ9J+u00Lr5M??VshmXv^ks%j<00(qQ zO+^Ri1``x593_2$M*si=s!2paR7l5d)md}f$Q6d+2P+A1(_(A9jAoKt8kU%3Bq-@Q^UDX#|=zej|>Hf|a`Tr3aF!92T1=c=$wEOzycO(77 zI+huUC8l2a(KVK0jP-)nQTQHzAo{w#c5Ccl>}P-R5)Wodja=hD9v#nnhv$Id9AGDN z<639zPu`lNkjT|yOZPg%^HkD5knYhG8r4w0;^nyp7xgU&G8nvyBt?mhbkh=7fONl|@ zN@J@N8|+0cr3SH?LaGpS);?;U0~TJpR9hlaDn%A*N0E`KR&SzLIanx+>`!M;fS_>@ zqf)69TA2sINUYM>IXc*RFt(65YM$4i^~u82p!VKBm9FGkGe3&_Vk1FgFL-0HbMIbz z0z?QlzSzn24l2yFKDuy!rT2@qTh}57tsppBS?fiH^n9b4T#VEUZ%k#@Mmi5#8>t(A zmAmsnB9+RedIPB#LF*us=oN0wtaN^s89A7md+o}>AV`e#j@r|MdIC^OWjc*PuaSA< zLaef`R)M_1VjEof;xl5_NrGHz0xll?J-rCseNx+4_ z`GTiP+w6wt`+|E0}xbrNY!pV=ML3HO5La(K(3+wOlEj%5>h5n;2vwv9Z+T z^z-Da7RM%nd+$9u7`yhurMcW7w~(o<)Fxhteef@z9iIS$PHLof=U!{(t+_^LDz?@d z%k>(y*hKA}4|axsCqQbd)Y;hSWM28R-ap(a1^@M+H!_jy^?J3fy;lER&U0g((o`*R zu(c8_y?3WUZ1~odgT}^0rc;=kNknc%Vn;V7 z8V6ggwfbx>1L@bdc8a6bImjgz29YblLE;8?b_PNJ%!tknWjN1NA=cW76vi@O@4 zHPh4m_1M>;v~e*Yyv=DvK??8_R41$b&TB zlCZ#;K$Zgd26zD+N#7@|(*>r4HLih6@2vTROFrd-B(8u7Fed!HrJq_vG?xofG?)3$? z6!2g2+6mwo_)>;b!p|sV-?t%Mx}>4C2AX)K)mCahmw&!5-J~wJ4xkd*oRZVy+S^w^ zhe*4MQXkUHcY>PxeWe?Ywdg{+oiQ*YqWV~E)@u0{_*v~wiHM(R=9gOFSHia~dEKjN z1)R(4j!4T(?{5HP@yV$ESK_Wn=W2*TXM`V51hf*s4Nm!5$GrR|*+Pdj@8tf!{sBP2uA-Y%2IrYt-Fv<%O;^DR8^FG^IK4 ztRt_cg4>>L6mRkH=}A-(Is2BV@i_|&iOhJSmR*H)i5k+;5~*cQP)#B$ej(yyu?-;m ze}9|s^OQ)XEF6cF86a!W~6MMn-lYfKB zNak6L`kL}kPz|DNXQ5ft0OeqFD}WmntN%gSbi?gxR=38?b*{!Ot&ns6sZ8gdoyp^Z zlIvMDllMnYeIQwHtvOIO1#(B3{d<%$TL~@!PKiKHx8)y}NB=R&fa_2?*)3(}-=nyr zQM1&Tk`Z@=E3#5{cF6fGfb4=DX-zT^b2QKT-}6qk;fm78`jmf4Ig&m2lbZ8=-84CT y Date: Tue, 20 Jun 2023 14:27:23 +0530 Subject: [PATCH 0866/2055] DOCX Output: Fix multiple SVG images in the input document causing all the SVG images in the output to be just one of the input images. Fixes #2024433 [Private bug](https://bugs.launchpad.net/calibre/+bug/2024433) Stupid python loop variable binding rules. --- src/calibre/ebooks/docx/writer/images.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/docx/writer/images.py b/src/calibre/ebooks/docx/writer/images.py index d29a0d8a41..6345733c77 100644 --- a/src/calibre/ebooks/docx/writer/images.py +++ b/src/calibre/ebooks/docx/writer/images.py @@ -203,8 +203,12 @@ class ImagesManager: def serialize(self, images_map): for img in self.images.values(): images_map['word/' + img.fname] = partial(self.get_data, img.item) + + def get_svg_data(img): + return img.item.data_as_bytes_or_none + for img in self.svg_images.values(): - images_map['word/' + img.fname] = lambda: img.item.data_as_bytes_or_none + images_map['word/' + img.fname] = partial(get_svg_data, img) def get_data(self, item): try: From 46142b590cc5f9b7758a77868958ca8566f2079f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 20 Jun 2023 15:24:58 +0530 Subject: [PATCH 0867/2055] Fix #2024366 [Review Downloaded Metadata: Visible but unusable clear buttons](https://bugs.launchpad.net/calibre/+bug/2024366) --- src/calibre/gui2/metadata/diff.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/gui2/metadata/diff.py b/src/calibre/gui2/metadata/diff.py index 5140fcb6b6..e9c9f4b872 100644 --- a/src/calibre/gui2/metadata/diff.py +++ b/src/calibre/gui2/metadata/diff.py @@ -47,6 +47,7 @@ class LineEdit(EditWithComplete): self.metadata = metadata if not is_new: self.setReadOnly(True) + self.setClearButtonEnabled(False) else: sep = metadata['is_multiple']['list_to_ui'] if metadata['is_multiple'] else None self.set_separator(sep) @@ -124,6 +125,7 @@ class LanguagesEdit(LE): self.textChanged.connect(self.changed) if not is_new: self.lineEdit().setReadOnly(True) + self.lineEdit().setClearButtonEnabled(False) @property def current_val(self): From eec2b8e929379459346e1cce1c1afb3ab07462b0 Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Tue, 20 Jun 2023 15:44:19 +0530 Subject: [PATCH 0868/2055] Update Hindu to include Sunday Magazine --- recipes/hindu.recipe | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/recipes/hindu.recipe b/recipes/hindu.recipe index 9c82b8dd03..83771873b7 100644 --- a/recipes/hindu.recipe +++ b/recipes/hindu.recipe @@ -16,6 +16,13 @@ local_edition = None # For past editions, set date to, for example, '2023-01-28' past_edition = None +is_sunday = date.today().weekday() == 6 + +if past_edition: + year, month, day = (int(x) for x in past_edition.split('-')) + dt = date(year, month, day) + is_sunday = dt.weekday() == 6 + class TheHindu(BasicNewsRecipe): title = 'The Hindu' __author__ = 'unkn0wn' @@ -54,9 +61,11 @@ class TheHindu(BasicNewsRecipe): if self.output_profile.short_name.startswith('kindle'): if not past_edition: self.title = 'The Hindu ' + date.today().strftime('%b %d, %Y') + else: + self.title = 'The Hindu ' + dt.strftime('%b %d, %Y') def parse_index(self): - + mag_url = None global local_edition if local_edition or past_edition: if local_edition is None: @@ -66,8 +75,12 @@ class TheHindu(BasicNewsRecipe): today = past_edition self.log('Downloading past edition of', local_edition + ' from ' + today) url = absurl('/todays-paper/' + today + '/' + local_edition + '/') + if is_sunday: + mag_url = url + '?supplement=' + local_edition + '-sm' else: url = 'https://www.thehindu.com/todays-paper/' + if is_sunday: + mag_url = url + '?supplement=th_chennai-sm' raw = self.index_to_soup(url, raw=True) soup = self.index_to_soup(raw) @@ -79,6 +92,12 @@ class TheHindu(BasicNewsRecipe): raise ValueError( 'The Hindu Newspaper is not published Today.' ) + if mag_url: + self.log('\nFetching Sunday Magazine') + soup = self.index_to_soup(mag_url) + ans2 = self.hindu_parse_index(soup) + if ans2: + return ans + ans2 return ans def hindu_parse_index(self, soup): From 5b42712302f9165e255319d35236a5b2e7a76c50 Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Tue, 20 Jun 2023 17:05:00 +0530 Subject: [PATCH 0869/2055] Update hindu_business_line_print_edition.recipe looks like BL doesn't load all articles without adding date to the link. --- recipes/hindu_business_line_print_edition.recipe | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/recipes/hindu_business_line_print_edition.recipe b/recipes/hindu_business_line_print_edition.recipe index 7a83251a9e..c61df77105 100644 --- a/recipes/hindu_business_line_print_edition.recipe +++ b/recipes/hindu_business_line_print_edition.recipe @@ -39,7 +39,7 @@ class BusinessLine(BasicNewsRecipe): ] remove_tags = [ - classes('hide-mobile comments-shares share-page editiondetails') + classes('hide-mobile comments-shares share-page editiondetails author-img') ] def preprocess_html(self, soup): @@ -50,13 +50,13 @@ class BusinessLine(BasicNewsRecipe): return soup def parse_index(self): + dt = date.today().strftime('%Y-%m-%d') + # For past editions, set date to, for example, '2023-01-28' + # dt = '2023-01-28' if local_edition: - yr = str(date.today().year) - mn = date.today().strftime('%m') - dy = date.today().strftime('%d') - url = absurl('/todays-paper/' + yr + '-' + mn + '-' + dy + '/' + local_edition + '/') + url = absurl('/todays-paper/' + dt + '/' + local_edition + '/') else: - url = 'https://www.thehindubusinessline.com/todays-paper/' + url = absurl('/todays-paper/' + dt + '/bl_chennai/') raw = self.index_to_soup(url, raw=True) soup = self.index_to_soup(raw) ans = self.hindu_parse_index(soup) @@ -74,8 +74,8 @@ class BusinessLine(BasicNewsRecipe): if not self.tag_to_string(script).strip().startswith('let grouped_articles = {}'): continue if script is not None: - art = re.search(r'grouped_articles = ({\"[^<]+?]})', self.tag_to_string(script)) - data = json.loads(art.group(1)) + art = re.search(r'grouped_articles = ({\".*)', self.tag_to_string(script)) + data = json.JSONDecoder().raw_decode(art.group(1))[0] feeds_dict = defaultdict(list) From 2b149227f8cb4df9f9e65504f783a47624cdf470 Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Wed, 21 Jun 2023 12:53:16 +0530 Subject: [PATCH 0870/2055] ... --- recipes/hindu.recipe | 1 + 1 file changed, 1 insertion(+) diff --git a/recipes/hindu.recipe b/recipes/hindu.recipe index 83771873b7..ed8ade80e5 100644 --- a/recipes/hindu.recipe +++ b/recipes/hindu.recipe @@ -98,6 +98,7 @@ class TheHindu(BasicNewsRecipe): ans2 = self.hindu_parse_index(soup) if ans2: return ans + ans2 + return ans return ans def hindu_parse_index(self, soup): From ad7957ecd7e8253f92f984bdba63b6e9484e8685 Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:08:00 +0530 Subject: [PATCH 0871/2055] icons --- recipes/icons/observer_reach_foundation.png | Bin 0 -> 2202 bytes recipes/icons/the_wire.png | Bin 0 -> 672 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 recipes/icons/observer_reach_foundation.png create mode 100644 recipes/icons/the_wire.png diff --git a/recipes/icons/observer_reach_foundation.png b/recipes/icons/observer_reach_foundation.png new file mode 100644 index 0000000000000000000000000000000000000000..caf156efdf2500ec8497814fdc5a8ceebf2010d6 GIT binary patch literal 2202 zcmV;L2xa$)P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D2ro%QK~!i%?U#8} zQ&%3xUp5RRBw;%VTUZ4Ygict67QwMNFo3Ok9EuCIJzAGK9%rO3c=TAL4oB-YF14+@ zt)SE*GN1?|Aj2j*h+u%QC4rEz1PElEci&tnk>DB6oHO&!`<#>We&64F_q}`X@BZ%n z1;N3=!NI}7!NI}#zd?XkJ^FPzdTH6qsNA~tnC9+*B?BYl)+&{TVr*oju(mMm3H=>ZII82(@lgn=S1JC8*sz8q{ z0)e2!8bSJGGVZ~f&%RY@wIm`!&_VYcw1|$tpi<>?25`Hm{?n5W%8~^A!>-UZFTO6; z%I)T_9%6VC0Z&i2Vvxx0MZuaHSg*5bty+ik3uZQxjkO+v1 z2yH~C(;02Mn0@2R?{lxc*?B63Ji($-Wn>~@3c4_=+eJaEQht7VHoJT;bYY;+PeK!& zb<(7r1*6NslGy>?0o@plJ(BmNx%2GAx$JVypQD3L zV~uCA!rLkLr#)*npr%?Rmor-h5}$K9Rf&a-f(USbY*NWQbhF?zI)N+D{3T}T zHzDr!dJzU_2^yjinWDr7@ZIAVJNMtrpXt0aht>AO_Skq|SDWf{sFuleOtpI4gsd}C z#`<|=qOQ!$@|Lw<{*e14=m2Pg4&r2aTTj{SccC=HCrAv@uN;WC^?W)1ow4!L;Rca7npT_&YyxrqXUf9`9t4h zLLMHQG>6Y3Tq_pVk6Jat4rZ3Cq1N zqn%Gdqo{%IIoMk=I?d_ELt2e?&LB)psKaDej(OYk$y>u02Wqw21>&)>lJ=*KpZ5-q za5shdT%l7JjHQ(}ZHfFdNc5-*9-c2(2XqOS~eY68~XJDsOKa3lkl&=mJOvIL1^5FD}0=DphmY)68`j$}7!qGs+sZvL0pEbFa%1>=m%)9W3#SsDE#*f6&KVm%@Vd@s)5T}9UL?*A zafFZa8J*z#=~Tv}YQE4Hx{zvq zUjVq)@%ym~6Rg?v;)V^ouH;?;8UW6MBXObofUVQ&43_Dj-KA|SH-hyKT?i;gn!7}v zP`)$R=(er&yYO+~qBP8d@>(SGh&=Yw^DP&%Zl&HYIdti9^={z*{z>)DWA}=a6VBYf z_r>L$TOdKP1}H~A3ETT-9b^c5OUvHNJEAvEmmr{)`usw6+Tfs;fj#h!%!KFauGoaQ z5U-n1V!Ow^(w7^K|5BO^q5^#2V<$8JdFVk^+f3NO%%h!8G*pSs2UwnmE4x`KD{T4C)M6H1jaC^>e=1ReiRmL?7k4h{|u4h{~^ c{~84O7pzgLhI#a4r2qf`07*qoM6N<$g1S3D`~Uy| literal 0 HcmV?d00001 diff --git a/recipes/icons/the_wire.png b/recipes/icons/the_wire.png new file mode 100644 index 0000000000000000000000000000000000000000..cc62974655607dcf257f88c234a1b6c598403b1e GIT binary patch literal 672 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstU$g(vPY0F z14ES>14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>03QL=aQ4ZJ$m%! z(4h|(FP;hyf4Xknla(t!Ub%9;vGLu>lh>P?_8A&pt*$=o?0mVj^#075_h-$zR$Kde z|NgsEr|#C$+O46nM@ws;k~IEF}EPEJ_Bm5}1{)Yx#d!KO_c zH*I1Tl=hg{Jh{7hcKh_{{qYU~9ugW#N?K}qikhmr%G&Dd4=`Ohw`|3dHH%g)Teoo8 z%B8$P2Ujd!z5INxf@XR~N=_2jrUuUzN0*kHJ~!@MIdqBZW}cf{3HtCy&nOiTJTUc4xd$I_#u!2j2$>9`c<;@`qr*B+2apcSqnIr6{8$1?x=`p+# W7cBYYWI7dS1%s!npUXO@geCyx-1!m! literal 0 HcmV?d00001 From b5d6e0a9647226fe9f7d31e67d0e5dbf0679b962 Mon Sep 17 00:00:00 2001 From: NogNoa <53447424+NogNoa@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:44:42 +0300 Subject: [PATCH 0872/2055] add altimg to link_attrs --- src/calibre/ebooks/oeb/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index 74d17da6d3..63f9687763 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -167,7 +167,7 @@ def itercsslinks(raw): yield match.group(1), match.start(1) -_link_attrs = set(html.defs.link_attrs) | {XLINK('href'), 'poster'} +_link_attrs = set(html.defs.link_attrs) | {XLINK('href'), 'poster', 'altimg'} def iterlinks(root, find_links_in_css=True): From 44eb122abaf57f47cafa0deb79b7b2c583386418 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 22 Jun 2023 09:59:05 +0530 Subject: [PATCH 0873/2055] ... --- src/calibre/utils/podofo/doc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/podofo/doc.cpp b/src/calibre/utils/podofo/doc.cpp index 18479b6e0e..8407e2bd1d 100644 --- a/src/calibre/utils/podofo/doc.cpp +++ b/src/calibre/utils/podofo/doc.cpp @@ -392,7 +392,7 @@ PDFDoc_append(PDFDoc *self, PyObject *args) { for (auto src : docs) total_pages_to_append += src->GetPages().GetCount(); unsigned base_page_index = dest->GetPages().GetCount(); #if PODOFO_VERSION > PODOFO_MAKE_VERSION(0, 10, 0) - dest->GetPages().CreatePagesAt(base_page_index, Rect(), total_pages_to_append); + dest->GetPages().CreatePagesAt(base_page_index, total_pages_to_append, Rect()); #else while (total_pages_to_append--) dest->GetPages().CreatePage(Rect()); #endif From f13cc53ddbba4905143ad9b15cc4af71f7f91cfd Mon Sep 17 00:00:00 2001 From: ping Date: Thu, 22 Jun 2023 17:51:18 +0800 Subject: [PATCH 0874/2055] Add recipe icon for #1927 --- recipes/icons/prospectmaguk_free.png | Bin 0 -> 896 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 recipes/icons/prospectmaguk_free.png diff --git a/recipes/icons/prospectmaguk_free.png b/recipes/icons/prospectmaguk_free.png new file mode 100644 index 0000000000000000000000000000000000000000..efe40e1892e37e1008e8e02b3280ecc86b85cd9b GIT binary patch literal 896 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7e6l0AZa85pY67#JE_7#My5g&JNk zFq9fFFuY1&V6d9Oz#v{QXIG#N0|TR7fKQ0)buYu~KE^kEO>g*`T=zD=hfuE4E5ktmQ9T$zQQkzUHF!tiS&K&h>9r%z3?J`s<}L-mIMacKwn!tLH!Ms=AvN zc+Exos-yZfSDnZ8x$n2G{`2wu|NsAg+`ajhEn_uJRE8e);tO@8A1*Q8)a}?;n^U!-a zyYI54oD7!~FeDimlf2zsH=O_UFvJVuJdU{ydTaN=tjXIEGl9zB=i8 z)FB6v7V{%lSvs5COgNOg1hbbMS>bV@$+n9(=f>~f`x}1Dyt839|A9N_{?E0wPUm>3 z<*8^Hz<+-E-rBBJtGq1k$X{G>-uPc|MFYJpBC_XJ$@T zP2042#^FCr8+Tk`yS(hGr_OB4CxHgt-?}z;ZT%|rmLVndINPbbrMq^v=DxY2!geE< zxjHZP{{sPc3o(YL9qIEe_=1*~o?<;G^7;?c^c`6zTDVQNzLsXcAe=L${($Z~qwj0m zpF9>`7^Bet{84xMZd0>Y4?6F>j_y2VWBAY zTfBYNNBws~j){v}-|tFjiQg$7zGX@LtqHEX{P*WcXy>rT-aY~hIn@%^h?11Vl2ohY zqEsNoU}RuqtZQJbYiJx|WMpM%Ze?PwZD3$!U~oHddn1a5-29Zxv`X9>R`KxL0ySvB gZ79jiO)V}-%q_sJ=dYyRBcL7zPgg&ebxsLQ00cmu$p8QV literal 0 HcmV?d00001 From e7f8ee5cd6ef78d3b652ac08a6d91ff99a7a5c58 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 23 Jun 2023 09:28:24 +0530 Subject: [PATCH 0875/2055] E-book viewer: Fix selection popup not showing for some books on some platforms when the selection is in the top line. Fixes #2024375 [Private bug](https://bugs.launchpad.net/calibre/+bug/2024375) --- src/pyj/select.pyj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pyj/select.pyj b/src/pyj/select.pyj index 0a149436fb..1763f3b179 100644 --- a/src/pyj/select.pyj +++ b/src/pyj/select.pyj @@ -95,7 +95,10 @@ def range_extents(q, in_flow_mode): end = q.cloneRange() def rect_onscreen(r): - if r.right <= window.innerWidth and r.bottom <= window.innerHeight and r.left >= 0 and r.top >= 0: + # we use -1 rather than zero for the top limit because on some + # platforms the browser engine returns that for top line selections. + # See https://bugs.launchpad.net/calibre/+bug/2024375/ for a test case. + if r.right <= window.innerWidth and r.bottom <= window.innerHeight and r.left >= 0 and r.top >= -1: return True return False From 1a44f006d54a095fd66bef2ecdaa33c0447a76d5 Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Fri, 23 Jun 2023 10:30:35 +0530 Subject: [PATCH 0876/2055] Update livemint.recipe remove ads --- recipes/livemint.recipe | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/livemint.recipe b/recipes/livemint.recipe index 2e0f497808..e46374d091 100644 --- a/recipes/livemint.recipe +++ b/recipes/livemint.recipe @@ -98,7 +98,7 @@ class LiveMint(BasicNewsRecipe): classes( 'trendingSimilarHeight moreNews mobAppDownload label msgError msgOk taboolaHeight' ' socialHolder imgbig disclamerText disqus-comment-count openinApp2 lastAdSlot' - ' datePublish sepStory' + ' datePublish sepStory premiumSlider' ) ] From effdafcbb1bf020d6c1425d32a13aeac26055e74 Mon Sep 17 00:00:00 2001 From: unkn0w7n <51942695+unkn0w7n@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:14:49 +0530 Subject: [PATCH 0877/2055] ... --- recipes/icons/fifty_two.png | Bin 0 -> 495 bytes recipes/icons/himal_southasian.png | Bin 0 -> 516 bytes recipes/icons/press_information_bureau.png | Bin 0 -> 2460 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 recipes/icons/fifty_two.png create mode 100644 recipes/icons/himal_southasian.png create mode 100644 recipes/icons/press_information_bureau.png diff --git a/recipes/icons/fifty_two.png b/recipes/icons/fifty_two.png new file mode 100644 index 0000000000000000000000000000000000000000..8b9b2726154895d5fd6be0496d86f09a51ff9077 GIT binary patch literal 495 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJfYb=jG+$o^Eg+kNfrXKQfd$9{ zQb2+c!EVolv+Wp#;B1gGpu$N&ogf;_0}>m3BhLXTTAIyR9OUlAue)1^%=XryMhD-%r`O*U`nB8O#fWguApX6m92tR%l?wFX& zd}P&Apt^n`raMflc!0Rz-$P|}!y^|MeezCjDrLEP=!(2mkMWL-B{+&t zm_GSnK#OlrRKtoK4(=)^&Ym>B1*#II2QCyIIPC7vc8xKrs?Vg5!OgOPS7Cb9m2+J# zmJMQJlix4|ovND=l~Z-$sD%4(*2R24Egj_y47X|*hp3q+-voM=!PC{xWt~$(69BPl BiCF*u literal 0 HcmV?d00001 diff --git a/recipes/icons/himal_southasian.png b/recipes/icons/himal_southasian.png new file mode 100644 index 0000000000000000000000000000000000000000..f7a2315d6de934aae96dd31f545a4061854bf9b3 GIT binary patch literal 516 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 a?ED{IXwn7D1j%jy0000 Date: Fri, 23 Jun 2023 19:07:26 +0200 Subject: [PATCH 0878/2055] optimize images --- manual/images/cover_browser.png | Bin 138016 -> 137480 bytes manual/images/cover_grid.png | Bin 408184 -> 406583 bytes manual/images/custom_cover.png | Bin 99432 -> 97848 bytes manual/images/debug.png | Bin 4687 -> 4666 bytes manual/images/fts-button.png | Bin 7140 -> 6297 bytes manual/images/python_template_example.png | Bin 10757 -> 10614 bytes manual/images/search.png | Bin 16815 -> 16791 bytes manual/images/search_button.png | Bin 1819 -> 1797 bytes .../editor_demo/images/icon.png | Bin 4035 -> 3347 bytes .../interface_demo/images/icon.png | Bin 4035 -> 3347 bytes manual/resources/logo.png | Bin 17440 -> 16984 bytes recipes/icons/20_minutos.png | Bin 1190 -> 1148 bytes recipes/icons/20minutes.png | Bin 2297 -> 2249 bytes recipes/icons/7seri.png | Bin 247 -> 246 bytes recipes/icons/7x7.png | Bin 1415 -> 1195 bytes recipes/icons/TheMITPressReader.png | Bin 904 -> 601 bytes recipes/icons/abc.png | Bin 553 -> 549 bytes recipes/icons/abc_au.png | Bin 717 -> 715 bytes recipes/icons/abc_py.png | Bin 2302 -> 2301 bytes recipes/icons/ad.png | Bin 312 -> 308 bytes recipes/icons/adnkronos.png | Bin 1075 -> 1051 bytes recipes/icons/adventuregamers.png | Bin 304 -> 299 bytes recipes/icons/ainonline.png | Bin 409 -> 382 bytes recipes/icons/ajiajin.png | Bin 157 -> 136 bytes recipes/icons/aksiyon_derigisi.png | Bin 216 -> 195 bytes recipes/icons/al_masry_alyoum_arabic.png | Bin 1379 -> 1332 bytes recipes/icons/al_monitor.png | Bin 3721 -> 2595 bytes recipes/icons/albert_mohler.png | Bin 1475 -> 1460 bytes recipes/icons/alt_om_herning.png | Bin 2265 -> 1671 bytes recipes/icons/altomdata_dk.png | Bin 609 -> 588 bytes recipes/icons/am730.png | Bin 2015 -> 1638 bytes recipes/icons/amspec.png | Bin 445 -> 442 bytes recipes/icons/anchorage_daily.png | Bin 1334 -> 1224 bytes recipes/icons/animal_politico.png | Bin 1349 -> 751 bytes recipes/icons/anthony_muroni.png | Bin 2653 -> 2454 bytes recipes/icons/ap.png | Bin 673 -> 653 bytes recipes/icons/appledaily_tw.png | Bin 382 -> 379 bytes recipes/icons/ara_info.png | Bin 577 -> 568 bytes recipes/icons/arbetaren.png | Bin 3288 -> 3241 bytes recipes/icons/arcadia.png | Bin 571 -> 541 bytes recipes/icons/arcamax.png | Bin 1827 -> 1796 bytes recipes/icons/arizona_republic.png | Bin 1977 -> 1964 bytes recipes/icons/ars_technica.png | Bin 263 -> 261 bytes recipes/icons/asahi_shimbun_en.png | Bin 1302 -> 1255 bytes recipes/icons/astro_news_pl.png | Bin 505 -> 497 bytes recipes/icons/atlantic.png | Bin 230 -> 227 bytes recipes/icons/atlantic_com.png | Bin 900 -> 862 bytes recipes/icons/auto.png | Bin 1911 -> 1862 bytes recipes/icons/auto_prove.png | Bin 1911 -> 1862 bytes recipes/icons/automatiseringgids.png | Bin 3627 -> 3580 bytes recipes/icons/autosport.png | Bin 1062 -> 936 bytes recipes/icons/avantaje.png | Bin 716 -> 701 bytes recipes/icons/balkanist.png | Bin 578 -> 557 bytes recipes/icons/baltimore_sun.png | Bin 742 -> 715 bytes recipes/icons/bangkok_biz.png | Bin 324 -> 297 bytes recipes/icons/bar_and_bench.png | Bin 1024 -> 987 bytes recipes/icons/bay_citizen.png | Bin 1100 -> 1042 bytes recipes/icons/berliner_zeitung.png | Bin 780 -> 638 bytes recipes/icons/biamag.png | Bin 565 -> 534 bytes recipes/icons/biamag_en.png | Bin 565 -> 534 bytes recipes/icons/bianet.png | Bin 565 -> 534 bytes recipes/icons/billorielly.png | Bin 312 -> 275 bytes recipes/icons/birmingham_evening_mail.png | Bin 392 -> 368 bytes recipes/icons/birmingham_post.png | Bin 559 -> 553 bytes recipes/icons/biz_portal.png | Bin 1048 -> 1045 bytes recipes/icons/blesk.png | Bin 278 -> 274 bytes recipes/icons/bloomberg-business-week.png | Bin 707 -> 668 bytes recipes/icons/bloomberg.png | Bin 707 -> 668 bytes recipes/icons/bq_prime.png | Bin 2007 -> 1445 bytes recipes/icons/brasil_de_fato.png | Bin 996 -> 985 bytes recipes/icons/breakingmad.png | Bin 331 -> 317 bytes recipes/icons/brecha.png | Bin 676 -> 648 bytes recipes/icons/bsi_news.png | Bin 2905 -> 1240 bytes recipes/icons/business_today.png | Bin 807 -> 767 bytes recipes/icons/calcalist.png | Bin 258 -> 235 bytes recipes/icons/calgary_herald.png | Bin 1138 -> 1083 bytes recipes/icons/canardpc.png | Bin 1354 -> 1319 bytes recipes/icons/capital_gr.png | Bin 1282 -> 1279 bytes recipes/icons/caravan_magazine.png | Bin 1305 -> 1206 bytes recipes/icons/caravan_magazine_hindi.png | Bin 1305 -> 1206 bytes recipes/icons/catholic_news_agency.png | Bin 503 -> 478 bytes recipes/icons/cbn.png | Bin 578 -> 565 bytes recipes/icons/cedar.png | Bin 3409 -> 3398 bytes recipes/icons/cesky_rozhlas_6.png | Bin 1250 -> 1200 bytes recipes/icons/cherta.png | Bin 703 -> 671 bytes recipes/icons/chetnixploitation.png | Bin 274 -> 264 bytes recipes/icons/chicago_tribune.png | Bin 301 -> 296 bytes recipes/icons/china_economic_net.png | Bin 581 -> 572 bytes recipes/icons/china_times.png | Bin 650 -> 644 bytes recipes/icons/chinadaily.png | Bin 631 -> 593 bytes recipes/icons/chosun.png | Bin 353 -> 349 bytes recipes/icons/chronicle_higher_ed.png | Bin 1176 -> 1148 bytes recipes/icons/cicero.png | Bin 1645 -> 1351 bytes recipes/icons/cincinnati_enquirer.png | Bin 2188 -> 2122 bytes recipes/icons/cinebel_be.png | Bin 683 -> 680 bytes recipes/icons/cio.png | Bin 1128 -> 1099 bytes recipes/icons/ciperchile.png | Bin 1370 -> 1361 bytes recipes/icons/cityavisen_dk.png | Bin 300 -> 262 bytes recipes/icons/cjr.png | Bin 1091 -> 1065 bytes recipes/icons/clarion_ledger.png | Bin 1997 -> 1975 bytes recipes/icons/cnn.png | Bin 722 -> 711 bytes recipes/icons/columbusdispatch.png | Bin 654 -> 644 bytes recipes/icons/computerworld_dk.png | Bin 643 -> 631 bytes recipes/icons/consortium_news.png | Bin 859 -> 699 bytes .../icons/contemporary_argentine_writers.png | Bin 512 -> 506 bytes recipes/icons/corriere_della_sera_en.png | Bin 281 -> 278 bytes recipes/icons/corriere_della_sera_it.png | Bin 281 -> 278 bytes recipes/icons/corriere_dello_sport.png | Bin 254 -> 252 bytes recipes/icons/cosmopolitan.png | Bin 515 -> 510 bytes recipes/icons/cosmopolitan_de.png | Bin 986 -> 961 bytes recipes/icons/cosmos.png | Bin 882 -> 528 bytes recipes/icons/cotidianul.png | Bin 383 -> 380 bytes recipes/icons/counterpunch.png | Bin 993 -> 935 bytes recipes/icons/countryfile.png | Bin 963 -> 928 bytes recipes/icons/courrier.png | Bin 2353 -> 2317 bytes recipes/icons/crikey.png | Bin 1359 -> 1006 bytes recipes/icons/cronica.png | Bin 682 -> 627 bytes recipes/icons/csid.png | Bin 216 -> 214 bytes recipes/icons/cumhuriyet.png | Bin 880 -> 863 bytes recipes/icons/currenttime.png | Bin 441 -> 410 bytes recipes/icons/cvecezla.png | Bin 512 -> 506 bytes recipes/icons/cyberpresse.png | Bin 843 -> 824 bytes recipes/icons/cyprus_weekly.png | Bin 1155 -> 1098 bytes recipes/icons/dagens_industri.png | Bin 562 -> 551 bytes recipes/icons/dagensmedicin_dk.png | Bin 1130 -> 880 bytes recipes/icons/dagenspharma_dk.png | Bin 812 -> 559 bytes recipes/icons/daily_telegraph.png | Bin 461 -> 458 bytes recipes/icons/dainik_bhaskar.png | Bin 1469 -> 1438 bytes recipes/icons/datasport.png | Bin 3054 -> 2997 bytes recipes/icons/daum_net.png | Bin 1685 -> 1606 bytes recipes/icons/dawn.png | Bin 1013 -> 1007 bytes recipes/icons/dbb.png | Bin 2370 -> 2233 bytes recipes/icons/de_redactie_be.png | Bin 1310 -> 1247 bytes recipes/icons/defensenews.png | Bin 1561 -> 1522 bytes recipes/icons/degentenaar.png | Bin 424 -> 411 bytes recipes/icons/delco_times.png | Bin 1639 -> 1581 bytes recipes/icons/democracy_now.png | Bin 542 -> 538 bytes recipes/icons/denik.cz.png | Bin 1387 -> 1375 bytes recipes/icons/denik_referendum.png | Bin 890 -> 873 bytes recipes/icons/denikn.cz.png | Bin 1057 -> 250 bytes recipes/icons/deredactie.png | Bin 1450 -> 1443 bytes recipes/icons/descopera.png | Bin 682 -> 668 bytes recipes/icons/desiring_god.png | Bin 261 -> 259 bytes recipes/icons/deutsche_welle_bs.png | Bin 926 -> 727 bytes recipes/icons/deutsche_welle_de.png | Bin 926 -> 727 bytes recipes/icons/deutsche_welle_en.png | Bin 926 -> 727 bytes recipes/icons/deutsche_welle_es.png | Bin 926 -> 727 bytes recipes/icons/deutsche_welle_hr.png | Bin 926 -> 727 bytes recipes/icons/deutsche_welle_pt.png | Bin 926 -> 727 bytes recipes/icons/deutsche_welle_ru.png | Bin 926 -> 727 bytes recipes/icons/deutsche_welle_sr.png | Bin 926 -> 727 bytes recipes/icons/diagonal.png | Bin 993 -> 969 bytes recipes/icons/diario_extra.png | Bin 248 -> 227 bytes recipes/icons/diario_la_republica.png | Bin 1723 -> 1161 bytes recipes/icons/diariovasco.png | Bin 321 -> 317 bytes recipes/icons/digit_magazine.png | Bin 703 -> 699 bytes recipes/icons/digital_arts.png | Bin 3614 -> 3583 bytes recipes/icons/digizone.png | Bin 254 -> 252 bytes recipes/icons/disinformatico.png | Bin 267 -> 266 bytes recipes/icons/distrowatch_weekly.png | Bin 1625 -> 1543 bytes recipes/icons/djurslandsposten_dk.png | Bin 1222 -> 619 bytes recipes/icons/dn_se.png | Bin 273 -> 271 bytes recipes/icons/dnevnik_cro.png | Bin 646 -> 629 bytes recipes/icons/dobanevinosti.png | Bin 267 -> 266 bytes recipes/icons/doghousediaries.png | Bin 1109 -> 1084 bytes recipes/icons/donga.png | Bin 864 -> 840 bytes recipes/icons/dosisdiarias.png | Bin 267 -> 266 bytes recipes/icons/dw_de.png | Bin 1261 -> 1215 bytes recipes/icons/dziennik_polski.png | Bin 703 -> 694 bytes recipes/icons/dziennik_zachodni.png | Bin 245 -> 241 bytes recipes/icons/echo_moskvy.png | Bin 444 -> 428 bytes recipes/icons/economia.png | Bin 1672 -> 841 bytes recipes/icons/economist.png | Bin 765 -> 764 bytes recipes/icons/editor_and_publisher.png | Bin 1787 -> 1700 bytes recipes/icons/ekathemerini.png | Bin 669 -> 664 bytes recipes/icons/ekot.png | Bin 399 -> 395 bytes recipes/icons/el_colombiano.png | Bin 673 -> 664 bytes recipes/icons/el_correo.png | Bin 551 -> 537 bytes recipes/icons/el_diario.png | Bin 1462 -> 1112 bytes recipes/icons/el_diplo.png | Bin 438 -> 393 bytes recipes/icons/el_espectador.png | Bin 1636 -> 1591 bytes recipes/icons/el_mostrador.png | Bin 1277 -> 708 bytes recipes/icons/el_nacional.png | Bin 843 -> 820 bytes recipes/icons/el_publico.png | Bin 1688 -> 1059 bytes recipes/icons/el_tiempo.png | Bin 357 -> 341 bytes recipes/icons/elcomercio.png | Bin 317 -> 312 bytes recipes/icons/elcronista-arg.png | Bin 482 -> 450 bytes recipes/icons/elmundo.png | Bin 464 -> 251 bytes recipes/icons/elperiodico_catalan.png | Bin 308 -> 302 bytes recipes/icons/elperiodico_spanish.png | Bin 308 -> 302 bytes recipes/icons/elsevier.png | Bin 461 -> 458 bytes recipes/icons/empire_magazine.png | Bin 289 -> 285 bytes recipes/icons/epoch_times.png | Bin 734 -> 642 bytes recipes/icons/epw.png | Bin 135 -> 114 bytes recipes/icons/epw_magazine.png | Bin 135 -> 114 bytes recipes/icons/esensja_(rss).png | Bin 328 -> 315 bytes recipes/icons/espn.png | Bin 6217 -> 6197 bytes recipes/icons/estadao.png | Bin 566 -> 562 bytes recipes/icons/eu_commission.png | Bin 183 -> 180 bytes recipes/icons/europa_press.png | Bin 336 -> 327 bytes recipes/icons/everett_herald.png | Bin 293 -> 288 bytes recipes/icons/evz.ro.png | Bin 298 -> 294 bytes recipes/icons/expansion_spanish.png | Bin 361 -> 346 bytes recipes/icons/express_de.png | Bin 1171 -> 809 bytes recipes/icons/fan_graphs.png | Bin 377 -> 371 bytes recipes/icons/fastcompany.png | Bin 5206 -> 5120 bytes recipes/icons/fc_knudde.png | Bin 5574 -> 5506 bytes recipes/icons/fdb_pl.png | Bin 1056 -> 240 bytes recipes/icons/fifty_two.png | Bin 495 -> 170 bytes recipes/icons/film_web.png | Bin 454 -> 261 bytes recipes/icons/financial_times.png | Bin 1935 -> 1917 bytes .../icons/financial_times_print_edition.png | Bin 1935 -> 1917 bytes recipes/icons/financieele_dagblad.png | Bin 1572 -> 1006 bytes recipes/icons/flickr.png | Bin 378 -> 372 bytes recipes/icons/flickr_es.png | Bin 378 -> 372 bytes recipes/icons/focus_de.png | Bin 2709 -> 2698 bytes recipes/icons/folhadesaopaulo_sub.png | Bin 433 -> 425 bytes recipes/icons/folkebladet_dk.png | Bin 2203 -> 2164 bytes recipes/icons/folketidende_dk.png | Bin 2370 -> 1056 bytes recipes/icons/forbes.png | Bin 212 -> 210 bytes recipes/icons/foreign_policy.png | Bin 445 -> 437 bytes recipes/icons/fr_online.png | Bin 686 -> 682 bytes recipes/icons/frankfurter_rundschau.png | Bin 686 -> 682 bytes recipes/icons/freakonomics.png | Bin 3733 -> 3684 bytes recipes/icons/frederiksbergbladet_dk.png | Bin 300 -> 262 bytes recipes/icons/free_inquiry.png | Bin 3886 -> 1308 bytes recipes/icons/frontline.png | Bin 1847 -> 1826 bytes recipes/icons/fstream.png | Bin 276 -> 275 bytes recipes/icons/fudzilla.png | Bin 250 -> 244 bytes recipes/icons/galicia_confidential.png | Bin 484 -> 475 bytes recipes/icons/gazeta_krakowska.png | Bin 213 -> 211 bytes recipes/icons/gazeta_lubuska.png | Bin 901 -> 900 bytes recipes/icons/gazetaua_ru.png | Bin 1882 -> 1861 bytes recipes/icons/gazetaua_ua.png | Bin 1882 -> 1861 bytes recipes/icons/geek_poke.png | Bin 459 -> 449 bytes recipes/icons/german_gov.png | Bin 1100 -> 1080 bytes recipes/icons/gezgin_dergi.png | Bin 618 -> 589 bytes recipes/icons/gizmodo.png | Bin 337 -> 331 bytes recipes/icons/glamour.png | Bin 959 -> 934 bytes recipes/icons/glennbeck.png | Bin 1690 -> 1689 bytes recipes/icons/globaltimes.png | Bin 274 -> 250 bytes recipes/icons/globe_and_mail.png | Bin 371 -> 370 bytes recipes/icons/go_comics.png | Bin 1501 -> 885 bytes recipes/icons/goal.png | Bin 445 -> 439 bytes recipes/icons/gofin_pl.png | Bin 601 -> 594 bytes recipes/icons/golem_de.png | Bin 919 -> 897 bytes recipes/icons/google_news.png | Bin 1553 -> 1477 bytes recipes/icons/googlemobileblog.png | Bin 1139 -> 1128 bytes recipes/icons/grani.png | Bin 208 -> 178 bytes recipes/icons/granma.png | Bin 829 -> 578 bytes recipes/icons/granta.png | Bin 821 -> 531 bytes recipes/icons/gs24_pl.png | Bin 252 -> 249 bytes recipes/icons/guardian.png | Bin 478 -> 475 bytes recipes/icons/gulfnews.png | Bin 1282 -> 1272 bytes recipes/icons/habr.png | Bin 653 -> 453 bytes recipes/icons/habr_ru.png | Bin 653 -> 453 bytes recipes/icons/haksoz.png | Bin 2352 -> 2347 bytes recipes/icons/handelsblatt.png | Bin 232 -> 224 bytes recipes/icons/hannoversche_zeitung.png | Bin 225 -> 222 bytes recipes/icons/harpers.png | Bin 1401 -> 1380 bytes recipes/icons/harpers_full.png | Bin 1401 -> 1380 bytes recipes/icons/heise_ct.png | Bin 1821 -> 1479 bytes recipes/icons/heise_ix.png | Bin 1821 -> 1479 bytes recipes/icons/heise_open.png | Bin 1821 -> 1479 bytes recipes/icons/helsingin_sanomat.png | Bin 677 -> 657 bytes recipes/icons/high_country_news.png | Bin 580 -> 576 bytes recipes/icons/hindu.png | Bin 425 -> 422 bytes recipes/icons/hindu_business_line.png | Bin 1678 -> 1577 bytes .../hindu_business_line_print_edition.png | Bin 1678 -> 1577 bytes recipes/icons/history_today.png | Bin 1177 -> 591 bytes recipes/icons/hnonline.png | Bin 635 -> 632 bytes recipes/icons/hotnews.png | Bin 386 -> 384 bytes recipes/icons/howtogeek.png | Bin 477 -> 466 bytes recipes/icons/hvg.png | Bin 437 -> 430 bytes recipes/icons/id_pixel.png | Bin 325 -> 132 bytes recipes/icons/ideal_almeria.png | Bin 278 -> 241 bytes recipes/icons/ideal_granada.png | Bin 278 -> 241 bytes recipes/icons/ideal_jaen.png | Bin 278 -> 241 bytes recipes/icons/idg_se.png | Bin 454 -> 439 bytes recipes/icons/iht.png | Bin 771 -> 767 bytes recipes/icons/iktibas.png | Bin 1689 -> 1668 bytes recipes/icons/il_cambiamento.png | Bin 1279 -> 1241 bytes recipes/icons/il_messaggero.png | Bin 778 -> 772 bytes recipes/icons/il_post.png | Bin 614 -> 569 bytes recipes/icons/iliteratura_cz.png | Bin 559 -> 515 bytes recipes/icons/independent_australia.png | Bin 1067 -> 1049 bytes recipes/icons/india_legal_magazine.png | Bin 1958 -> 1937 bytes recipes/icons/indian_express.png | Bin 273 -> 268 bytes recipes/icons/indic_today.png | Bin 1538 -> 979 bytes recipes/icons/indy_star.png | Bin 1686 -> 1627 bytes recipes/icons/insan_okur.png | Bin 1575 -> 1540 bytes recipes/icons/insider.png | Bin 5081 -> 787 bytes recipes/icons/intelligencer.png | Bin 414 -> 411 bytes recipes/icons/interfax.png | Bin 3040 -> 2378 bytes recipes/icons/interfax_uk.png | Bin 3040 -> 2378 bytes recipes/icons/irish_times.png | Bin 379 -> 375 bytes recipes/icons/istorias.png | Bin 880 -> 871 bytes recipes/icons/istories.png | Bin 9680 -> 9314 bytes recipes/icons/ivanamilakovic.png | Bin 2276 -> 2232 bytes recipes/icons/jacobinmag.png | Bin 231 -> 213 bytes recipes/icons/jagran_josh.png | Bin 1104 -> 1044 bytes recipes/icons/japaa.png | Bin 1480 -> 1447 bytes recipes/icons/javalobby.png | Bin 3563 -> 2341 bytes recipes/icons/jbpress.png | Bin 704 -> 680 bytes recipes/icons/johm.png | Bin 873 -> 872 bytes recipes/icons/juve_la_stampa.png | Bin 715 -> 699 bytes recipes/icons/juventudrebelde.png | Bin 1235 -> 1139 bytes recipes/icons/jv_dk.png | Bin 672 -> 640 bytes recipes/icons/kahokushinpo.png | Bin 1726 -> 1715 bytes recipes/icons/karsi_gazete.png | Bin 1267 -> 1252 bytes recipes/icons/kellog_insight.png | Bin 729 -> 708 bytes recipes/icons/kerrang.png | Bin 1565 -> 1505 bytes recipes/icons/kgsenghavebladet_dk.png | Bin 300 -> 262 bytes recipes/icons/kitsapun.png | Bin 2161 -> 2136 bytes recipes/icons/kleinezeitung.png | Bin 2002 -> 1188 bytes recipes/icons/km_blog.png | Bin 321 -> 320 bytes recipes/icons/knack_be.png | Bin 2885 -> 600 bytes recipes/icons/knife_media.png | Bin 1924 -> 1696 bytes recipes/icons/kompiutierra.png | Bin 918 -> 692 bytes recipes/icons/kristeligt_dagblad_dk.png | Bin 460 -> 454 bytes recipes/icons/krstarica_en.png | Bin 488 -> 487 bytes recipes/icons/kurier_lubelski.png | Bin 292 -> 289 bytes recipes/icons/l_espresso.png | Bin 1071 -> 1032 bytes recipes/icons/la_cuarta.png | Bin 339 -> 335 bytes recipes/icons/la_jornada.png | Bin 697 -> 689 bytes recipes/icons/la_nueva.png | Bin 599 -> 596 bytes recipes/icons/la_tercera.png | Bin 227 -> 223 bytes recipes/icons/la_voce.png | Bin 1121 -> 1068 bytes recipes/icons/laprensa.png | Bin 358 -> 356 bytes recipes/icons/le_gorafi.png | Bin 1651 -> 1615 bytes recipes/icons/le_journal.png | Bin 1660 -> 1621 bytes recipes/icons/le_monde_diplomatique_fr.png | Bin 258 -> 256 bytes recipes/icons/le_monde_en.png | Bin 333 -> 150 bytes recipes/icons/le_monde_sub.png | Bin 717 -> 649 bytes recipes/icons/le_temps.png | Bin 384 -> 373 bytes recipes/icons/lega_nerd.png | Bin 395 -> 394 bytes recipes/icons/les_echos.png | Bin 2191 -> 1336 bytes recipes/icons/lescienze.png | Bin 2335 -> 1739 bytes recipes/icons/lesoir_be.png | Bin 2047 -> 2022 bytes recipes/icons/liberatorio_politico.png | Bin 445 -> 355 bytes recipes/icons/lidovky.png | Bin 1396 -> 1361 bytes recipes/icons/liganet_ru.png | Bin 587 -> 555 bytes recipes/icons/liganet_ua.png | Bin 587 -> 555 bytes recipes/icons/lightspeed_magazine.png | Bin 2056 -> 2054 bytes recipes/icons/livemint.png | Bin 343 -> 341 bytes recipes/icons/living_stones.png | Bin 1368 -> 1216 bytes recipes/icons/los_danieles.png | Bin 1409 -> 839 bytes recipes/icons/ludwig_mises.png | Bin 2356 -> 2283 bytes recipes/icons/lupa.png | Bin 303 -> 301 bytes recipes/icons/mac_world.png | Bin 292 -> 280 bytes recipes/icons/mac_world_uk.png | Bin 1086 -> 1073 bytes recipes/icons/macleans.png | Bin 533 -> 532 bytes recipes/icons/maharashtra_times.png | Bin 766 -> 764 bytes recipes/icons/malaya_business_insight.png | Bin 1520 -> 1480 bytes recipes/icons/mallorca_zeitung.png | Bin 1927 -> 1906 bytes recipes/icons/marctv.png | Bin 1219 -> 1203 bytes recipes/icons/marine_corps_times.png | Bin 923 -> 909 bytes recipes/icons/marketing_magazine.png | Bin 2519 -> 2480 bytes recipes/icons/mateusz_czytania.png | Bin 346 -> 342 bytes recipes/icons/mdj.png | Bin 1868 -> 1829 bytes recipes/icons/mediaindonesia.png | Bin 1031 -> 993 bytes recipes/icons/meduza.png | Bin 513 -> 504 bytes recipes/icons/meduza_ru.png | Bin 513 -> 504 bytes recipes/icons/merco_press.png | Bin 416 -> 409 bytes recipes/icons/metro_montreal.png | Bin 1461 -> 1427 bytes recipes/icons/michalkiewicz.png | Bin 727 -> 715 bytes recipes/icons/military_times.png | Bin 1194 -> 1155 bytes recipes/icons/mit_technology_review.png | Bin 1014 -> 938 bytes recipes/icons/mmc_rtv.png | Bin 1066 -> 1039 bytes recipes/icons/mobilenations.png | Bin 395 -> 388 bytes recipes/icons/mondedurable.png | Bin 2005 -> 1400 bytes recipes/icons/moneycontrol.png | Bin 1532 -> 1448 bytes recipes/icons/montreal_gazette.png | Bin 1047 -> 1037 bytes recipes/icons/moscowtimes_en.png | Bin 273 -> 252 bytes recipes/icons/moscowtimes_ru.png | Bin 273 -> 252 bytes recipes/icons/msnsankei.png | Bin 282 -> 278 bytes recipes/icons/mult_kor.png | Bin 455 -> 444 bytes recipes/icons/n_kaliningrad.png | Bin 1593 -> 1572 bytes recipes/icons/n_plus_one.png | Bin 611 -> 408 bytes recipes/icons/nachdenkseiten.png | Bin 149 -> 128 bytes recipes/icons/nasa.png | Bin 4227 -> 3370 bytes recipes/icons/national_post.png | Bin 875 -> 842 bytes recipes/icons/navy_times.png | Bin 1396 -> 1375 bytes recipes/icons/nbonline.png | Bin 1266 -> 956 bytes recipes/icons/ncrnext.png | Bin 705 -> 693 bytes recipes/icons/nepszabadsag.png | Bin 328 -> 303 bytes recipes/icons/netzpolitik.png | Bin 575 -> 572 bytes recipes/icons/new_london_day.png | Bin 1377 -> 1373 bytes recipes/icons/new_scientist.png | Bin 543 -> 532 bytes recipes/icons/new_statesman.png | Bin 767 -> 578 bytes recipes/icons/new_york_review_of_books.png | Bin 629 -> 594 bytes .../icons/new_york_review_of_books_no_sub.png | Bin 629 -> 594 bytes recipes/icons/news24.png | Bin 2005 -> 1927 bytes recipes/icons/news324.png | Bin 1804 -> 1457 bytes recipes/icons/news_busters.png | Bin 645 -> 594 bytes recipes/icons/newsbeast.png | Bin 2162 -> 1831 bytes recipes/icons/newsobs.png | Bin 397 -> 374 bytes recipes/icons/newtab.png | Bin 576 -> 571 bytes recipes/icons/newz_dk.png | Bin 1884 -> 1045 bytes recipes/icons/nightflier.png | Bin 274 -> 264 bytes recipes/icons/nikkei_news.png | Bin 674 -> 659 bytes recipes/icons/noerrebronordvestbladet_dk.png | Bin 300 -> 262 bytes recipes/icons/nol.png | Bin 328 -> 303 bytes recipes/icons/nordjyske_dk.png | Bin 3161 -> 3148 bytes recipes/icons/nortecastilla.png | Bin 748 -> 733 bytes recipes/icons/nos_nl.png | Bin 380 -> 377 bytes recipes/icons/novaya_gazeta.png | Bin 417 -> 413 bytes recipes/icons/novaya_gazeta_europe.png | Bin 2634 -> 2317 bytes recipes/icons/novaya_gazeta_europe_en.png | Bin 2634 -> 2317 bytes recipes/icons/novaya_media.png | Bin 926 -> 864 bytes recipes/icons/novinite.png | Bin 347 -> 342 bytes recipes/icons/novinite_bg.png | Bin 1301 -> 1263 bytes recipes/icons/nowiny_rybnik.png | Bin 410 -> 408 bytes recipes/icons/nrc-nl-epub.png | Bin 705 -> 693 bytes recipes/icons/nrc_handelsblad.png | Bin 705 -> 693 bytes recipes/icons/nrc_next.png | Bin 1037 -> 953 bytes recipes/icons/ntv_spor.png | Bin 1761 -> 1214 bytes recipes/icons/ntv_tr.png | Bin 2171 -> 2092 bytes recipes/icons/nv_en.png | Bin 1579 -> 1558 bytes recipes/icons/nv_ru.png | Bin 1579 -> 1558 bytes recipes/icons/nv_ua.png | Bin 1579 -> 1558 bytes recipes/icons/nymag.png | Bin 562 -> 541 bytes recipes/icons/nytimes_cooking.png | Bin 963 -> 940 bytes recipes/icons/nytimesbook.png | Bin 1021 -> 996 bytes recipes/icons/nzherald.png | Bin 302 -> 299 bytes recipes/icons/oba.png | Bin 760 -> 729 bytes recipes/icons/observa_digital.png | Bin 1731 -> 1117 bytes recipes/icons/observer_reach_foundation.png | Bin 2202 -> 1053 bytes recipes/icons/oesterbroavis_dk.png | Bin 300 -> 262 bytes recipes/icons/old_games.png | Bin 400 -> 206 bytes recipes/icons/omgubuntu.png | Bin 314 -> 305 bytes recipes/icons/onda_rock.png | Bin 1342 -> 971 bytes recipes/icons/opennet.png | Bin 990 -> 942 bytes recipes/icons/opindia.png | Bin 1072 -> 1065 bytes recipes/icons/opinion_bo.png | Bin 771 -> 730 bytes recipes/icons/oregonian.png | Bin 784 -> 778 bytes recipes/icons/origo_hu.png | Bin 251 -> 248 bytes recipes/icons/osvitaua.png | Bin 1385 -> 1340 bytes recipes/icons/osvitaua_ru.png | Bin 1385 -> 1340 bytes recipes/icons/ottawa_citizen.png | Bin 1208 -> 1150 bytes recipes/icons/outlook_india.png | Bin 1891 -> 1870 bytes recipes/icons/pajama.png | Bin 823 -> 811 bytes recipes/icons/parisreview.png | Bin 1421 -> 638 bytes recipes/icons/parlamentni_listy.png | Bin 364 -> 363 bytes recipes/icons/pc_mag.png | Bin 964 -> 947 bytes recipes/icons/penguin_news.png | Bin 1437 -> 1205 bytes recipes/icons/phillosophy_now.png | Bin 167 -> 146 bytes recipes/icons/phoronix.png | Bin 1460 -> 1437 bytes recipes/icons/piratske_noviny.png | Bin 877 -> 708 bytes recipes/icons/poche.png | Bin 532 -> 501 bytes recipes/icons/portfolio_hu.png | Bin 1715 -> 1110 bytes recipes/icons/poughkeepsie_journal.png | Bin 2145 -> 2099 bytes recipes/icons/praguemonitor.png | Bin 477 -> 472 bytes recipes/icons/pragyata.png | Bin 3125 -> 3104 bytes recipes/icons/pravda.png | Bin 1710 -> 1616 bytes recipes/icons/pravda_uk.png | Bin 396 -> 193 bytes recipes/icons/pravda_ukraine.png | Bin 396 -> 193 bytes recipes/icons/pravda_ukraine_ru.png | Bin 396 -> 193 bytes recipes/icons/pravo.png | Bin 887 -> 837 bytes recipes/icons/prekshaa.png | Bin 1360 -> 1130 bytes recipes/icons/press_information_bureau.png | Bin 2460 -> 2426 bytes recipes/icons/pro_linux_de.png | Bin 254 -> 253 bytes recipes/icons/pro_physik.png | Bin 2006 -> 1985 bytes recipes/icons/projo.png | Bin 332 -> 311 bytes recipes/icons/propublica.png | Bin 694 -> 679 bytes recipes/icons/prospectmaguk_free.png | Bin 896 -> 648 bytes recipes/icons/protagon.png | Bin 1285 -> 1275 bytes recipes/icons/publicdomainreview_org.png | Bin 1731 -> 1196 bytes recipes/icons/publico.png | Bin 439 -> 311 bytes recipes/icons/punto_informatico.png | Bin 311 -> 299 bytes recipes/icons/quanta_magazine.png | Bin 1368 -> 551 bytes recipes/icons/queleer.png | Bin 812 -> 777 bytes recipes/icons/radio_prague.png | Bin 262 -> 236 bytes recipes/icons/radio_praha.png | Bin 262 -> 236 bytes recipes/icons/rds.png | Bin 1346 -> 1335 bytes recipes/icons/readersdigest_thehealthy.png | Bin 1698 -> 1694 bytes recipes/icons/real_clear.png | Bin 1254 -> 1201 bytes recipes/icons/real_world_economics_review.png | Bin 512 -> 506 bytes recipes/icons/reason_magazine.png | Bin 208 -> 179 bytes recipes/icons/red_aragon.png | Bin 371 -> 368 bytes recipes/icons/red_voltaire.png | Bin 1320 -> 1282 bytes recipes/icons/regina_leader_post.png | Bin 1302 -> 1270 bytes recipes/icons/respekt_magazine.png | Bin 384 -> 379 bytes recipes/icons/reuters.png | Bin 582 -> 558 bytes recipes/icons/revista_cromos.png | Bin 1148 -> 1040 bytes recipes/icons/revista_muy.png | Bin 1179 -> 1158 bytes recipes/icons/revista_semana.png | Bin 575 -> 554 bytes recipes/icons/revista_summa.png | Bin 1343 -> 1317 bytes recipes/icons/revista_veintitres.png | Bin 145 -> 94 bytes recipes/icons/rga.png | Bin 964 -> 916 bytes recipes/icons/ria_ru.png | Bin 253 -> 251 bytes recipes/icons/roger_ebert.png | Bin 617 -> 584 bytes recipes/icons/roger_ebert_blog.png | Bin 617 -> 584 bytes recipes/icons/root.png | Bin 260 -> 255 bytes recipes/icons/rosbalt.png | Bin 2227 -> 2201 bytes recipes/icons/rubikon_de.png | Bin 463 -> 309 bytes recipes/icons/russiafeed.png | Bin 380 -> 166 bytes recipes/icons/rynek_zdrowia.png | Bin 246 -> 242 bytes recipes/icons/sabit_fikir.png | Bin 1125 -> 1104 bytes recipes/icons/saechsische.png | Bin 467 -> 431 bytes recipes/icons/samanyolu_teknoloji.png | Bin 3099 -> 3078 bytes recipes/icons/sardinia_post.png | Bin 1209 -> 1176 bytes recipes/icons/saskatoon_star_phoenix.png | Bin 1006 -> 982 bytes recipes/icons/satmagazine.png | Bin 1295 -> 1245 bytes recipes/icons/sb_nation.png | Bin 3046 -> 2945 bytes recipes/icons/schattenblick.png | Bin 226 -> 223 bytes recipes/icons/schwarzerpfeil.png | Bin 2698 -> 1803 bytes recipes/icons/science_advances.png | Bin 720 -> 712 bytes recipes/icons/science_news.png | Bin 1217 -> 1164 bytes recipes/icons/scinexx.png | Bin 248 -> 245 bytes recipes/icons/scprint.png | Bin 1777 -> 1740 bytes recipes/icons/seattle_times.png | Bin 310 -> 307 bytes recipes/icons/seminar_magazine.png | Bin 941 -> 908 bytes recipes/icons/sfbg.png | Bin 1466 -> 496 bytes recipes/icons/sg_hu.png | Bin 565 -> 564 bytes recipes/icons/shacknews.png | Bin 1893 -> 1297 bytes recipes/icons/sign_of_the_times.png | Bin 429 -> 423 bytes recipes/icons/sizinti_derigisi.png | Bin 1896 -> 1875 bytes recipes/icons/skeptic.png | Bin 1065 -> 725 bytes recipes/icons/skeptical_enquirer.png | Bin 2781 -> 2133 bytes recipes/icons/slate_star_codex.png | Bin 2116 -> 2066 bytes recipes/icons/slovo.png | Bin 1572 -> 1500 bytes recipes/icons/sme.png | Bin 1038 -> 995 bytes recipes/icons/smith.png | Bin 560 -> 538 bytes recipes/icons/sn_dk.png | Bin 1311 -> 1064 bytes recipes/icons/snopes.png | Bin 2630 -> 1473 bytes recipes/icons/sobesednik.png | Bin 2015 -> 1787 bytes recipes/icons/spectator_magazine.png | Bin 1487 -> 1186 bytes recipes/icons/spiegel_int.png | Bin 325 -> 324 bytes recipes/icons/spiegelde.png | Bin 325 -> 324 bytes recipes/icons/sportowefakty.png | Bin 355 -> 351 bytes recipes/icons/sports_illustrated.png | Bin 483 -> 461 bytes recipes/icons/sportstar.png | Bin 1088 -> 1042 bytes recipes/icons/sporza_be.png | Bin 807 -> 802 bytes recipes/icons/st_louis_post_dispatch.png | Bin 859 -> 845 bytes recipes/icons/stackoverflow.png | Bin 263 -> 260 bytes recipes/icons/standardmoney.png | Bin 370 -> 364 bytes recipes/icons/star_gazetesi.png | Bin 1363 -> 1325 bytes recipes/icons/staradvertiser.png | Bin 382 -> 381 bytes recipes/icons/starwars.png | Bin 665 -> 652 bytes recipes/icons/stnn.png | Bin 2306 -> 2250 bytes recipes/icons/stopgame.png | Bin 825 -> 806 bytes recipes/icons/strange_horizons.png | Bin 1741 -> 1183 bytes recipes/icons/strategy-business.png | Bin 157 -> 120 bytes recipes/icons/substack.png | Bin 338 -> 333 bytes recipes/icons/sueddeutsche_mobil.png | Bin 1277 -> 1133 bytes recipes/icons/svt_nyheter.png | Bin 1101 -> 972 bytes recipes/icons/swarajya.png | Bin 2295 -> 1699 bytes recipes/icons/t3n_de.png | Bin 569 -> 537 bytes recipes/icons/t_online.png | Bin 629 -> 612 bytes recipes/icons/tagesan.png | Bin 498 -> 483 bytes recipes/icons/tagespost.png | Bin 3068 -> 2852 bytes recipes/icons/taggeschau_de.png | Bin 1811 -> 1759 bytes recipes/icons/taipei.png | Bin 1610 -> 1301 bytes recipes/icons/takiedela.png | Bin 1240 -> 646 bytes recipes/icons/tanea.png | Bin 872 -> 833 bytes recipes/icons/tayga.png | Bin 1401 -> 1356 bytes recipes/icons/taz.png | Bin 731 -> 491 bytes recipes/icons/technology_review_de.png | Bin 1496 -> 1460 bytes recipes/icons/tedneward.png | Bin 1790 -> 1753 bytes recipes/icons/telam.png | Bin 439 -> 422 bytes recipes/icons/telegraph_in.png | Bin 932 -> 918 bytes recipes/icons/thairath.png | Bin 1306 -> 474 bytes recipes/icons/the_budget_fashionista.png | Bin 279 -> 258 bytes recipes/icons/the_clinic_online.png | Bin 593 -> 583 bytes recipes/icons/the_diplomat.png | Bin 1036 -> 984 bytes recipes/icons/the_federalist.png | Bin 1090 -> 1038 bytes recipes/icons/the_friday_times.png | Bin 302 -> 241 bytes recipes/icons/the_insider.png | Bin 364 -> 185 bytes recipes/icons/the_journal.png | Bin 726 -> 372 bytes recipes/icons/the_manila_bulletin.png | Bin 691 -> 675 bytes recipes/icons/the_manila_times.png | Bin 2339 -> 2315 bytes recipes/icons/the_marker.png | Bin 1587 -> 1477 bytes .../icons/the_philippine_daily_inquirer.png | Bin 1250 -> 1219 bytes recipes/icons/the_sun.png | Bin 1082 -> 1022 bytes recipes/icons/the_wire.png | Bin 672 -> 359 bytes recipes/icons/thebell.png | Bin 658 -> 622 bytes recipes/icons/thecultofghoul.png | Bin 274 -> 264 bytes recipes/icons/thedgesingapore.png | Bin 1057 -> 1055 bytes .../theeconomictimes_india_print_edition.png | Bin 1156 -> 1113 bytes recipes/icons/theindiaforum.png | Bin 1224 -> 1157 bytes recipes/icons/theluminouslandscape.png | Bin 683 -> 681 bytes recipes/icons/thenewcriterion.png | Bin 1531 -> 105 bytes recipes/icons/thenews.png | Bin 361 -> 356 bytes recipes/icons/theoldfoodie.png | Bin 267 -> 266 bytes recipes/icons/theprint.png | Bin 1518 -> 1330 bytes recipes/icons/thewest_au.png | Bin 593 -> 586 bytes recipes/icons/think_progress.png | Bin 737 -> 728 bytes recipes/icons/thn.png | Bin 379 -> 299 bytes recipes/icons/tijolaco.png | Bin 1127 -> 1106 bytes recipes/icons/tillsonburg.png | Bin 971 -> 822 bytes recipes/icons/times_of_malta.png | Bin 649 -> 631 bytes recipes/icons/tjournal.png | Bin 385 -> 192 bytes recipes/icons/today_online.png | Bin 594 -> 579 bytes recipes/icons/tomshardware_it.png | Bin 1460 -> 1225 bytes recipes/icons/tovima.png | Bin 380 -> 373 bytes recipes/icons/tri_city_herald.png | Bin 332 -> 330 bytes recipes/icons/trombon.png | Bin 342 -> 336 bytes recipes/icons/tsn.png | Bin 1834 -> 1814 bytes recipes/icons/tst.png | Bin 1978 -> 1957 bytes recipes/icons/tvsyd_dk.png | Bin 1213 -> 1199 bytes recipes/icons/tweakers.png | Bin 1196 -> 1178 bytes recipes/icons/ukraiyns_tizhdien.png | Bin 521 -> 317 bytes recipes/icons/unian_net.png | Bin 396 -> 206 bytes recipes/icons/uninohimitu.png | Bin 938 -> 934 bytes recipes/icons/universe_today.png | Bin 2119 -> 2117 bytes recipes/icons/upi.png | Bin 1714 -> 884 bytes recipes/icons/usatoday.png | Bin 434 -> 411 bytes recipes/icons/valbybladet_dk.png | Bin 300 -> 262 bytes recipes/icons/vancouver_province.png | Bin 816 -> 775 bytes recipes/icons/vanloesebladet_dk.png | Bin 300 -> 262 bytes recipes/icons/vedomosti.png | Bin 1035 -> 1007 bytes recipes/icons/veintitres.png | Bin 119 -> 98 bytes recipes/icons/verstka.png | Bin 2032 -> 1756 bytes recipes/icons/vesterbrobladet_dk.png | Bin 300 -> 262 bytes recipes/icons/vice.png | Bin 657 -> 649 bytes recipes/icons/vikna_ru.png | Bin 1186 -> 1148 bytes recipes/icons/vikna_ua.png | Bin 1186 -> 1148 bytes recipes/icons/villagevoice.png | Bin 969 -> 829 bytes recipes/icons/vitalia.png | Bin 417 -> 406 bytes recipes/icons/vnexpress.png | Bin 935 -> 911 bytes recipes/icons/voetbal_belgie.png | Bin 2084 -> 1941 bytes recipes/icons/vreme.png | Bin 411 -> 403 bytes recipes/icons/vrijnederland.png | Bin 3582 -> 3571 bytes recipes/icons/walla.png | Bin 608 -> 589 bytes recipes/icons/welt.png | Bin 424 -> 418 bytes recipes/icons/winsupersite.png | Bin 451 -> 446 bytes recipes/icons/wonderzine.png | Bin 935 -> 888 bytes recipes/icons/xkcd.png | Bin 330 -> 322 bytes recipes/icons/yahoo_news.png | Bin 1795 -> 1765 bytes recipes/icons/yementimes.png | Bin 740 -> 659 bytes recipes/icons/yomiuri.png | Bin 239 -> 236 bytes recipes/icons/zackzack.png | Bin 2215 -> 2194 bytes recipes/icons/zaman.png | Bin 440 -> 434 bytes recipes/icons/zaobao.png | Bin 1104 -> 1089 bytes recipes/icons/zeitde_sub.png | Bin 602 -> 576 bytes recipes/icons/zn_ru.png | Bin 492 -> 449 bytes recipes/icons/zn_ua.png | Bin 492 -> 449 bytes recipes/icons/zougla.png | Bin 799 -> 749 bytes resources/images/apple-touch-icon.png | Bin 15195 -> 14633 bytes resources/images/auto-scroll.png | Bin 822 -> 778 bytes resources/images/bookmarks.png | Bin 1970 -> 1864 bytes resources/images/books_in_series.png | Bin 1019 -> 1015 bytes resources/images/bullhorn.png | Bin 2616 -> 2603 bytes resources/images/code.png | Bin 1577 -> 1493 bytes resources/images/column.png | Bin 6855 -> 6250 bytes resources/images/connect_share.png | Bin 5682 -> 5654 bytes resources/images/context_menu.png | Bin 2710 -> 2708 bytes resources/images/copy-to-library.png | Bin 8397 -> 8144 bytes resources/images/debug.png | Bin 6831 -> 6821 bytes resources/images/devices/bambook.png | Bin 18788 -> 16805 bytes resources/images/devices/ipad.png | Bin 61808 -> 56413 bytes resources/images/devices/kindle.png | Bin 11032 -> 10109 bytes resources/images/devices/nook.png | Bin 14373 -> 13665 bytes resources/images/dialog_error.png | Bin 4524 -> 4325 bytes resources/images/dictionary.png | Bin 2888 -> 2797 bytes resources/images/diff.png | Bin 2679 -> 2678 bytes resources/images/document-encrypt.png | Bin 3798 -> 3790 bytes resources/images/document-import.png | Bin 1668 -> 1647 bytes resources/images/document-new.png | Bin 984 -> 974 bytes resources/images/document-split.png | Bin 2028 -> 1973 bytes resources/images/document_open.png | Bin 2023 -> 1988 bytes resources/images/donate.png | Bin 3591 -> 3540 bytes resources/images/drm-locked.png | Bin 6299 -> 5959 bytes resources/images/drm-unlocked.png | Bin 5842 -> 5653 bytes resources/images/edit-clear.png | Bin 2940 -> 2939 bytes resources/images/edit-copy.png | Bin 3640 -> 3553 bytes resources/images/edit-cut.png | Bin 4880 -> 4871 bytes resources/images/edit-paste.png | Bin 1372 -> 1244 bytes resources/images/edit_book.png | Bin 2773 -> 2723 bytes resources/images/embed-fonts.png | Bin 1218 -> 1197 bytes .../images/external-link-for-dark-theme.png | Bin 1290 -> 1226 bytes resources/images/external-link.png | Bin 1290 -> 1226 bytes resources/images/filter.png | Bin 5857 -> 5430 bytes resources/images/folder_saved_search.png | Bin 2793 -> 2791 bytes resources/images/font_size_larger.png | Bin 3203 -> 3202 bytes resources/images/format-fill-color.png | Bin 5813 -> 5795 bytes resources/images/format-justify-center.png | Bin 1668 -> 1465 bytes resources/images/format-justify-fill.png | Bin 991 -> 903 bytes resources/images/format-justify-left.png | Bin 1126 -> 1009 bytes resources/images/format-justify-right.png | Bin 1120 -> 1020 bytes resources/images/format-list-unordered.png | Bin 2221 -> 2212 bytes resources/images/format-text-bold.png | Bin 7025 -> 6790 bytes resources/images/format-text-color.png | Bin 1666 -> 1625 bytes resources/images/format-text-heading.png | Bin 5986 -> 5521 bytes resources/images/format-text-hr.png | Bin 232 -> 227 bytes resources/images/format-text-italic.png | Bin 4145 -> 3978 bytes .../images/format-text-strikethrough.png | Bin 2183 -> 2168 bytes resources/images/format-text-subscript.png | Bin 2617 -> 2599 bytes resources/images/format-text-superscript.png | Bin 2630 -> 2611 bytes resources/images/fts.png | Bin 3193 -> 3190 bytes resources/images/generic-library.png | Bin 3209 -> 2939 bytes resources/images/grid.png | Bin 8670 -> 8375 bytes resources/images/heuristics.png | Bin 5245 -> 5229 bytes resources/images/highlight.png | Bin 2803 -> 2796 bytes resources/images/highlight_only_off.png | Bin 1225 -> 1181 bytes resources/images/highlight_only_on.png | Bin 679 -> 650 bytes resources/images/html-fix.png | Bin 2845 -> 2841 bytes resources/images/icon_choose.png | Bin 6486 -> 6435 bytes resources/images/insert-link.png | Bin 4436 -> 4428 bytes resources/images/keyboard-prefs.png | Bin 4990 -> 4573 bytes resources/images/library.png | Bin 100612 -> 89485 bytes resources/images/list_remove.png | Bin 2951 -> 2796 bytes resources/images/lookfeel.png | Bin 1666 -> 1625 bytes resources/images/lt.png | Bin 17420 -> 16984 bytes resources/images/merge_books.png | Bin 2815 -> 2687 bytes resources/images/mimetypes/dir.png | Bin 2456 -> 2397 bytes resources/images/mimetypes/docx.png | Bin 11911 -> 11886 bytes resources/images/mimetypes/html.png | Bin 11064 -> 10998 bytes resources/images/mimetypes/odt.png | Bin 7496 -> 7126 bytes resources/images/mimetypes/opml.png | Bin 12232 -> 11443 bytes resources/images/mimetypes/rar.png | Bin 11802 -> 11774 bytes resources/images/mimetypes/snb.png | Bin 5485 -> 5409 bytes resources/images/mimetypes/svg.png | Bin 7180 -> 6965 bytes resources/images/mimetypes/unknown.png | Bin 5203 -> 5030 bytes resources/images/mimetypes/xps.png | Bin 9163 -> 9068 bytes resources/images/mimetypes/zero.png | Bin 3270 -> 3237 bytes resources/images/mimetypes/zip.png | Bin 7663 -> 7335 bytes resources/images/modified-for-dark-theme.png | Bin 3727 -> 3715 bytes resources/images/modified-for-light-theme.png | Bin 4055 -> 3990 bytes resources/images/network-server.png | Bin 3234 -> 2974 bytes resources/images/page.png | Bin 5382 -> 5374 bytes resources/images/plugboard.png | Bin 2341 -> 2335 bytes resources/images/plugins/mobileread.png | Bin 445 -> 441 bytes .../plugins/plugin_disabled_invalid.png | Bin 5368 -> 5239 bytes .../images/plugins/plugin_new_invalid.png | Bin 6245 -> 6221 bytes .../images/plugins/plugin_upgrade_invalid.png | Bin 4870 -> 4479 bytes .../images/plugins/plugin_upgrade_ok.png | Bin 4429 -> 4295 bytes .../images/plugins/plugin_upgrade_valid.png | Bin 4557 -> 4525 bytes resources/images/polish.png | Bin 10607 -> 10059 bytes resources/images/publisher.png | Bin 3537 -> 3536 bytes resources/images/quickview.png | Bin 4151 -> 4147 bytes resources/images/rating.png | Bin 11183 -> 10791 bytes resources/images/reader.png | Bin 1776 -> 1621 bytes resources/images/resize.png | Bin 1500 -> 1407 bytes resources/images/save.png | Bin 2316 -> 2064 bytes resources/images/scheduler.png | Bin 4399 -> 4395 bytes resources/images/scroll.png | Bin 3828 -> 3787 bytes resources/images/sd.png | Bin 4565 -> 4250 bytes resources/images/search.png | Bin 4136 -> 4124 bytes resources/images/search_add_saved.png | Bin 5885 -> 5790 bytes resources/images/search_copy_saved.png | Bin 6079 -> 5851 bytes resources/images/search_delete_saved.png | Bin 4544 -> 4531 bytes resources/images/series.png | Bin 1019 -> 1015 bytes resources/images/smarten-punctuation.png | Bin 2029 -> 2009 bytes resources/images/snippets.png | Bin 2189 -> 2105 bytes resources/images/sort.png | Bin 3300 -> 3164 bytes resources/images/spell-check.png | Bin 5017 -> 4995 bytes resources/images/split.png | Bin 1383 -> 1339 bytes resources/images/subset-fonts.png | Bin 6696 -> 6318 bytes resources/images/sync-right.png | Bin 1965 -> 1813 bytes resources/images/sync.png | Bin 2853 -> 2619 bytes resources/images/tb_folder.png | Bin 3867 -> 3613 bytes resources/images/textures/dark_cloth.png | Bin 19845 -> 19830 bytes resources/images/textures/grey_wash_wall.png | Bin 44139 -> 43718 bytes resources/images/toc.png | Bin 2363 -> 2358 bytes resources/images/trash.png | Bin 2998 -> 2908 bytes resources/images/tweak.png | Bin 45796 -> 43869 bytes resources/images/tweaks.png | Bin 1650 -> 1605 bytes resources/images/unpack-book.png | Bin 3473 -> 3469 bytes resources/images/user_profile.png | Bin 4536 -> 4521 bytes resources/images/v-ellipsis.png | Bin 1213 -> 1208 bytes resources/images/view-image.png | Bin 5097 -> 5084 bytes resources/images/view.png | Bin 2990 -> 2867 bytes resources/images/viewer.png | Bin 39065 -> 37590 bytes resources/images/vl.png | Bin 3173 -> 2877 bytes resources/images/width.png | Bin 1443 -> 1373 bytes resources/images/window-close.png | Bin 2627 -> 2512 bytes 768 files changed, 0 insertions(+), 0 deletions(-) diff --git a/manual/images/cover_browser.png b/manual/images/cover_browser.png index 71b693537bbf6dc2d4678d11c1ff8fdc505af3cf..6d475dc7187bc2986d12f9caac9a1a08737a1401 100644 GIT binary patch literal 137480 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

CeP}0jnBVai!A!25#|$NR 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

6%-(!m(;9HuXU7SqIN) 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

6J(f>3Z2OzAl!&X7@t6_$2(5^`%}59b+KAufCJK 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℘3_GmV=7rtxE=6RX5xTQmdb?S3(YrX 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

~>!2>QWGTH+sZtqz#7!_T+BEQ@k` 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 EKf#%5VE_OC literal 408184 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 z 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

F09j|8^z{-z!OZ{`n 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 t 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

0Fqi}TgcZ#TVNUrmzL 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 b@C`lz*lQ*2&WTYh00000NkvXXu0mjfPqN4@ diff --git a/manual/images/custom_cover.png b/manual/images/custom_cover.png index 45c3214cacd12b3f182c6e15453d189135199a95..8c40e17a375414dbdf9554d151f9dcc3f51c8e13 100644 GIT binary patch literal 97848 zcmV)SK(fDyP)g1N{jcl9m%8gb+dqA%qZ^ z_I`bYuo(hiP%JzkpcQfEzF>kBmLV0AU1@}!OeieUV{>_17#kO;F9N(UtaZea zB>C@6--L!B#Jn`V-jfKA%EJ@hH|7HZNf=F_T&xkTeP!q=ux5RI zsW4F{bk{QGw@QAiNQAmXtnXzE1ZQ)RrvyUvnrKDXkW_1f32R=sjs=n!YXx_A=4kbJ z&yXiL7bjIPktVDwSa>b6gg3mOQJ@&(p+J>*P@ucXwAFjfDKtKT81YY%%kO!6y}rC( zpPxQHzrI=TuXb%&w6dj3mk%br!|=byPZf&uCkpez$y1JAANPT#ME{&0=Kca$yCqB0 z!=B8|`@_TVGhN0)XsAY*>0useG#W{kY#C(PvSpM>hL9mlk}a?;gkkFG>1pn&F0M;; z8GeR?@bR}XGks@e#I1?$j;_(vOjX|-8Rz`xoR9X-m20hcz3W|KBp-YBv1i&k{Xbkv zi_1rDmi@%Jk4?7L^RL`LI{Gi}pUsaa^SOEK!p8Y--uoltS+TOS-n(f{TenTEk|>Ie z(+B&f<2zv(RM&;iz3TGN&)z@oG=t+5WL zH#RpeHpV!xG)N(eIFc|7ty0QZ>s%B?N`Y-@~P+E`e6Kb`)1s^dVRoJzsJeJK95EX6W1XPgDTXm2|}GFI@Cc~ zGnp+>fufs4(AEUn5$9cWn2=;4aTrrAmbkV=1tCq{l4Th|tXVAQG_@m8AxbHf(gb0E z4xlP)f*>H&8fzPr)@ZE>0!^pe$6CX(SfT)9TKQY2ET%PpA|xUtKnI$#s%Yv4<67#r zMgdV6k#}>VG^VT!I&h?EjMfP#O&mqErWFGUbck4jZGn*1G;-6ma)hA*6isVz#u9}A zX`0D;PKXOLhRmLVwThkh70~;ir6lI)qtyrm8U=RdOX@{|vFp89E zY~Y-;-EQu*)^532>b5n3bxw@jw5<)pNM~tEl0+udP?m*tPFqxQKjNpp^;2z&h)6Yvs0>Kq(gl+B#>OqN)0IT|N~jsn%hDb;x}wmP@%e zI#5_+Y*D(0P6f3wt=2(c(90)VQH~goC#&Knzkm%VxS=mp{Wd7B{(XAAOZrE4&|cv2VEBP zB?=2zZ0m?rh_RJicwC4I4QMBuzg*U69iW3iHgVe;qBsKQ#Hi~U;#8pmO&n)fTZ?Bs;9 zYSB7kxfqE-cKho+&#sN_rU|WN=h#|*uf-?@K<@3vRbs&d~XQcv zR#sQQMeOhIvn&g2SrRHu-58wK=qRGqij}ni$;vA8$?3oHg_qv@WuW~X*Kao<^IP+( z;2+9>emlAre0vdI+1mJHD%01qPRMF+oivOX#Devs9Gr|;-`L=>tqmemQ`&?m zNZ8p}BZ&fry&j!T#-%H}?3~*|$A+DqO@hd>eXdX53AuXx0@LM)XP&-BwU}~lXN@>) zxqfYnwko-D@dD-SjHe!d3>w2=Rg-pItHX?HIcG2!P!uy(RtA{5rkjS0PRE2nLR-}g2YoSktpWn4 z#M62Y*|M_IVRK_mHq^PDE$U@S7-VE=%K3Afly%Md^A`vL#m?>)fezT&*+2)->kWwG zK(0lvlhf~IY^?RTa_NG6md*7Iy4{T7+K|CopUsU;!Z2WSYn9#IZF=1<{eDgoMXauE zlI9&Yx3)<-uzP-uAaq>1a*>vbYmZ$ZP9s(})(NwS^H(n5wBg)^O@h#H{`?Ns)U2%c z#R%8d*TrLRZLN?bnqI$4oCIubt%}Ef;>J~q`Gj-lwtKSL0jb~F zhzP}iu3r2Xs@m;lA@l=7Z`j9}h9tDKD&p4O5l>w>&qh~c0s}5#Fo@XR9??^d7}xYWF~eR;SucoHNVlVDnuwLPkT6W>uY{mm!Z;(0VKB^4%914! zS>B}+2gKTt<`JQCB(Ww6HCkDMD8MK~7-^Jt#EB+A5d|?x9200{BPJPiI|E@(y z%*s}WxTlD-l)M|Wx}MSN=X84^N!n#)qmK?@V`Cks0@5^RWu;HI8xzOkxy0b}JRs|5 zB)y2ubA2{9`)F0O((mHJh~v`<#d7l2-s$}2cLE}Qx1XzjNa$MsPKM(b(eL}wPyf3Q z?jQVY9BZDtyv@euD#d(;Q}EjDBleC*{OG4{P%mc8mVvnNwLwZ6#iVgU-PA0Kf>0|Y zX%9odq9aG7y9BPKG!1#&MXNb++@)z7oJ*+MIVcT8vSA9zZ(YO-8Ljiht?p^-?oMzj4`$(%K}URNgiR0A&F9qn_;Smwk=rO z7^0Pu@N8L5Nb@enL_h_qp_BKS&&NbjDt{A&mZmm@VN4AWH7u8BBw>zrDaJagszDN9 zjL-9em{2?M+Ri%hN=cT`D$8!wbit-=9HgY<45=|0mDyMn<-Mf79i7PyNrAr*8 z%*G+Mtaq`jD`F$9E?%Qy{=7K}oNAhcq z%=3gG@S)OhEy1Z)@_=5ikJCnySO+-es7!?lBbG&p6VDi!=t~7O3qP2s-$-!Wpp{k7sGiX@M7obwC)hw4w zpT9H$eyW5N)mw&Ie)i`@2~nNZCp!BBbGHG?xVPb5C6+ zY%7*+$jN9%TN?VE6csq)IA&fn%;q)KY(bV<(o9iQEuDOTGE2&3i`8-=1nt^?G!tVR zu4LT1c?3X~^w4OZEYBO+xXYquel{fuVqB2nbiiUVexIfc`-Pg7Zzvn5qqqg;qJj$%2& zHV)Hh6q-j5&S=Vp&CQ&08JBqtx_SX&WCnRqK2BoEr(-$P^B zT;1fockVDA&%}_5rDZWGsEawBP7YSFm`&t_MulW?!pX@YCkHJrzHpJV<1-#SobZ`X zKg-Hsz|qM_!rn+%5{5cs867XF3Pn{Iv{oGLA9HrNWMgB6(Rjwg2V;^rrL9}q+A*C@ zv8|%t-H?BePA8NLSk4VmYMCv@?B8FqHtaDS&)M6Xh|#oFOSN3$1SASEZ7dN%9_5(U z2)K~NeU1-D0<;WQ27K`D0nRw_=1paY1f?}3aYhtrrjr@Vd5LXmmh&a!SpjGX>#JfR z2XkWrPR~wX+CQ7W_d6TVZ%5sJC!U<o zaxiLmcyvak3tRxo$FCGkLsfe;sVXa3moDxQ2N`FhQ+6)pJpICXy8VbC3K_14T)fg{ zb*p84HDTE8vU_!dr7LlfWApq57p`rytmjnCf~Q`1oXv|na!QX+#`HRUBCS{(uF%PQ z=m-g?X5&U+-xojiIM<&(&$1q|eX+-tCx#Ge#$$uSNb(#-9ocxjPLGX^0aZ04O~+FGG3aId$PYiqGf!=iMwXRf#P#c&l1p8=wgD>OiD#||d_U}EJn>|YXI|*D zvT7Nf9CP9PHrF3tVK69(f zn&C>y`ev7({Hc%g@lRZ#(@6+)&Erq*vbMUxg^L4LS0kQ#?gqV~foROuxgpn|+~%3* z&Jo46T<<^q7e2*P&u+54HDqg}$7eqCoa7^){Ol7v_QX2RzOc%r%RN?BJ3RU927l;} zeVR}Fz-2lshI1GD{Jx)ko)j)|~8*=ybazVL}_0C}n7xfG7>fL&eH!!208x z#F6IJSMJiZmZP&V&V@XFBO{Fh%6W-yGtOPec<1#Q?|$oqXP&!CmKAi;hGMp4tl(_C zVDG_5Zf;SGIXo!9Ra`v3hLTWcjrZPuNZRXg_0l^1tYQB&=HPI|=;(~Y<0J0fK9l#j zvpb|+8f>Fj*ctsn&fdY82lwx?yL(B(zRj(SW?`v{nmhLn*nhZWeb{0Den{h{^zseD zV8-sbRo&eiA!&c>%4jSS^nb8>h_QL`R&R1W($NI3#`Q4PsbOL6D zqvIJL+&kmsq(Rx1S6;fu!QLLLy#edJb;4|)!%0k4YF_&4+dTgG6}p3l(PRNlL@_Uz zno~x{3zl_*a+dY2b-IHtI~PLc3q#XXv~5YtDn(VGj0J-RV!(Vm-y!thqrtD=rA5c- z@19JGJ~}>qn8jfmXe36S&P#$YV>X*&8iO%PkeQ}#r7-D?4-2q{P+6AK3Fo$ZoZC#; z-Ojjnqfe5AoZC6iYQHPBg2iGXu;-Ic?Xs8`oE;eg`kalqMDmyqFM037DElMm4SZd9o@6(e6sda?!FJx0J<{)%A5A z?TtA)K4EpZ#@Xo+i^Ul?p16RiYN=UV*d4O7vrT`n&ea>M33$fh1} zHaSMe4Ua#59jvA)pf||a*zB>jwZ{5Jk8?Y##NmYPjoybVWqfv@es6`tqXVjT`k|Nj zEbTnRR%5PSILD>)8>|cs=e9Gp`>R9_nx*5%f8tZ5-31SBjR}lnb~Zw{nm_c1USwx8 zWOP`tI_&WHjScc7(a%=6ba9tnXO+>}f?Id)as9DttgUw?3`Hf}zkkH)%HMyDb!LlG$*;QE2A3|a zv%cPA^TXBeBxHF+;`3O^S)(|_whhWSszre{Mgfsugnki#U%!hHsqZES3j87j@AAfz zS$%$c{qt>AZ-ha>`szRsvH7A9Mu4*#_P zB>&qRTi^m-d-XoWvXta_b#;a1azbMxZrwTN=wQzJ)&^nH((9+xrD3odkt7kT-4%`w z4mdrTQZ5up&}DsX9UWE@BE0d9>kl|NXmHeAzOv2AO3r9}!b@M> z2e;(;kFA5rS>KE}cy!9%7H;5vxT-v56 z<}8+$`C`FjQj_OB;wT`F5|)dC`K)4IE^i%-mp1|PyDaM$=67HuxPO!!h~KFn$T~Zg zJjS#vmP?GW0u$#+OcX>?EzUa~NqUW`ue^jEvfYZ}G zvbX~(5(>|y$5yEtN80W2>?h9&blmCYynlNit2^{oHaI#ylG7&6Le54D&Q5AcMz^+y zM1c~IcjfYV91RyPtg<@nu_#9T=#QP}`jvHNqms6)$kUiK25lS{cUE}jsi$~wZ$zkj zTz_nh)y|#0#FZ>ADA(fpW9MkvLQdJW zl`Wwp^#(Cbt!RswI0`X#OjVC*iV&Hr%{@K$ye~*4CITPk7NTK}ll`YOj zW0Jh&nWxUt4J{vg;RWTwo=}(*3k;{!Ab4n^P*;1*Q~Gh$vYX#szQfQ%%|A8W;k3I zWRISRw(_JG~(KAlK$X?v4PJL^PlmE|}>y9wPa zB2E&{CN-l;gQcdZ&sdgYvOJMQ+M1HK(m1PF*^CLIn&Umqqx&P2u1Rzcnm~%hw{IQr z(#!9W4RgAkkWOZ#&ZFFnJhyba5eJ7S6h(_Q4NYBfa#%o>2=8t(YgrUEp*E6u4|^#I znr_cZ!XCyMrzcC66Hk8%u-HaRZgzY!=iPVqc<|tiFzkZUC|fd{jIpZX-1$_{wbQeu z)M*rkgOdr18Xi5Iap(3PwVAPd{sNtqmb@P_ISFa2F&EG6a{kg5VYDQQU@>zj8wi@Z zY#K(>8N~u3<4{3Me{G$ub;mI2&`rB+@5T&!1I~^tM~73AfuY-1+`4r_L>Fga@8Fcl z;gl%M2~>yUqf=(%g0^TlcPR#KIXx^0lZw-EP1!oyMv-(pU0!_d62qQmIfb(5@#t{EZ0d6$hZ6Lu8-(KLDtBmoq&DfJ?vZXAnA z%wV{Ii3=vnN>0Jm)tF8<75F)h2V|Xu@nlSEL!#7>XPP|AL0gW-XXvz+u2y(oRFx)l!Se(~wxlI8!kkrc{;1p;_NdsH!QWvl-_vY!C#QgspLy(jRm< zIGmwPmwq>5F|BAyN1jAX&uXe7WiaS7pN%Q&IV(Gg;mRgwXAQGvMidXZ^}#6z;}JG= z>>Zx4cdwFbd@?I&l;!fpHL4PB-JYUUL)lKbe|JpVCUjSzSS)eN0o&;&ohYIgRTxup z>*129XE=8;Wpbn_o0@zzl^Wpv`^SV)&R}JgqNq4JIU!F1I#D7A?&)#EGF&ZtoaAS=iN!Z@*h{vnuLy3E9JENWly!7e=wzgL|IXL9* z;fQ{kvK*J(xjSd)oZ-2vk7GV-cxP`>F%J(4>JO$|z24#eqn4vXC(MQ@h-qxe-Gc=$ zzId4%PZXSgCgIg@&RCp*T_$9?X7BC+_MIskgC6$|-ea~foZsH&%7tAXy!R@@m2Iv) zvBrb@32BlsI%(P53W&0rrghx9ea50PG^Uiz)y;EeWg$h&$>e|^y!rxv{Ey~jeZ#U2 zdG#A3*0xRw;)AX*vOJC1Kf2HF`>D@Ew_$rd;@(#eP;HmdqGnkY zl0V+QIc0S-MY)Xa^A~yW@F9z$C5dvz^$F+B^@RWEw3WJ1e-N|1y~h1}M<^SKmvu_v zoFmOr2&1##>Un-Ear!&xc>Lotpwr3ZbZfZf+Eyz|E4(JGWj>!%6g8`>1A;`M+E!>6 z{o#PRfq6McX#{dU+&f}&q8QD_!e1(?lI3K{^~*b4yw+nSQ&f|bU;5&kOzVbj+TrBH z(zGR4t{6V~naj-PZ*lwmQ_^mShX;3<9Y$0y|+gv-eCKQ z9Hn7>BV+Gy%KP^YAkn=4{+#{eQ?6c4$4K}CO?3v1_aiS9$mN5of0hjt|F#kA*zEdy0-aaxe^rn$P~=GkopKx8yTM zVaj~*KIhI|VRv_(`}dBi>Ik#6l3xXpWjSxr1XNYR#Y>yKIV$HzgqM9J9BhoI9eD!c)4J+6Y#{j zRlf21l!Nh{;!MGn73#*aShmcLGs4n-n15d2ul?MN|LDK`HC)iaELu{n`1B84B##qj z<0*%S2U7BI+H!QT&*y&llU%=cnPzdu=D9A*s^#l1Js?UW3`^p!VtqAdVCG>IB(m8mN4X|M)OE6H8!YUbra2#U@*0s@qY zgm2azWH_fdJ)RR~9S%ij*PrCtz4H+_E{VD`Q!i8^NbfQM+alBo!j8E zpS{EneD(rA`%};H)b$l~nDWZ&M>N%xKl^8Xh_%i-@4T`vxz; z?2B7G_v~dJyMCEV7j`&&c$a{PPTZ&0Z@K=&Im$|Na#UjLl;@tj$YVD)xc$LBVW2=VNH50w@+$aFkG0e}X-{!!!y!ha-ft>6BYhkHk-+pDXmPMa+!S=Dif5h!Or!1DIJoWTBe*e$@ z5bwQrlb`RXkUMuzc>Iag4-MxW@4sL0cYp3CpZwefE^P(ey=C~-U)$r( z-hw~&r#{I~{Jv{Eyu0ABr!Vtszy2zRkBksWhbu9Il|GG`31j*Pe(*U?Pmg%@<+u3N zU$*@C&s^u9`wKtD|M&m(HU7k(f1ayXR{8Z`dzDvT+oP>w{@lOtIYQm=?tAxn;`u8) z`Fz4_uf6x-<3FIScKPh*ce%W?#((tx{sw>YpL>QM`mqgu@$bFQ7ruB8nuyQ;;FE-5 z$*=z6OI*HkfzN#QDzAU=0bl*~6aL+Q_l!^e&-F3oGcv@b3Hfc>mo)PRJvR4yYU#)>4*>ByGteTa7>)X zZ0u}MOh=p?PFP+4`>(meqrH7jM=gzQIJev9#y}Zfo55~gj%6f{{MNCIiR=VMb zUg-IzM@EBUhoQ)^E`tnVV z4-0}IXZK=<8#gw2bZ5fD2P1Yab=lrsj@duIjEhI z@8WMVACPsdMJfbNzNzxID)p*=;>-IxhRq)oU zhp4*8#@ZHf)=<|aZ@zK}R)5H``xKMWm{(uAMHH=&PTVvKP&he2 zTidd>n(@Sw7v+GM%w`l-BekafAYrzcQCE)XwBqn^#-~2_1jB)r=w|=nh>evUKK;3K z1d-$J?L!HxC(HQXH*Kcq0{_Ta-0N@{uk^S2ZXlrBjPbkbP76!epZ)y@o zLc?NNkR}Oj)1s6jiIpH(c@{G}Td=m8ll3&6UPl`3vQEUM>pNVyw8dbxOArU#|6s(` zD;Ie7g~vELc|g_)1Y$ioJ|a{d;;1Ev7Np4vSOhgoW1n|9TjC3>5ifq?G8;Q7Vc$}e zbLv8ghf<*?vbSa*tDjcEi zN|Tv#2Gb@u8{(AZh0mO0`+Pu_r8I3TUNTfYl(GbILmcPw_fhPmNT>siauH1h=dL7D zH+tix`=njXyqHM6D~Y<;))K`Hikwg!O`hkoU43mL=)2OXZ*fD`Z)bH!^YMI^TkpQ!Z=i*Anhu`u*1U#$D&t| z=Q+3r=fIi-6zrVKId`s$Z5OykF&-bIh~&LFJtYis*&|9-7~232t*sP)3E+i~U1n_~ zCGU0cgwTfLy#t(y+1b6y=5|hZ7|CZoJ$*=T5VCj2@$NhKxN>7a5_T!8TJqsmMI4-s z-Z>i0?*q+0ssV-n7?oO_LTi7;tnXn_0afdm&F2_tegaTfV{x{on|H-4l|@6-I;l}) z)0*AQHI7H8($e5u!-L}~FTHle`SlHc-yeR0uYLV}zWT+t==V|ysj@gH4h?|{(5efn z!YR-goLgcE&}29aVQzWtwFe*O2XB(Eq-0S?kR+vQ95I?)*wi}JxVFU_g|!*DN%%Pr%RnqRu}KJ~0%Wss0|BWbZvW+{{@h_InWi#|aj2Zm#bbrITH z+1S&OJ6+JnNz`7~`-_0%|D-i2~`NGEU(L zh{KGN@szzsCkzKyCER=T@PJDfA7i(-!DulS#&@|asN0r)f5_R%h<9F_GF)BxFu(g0 z`+G+mKDtG>r&%tHTptAi-{pVb(SZITd&8YpN@Yos7K;J}w9+)!VLYopTos`cohAs7 zhNmckVp))OV)9{!N5^9pwxV3LqL(mONvRK8s%cAC#YF4tJoVxZ4-b#H_1<09dn-gL zrCk&xxgrP^Xf4Kqt+Adf3l6jbhi}@dtA<5Y62=|MnFC$X%>tZ`L3_ayM=J(}RwG_x(xK!{+TpurTf_835v3`0A$gQAB`g-LH7|DrT#S4%$m0b6P)F!g;%6~hXD!}s+#1NHo^4u!E%5IVvTa0-GD+h4R}HO+sy2 zVjz?aUP(f)RDi?cVSfj%MLQ)r1jbpEbMl^*Q{qLevN&hJ1=3t-8!HB5#kj15J6eVE zZ>24vRag~+H3ZflGCESIAd%M&oW&>rC(mn9I3fueDy9xT@mE4z3wO$hQp&{HqUL)yL_JG2m4INBWVxL!UV0s0T!P$ z8{{VV93lwNC{T*l7@Sd92U>2NmcOxBti^A7rDa1pkoIxojCcm;oOl{=1WHRT=ad7S zCn{PaKf~cSwYAvVtLtd(_1ywA#(24>PzPch@|<2$3Vfv;i^Us{1#1cLhT2#_X;c^z zX(xqgx#mjw4X=FLvi3@XfCB3c%K7J)oWm#Q$a{7w5W=c;+iP>nG7u?e%uf;`RW z5hCXQUH+&4F^QeKx~^5-w&Fg<_v|xqePNvzAP!qM*8hdi+HN@mo^yB z&WMr@D;r&Q&Yh#1t};Gtr9mzRKKb#B(otz^c;=bM`S=e!4Y8FbDSYrOH_$#(^W$ zJ}lBGzqz#-oNTO45{vYgM#;wWhN- zz<*|ery~ie9a>qy;E>!*u8-2nzorI-TpL*ebYR6(%HafZ;8_dK8qzoyX{|I-y!4g# zdF!HV1^M-)Nd67bpuSt@419qUa zH&pQdR^A)PSL^VGp%j2O9^;?i;`~FD0YG`101-blDDgUYqu2QN4gg~0Mq6B+at~G=T2>}DuN;uutitP*V%nO$|f8ipw ziOCbi=B8q2cTEV)Z@#rh5O!G>bIB>SwuF%tQI)!q2qXaT3-|p9;rs%Q&&I`LSM4Kb z?7)Qf@M>pDkwSaTzD3GuI=i+fb3jeC6_2E?D4KczSORE5_QBjBk zj8@<`KiPpd#)Tdu2d zXz)e}D62us0U<^Xpe4*}Fvtg%SPU;>S`M#9Lda12%yPdvTFo!j?#{nfklyK8K$ z^_d>$qc132(v`!?z7KOr?HuxTfaNl_;rF0y}L{UK7G_skZ zC=@wX=OEA$cY2rY)iw#VzbUugyMr?UPd#^yATB6H-z}os-=b^^K6w9tH{Q4_f*g66 z@QZ)*OT6**eJ012swjoz*g6Y<6{)0FPEF8(`G{do+5#-Gxr#Tx5F7VCUs zT({V!MLPh8gO4^gvY~x=gA(uOm$BZU0{zkH8~6|f{9`}Lhy3AgF|vsO2|JZ6hyU9! z^w-RY6$NO2deexfwBY5g(Ef3Lb6W@2%i+4#qO3(33vy5>klzD%L-Zch=YraMS@{Z} zv^RVLCC1>rocz6qx5c0pVHn|`BNVN_L~^uHLPA9U+xQSqg97mP0~E3x_yf&_KrAxC zNtO|131O1T0bs48F^-~I^4trT81y^5{n|c{-?+?+pE^f6RG4szwJ<*wwO64o4F(xi zy`)?^q9_sJ&%tnoKn41Hio$&-SJ$>iMPVQp3mr@RT1vmIrdU*{M9W14z!)G9+`efn z{lN-bJ3UEuiE@q(3gS3oR!l{rXf~-?*<2COAWvabJKlQrmMDlOvA8#oz2U$(l>A#^ zjPtb)9Lg&EX|7~7Qebt0WVJq)2b@R81Z}c-&1Zu#jU?`fA>cPZJ~VM26u{wMKk$YL z&f@JWBvc;FvaRHX{xN_rlsZCri~tOha9hcZz=4Oz{lNf&ZUWj7dQbvyd>$YG`TQQ- zu%MlIMKKhd+#_(`HZ}|1h`=8Nh|s==ltBbVa7e4PyuKBfMmDq0eLR$nfB#tj9*}1e z@9tu;q`U~dYGfwiYFbt)}BOccwMBeVoD*yR^@X4YCu z>uw7WgoG-fsT#W74uejPqCrQJ9I(E+F3oYRQ3O#&S(X&bk@VVMy?pUQqU5KzaebXH z{l>eTjwh_HWTGvXd8uAdKEH^fNW5MkH!MOqas57g#38rEI6(t_SZ2|Z7x+zz&z~^% z+u@Y;r!)!xIf7+b5To!R2(*o6Hclw!T8t9cf^tA4p4I~cfKw9JJFNFm4nmNxGTOix zU!-)PT1$wQzi%s8&J6QO!1Od@dYrNxg*f961OSCXg#v>BphG#d#0Vu^QlLWbVLYbd zb2fzrw9lu+sFknXL|&j0jW>4XJ(wmyYe`lBCCfVRiLG-q{?PGZp^|P{c|Iv+C?(z* z@J8Vc8XO>Cg2FU4&2oV$mtw>kMXX{pBGM=VjaRQ-WOuhm6t=AN5}vreO)u{Y`St46 z9nM`GkR%z$#}i?Fq**{y)?hu-s&KvUr2&0M)I88ZWAH!>$vI>J_;AZZq^9*Jp7zKA%Ku&-k9B|(PZaq09j!e5#py=0 z460?u=qMCH!Dl}+=iF6CU0Fc@=aZDC2x)7i5NoYwa-32vJ186BY=iO;u~#f|?xRiW z>p-p7H{0t{&O^GLh$}Z1eDX&g@u{CYAX$LVMUAP~h`iTI4s*M`Z(zK>pIsZ<+;v@DD!4vrk^+_|cTS8?e6F zC+ooaX2{w`&Xubh+<0P5LRi|6BuypSnV!t4O9-Q=69y{$o_e6)y+2&lu5}peBs@}5 z+17Ztv!TIaoRJtAlrRxg8~BS4!Ze=EPob?bw&c;?8Si{+^r6>zhoApDZ*wx9@WeA4 zBHvmYuSwv=;|Ly<7 zP5%7<@DBgnzj2Fy<->pf)IWVw0Ft&CNM2-UP&)A=OlW9r<2Soa1tA-sSd@zSqyMe< z*?s(k**L;9Aq0sSpcFB!zZPX%3&IvSP+}BLl5f@610!;XD9iFj6bj1aXRvQUbIXS4)f27K`TQoe5Q~SzYV!@ZgkhzVe9M zx6TBm-QDdIClewflrmg5DXuSTL{Ew0(c|z`OHVP z8}O)>RTjX56G~A{y8QT`x%=Vfe#Gjk=H7b){_6ib{|Nuv48pSsT<_~-WM4O{;9 zzn{y={ipwxxA>#~!ic~5AFc3(pBvEjGGf*ElQ+-+V(cmrpxIjzZ z=L^~zZ*-yDkJcFRP<*>iCO^|!V_IMKaDc77vHH9Xx_OU>x2Jsbt9N+*V>>+Y%z5TT zOO|SEv!tyn9=;z^xl=Zx2%SJNnxL17Ow$-o<`RA?T=~5M5!82RIKm$wf${}LeD2VC z3P`utrCcr~p$vgA4oR{U+xn0zh!l0z(l!vsh=OEU)DkL@|wMd7lK-V|`0)-Gj!J-27QFY!K1y1|$!E?)STFdgsASe_VLt_l!9}D=K+8UJe z-&;8pjP&9RH!MH>XGi2+_*?(MMgG?RZ-=W-mNdoDhnxB#N#=O-D;t!{fP;G(|LXts zRX+A(Q@-@`J^t?h=Ng?HoLY(jD;EoZ;6oelxr<56AN&{JX( zbARB$htILby>~M|ekC7qdWaM++p0$p6`&TFR-@ejrCSIJnj&RB3HfLK^|!hD{3HI4 z|KcZ^oOD^+Il~ow=5t7rxg`54c3>hrl?IFhqXCqlYrZt#0F6L$zdi~%JxBpyy+_gk z4sbZo9>DP|1}l2kNfb*x&NK%991@=SiG4nT@OhY$qN`}3S7j+bGp6FgxebX>Cd(7F z>oBhsvqeQaFX-kcJp00R7UhzYqqz`_tEQl*GylACL}oMMD01Js9Oye6lEQ_V)-kQH zm^~FGl#5d1tLvJ;h1f<@6|E0D>XMaSKsSf7tRz%gUteQwrB7Wgv2{YVP?QTpH_I5V zbx}b~>m1g@Za8Co)n7KC0R`Yb+KdS2Oo8)8qMZf6{(~?rU_C5Nz_(ce9%6QQYD^$? zj3f?-!X8NmzxFpe{9pdtpX6`;f44z9UikbmkG*(C5W=tg#VugQop*X48p18GHE(~t z&wufMzsku$?LmQ2BA#hEhq?@ed?}!Zt>_ID58qqofB(-v&%gCAewrYx_@DlJZ}Zsm zbDsFvlt1@BdY9qWjKBRK>~i*~$8wTVmkSgP%HO@b1e4R<{=d@1iV^fBcjrQg{Y>fFY!6Dy;Qh!Uc*XPQ-w4_9KoV3LMTGjz=!> z^dwE=X*(YB1^}Eauh9m`Qg|a&8t(}$cvw6DrSOrKqOG+E#6S1qIo7s&eCxFX4iA^& zrTU$a3p?ASVNQ4G7<5GA~VVqBB{ogu5Kd6d0YgsG|KmQ+I=HXprM$M;xe9Gtl$U~OnjKBK-e1cE? z)Phbg?O;sE^Pd~>2miU7oIZ+JS#Q|6yr5i$XhKhX&1Ss+ z&GQV_OaA=->`nd$|IRCn#w}m_dzU55diX()ul(Hs4{r9j{LF~|+Q0bSY%k5R^#ZZG_1nkg~zZvM79Bh}6o!Ge8J@KI(%|=XrU4OeWxDiRTd<8vqbg z&hrri9SAGk`3M3qfTAuOHy+=W;%QZC4i63mfXUN==&9ehG34XVZ}N#xWio{An_qm$ z!J~@3M`K!Hj>nSAMUi52eO>OQHCFwjGH?E|3oGVn9EX8TR1l$+7>$Hsi=q@^khs^u zga{LPUX5wmia6FpNy6!9&az&zy|YRbBJk&IG3E5EgJS`Bc&u5%Jy=~im1gD~*-|wPuJ|}R#gsOb<`w>^ohY>#4aY{}<|9Jf0X$9b) zQ-10if*=yPEv+K@Yt4rn@;11D|McH_it{&4`SCw}ht+dq-uOz)#x~SVz&C!`qf6Ic zob%uPzuh7UJD6JWFZ?gx=h9;fre^^vXuyTIIz|U2y`kdw|Ak`~(~MWYxX$YC3>~!G zeq)vCNrz`Ycf^0=LjYs<>XJ|V=!m?dc;WMpJ~X;F*gU`FGe0q6b*ttN{Dr&n_@f6s z{=|RhJwE@Z?lT_+JpJiYu04B(a-}W4}H6ZhU@GTE`7#+n& z;hghDTS6syk#O+eY)p`NTOcUFi@-OPXcC$)REEd?sqBZ@&E=ouT9Lr+2Zg77@xMiK*rb;z$w3@jq<6 z)BU3wkOKepI{^s0+Z*dmIqL+WCX~FvF*hx>RQ>%0wx&YU5^E<)kq#?`&?rba+&|;B zSKj2c*Y2_`Qr6cuxO8co;Y!HG>s_|buk-d>AMoJ*F`dq;aHhaR(6&V&Z0$ojf^XL= zq~WY6E14J(M=60MtKf@)fKq&$#OV^Z0QuBxYrMEeM zqvZedZ+wFJ)bZ#4r?=R;X!yJT{u7w0$HzW@$}j)N7bQ&XtdkGS~wj6e9#KlsoKUg70mJ;!_+^US9!=Ho8!y}ZLw7vVMNT7yiZ;^Kr)FY#=$5j#>f$q5Oda@X=It=kWJQfBV`8l2=)eNXnrmv?7mwDnn5i-g@gc`-czN+#X2#_wAbx zcyPBQP^;2Kd;N)Rin0|U`pXw@aC&HX>-DX6ZBDT0OlK>{vh zSvhK3P*)YpMZ>vs7bs?>T;#T`1-1>fd)*KP%bP#`p56_lYe@`-~12G@zSpk`1AkcH+bQP7W}3E z=Ne!8O&TsrCv$HN+7fwk!W1jj{%`3lto^SkGKrzuD!r{008~?#| zVR66t7Ny6B4cbJh@qp5YF7Zy|?}_F+(K@ZLj%2Nfh-R!B7Pkw@}%H9t@d zKx;83Phiyw06^}Q19?s7uRGS2^adU7-J5WFHl-{SyBBwPbni%vv8)w>sgER*R}gf05LjEh&cA#zlOVschf7Ntx(xOeZA zB-K3jr~mvuU;o8b{_4MdgUioO*|<1o zJ`4Hjzi`0s|L5;9IgVJ{s`<13+B;YWzx1D9<-+4*w$H^PzJY47ZOU?{Id^r&>UzZw z{h?d@Gymfc_^1B0yZl>!>uY@KXZH9@|Ft z{Qu%Mi%G(JuMT+Xv!|H4$JT|GPyWnpdH%;wOLj#2== ze+MxjLu<{qLs*OdUY12`hYtMV0@nBZD2I0LqrZzH57ql{S$lL-cyuAYzS;Dh(493Gs=IG6R6go8(SSQ`w4 z+}gISTdnF!ug|45H+YWkSPRtnviHMkf^l{t+hmP zMr9O7<2n627BP$@?QnW}$er6qY;E^g&gX2bXIwmYk+NyI|8S4-dlkbxmf!C^+Gl(^ zqd&~K@yrDtJUpN}^CKezW$^AFoC6EqFjRm794#Jo1E2R;|93tzLHlB%Z}9RbCQ%&w zN7jw^MhYOnDi1k_@t9xx>+96@Ds5|}ZTS7ya{k)Cw~05#fa6CQKl9I=^8U-q552-A zPWDzmG@e_$^-|2{g)ui?IO8jS`#NubGoZg(V%iYLl1?w+=Gy_k`M1}3=CeoK`1mQ4 zql|Z6i9QUu9%t{ptAxpnn{RIMLx1@GhaPa3Fa3?@SUtDLpZ@P0@Zyi`@t^;nuX5*& zO>_X?`cj|uZOvc!U%rCoE`Rm^`U&2DW#z+y>Kl|xxcPR%&2L^H=}gfvC=!Vfs-{8s z^&SksI3%aBrX|3mm>&_HwEv3arTAP3jnNtlXzTD{oCg?;@^p3I1uGmtZ(w*)nA#|m z1rI)i!W>Vth@0O7q!>t&L4aM>Agf@)4Wo2Q-alv7E>x?;vSV z4)H))+U2|Fl0x|1hHik0ZCPZ2N2yk~*04=0;ZSao^p)!3}PIf59b*Z3hN52s)W4+vhaZM# zGybE0^9HYeC1iDbhBGNnJYh!GFBzZqJ`4r>{QBP-5=1aQSmE>_B#I3Et%m}leBp1d z%QD7r`I)=aW$>XlyaMV14%oeR#+U!jkT6PkbVu|5`qw|fd{W6YjLAvPzyCk~n0%f< zC3L$=+7?g%!ACH@&wV_HPoq4GK8#T~TG#sDeUv&%X+ew)F~Alp{JU?1HO3gpTTxni zhXU&e5-D!-ZNGqcB8Qbw5&)bq{dc!k_{-%grM{aniBb;trZVz;XJ-TFO3K=^xtB#N6qDI(!F)NT*V_|aVQf$VoE+pbKPT(eI5#CUA=V~nDK?kF6frLHEh*kpd}J8DqckJ{(BB3X ze47X#G0kZW`rB}|2Lc=)wR4Mc0j;S%YLW0Jh<^?Fp^p-LKIO2^vrEp7Cw%g!S6JV! z`0M}S0XkCbUV;E0 zCls}#s1~%#kmWeR>6+`;*V(tH93Six-9*BUP-{YEasCdYK5F1<@GM}B^Hm9E^QEWG zD*(?(Fvd!&w(_)|Z`Xft;(hR&2IpgWd`G3iQeoN}gc1Te0OfMe2g@okLKRGvPragZ6kDPGyFyycOJ3I6TmN-H-3ob_M2JJlj>+}MhDDp@V(gMDB$#cyj zFVvwb2~!~!Sh=-JRA>mCm!Asb7F;aX-Rg>@TN5~qZ36&V7W&$>RbA0{ZxJfi@0A#3hjT zi2?_{h${K5=OdP7&5ftFh~k|8@;|zbvN1pV2OeWIdc^E>2`cB{!IWt+<>b)O?LvRp zN9(1$X5F;(d#lV#gINCe*??4}gTgq&Y*`VRiplYqK!tP%O0>FKm(uNZNK4u*NIfxB&4wPCP}64vd73&g5d`P|HRaqAZDTi`Jku5Y$*3V@!?{rb8jd zhXT{!;pQMlTTgDau9Z@OGunq+p{HHZn2%^uq2zc*ybAu{k*`L9(Vo{9I7MI;CXyfcQAF5kdMgQ|iRJhp;QZBsz=Yg+r{zmu`G9Ah zJxAUNscTJL1su+gXkEr|(4ndpC>=AM6c{TE@b7g9_uF0G-}&{geB%?(Kl#6!Oc(#F z$hBlqz_O~zRZ9|OG?gJpjMRgo)}nhIBG<)IVuKdfrX24(4(yoq)h>sJcj*lhK7Dx` zV-&`ezURJJ<@T+8+Q!kzA}NRgG*0|Kp$6a548~bvr%3XgWhs=6Z+oWzTrE8)QnU-a z;0V6B8WLJ)u{fpW=3D1LeWYI?hNTGbK&`LHV>Mpq)uL3#H{yk!KI3rUA{t7xGXyfO z((&jv;BuKZ?NL7s(zoMLt;UrguagQXH&i|WuS^39kj?D#G>roXN?TBYXmMkl??}}x z)tB1opZRl7@a(mx z`7i$RD_p+Xpf`=$_C5kxIsY&%Y1pj=&+@(47(Rs*g3x=lgXMO^m&SFd=HQmLwzpel#;b9Dk3lbCz~GY z4Gaa!bM#~>4F-p?KE?;<4L}FbO27X{VTY4kNeL9Hl&1+Pz=tFngF-2T@{}JOfK&2b zF)>Pe*0XXkot~0BrfCd1)TqESl#vju@ta6{RtC;u(c+1a*O1rycKf%_E701vRVysU zH2~rbYtw>u!b8*!53)EPu0nv-4O&@GiL%y%2)>yR0*6MrN+pDnd`D_zA;uV4cvmg& z0g%UuEh{kMk$lshHv&9YQm9jbLDta_Xsgj7pJZeTQ-F`3Hy{zuJ$+&}gK zTkqf5d+)QWtAEMr=+6y$T_Nq&RRc~55*OeoTSHZsWGVF0gsQ4VAR^mH+1VM=TF0QD zakzI#U0V#WC|gFS3rGvDT)x8TbjIm;$*?nkw(xpnlEfPPO#+3L4cRoU&v`s_imzFC zZd;%bPyny*=EFJs=D_jo@B+Zog**iXEw5vh2Ju=DYCdY-!>j9f0{~Fq-xnTEmv6uU zzKPF4poO6xprq&r4wTO+(9(3~3zk@aP0ykZam zUZfE*G!+WUCV%Hy972uy2lGn*GlA_R+4P>q-RO*9RO{3{R_&!4}-ypYpY@pYanvy-WO?b50K%HjU4wxYis1Pw}S_j^lJ#3|iWEtAOt(DeFUVJ>&G6zv?b zj?hsITHzYs)8FmJB7arRA#e$y3Zx@d#So`Gw$E>I{>mnY4-1w>&BtE6%4(-8dfHAI zOk-(V0F46pwl)Fc^c~=TjupVbd!1H*0kDk~?wqzx;CHX<4fv1&@P>yh4l@)-U-N`h^?tuzi-HbU)39^-!wpg@dAp#LQNcAW)egZlRc z0F7s32<=7?HK)O8y!rDH69XlXy*$3L&=`enwKphv9&dywK00{36j(rQi~uPbMTaC}X2!*oaiKh;5IO{_=;3EMOk2_cb>kF%2 zd;KQA@VDP5Yy&P{N+eHy~v0J$I=0fbDW-@pyhwX_gowM zcayhD`@PSU%MxRp^m}+EOmHBfO5=|d`9BC_IZZ{1)@b(kPuSaU=?y&__jDglPHwZY z+GV)jm0`9oee-=j@v#fsxO|>hzV!h{%O({#T!q3G_DY)oTI2u6$JoY#CoF=sK1R1K zXu$bR5aQue<4;Gd^6)7dz=;v~P{YdKI58k4D3y4^y7pj&-%xl<7Nk!M%R{=}5Vh~K z@u#{!&Ou)P+YAX`n*xVtIBRjHq9-5**!V-hhhCDGK%zsm^~Q_!Uar>Q00bV^vYCx- zCA^K$)XLG&l2~Z!8t)msC$bp8+bLrE4o~liB-cdP2H**|coYoX0z-;Tj^(g@7NX@p)3<*{nDfQWO?iN2(2 zl4My6Hc}9H!}GS|z@M0a#R9(j5(&X7A7+7cx&j_1_TiBM6<9nm6O={_wYBnKtO7&p z5h@h`jz~v1|Cwn5D|H}=4xPd)#X3RH1U42F&WB}z1FgIcb!!0O5GsPm8>#<1{xQx# z9EM`7jcvrh6+n!qOu4{^U)Ff;8~&Q1B*~Eo0E;&=>qAnVN7Q1_M%we0^C7aM#o+w= zCZukcoWHb2GjF*2{sGT@{CV!*-{;k@F1dHV=Ejqo{O}K76m5)GUcS$rTNC=dTqdgD zeD{D}cZEDhioUbygn1R>lri5!1NzRC?<`K5dU-%^wS%cD(qT%VEp<7^wzc2n7V!p6 z<4BW$C=SHMU%qsKTMx#>DPnkO6f-`Xb9{Qt;lY%R{u+PyXP@Tb{zG1RX-aoxD2#92 z_-xfl9)x%OR-$~cUR}rfP!4UNljJNKOQl+rPGwU?68ad7ifD`_&_0I8dBH`Cm&R4! z?!FWwbRUgtF(|CCARNd>3Ggtkk?-+Fj0YtAmIr)@hx1$_qBIc1%2=N_;3G$zQ5X$2 za72OxHYg)qxBdV?iLpzLWgQ_3e9i(M!2-0;bHrdYvPHcSkTgb&x(T$)EWUgd^@;Y zV@C*-qk@Z{(0u0eKfqu5FTTRB{l+vS;xBjfIn=sEKAICZ?D4eq~ojhY_E9c;3 zO1mro1VD&G<GaVz43%E#X5(D(g=^U1yMeUaA&4fwE5{&#_dG=X^QMskc+ z>P&tDgWsWzYKt)7AK#lc9Oh7`z zZTp4CP35D9D`$7p!y_w~eD*wvyWgPUArqhY>{}5B7iu z7qY0&^lZld{zJJ3(=*L%W`!3A4NcX^_3RG1k_+W|$oP21bPOBYyA<^)uF*1+s;(*m zVxlk#zZV|J{X@#+jfM#d6`r`&5J?WISI`9h{+U zBo{SKQkL^Zrg-O_T&V1oDfq&dPI>Uav3X9(<1rQsIK05$(iDu$5+_25jwH}*uJuv2 zVlf+g&vQ1H8@{!5j=bBYX)ULti6r1j0ysFDozdxZ!5U%yR&_&B6yP_DK->mT(a*C? zw9}^ZG0xAs5%jOA8_~9lW8eF)BaOiVirC9MNfRL;C?yVTWoc@Nu$o z+M(C&NuSHIw$SZh!yJPmQVrVGC~6w(Ve*Iw=dpRY4{;jHCUXwrBoZ%LOs6Q*((U%- zdM1%3>?Ifire%TBj=URE)h%^X0HD0sp*JAa5;!DhRsH}IsWEIrT^9fl0XRZwv~1+@ z_@+rYL|hzD=mOPR2~$<7J&01bgf=F_GbMsv$+Qaj^ZwKw!(RA9Iq#)fXGHO5nD;SxD}=?y zMN&{bU7kt^H%R*!sT(PxIFijUE2i>q-3vr0W8N6AQqve(qe(23rh#~gwI!=7+ql?r z^W+28cQ;v{o>6N>nHAs6TG@;KujVBgA26?9GYfHXhR7-^E5}bhGjjM zd(iLqB*(FBg>_Dlus}o6EYLVHKF?KcF%GDdgl=gVGi)_J8f}Qui2Dyq+C|C6&XCcx zpeZ$*YZp2HSjf@Iln>t9XX`==W`if5-{j&>NIA=?Tuj!jP>n&#Y7!6oINH-CfXO@c_0ngmn;nBTg2}Q17zrexi7+bY~Md3H8BJ|;xf^xBBT+Ycl16Ehp zSsAVgNpd_HadtY!p(u=J0_2-p;%PcbP9PO|O);B#E*K%EG9u3BXOFhTVJx0zHeHBe zL_|Vk(xH|CQh5?F=np;k&@?iN(ujv>z&b&nf}}$)4$Fzm0& z>))N+CW&$$+uh}OJm+{hCDIwO3TXjr6mgQFtwI@}x3mg^G?VKQT16+0BzLR}Loe+z zSXpCrxXNO3O5O=@qJ&hC1UX^gsOC#Ars6c(Dls?#CR!_=me%-CtMN*tm06NGO%}ug zdz+#brKCV>`AiWZrm5xeCG6`&!+l|_p3lg2TFwGP%+;?~=G!TQEPa-rF5LT`}D zsQ0QVsmq-G!!z2VLhChd-Wo|b@XW_Al64a9KRlDo-m1pK!~vdiWAWZ14umT=Iy)m? zBxHFevHHfwCdp1L$@AgaSi-o;WQ22?PNy#u#GP}}coj+$msp5D zI_>0pN!~$5o8UaTvXghjhyrQlh$OsQEEaN-`wG4y>vXAG#dNwLNdlRlw7#(;VeoW3 zV|8@{*Ve?Y;oLBxSe{S`44w*7SXR)q3j#E{_JD#@kY^ns*Op}=NqN~e5{79EYyB0` zN-Lv?JWEhf#CkB~-u{Hf)`X#x@iNBOoNyr5#zxV%Md>~7hFHfqVcuJbGOTlcrfQm^ zeCxa{D-02zTLORrg$yCkI2p#)7>5hgM^iRXmPk~78kB*cjrrtfuXFKQmk&M|(Jp5E z;HR(Rk|{^WidVmMjjw;{fN4@sgb_&)fm4#Vw6;ZMiG&hy;^EiP=uDp18Y761HPGv= zf=)R(+L!#xIY(8`;70 z(eDOiX|2WURb@@y)pYX?O*NybN}?!bd^+OH9(%PK6`8%jDlhS$GkWBm%$)ezcwu`}FcKChV1 z&SXYw>lEkCU!*-Qar^e4cnIeJ^$%lA#u%pqFQ!=6Mo6X74IFDmqlt{xOQSAj(-0*Q zS)ydJH0#!tv>dKy}EC^^j%t6c;t1Oj2d zYgZDsJ(@C>Yo2F@wP6OTBJKv9PR}?#+GC}g5kwslEZWpmCZnkqalaqt9F%F{08=+@=K#NDiV^^c^9RFKp9{1V z!+xK-wJ4Cs4pvs^b#g!xif3P=c1nPa@%T)<`*3Z*wR7jG%SuAhx~zp?*S000hAec< zMq^alm7H{C5Yp?db2>X@b+ymME7y2<=MK5jq@9eWn342WxOY}~d>i2gyDFaQ*dF@mV#>_J>$z?xWm@@K0(ms&RdUo@My;Fg>CNMdxz!3(pH+bdBC9GBkv5EF6QK&jFpXZ zynAPiwXXWTfWiSV9Q0O>j;BN-k{1!l$s7nuv%0=cvYv7@n#)vxKq(Q3w^p%OHsrmS zBxty}lW=kSD(`B`MC-n5xF=gq6WMv-v{8SZ5GpNz{6J?;~=!Db-^n#pBMQnA4Bw@j#SW2k0GU&6~ z>v3Qv-1*>)?M{d5yF>PFp0ThnD-OAwb=lrnL)Dgzoh_QO;CRuZrbpm3E!s0Fs#0>4 zP$}waLC}UM;}Jv!o@b{ub+h>BaPS9>lkiL7u+I3;Z7r?v{1n#upIhIfI}de(-YxKDhgkB&*5O2rc6_TLM<-^yk<*k~&kKz+l*=ZY+zL;laTJ ziYi6%9Yqn;_l(LDMLv8{aft7UH-IJWc0g;=D8-pM+WH2!#k@v$TZz}}A`)$cwbh)% z{V~o)TzhO2XN+{ShEYmyMf31>#4BIFMW@$CX#=Lg*#sqG7ml(r9F3-I4?BYPolF)C zvYdXVBtKZp7A)r{(gmxNP&DbX-hjRNh;chXB_W+Cr>YfI1*eM!76oY(qFg~1g$(;E zL{TiEs?xbRtxMuClhe5>3lBut5aT6}KMLEz2r(RKoeq(|FZ_@# zX&kgJWFEaF>3kOH(WID5Mn6qr48DiYclFa40F17DP#a z2UVcOVvQ1}i35zXB%z2&ew&G{HQI_wkL1WjQKA)q#$r9M4&-lJ;rz8ygasu4&YfG2 zxN@z}5B<;$?%#RH(W5i2US1;~dMV(+U`zJGy*qcge)Y1b_l(XCNIL^D@Lo5Q0mRNZ z^?QX2CvRL%54?CpRh3wmVT|JVY%H^ajP*%v8Y_mYIo3E4z=%W5U@hbRo}w-^*RG^& zZN@C7mc2(6r)Lk?xzOR-wJRJSPI&ZSN+=1Jq1j0hd(g>8o1dd`k^L(+$ z3Fs1cQz3a)GFgI;WTB3Z4nizw;E(&*L#ppEAN`ce1_XMH&$5Je}CdU|dlC=FTG z;q_NO;L3&0hxyNSzV@a2Oi%Y%-Ao0^&9Z{Sqf=BEq2xe37V>ggSd3n>C|Y{`Rl+D} zf6o|@v#t?zNrRO_n3j`zzO1ojC2b2uRY-!_mJQ3roGi~-E(@vVU%R%#t#=A^-X-l+ z+<#Croi9ZBg=oO5uTCg9-Z4jbEORt}CI?7m7Epb&* z*N|-tq%*QwPD%5K!}*xAySK=?85jC1j7Jq^6#ylzE>J;^Yhs)U@zcaRq9ah$Fj zCI1RpSq*Smi0-TlZE0FBeZ2=us2$o_8dD1dTU5Sa7)qTd@ntYmir2G1)yz=371prR zk_U)~(y`~sS!p%!`H0gVHn7+bPesxpd76o@8;$+I|AeZ8ku|BZ6_Jn2Zc>zqZHc ze&TUH`Ge;G8+#ED2-X)7U$7*-T_x=CyA=5H`Y%C$Dhx-G@Nvd1TH>-eq+l?a!6~V~w1St*#*si9KAo{sPuShqr5{C1CWa!YI4)1IMM#`02jR@Zat zddbm#L8B{NvcuKQ4o5SCSsIdfB2&tnvLeY6Z2j#`K-duIlrTypM70hq7Mvo{k%-(o zAkBM%1~#?^Yk`1Xx68r79!OVbjBTA5aukJ}o=w16@~kKCS7|E-*tl528quqcVot|1 z$|PadT9#pt&^T5y!_rPz99JZ9kIqJygM$g<(TKd$p}#U@(9r^SFM~#mIUe*Fk7nZC zhPft5gy|trb|(yct9TS*RUl$4p^83&v<;3l&ro-}(7BNPB(78NEFe!?2i$mklO!MV$}8{j_~RM1Sql5UC@h94jpSjf z^A9U${ZHBhsisxi&IWR`MORrxEQ*TtwZTXIHCd8NCAe&A;a2rJDS6gqf#F+k+@`Er ztW7w4bcBuqPRDa*#{n;X`~oU!sEU}cefe#E_=i5m%4(mCG_FU%5xB*UUHbq#q& zh^DVBDqj&8*Qtag(Q?w)RYPNzXa%86H?K&7lupNx4^qa{F>zoBb&3+n+X_Tf?o?8cjP?>BH$+Z&ke1eTc^p=kh*QC zrZZfqXniLw&iXudJ}-sE^5VxfXzB&O@@qF)U3UyR3TrZgAS3CPQsNlR3T`}hjnVNL zVc2-mY8Xp?oOL>cap-;*$MC%o74Zv>$FucVr$nK{DT_l(2r*w4=r%;z7>lMB&!j{Y zLsJ7jzJ0#gg+~P2yV2($UNN5;k@_U8+C{oO&=lp{_zH3J<$O zvR;>7SmWjgREtxVS;!=Wj6R{xXjMxRJMtu?nG}*IAWUYkt|aNl%tr=Imnd$@l7_aa zMf)y@W2VbWuH~%qA|>6<3jJP0rW`?2lP8+4Td=t@AnG>s0yrEStV!q$U}YmAidqJn zt1K1~)7cDT1iltZjMk2}@hp-s$Rx+rp2;rPsB3(|*%Az#SR+;GNt?4^)32O?C_1Rzt8^u9@idQVP$oV+1Z%qpSsRd zFJ7cMoYG-$6M z02Z`}Ni_aq8%u4SaJ5_`%AO*U*c2WHe&JJ3V{9oQPaa1!O+`0ND2q97zx5us?pPkb zvBmb*kgtB>KA0G*e6k*)iF6>LmQ{vM4+$Sv^MoX-Lj3O=Y$d_XsUWf6xOWt`&g|QPYO~GH24rRPo&_7j@a%bM9$|9oggBjkzrSj zb&w@Agp8(3AvBJfl9hg!E4!P-**bBvBuQIL0znlJw;63aB2ElWMBFuL-o>?Aa>giL zQk#k}i0Q;RSp-Q^Q#DIraukiF4woc-FW!E9dLW>~g^hDu+S+0?9?5}2mZQ^w(40D* zoW4?w%LN;o5t}O^2ghSdTTzzSjr%jAyJw5e^5|_$lRqCI+yU7C*r-ib@`T_9C<8C5p)$r9&Qj z`W$%>a`kG$-h+KkPYr3(C5)E5_3|lO7rT7?b3IN^r%X>`sef5J!#SCG8~mOD5u%X& zsZ63X#5iI8DrebR8;A(}&D)P?jh6V;por6yw!!y-l#b=>j4M~RdGzRj;b29W+{Z`9 z)TO1VdR)Gwc;bmZ4{q(#G@;Z3jPv1?)nEb{sUkN!SxYc3BO%AqGAkFfC|VOqD26Zk z;U_y-Qh}bAtoOP!ZXx<%LDZ!QQ^G7G@Am~x?W5?Vi4;YXPEKttRkf58-@1m=*(obm zKEXE3e(Qr1T(rqrug7`28!Ip1DEW#LSpW71 z2RyiUz_rVlD3*>#5ASnfYX@f{+M*OGHKSYR^9h?f9SI3v{l*i=gsH|x4jP3Ko|(agqH7=px5c8NCXlLp-i=7o zfK;VYm|advDk{Lryr9-8HhUqbO(76&9;6(Hj#h`lDA$3(pqLkxcmn4TS)_B@8w90g zUW`#eEJR3WBTQrIb$VWU%e1IS3({nbraS0im#u8VY;{dIgVkcf>ET`Oj$?}63M;85 zjVBP+ESm)z7uI=fE#k4qpJIJ=jef67)SL6*&O^TP8!u5U_i=8OqISNOAdKiHA(RDm zF`^S8Y=~ulz+~QXIzB`>$4Y+(73VCg1|7sqrb~9#*63v#6D$dJ4MD)NZe?h%A`nAb zwk;~iaIO{Bd|4VX3LSXw=!GgHwBsX|0;0#Ki4zh*t^LQ7S|3 zK(&fAk2yG+Q7l`T_AywGn4F%mIE%TnoUmM;G8k^sm{!`qpLpsbC}e<}R+h+Rk|$Q$ zG8;F9ZTofndxu&S*EZb5j zBA@>B+ohn}#%1g$a%1OxkHds^+#}-ugjG z^O>K>jb4KC%<3T2qLVCB(~QAdI6vHCsNrln@%fo{}4v!COO55GxUEeUJ@v~==>L6kDlbB-5F&d-N@@+n2Ha~m7( z^46^mzwxz0dOfi1Ey)f4z)ydgAOGAnu580fZvz)rBzZ+=AV;Rsf0Cz`671*?)A8*?3CpLJH~3jWJHq0K~9te4BSu$KnNM zMJWH-1zAZ>$`?8{K_6H zYdK+P8J|u?YkX}zqnTR3@q5MtrD@`v6M;WjW!X@KwVYOMYpH$RLM{rTbh*Tukh)3P z-!Hj%d6nTH=lFP!)!_pSk-d*#wz=FD{kMu!?Q15WmX$b4^O>N zCk<6$t_SAfWPlZ%p{PP)3V>x^u?Y zwvYkE0xqR#=KG}xrH4ukOGN|@q3Sr`Ti-ljXS>gnH)5gbbmI<32VJf`*`=6wc=?;} zaekf&oBm+`fZgpYD4R(r-I$6(YHHR2>i3EPnLvR<<7dV|3)DbWHDCkKVB1RI&E8;D zIAy&!Myn3DZcaJBTQM6=uuaV7?tp%Oold{u?)^hf4oaT9u`P-8*=WWOfBp$R{;6~P z`fuLh{;ir}ofkqnp*0%-}^Q9+}CK_WKDHPV$^3L7c^t-uiouTC*3q|F1!P^Mh=Qw%j2-8FU*pK%)*IVO-tLF)-jKBEr{W1sD5$#dI(Up*M zPo1Zqu5*0*0oCk~jpv@=;`tjNZl(t`>kWBlfHNtJvh9B;%JgYhuT?VP8S=Znvcgw~Gpq~%CBw5{}f(l{#8^?H35k-z4{E@4a zw&v|O_UL!d*xuO1)Rw$cva`F&{ksQjZEs?XqiW|guA*6_QqwZ0BaAh_cVw-arm1X} zJEcTb8rwK5j>amEM+-5cAc`gKZOf9$xFpFUg20fZmi|i4qld?|h2r9sM39|>{SoIb z$82tOiFL{|Pw#N+{rjAq)qLzzt3=6+#bSyIB2OdIUdXRC))QK(vCfG1E9x4ntlTgw zgTD;3szyf{deEZ@9iW&jXM|YVQ6o2VYkLPpL|YnyFau(o^@Uok0=ikwSv!$%a*aMj z39SubIj7$#h|stoq!+_j7nr`~e3Ni>{T$QIgxT4I3+MZye|LT>BFgvJJGjNY*J76A zl2~<_+%2dk_qlxSGnmspw_bmV?HfY@2c9{1l`p5?;^eSE(;*9TQbJlcBS{@`XbFQZ z)39MWnKBrxiB9xvQc$=?(8zA5BVkx!mvqyJ04*H9#&rHxBW(OHFBy?S+r*?7)$ z)XGrdl{K#scYIWF``s}or$_wcPd>||qr3d|zxe>9gAOB{ok2&Af$_n!)M{B``1z6WbC5J$9lC1XTR!&;B@Xee2h` zxVcWxDw?ASla(QcOWZkr6Wi4!x=-L@?7U`lyXB2{f0^62-{wcdr@3_HI^A@a&5d>5 zc;`)K(=+;AN0#PvRzqrQXbVdcFHvDFRrrnJCac4xB=`Nb9(kUil-Ib6b%P2VrZn<3 z&r;?^i^CA7zO-Qf{;D0uXy06j`e<5%4m2p_=gX>uNQw6eoWvwJWu=HIVlz6RHIm=> z@Y*_0wDrodLe>p}NH+RpF&146We^7bg{vz}M~<(3={A#c!5{t;pTjI_9y}T|3nI~d zIGfCPK?gf4cZt&ISWfShyDBG?BA} zwt~hC*1G6CzjpKRU9p0KVvZuaBVEWPu9=LQYe}U1co5AKua8Jfyb-ThpAbZQ?~`C zTHstt7#JK5tG%LWq7{Y)UHROljTE(2IDtls8n8}*%V;Vm zA`n@(MTd^N_YQde*(Ye-l)bktXD5!8jXoEz`crCrnDL8$=WW`iVtZ#mQ+w?< zxKojFIFPzc+*pw@YU6|~ipav4&?H#ZV3qaL9dMXd3pv#V+DkTB=NS&hl96Ck#G)y^ zj+(OIyyDHgcC=wYq!hZEFs%;>k|7nAs1uU(GHTthXy*j3k0}gkmQgIG;^F?zU;QPn zZC_?%bC>b#9-TC#2@P3SvD)9kHLuas$3op%SzFB2*Q^6@{E4BC$*AdUZI+tnh+H^ z@Cv9thcYdJPK5YcFQ;VI(r&}&fBHO^ zZ>&%St+Z~h=PUHO4VND~&j;^5CpQca@v(-^aF^=Rf`=Wi_GOi*Z3DLL<*T z)#008bKH6RP$aO5c!{cn!HUa$NMWmnB58<1MO-5nK1dRhG{NYGwrb=QbIL(vF;-z* z2r42DywF9X3`r9D7UQZ!Dc|4JC`33T5?bo&-JU;co#bV%1OXp<>x z=S#AUia9gZ;zNRApOq)Bp;yjvv{cMaZnDNPk8Q3JZhxG$?GLzjc*woiTJG%c5wDbN z?J7niINLkIZnng0eT+})>33F8L5*@oilKGg(v+3_ZrIOA!xc(X;v&VkRs?ZI&`qHU zs0h(%SG<^MTf#8GS}61!M@wWCfdWD>Hk4dA48dBj9Rc8*@JbvWga`viVl}R*3Bphy z^rESyi}(0=&R4$hDnIj6pWu_9zRXvC<1U-)xoRBx7gmwxcfnX*2)hp zt|~_335y~?1;1kmr@m)1Qh&=*)=?#KNZ|F2{pj;3MyZHES;8pr9z!(HmXhd~l_AR_ zjHy|c6P!)CczFXAEwN?E{aaHebI5y+Pkj0uPdv5C!M!72V@Ned+6C}~u^L0wwn77m z2x(kRP*zlJL5K`DmfD7l<R!|Ehn~+8t=Uc=+5)N_JVS*NGHQGet z;W}|wrp#UHcQ{n?}!eBsajhw(TtXTQ*veU^3+YRPt5BbGk`g=V6xj)Hs&pplE zSLO`!oWt99*?apAPwqZWq`FMnnmAnL_@HGxp0asW(_U*i|4;omPj3An7s?Ks&;P%8 zeq%&Ay~Mx!AMZ1Zmc01l^MvICM&lFOu))$H&4vuuEeD4O1mzS|MmekK*AV9^i{k~2 z^l4eyjMZY!$>D@NO{D}-M2@01(&ZaR0Zkc8SQf{i(L&ZVuE5tq4S_T0c1k3)Y>QKY zpl_mk2PmxpWCAxJsA$+|+E9^2vHY!V4OV-BnzAee=6-O$=AE1OdEsN*Wbd5g;J8B7 zC68SnGM!KO#@Fw$yR${0eRpyg2Q12kw7_g^u5((9vBostX`g>jrgQ@;&yvX4a$U)Y zc!*U%UDrZ=Nz+V{@eG;M>F0Ka5vYJgQKQ3#_00{ArzbLwylyI*i6aVoY;GymHZ6B< zo$-ZVy2}s#=y@(&8}R09XY_PWICV}NnzAO266rCD8iO{PI#NWDA_-ICEQ2r-2=naZ z1g8|KicvTT!JKV{=xVJIxWn~xaVK=s5M>qfnIxvHtoGon zl<-a!hBS(3E!bG=GC7QS{naDB{HyQt$N%&vdGy|wn8s(UeBwGEO9s?m zdq9*Au%^%S(UL(EvBnCsYh6D5Cx43G=8!kO{v~?(5r6JyMi4x~zwtkLnZNeM+pO*? zHhQaUt@T+OPMFRz4vzw1$J?~W@$r;FoYB~bnVq9zClK{~R^V`elU5ULEN|XFl3~!= zl{B%UskNu+L}1zskHlF5m*NMWEpX2I_6_HVlNN=gX^=WkSk+QQ)uD&CHA*z&0NS?j zeMpI-Gt5Y({bPwHCP-pjXi%!;_+-LYztNGQ14+`dveDzwqZ3|xea7M5gzen{gF(R2 z(S))HFilJlf|G{DC3%{BFGWSZGq~`~6F2_&Y&PEs#T6UT{X)bQD^bMn4ZGkBwvrfC zlzCd)lBEg7(lDEslrtDr6OtIYc}3GOpGK^2WIXeUHNNqi_ju#Ahg^H?DqDvE3~rLi_oI$mU=#w9v){cF(I~T2txglUUO^wFkJ;+pCFDUkE&Zk z;tYPCk3gw{LOBx>rVV-QBzMu3<7Cp(W+A}}v|6*iwL@}vDuO0w$`K6)L@L6$8CgfO zxijSGLBdym?Ixf8#1rgX8*rx>q1HE9iLUV0Z@$6RSeQR*4v#&It0u{^bK)+1^COxoq5LlmviixOsw zIYAZ>_9OanpEwN}k0;`V(_l&5jcB_)q5X8M!nD>4zX$k%%dsMGP_`*b4<%oVWg-OF zSc|8(f?&1~hLo)j>8wy}e2cbrh#`5bE~Hk_1_mPq5hvZLjX}iX0~HXemd}0iX^zG< zufBc2T0cdXQ*^31TUMm~Ek5_d7fI8Kx8E4?@Zlr2cZLKiM=40N5WO^Le%B$x>U&lP z`ozay_zOoTjdzburFL4BYSa{P@YX5lX)Cx@BMmzLw`vVIb|&eo{h+3izl980m;C>7Zgo+7GB^ z6SD45KF6q8(n#Cz8P;0*`H;!koHjD_);gYIQyPM(6%y%J_6bQ0}9Ser>_?&tB(KpS{L6zWy4`(%}ar8B)I=81#(^}uolQ=7^6DlL zId{(PiD1Xs!6AW43k9~HN!&hEr=lW%W zYmf8zk3EmRc*6Mh9U4`!KWUg8CEOjqOQ9DG#~ohz<|$vfd7m%5W6|9qI!RbIQ*4|H zHRzXqWyEKny)N2s&2m8+^~Af>^@7dKKFhi!AP_m@J9qC&ok-z_oP`OXkPyr%BOzX# z<}!w5J}bc(hTWKI*`lf$c^FeWSlHIfnh}8Y0x2$tX&NJ;pv8V9stDl6owR`0#m-eg zu0sL?Z2)!EN+|z{Pi}Gb>X2`|e3!4k@&TWE?mW*vGosVY>942UzP%?0RA$69mCO(J9^PJA`fO+J&v*=E}yOzrS}ME4(F8Q6zD- zCc)AOP|!3>$t#@jt17gPg;HLW1-(HJvzT*fXN%pvl+!kj1iKF<$WcgM$6T z2W)Me<8wc7gWGp+arfqgBwj%&gT_+3mOSs!Z95do5eE>JjwFo*_70tr*mt(9K3pYe z4bDo@G0eNd9A3{;OjDu4Ts&ZHN_3FP!C;z}P)FESNiTnGYoWB{Dn|DP=-HBbI%1*y zh()LHF@Gn)m@%{3q2z~iG#b?=xM;x>PoC%DdqduM{UP6aBZpeCcjpe*p1zI>H2TI4 zMYD&SIuXNv^R0X6;R2VFAK zdNJAroE}anmJODgI9VZXEuotdhXT_J(q$@ul<{&V9kDXI$hIP4ZDd9_{NlST+?eev7dSsyXF6U`)&?ENa-UsUf^xLVpk+>x_f9A<4q}W%rU zgYyFLp#40d>1=`)QOJmgdvo&6AyHOJQTFDY8Ox<3>r_N(Mp4h`tQsEe-=SJ)&h4D@ zy5C1rrVHf$TI3LvtqW$0DdoJ7u#Oa(<%Gsgr4J@d z1A$z-I)%2vwtgO~vX&~Uh*ZEzqeY7MT0iG}G2yhkRM%be_6zV?lRAA0Twc<-IR$5ZWus8xLB=iWgT6-lqdI}hLD zq_uFI;K~*1PLDgMM;umjbblbi?7C_t2ca!+jiKM);jpckpB8*>XM>1HL{)-8k8xen z&Zp=!Lzjxxw2SkM06FXqtD3#(g5`M1%3w&;I0n6hy=Ed*pbb~Dwj85v!FpG7G_BcK z2|2gk3rF0BLSQnrv80N6qcT$U*Mlhe$s0f^`0fTiN z-g)Pgn>X*Xw$kIXpLvE~{k1na*jtjX%-A_M=cy;QSuEgebjIdZO5JqW-d^GDH{Qe8 zCC)+^L2sCoxt1^tn(sphCkudI`{o|2BTDCS<6^W2b!ckhVI?O8Y?%LA@I)u(MT-g)OC_wJri zRSBJLfX1QQNJxe_KP1>$&nO08>roEC5k!$pe2AANxmEP~1A*C(Mq_$OPhfNvv}|sy za`*Ng)vOSpV;L-28?KSXeol|69lP5bqAQ1K=x8Ov9EF-N8;&u{A!@lxxO5cdg6`@z z=h}$V`2&ouadkqX5ePg}Dcz`IkexB?S>~fN9=@%~s~&Is{5Rv!(* z^*4?PxB3j`THxsYw&3>3F(yoiZGg5=+eEx=;3^COWgXEFa<;IH=M|Uoh^!M)WQtZ> z?moE3)6YFmQ|AKcL}7=+{RvH_m>WeDXYBNrUDPFK#qCrqk`x@AawMIZ@gn%OgJVnRVJ?#Op8LBJX!;nv|q_M&5(Ts6~^qwY|c0 zzQj4uB0SL&T1})SIFgO-ncs%AmtrL6@*?itZeL(?FBNOB29)+nn`s=-%QaUnTxMA) zmdhE|I2NO0Tu-4R->#f@yrx~+>0`r$jSClvqZ6vg5r#c740&9knoL62OII#2ek;US zu-egt4Mr!db$7@JNzQCsFkO-l%2 zD!@(KT70fkvC>OW%Q(9Eoq=tV66MT zj=5L!$>=}qchhn-v7F5pEEg5CdBL(QDe6+RiIXT0q9*v!7VE3qXqSkmS}wFyo7Yz} z-uUJ{@?@1uSI_au&pgf(PYk5Pa&%OJ3kaf$FfzXPzx29kT2wRrE8`rsksvEt6`Jj=L8e|N!^r%%aaI6XXIS&kX>FNiv4NJyIW2}4Kd zLfpLN+=k)8reV5Y5^VJt4K)X4!jX;WTyAO19)}MVN}Z!!!ZOTwI5HfMEVetq#j(Ka zT025fumS6Q0&lA+MY|MPtJzGWY)B_lqDgLS&9W+}O+yeSByq-gJjQp4I<#IAG83Kb zP7u)7FssJc;}NTqhBOSN)G(P%MY|%=0m?#>Xm%4yQqdiSJyUoTBO}UG2yT zNIbMGwWvz@@taZI5MfY}B@npVfv)_>%Bp6)qj~(wd5LPy<|i!68CNc^5XLnZF0HdD zji}saVJ0fEsF2oH^anlGH#P)mZQHgm)|u~x2U5S=&)>cE=-@{_{qf&CnJ#~_*ULqX zb6K^_%ND@T4b_qKyK5SXdWjp=QUmA?6j2;O0ym9~VjlLLy# z(lwg0UNQ<|VwF&xEHTT1z-rpE7AfDlwzPF1iF}+@s2VV#bkzn{(Mxky)Rbv8X1W>+ zHEX=Aai)hUTd+zb!JKP-v|&AJY2tvTmjV`pX&Wo_swiE-S|tEYp^&^$rnPJ6CplRd z;lf0a+}1Q`tQ-JDmPan11QkFD_Z^C|r+L$aM!R##H#A==xkxc^9FbwW37q|N*G zJ8$yln+F_@bH>FcZTtjg@HmU^4Z7Q(B{}yPcin=o-9F-jJtCMp+IxmJ zanj?1N|;OwYJ;@I+ol%OuWD+k-JqbX3;AqHBVlJ8DI(|v8pWPy*TB;hQ?W(ae@MxMuSOYVAsk<#$d3)w_d(a z(@yxQpL~uh*LnnL#c(a6Tvj}IZ_Lr*92?Z+{f-~Kub|t>>GwPGKBFYIzh|a19E~RT zv(=u|SOU>*2CS8(btoIxtGk495*6h_es1cJx(xZ&OAp!I9&-Ig%JIR7mF{_-e0q~8 zwv0z}Zaz4ms4UN&OL^wGHD3GX0Zjv)tPg60ql5Bco1dW8I(mbFZ?J0({eGX7VUNSp zW2TccqR2BL!X%Q-5;-RlPL(jR6)264A}Q<{7m{`dvKgzg@O>@8m@q!z`jatdhj22T z@Z9z}PDc(?tx_-j$y$dR?OG8f+&gQr`@+hj)3@Zq1%qzGvzK={TL%2CFYaS)$}nn0 z(s*?x~^93l__mqvMgxN|GGTiGZSPUavU-Ng@SG@A(Bt(SUHmq`tMJ zU~P4cWmO2VQlq7oD24F*UhB%AlR5WcP-HLZjxnPA;ju9qOnF)Y{D?d z=q{64gRQ|C$O6k_yE)rg#JOyjtjP&1=qP1Yhn&B1g-?I-GLK)bc=Afg^EWK1-s9F= zZ}Qb&_$A)>!Z%n+&Oy+l(Jf9*scp;NK}N$aZFk9BpKvm%WIBR&23&)dR%s{3BXGXf zA{PuAKb28q6@;R-S5p)-Y1x=B=Q1&Gb$yLaufxXHhJXkTyoO)hNWN&X+`E5QxftWb&We(-=F`{C<6{q#C*RdVHWpKI4H%Yj)n330wn((RLWI&AH% zGaWYsfu)$2WKqWU)|O28Q%b4tRS3uLG+5X#09aq^tt{px0T$)k0Z>}zhl&x!aRkbG zAOSc&ScvDrrf86MgE!xJm#Pfpai=F^9z7aiOjo)%pM0v%&-~~UjQ8M+zxEDE4y}!t zSEW$hgHQ__J5nhje&RzGYZOi@;K*nVL}7C}oss1o@;H-v$GkR_G80G>5ep03G;>kp zL0dvs(btwdhy~uBP70#hl9&OyJ;&@MCy0i)Xik(HvSC1&DUrQpITslB;`SPs)&gF< z)ZwQ;b)IK06nyT}*bF;E1~|`zK@3 z(5QomY^~2^VF|i1rOKEZ!@Q~(osNW_6i4y*3da7X8}Z=r4+Tyh>%cP zj8Ty=43^~yi&|l*NwlKdv_uuyy5(@ZV7nPHOe1vDCoC%H*DM1|9XXa}L=_bDl*KKk z;t9)nLh3ST6pLxdXfkE<(k`F+!OIZ$*cxuKeer|~y^O#9@_nkX$C|MyeaceBsA_=L z(CU$&0b~LUcul;f_U#e?_)+}|uZgCVXT}GumL=`lRYhnbluK}J$apr#>4?pZ6?$nv zozzSw6PyhwZ9=1vQo*e7vtcWPz}-eA)Ww+Dq$P_xG-(IpB7(;A99=la(qO6-RR(D1 z$cTuo!o?8YR8Bb1_QsJ`3WU;o37wf_!3b46Ln zhu3IqV^I#O#ZrtVjuM=MKu2`DUBGcR8cC0Rnkb^cvc4IAxOzOiGw0g%D_p#`!m@1H zd-#Z>y<=Ye>Wo)jdBD!)nB8+diE~@GAPiHmk$@CVyo?1DP3@I0i=vd*Y??;Y|KlW) ze@p1o8kBJo=R4)+mDX)bD?^GMMOmX$C zD(@ps1L?6*k*D^cEjDN|TGP5rgyT2YBDQ)VXAkz-Ss(DP{i{F2XP@g5nNyOyLu(>w zr!J})WwFE>E3cR5IT}zFK;y*8IvTBTo?#BwP_zXlM$pOFL7wDHW^*3yJrJS)ix)16 z&VMa&f3O+qmvV6Gt0{AI?p_J zfhV8oa`*NT)7hNM*9R zRXC?Gjm0PUh=-Ozyh+A#I$?hFkhrY~?2OZg4^Yc5@h5)jvcT?Vjl-r6)i%@%!?IWa zl2gbs#z>w40hMU283OAFoXF*R5yZy%0mp4k5NY~@0cYb0yuaWA=Qo~QvUHY zV0@k&$BHP?1flf@6AfBwLOG2wINxZ87kCNq$+neTF3UQc9>dqZ@;+y0`!fIZ>tDIe zFaGVbw&E`y}QyGl?eHt?S+rQ8Aj)R3Hpf1oj ztV}NONDy*jHmw$9N-M*Bx&$54+JMR_%38Dk@R*{UaQBWCL&)I5`pOWN;ISS?L?aiTCrixTJVPM>z!;M$U!iL@e@rJ^w@X(yKri*-aQ6U1&& z6xc@58xAQZVnwl~SXahx{LQ%U}&TAQiSXOiKcFw`Q zyLTB32OQ>7zHJnO+FTmKcf-qFR5^b;+ zx%gq6%1IJx4-(Xi%Ab-r@Zpy9uC&b1`@_KwQr$6X2I-T?I{vpNn6}0Q2 z)PQ?;_sLf+&%XE+$H%u=Oblt}yD96cK!q(TLPCh?>=c|wri!wZ#JSt+u_%|I6qr`R z&V_5maI!p+d8kw|i$zSQ8=+N<3Jb|O0=tfFW*7og9S|EwYfdSv5*rU_>>8RHXB%|s zq>;~5SvUeL{isp(2$+ZGa>0Cb%<$YipEli6TGW)D{IO zjSCe~qR^@Yt8qBV*)YPH6Ndg`S%b6E=5CF}Si`cM3$(ovI})Y929!n3`HOw7+}P!< zZ@owBCPZmSx%4ANfAI5HdGe`U4)-j-@XO!e=l;^`Y_DGu*|Ah-R7HzUTPf7emlkI= z#=7s-h{x|7&V_-8Qw@zs!fF=_Y;2v9(S&Vf{IFf^Xj+&rO397lv}WFhSe>!D9$?Cb zo$Xbb9co+4#fx1wcUHvJxAB-C{PE|xcwv< zamNd%FP8yT9gv1|6e+6KICX}y83A4Da7tFrS#=JV!h`mZT17nm;*Tub$iG;mr|+X;|GH`r*NKL(^6a;y$ZuD^hqijU}*_5JV`Z1>x7J#z`k? zQ_iRt(ikTM)w-;xea=Flk?z}iR(n8JUTf+>y#vSoX+lw=*Xkt zOeVBvSx;0@>t-gjBBj;$gpKV0z7wEu|H1yW)qd!2r2M2hV$O6H*SYH{6$ag(2 z#Y42tQW?#B*$V8ettAS&7`vca6kxlwwc*zL$4sXuT)%dM8;_mmt=C@T(fu=$!4}3D zVws=m6?jwwRDN1rcXjADh17de&zaM5^3W6&s0gQ8LepYnfv_c6c1j^g;=%pNRg<^?W^=&uZ9AlUKovE)RJ zag?($JF7jeoqJrAVy#8AvJC;luJ7@u-`fR-9P5)l>xg^ zh&4VOPf{%{`MvZU@7$ozE}!x%UXq>bJJjz5hKtV zni_&A6>p@K!C6U;r45|Ll+Z=YCnMI@oG9#AW2HSp*_dLn#54-!G(}O zu-NVNSzX;EN@J7`M1V316RDfkwL@!*vNMYLOwdfH0+B*Sg_vbanstO0vv+tVouEpA zQle%3QKzvOQXG9$P`8btlf`7(;#}Z$xTOdj3QNANY5`^H5Xqye7PRsN;P>Cz-oF1X&F$Nq+&QGv zOE~|;b-wz+3IF9=hkX8HeeT=}ISLo7t?rZ8Ik`}1T8gS7)e*)ioCz@)q5uZnh+&+d z)l4>^OJmB&ax$5cpUq_`*I;FvaWmm!q3CH#nL?Rsf=1%9n56HJH zQK!Jp17bI4Yki%w!xQnQVVY4Eg&>RFUQQesbX1^mn8*)K4orlKx>RY%?Yg9V^d76t zjP?X>Jif(Bw#K-lIXHPhUCanwAKT3^w)SHhZ6z|xS+s(2Es93Gl(R;rnFrmz)X0if zW4xyka6v1;IfB4@d*|@uAhq_)@i@mx=vkwz1KWVpKJ;rVLX@x+Tu~Fm9RXxo(#6^Zj~?uir2}~%g$blt za5)S*29y}75*t9J+aHLs6 zY9h4JH)LLh1P+`nh?1Hx?K7Q@>AM6QSwWhV ze6}1{Lxe)nP}MDAoHL&)>TbaKwWsO39bP|n{MsuwxpU_x7q>V0@jv)!%HuOm_6`~7 z4y(ge)S$!U;Q@Ab#HDi?hvf{jSkUdn;38Vvh~~Ef`ooUYOG;BQ>?N!YdOkF2D;ZH4 z>KI=-XhjBBIfH5qxpvSQGTNVG@)*08Fg`0}mS}nKNKRy2AhS|M1vGSEmFP??bt8HN zRa1JESEbO40^JG8laAEerZSiea7cb(td|*c78KwKRiLcJ>4X+TpsbWHtTjR*3W&iO z0u@V}J;V|+`drB3tOZdC3Mm&Y=dW~l{P`^o9*h~kGsDFZ=g+6i7A?Kil+HqPd@$yR zK5>n=-`E#nlqAhrx{PHxVq-x@oL!{l;#}rG!jD6cR|{4ghH< zC5S@^L&7w{IwN%aKpCpCX75qS;a<(?q@ma8GM*n(*@8>ktCGyFZuCgIecpd}#_HN8 zPd?iriz@1+!8yFno5J_|I3+cyD2Zi8jcIGrIHIgzJasIWn$cN7JqN0QIxs@QOv9AC ztJ&O+2;(U#s?fTXFhHbQB{f{?cplLt#|hL#y8&OjW2zL;`kPy zetyVL{ODCa_UVAfUtI9oNzq43AA#`hhdMZX$kz42|qqR2nF$TC}L+B^m|eW z-5sV}-sq9(ir6kmw3YgrQ=aT;8i9kQSem9G&NLjAj(DxZbDzG!R2i1qaC&;e=5U>B z*B>Jrc#6Nmp_C&G6edtCbxR#ODs8Ehp-?Sl?3lZXgV`}>#YBwNwtkdNYrvwY8Y86x zoC6);AveT>DPO1s99fpr$vXn>0P-FN{XX7%Iccv@Aa$NN%w?aXVa-o|{&7yvPPzYJ z&gM$Sjhz9TgH@*UTBNVeZLTrQAasV6en>adq7*fs7Bc8sMUJ{{?C+KNKn_q2M=H4n zQcN^qj>jL7RhJD&eB5v{I+P2twz^JT?DNcXm)YO1m@OZ&vel<-mYm#c*jnq5ZM10B zp)seZI1nP}$;py~qouS%I2;P)=k_>7+c*MhNrJ60I4ss7F}GdLaq|g<4k_E3xyxAQ znq_e)74swAW1(BtdkLL1BihpRGKX@xP>ZZ8Xqk9f+oZrHjdaMavC8R@<-I#EF*=^2 zBg?0*Z*gH~z|73)+XLJ@X06lm^ouWWZ)=lpe&uye#*bLv+T>wTa(}V!sB!h+}r zDRT|uvLdvW%mzf=Rk~(GyO?03SQyfpR0_mS1-{M}YC#Q`i$>^Enm%!L%Ey23D&b(r z(VO>aNrg#ZTFb%yBa(DT+>2Q*1xOH*sd15biTSbsCrky;_;$X26cL~!ExDm`UhmJD zTJo9xuuGOij3;9W!3ZMXvt=v*C_h15B@ub2CoFM45H}HKyu(wo{OdY&=xB^(FzE8a z^IgU#Q@-{~2mHW~4Ed?wca5)p;huCh4|@UQw&l_NQ<+|P>;9ZLG?W!oqQ6iA2?vKK zSm%Bx2co_&A)FvArWI*jr&|`ql8r%E;&H5%?!u$d2;(_hY@OSoH4EaT!InL~@#S|} z-&rM#GbZDjC`(x1?2xCH2Ztl>9Z#8^Iks23JpJM}KmWJh;)D0B6hDJFKskk0A#J5- zYe=IO9W*Fwq^fR}qOs5o8g$oD>Ofke>p0-l7Hm*a%}U;=4SJ?o8BJNs6SA;kkPaCi z^@)2+R(DodUDs4)L$ux}?MAGgTK3*K6+_v)wobH>qnk6&0Tw#QUv>nzYSxA(1 zF?B6KOrRv+kWCDVrt;ETsFbP*xZJfob!EU?@82WUJ+xA&jULm*n6uLp>BXulhjS54 z15M?mO(BXqKq(AxPp?tn9Erljf@;soa8i_o*S;(N10haaEF0Nj3G?i(89-%WW z#)N?LNS$-?yz`<#2R?t(&fs(;2I=8%=^z$n|DYFe^|4L9_SJ`+oM`d>$H$i6{MwPw zh+cSZ8|wnj%24j_$1dg+6N_ob^g1cV#q6Id^5E{6FjW+F$a0~`5=cUDjg!PyO9+Pt3#E-Dt&f?1Waxfw{CL=%V1=QHB|g6`&$&HjKtzxxAJJBNJz{gUn8 z3e)BhgL7SO-rB>YqU$Y8@X5)(unATN34_29Mmf%aYK%-LXiPv;HsE5k4r$$-qMq~2 z$F51e?C`-ck3DvUWn0m>h(1H%td->)RE$F5$3g~vY<*D(=g;9h(iw(eT3lnO(X>@1 z4EsQbQrk1G0Vjf)(u6l$Sz%N(v<8eepq+&6ZKO%_E=?_6uu^+MUb_NoC6b5|>&MO1 zt&mJRX~g0F319xDn1A)(_(L@AH<(V2xOTPApZT+&;^cTHJiN8t92=&b-0Sn)vweQ< zuOAXb9hq+w1rck*0mrivt@Q7i5KdMU=*~oDxPWtB!3H3YVkWl01c-=u)GD?%h9Y!m z3TWGsm9-wr$%u_}Yc$0%Yn_ZF=rS!zktK>_%d<}nxqoxQYv0`WYA~TiYtUMKTXr{! zg{~nZQykzxLfrrf8I1MJVx30POs9wm^@Lb0+1y;=bQyDcX6S~{iBh&YJIt3O^0h5S zvxxb01Vza4xMuI}Lx`3<^URCPPFt$Idr~JFWHHOi@nG+m#O-3Dj7gPHyIfA|Lg&62hmwM89fxvA$*4zZtPACFwl*k2Kh0cb z#MU_LC99OV%2`PG;1a{fR0IsTbH`waite`NTd z{4alnot==Kt(-w;h$<3va6&f?sQri=K+C~eRfhS(cSfW5y<tTMj*71(~|>%Njrm8Oyj7ErOZry^l(8p9`K3h&of!hnU@_gu(qw} zW__+)U*lvnqB4!t5Ii#DXsjiUMTJy~n|UrH)rIsM} zrYh35#nxj2H(_U`!?3Tou(3f<59zM&(Jmaj>yPv3>;b>|%953>O)fndu)aRvmw*0s zP98Z@97*JG$tGbQ(geBmMlA~1J1)ownN4zZw9c}PF|$fq%pa5|y!z{J!yJ0&a^86J zb^fjY+y9w=;a~n&dGY1f`O2?-k=~$#A*9tYZK!Ch6=BN7tf1{kQPa0w2MJo|QsqCL zmHgz7y&yVxckb=6wYyH7CN$HUEQuMNjR41RWfj|km570lWQ+slTf4(3Km~vTv_K6w z99o0H2<(l9IF2Q}i-bgqu+IDaF5|_VB<^C3_moE9=~|FH9clr3+ScGwEv*ZoQKI8# zYG?zn+MkAyj;D6JDs09u6$9Y!nG*0+d5g*7Fm z#JM4Pca=#w<=Vv+CbJ_>#s%YP#q>BKngUA|TkRw2aB zyc>g$)d@n#yAg2;jjOO4QJ)EnBMPBN4RIY2sRS!=d`)UohG7q?Q&c!*W@_fkbyimn z80;K?33+aHm4+!l`H3B-vv=rzdPXqsasMQvOfK=t%||R9D62_SfEkT>Xh)n*)(G4x zc|796hNE{l<0Q#=cQN7S!;(&~OVZD2jpFNHeU;PkzeVB_DL{vDjJB4lsieqjK{V>L zb(}6sMomo^X@=cYq_}hoJ6jn~UOms-Zylrh=P>b-z}2KthuR?;aDu)mjWLacX)>KW zifL#lZH>-DN>x!&0RfHH)Ik8w5`@mf<*JcU`p!5pBIku%ICtS9-3`m+-7(fg7)B@^ z6Sy2D($Aia5NFD_%r~VNtTPVNR>DE-(Whxj6c9#U{kAdzf9;pvGm_UjWAY1Q6G5(;qRR(-RrCU{Pw+LR=NSoH)Dxc z$V1I|vBWw}tN|RAYdD)s8FWH=-B9W_t+9|+>^&IMl+eu;Wf4lz>)KOYVE}yV>jyH# z_v4@5=HwTSsY)fiMoP7EBj(E~v)PQ@_47DW3QXQq3+att-yAZX&rzkOS~lc5Ccx1K zHO__P=_+B+5^4`Pw5?`kWtFXSD|~SO5%VRSzcQeh*O;l2Mz2Pd9POWAn*|%a9-~>q zq?jK?^tmZzR%Ky|79Yg_MgLmDQp-Zhh-Ld6F`3N=~OEvThfx0(lM6IvYLi-8&&_6E0uw^3+pX+_^s&PUO+a zh?UhonozE9ubZ zLKFy_SA_uz2(-_KY#1^vDrsAeR3z}O3lsqeY8NX@Ya4lvP#fCH;zG+*FF3b-fvv}{ zaA!Q>#%7PTVM10I3LVLYy8g^%v>k)4nbt$zyEkWlGN;Z%8ag7Vs46IrmE1W_7^xNVZox_vle}?E*~Vx!5NY07T?3vLvsjekO+881%7#=@L^Wvb zg(%evRqzAyy^0c7ii4(?v^6hEfr@(97utPCSs%a6pVGR0%jI zVI4{lAXEM1IfQ8tMDlvIX{mA2{HCzjrorLFTPzj})JRG9>C#yOr+v*XM(TId*+?Ri z&{GURg??akZZOtR7KSitnM`ZR0i(Df%2$b8hi9G`a`4b`Z*NXqjj=jnYj=zBvfvvp z?_nIwi!;u~HE4&jOX)LelK`kN-~aI@A1zfB#e5J2t-*y#YrJf(1-Y;(P8*E1I1o2F z9?e9Auh)qw77K7*wNmK{>uP%4n6%qvQ8YrqUs+ip$_^Nk;>v(YHIn*^X*K>i!GSyn zfdZ@iD0Szn*v>jX8>epQX9GeaTwO@Qon;!OYEUh0tuUcR=P8fI$83-KsGef;(u&k@ z?7b!R$dT(lE4?)WYViPd+vV_7F>f7b#hkKn5~?@@jjdVEN|ae;V=ZRbKjZyk%5Zj_ zrd{%bFK)9t?6Z3Oh=;Q+-gz{pQgeaW>&g-ki;7T0Abc?=p=k)#`s6$Dd8ch#k2ppF zVPIGmEwA6&BW+^R)SxtU^PI`-OsGo8GzLQ~1F#ImvK06`PBQ^oY7hiYq^SfNucVla zLslSau$s~#Nq>@MDD7J=oI~=OIEy_OG3&{padH3cBoDub&Ag+kxij_`I9QQCyO}{^2zL?0N6Q@INJa&#nH78Up zN_QDe=cH*Yz{vN=fWEU4X{BruMNU#3V+fs3+UmAp&>eC*o=H1)o_DYgVFCv@OzT8k z59=5X2Ur(L8?m(|NgR;Ift*0qY{d4;Dxdv>PqLVv@>l=TE0C>{b^#ey3r>LrtQI-~ zrY=#+^Q^#=8v}=Igend|gKIp(1QpBW6diT2ffKdPsM551r(~xFbx@-&g&dEMJ$xQi z+}_{kO41UyUFu1PhodQF0;O)mTUa7=oD0!0OGAh%9^E@7H7)gAVci))4Os=z?0`S? z!;kZ|dwst4-W_xbHt<>x0S$ScVXd@=%ZA1|oWI5>rDSQ}Cqx=VRCwq(%U20Z$g-RW z)8nyATZDlY5yK$U!Y?c5Ep=sxq6}w!)1xz%w(%s;Fz_I$=QD2hrn)VuvIgO$D<0BJO!Zhh^pI~+1xNjVyyU|V@n zzF*S5>e~y#Fw`mtmBlBd)_QFMW0C%hm0`}Z^6D|pIT@ClYQ>;8lo_fg2Q!w_5NHKS z3W;Pah^d@B#+s5FkFPOa9P*oAc$+xs;o?xj4h3S2N=7q=L}^M$EtLE)is_~iTEno@ zmjYqaSQ=%qA$0N_UDRTv+9?#V@W&Dy^~o94z9T9WWGK?4QmCSXQD17dyo;=@O-h_gG3MaeELa z(pVKRn~&%ZLz;RBE~Irxe&V!;$(kQDqjcy`$-t|VHnzoqFo_rQf|bEQC?ZxU@*#}| zYZZ};i6cvxG_+RHENX0kI97p7Xt1<@PvgBtep^L5=MS)c*q$AT2l&1zh0 z=)@seq~ykiN^v$ml8Zj*=2Xi zoNH+7kR$|eE*kBuP(C7rpXJqnBO2;Sa#`mifl|0IJ7m;dX>%MY(Z5U zo_P96+V&1Xx?njeF!cbO@*`VXE8k0N0^SQ3ML{Dlv%(uvi&y*67!M%e95|;$V_-b4 z}8l2ML@Ey9= z-{(kZL|UYGy&i^V#@9td5GqI`R2oYoW8GA82&LkAoV5Ep{NE+gRHrevCJa((!yiq~ zWIm8p6=#cDRDqH-70;K(F_Y;GO-M5j7>yT%(NZW}Q7iz797e89$-0Kctfb!`q5>=B z5z`p;{mOyVZ~gbi_U;dy9GywWskM;A3TqnCE3O*HWVV#i=7G{uFX(h)ga(;*<%AfY z`BToRmPIuaCIc=fO%MQJe{YN_LY{gqV{L1dFMVMIE@EqYAcay|eDhVHL>R;sl^{ka zFMC^61$mY;ix*x)fmkG_h!jDs$+C!Yno%`V5z7d5LN5J9 zNr|l-Nv3gB61tiy022@jB9)-X+n zvZy8GGA2hS0XjfBLw)_uiLBM`+B!WyGnQnS2&COqwUiC2rojXynT`ZSb296xMuon) zuG2+4n^T%ay%0~4N(Zt78iAA(9if95hn43LZEXT+%~&0-U`@h&K80RJkYz0EMs(F_ zVs<(aFFd(HX%}?kKC0d1(ZLB>Kf(~;T19^laOZ_P9&3j#G zjXuBYrz_0fZ6utUjFv1G4I68#oV&P&(j{-bG6tKVFlZV=oYXK}t39(<8Bm@{Y^@Q> zi)|}fmxxwE(mf(b=hTaJ!e|1~1XDi^*_1dfaY02}<+!RM3wr{)jys014+~Q>Y&4jF zCQ|q|^MKGI2S=+;39^cryUN@x=(!1Dd695d;%29;>^e^OEw;?iK`khfZUX`;x_Kmh z{I&^EE|Ey z4)!`dFQk~n!o3RvL5Kv-Mmc%yFpNktMO`8}L7Z5sYAQ)}p!!}jE=|b74vEvW7~j;k zvLdvTYU!cu)(d?|1DX&oZ9}BARM^i(3$#ll(H3rAQ=@Dtgi7spq(HpBQ?j)kk_3us zW~mkpbp>rJvt~a++W@=>r17DfY|Mlxl6C}XCGiI407n=&=Chh8&iQ?R;D>qqscp{2 z4^RPs-;j=q)-+tac#-QjZiq-^6a+Gys#-2+%7S7!$2u#0MoKA)s`U5QSq}uXErC)b zO2e|kmKC6A^MMqUX)hLa#(+M>Iwe*liNELF-}&N?O|y zWt>EZBtZgpfeSVmO%@_<5`~elkd*=yq@iyXYwesQ+{8ADq5-0su)AckP?Tj4vz)N@ z?3|rT4&HS#seINX1TyKM6;w;Ljfb&4Xdt^aw@daTn!U@L{3XY-diiGv<#0{XNPwUuO6X=Q zEUKk6MkaBDwiQo4vC9Wiev>D*a z4XXKwuyf4y$2W+7M>$FtCp-&rfN#)6<#RV50Z>+5U& z=XEQznsPBCk6<`}t=%C9uP^BL0xXD*gv0W;|Mp*(fmYpKSD5F+exH10C@?$D&{hRj zx@5sNz7tkChpVBojZkfJ9n%RSRM}uO(g|9+76(#Toy{jik(SR79}PzPC=^vFIS8tz zA=WjCil~<@i_r|NEr-K1F5S4m?ME5?G~ns0YrOr&l6t;MK*DOTgI-abMabd!=-xfT zZjNnInZVv3#-f#>mDT`&`zK^T-#Pbr;<0N#cyKb$Qh`2OmN@!@Tu%M6Y8Xi0z8H-Z z;M#c!09xa4SX&c?h6}p`X|TI^<2?6nok_A=Jk<320XHtL5(XKgX~XI1J`W!rfa$T) zUBfmd7KO74qct{8a5kj1OX|?#Y6#duJE zk7+iB=NM!y8km+NR1RucFh7pS>z167sP3??*BPBPGPrAZkn+y`F-fBF@=it&rKZ-z zT9bwvwPji6vGSxq${*8YdU#X z_>6-xVmI$XmhkZCKoCu*yl}s@1{?$^4=9)hv=Ry7s;%kt`Yaa{G3stGkm7nG_r?h^ zIE(-yKtd^Ll_<+fKnLp*QB*CL1-hK#tif0(VQOnMufBGRfBnDzc~)1}c48 zv)iIXd+}0_)%DBVzt>>RJ}-W9oAG2Msm02`qRWs6j}DnFd_D5J`|rC45vZH`&?#^V zK~VBkqoo-t!(joDI+8(E#ww|&$e2H+g#l1k!UmS+wKVGS;La(ylAr#W7o?8V)GZn- zF>;ud^m-e-`{s#IT;ilfC#`>N7vr?1R$6r2lrdC6LscSe;XCJgRErs9eagyuN+9yM zM{I00%+4xqzdz>DgOt3x!F@eLM^n;HMwGM+`Wbm*=~?yZ|5 z@2NA?PfZ&1Q`8p_+%YB_#5b(L+%Q#cOC&W6=(lM+^E-ZxCjR7K=rr zlzcC>lGr(lYJt_zQAqBxFco!Eh^}^H0+x%V7^>D0(jUV}$%M7-M{M-NT zEmpTJ!#qTV5pRD`@b0@uQZUzf&9u%L&m0X9v2z5)!nF&6 z(}y%ckE1v5dLSYS7*A$$^8u_W2!k$4$5I6^TTNgi3R0RVqb6XGI7!|Em64}jEb-i> z%WSNjae7d(n9Udrg`6vf6AL4`{%9gyRaIg*&O_K<+RGX|9PQ8w6p&VStugxVDDYcxmU@D10l0kuOi+r^&*3lxlzg?_te@Jg%yJ$fctxjvjfG| zYXR$nRYK8XuPAHB>o4DByfAEE=<@0}Cd55OZ5`!ONom6A5byky`+ns>$}bqt7S*$z z_&?hpSO02YEJ2#mn3Bo(Osc_g7|Kmw%$HIrj>6av0bVSK6D5h^>EVR!E1Rru$DE7` z>1urR$WT>Xf>g7;qgae0=EsIbXUzHY83%_c^9n*Dv>ajz94NpjgT@f)L{PrURH&jx zwJnM16HYg9CB%b<$;k=De92SS*SPlBE{pjYlZ9hzdxvt`=hbf>lA11g=Zds`*Kvn7 zbuu6<2m{`^J!Ajj9<9x2Tp&Fx0`53u>8QoD5#yshUVd%D`S1qee3drn^X}XC*y&W5 zx<%c&S1Nyf4+sI-yp;-GZ`x3|`hpvPL4bx9h< zLc=Kq7;vD#C`W*$X?>kXiTtyVR@6IOTi36p)7sr#ig+ z<|9_uQr3DY^I5`6-+Cwj!P;Pr-~Th`xqWBB!}ge!)gFuGjEI2EjU1yCue>$FHR|_9 zKhSS}<)y#$nHQh>*^`s;jZiCzS*MehJRX1wSX3>`vi9`*rj@?+^V@3_%?#rj83#Wa z&uED#iiUYLCyl`na{6e(`fkAPPR{Eu9Z22b#uGc7zqG~&AIylPmAe29%6M@<0)q+w zSBVyPvn=RD>!2c09Bqxklw)5+tk)zN77@ebCcE1Mu8!7ZIGEe&VXte7`}bJs52TOZ z3=)o;n5)k&xwbnbG*FH%b2Nv8*%XW;N>Yq%tJb@QmD28 zbaaB&frNA`gH`|1&w zE^T4!nqpCLczi&;%mMfw>B4>I)#-M=q{7In@<+b9T$Wy!V7jbvI>u8?1oxMMmNgEm zAj)H5K<^)%ifG6O@1Ak@X2JajQ*PazaPO|-V7y@Bmb~@ul+id~u-OpA1t_mdgB|$V}VJ(aC+J|eCH}=mk zp_M5FXCuS7g442M|E$Z&JVKj>V!yyPTP$2kWgRYv39})DeOlF}wjp6|$yNre4+mU& zJmKnR2K08Ej1^hd5f8@qxifj2n};Lbecy7jHzBi)3|`h9jX|NT_Wd!^`rP6-odWnM zd;{>cBIIv8s1Tq4YX-DR6XZtNAP7k9}>Cx(h1L7v} z_p>Rfu&5}Muq+HT-85%98H=b&(n|^Rls43q#^LWt4XQ#1&@`}I7Gm%sB7*Zq1cCs{ zGzTTQn`kpsbwL;_e&|PEU~?y9v7BI=nXt_F_f8luTR!%}4Sx3XkMYO;=+i7}$6x!q z52=Eh+pjxD#{q<;z!^coGBe8)QuHA<19&K zHor418so!G6obKvK zk3UN%x4in*L)u2;2t0#VhZ0hGIMn#lPS_AuVrpDSFJB?g2WXW`XsMN>wLb4?TWFh* z&>2y+iK8C%Vk(S^Jh^}|Jtj-dz5N0etm4Kw`>!vt(-sqsDb+$kCf|48V2s3XLP@FX zDUGwNuS7iaM8%K){+b{C(Ui^Jf}j2atNh77e}Rv`nDf+QIiLA~t9bnq~R_UNV^vA5F`yQs5yV}GRo!Lzke#%$=X)J z>$FphY7g5aiWZN7YmknmdKu13=!l@EN}i>w|Lu zk_**M?M3B%D2f&;PlzYV)}Snr7g%JuoKZH%eDZUT)70?RYlr;UkAIra{orMO>c^f! zM}~JEe8B#}geM<62lI%RzVsfS`|&G0^WsHLk18pmD;+SNEQNA}a^JId2EN0H6AliK zA6~hBwQ)@uMNvXjYvR}uY6L+EyP06`<5?{i>frcL23YkvIVuFxl;pjPGz;kDp6*eW zjw-2HEKWImxZv53?eN%RoevS1cPOVJ*_y=P0$Zz4gI0b6NIrlN5(5_yrbcdh-7G}{ zSFIpoC(4yXaYrETtmnMGf-y8$T3g7ZHQO7Y2hi60JofPmB4@RKv|#kD2b2d>TDwlz zSGXcXV?2=2R@m0aVG_roA4yX8(Y3}+Pl)UhgLp{ZW?a~97_Nlq(N)T2%*wfjZf~3S z-r0t`w@^001_m@B-o@8Qnx_6J57Jsm-N!jZdpwfaH=s}$qtHP_XHYQgIZR==`ObY_ zef1ql*f9nhAn%phU;%~oQ!kXlwUvjcl?H8vUpMIWsMI!-jnqeyAR`VtzNt(v!PZPqy{v!fY04oQ+s z+DY1~l}5wTYatLI;kG~Al%sK;nP6Ls3UiXQC!WoqLHd?7rL|Zckf&XetSj#=P#PzL zk2MxWoP?~dXVR|XwEn35M28{0!N9*>pin{Lkz3OcM9}G^>_05{*30);UC+7p#5!O7 z+AZFGZ^GAJdB_LvKVoNN$Z}>W>jhV@?Qs4=kComA%`$Fpdlp4b2Qpll3_rx$wYo(pRWdWtK%aiW&vGK_^NhG|{U%xk(yN}DK3 zWo2&De7->AeAA;cG`3=1FQvHcd2bp5OQ@kN7WiQViE!W=)6ihRmZaGZ{(z{2tx`^E zvb4)|KH*!h@1tVJW6wvd4tBYFujJmNo1E>P^7NA%{4;;@MgI2R{dLlAPXL6b7)c)P ztije0>6kc-zh?%-?=tFMRuBYj5=UlP>OfGg#t9#4xs2tMGp3N-z>g?&tgj6*g7-Hx zR?{7J=npL`Ynro@J`Wxp3pcKsFPTppqBNJrstY?Ep8NO~@4U0;`*y+zw8Od@)kbI& z5V{501q3b@5@a45(VBL(74!!TUV9lF4XB2|rGy5w3BiRFvn6pGQ8rV0-3^=@P)=)H z?-WyS@}R0^a>J2H>BrmXUs(+Zd!C)$u|VyukZB!}#VZsERIG6T@;lAwY{qz85QY$dz~ObHh(br2XyF4c zr!y9@ql|sHW@bytgF2EBIWeHJY;gvHu*R!BBL?IrmwS0F(|VS98wNs5RiP!+j>;K7 z2{6i0an0Fr&B6VBo_S%La~C!+t>Y7)=yH5iu=Zn5@$#4V_^W^Gt4zik?P_6A0Z>jfL)0N1zE&K_Uz653Rrr{q}=9RPvGSa6jgA7*TJ zhWwlVv;SDALO=ZEPq2M%ojc?EBxx!+m9rLXBSl87L5GTEu@vvVzLC+%GU}or#|j~~ zwl%$ehgc6~Xu#dk5sTU|tLJ1IS_5Ud@F0gaDDVYy15qeVgTgR3BFohX0D*%RODD}4 zW<63Osef4&5(v;>z=hJ<@YrMP{NzvH;N5rjcJI66KJ(N*p?H6XL`t|IT)Cc7P63I^9f8 z>E*(TCyU~Ss%yjhQv}dG$O{L(BM4R&X`(=oW@Hj6GVzI zh&g}h`iJYB6fL9J3Ok}~Yx1>CvSfw-{lD>-`O;Uv!nG$Z(iRn3@{AId#ONSF<42@4 zwPas(R&ut^ZL_+yE+E0xi|6^|vp2Z1y-p_#i6Vze6PiY0mS8Oy0?A2}IG5ZFBlb*h zkdkyl8tdc-YnvMk*M=yKyrv2jtp%*3lSBlO7K2vK=VRcQO>0)xH9!2*n|$@_clf!# z^*+aA%bh!lS6{!y{_#F*n<=0E%oSEw`gHm^W$B{;Xh2!wERoL=1cClO8PIP>;n0{c zjV!K3p(PYhv{Fdx4r12&F}*y*c+obBIkc6+nT9ki1z{Vmcj@IBn=3H*rDBr7I8F?8MBMU2JlO)(yrk zQK~^H3s@YMCsN;n}BN4 z;Odfor$^`%jVTbdWLnC4PLv?RfhQ+Z(vIfh)gei!fL4Yq+j>sH8Ed_m8>{f<>)&L) z7|{(nBtgfwYrB@p7PO|oQV4>YXNdqm#j+4F4`bkAS@Qnbgk>G#ng~-asftrp61cJ! zacQ;3TH2Rfw3sYuik3(zI#I|ViP#_bWH~)DPU?zy8mCj_a2@oQ+EADv+@K-n$QY=aoBPEyI;A ztzC$Onln%>t$;<=nfmw4lt%M^z&E(H zId?xeChNwMFdyvCxOg?=@}(V$`BDIfqk|cvv0-gk^7u2C`0^JPy#3}Qy8Tp6FO8!z zHFaC!@P$EZ3tU?ex<>kUbls3EMK^*}TY6#2diR{f&Sf=4#}1_vl$wFIMA3lqbj*BS z5hpPY<8|XuEYyTa{gC-$n+01;)Ez&;&KR5@<$-oBI8U~$YU3ejlL#YNIwiY34yR*Q zyPI^<9w!GUymV`ycW>^~O zA)FIO($*|BnsU5Hs4719`=8{-Q%~_Xe(@WmNr1zkf=H70wk-fqIwVTL8Ha29h>@nQ zWfNCRhq9VAmC}haY+ce(GkR;jecTYRKv5D%^JFNqidv{!+HyviLO)ePgjLE@pCV@w z-=~1Uio~*M6iFvRJLN-B8wm^i(xo2jYwP^{-~N_#>V|2-`ybro7k=R#8OPG?)hy>F zsE9NlaCX+xTM0!&!8k>?li?KXPnO{LzEOGHZ*yfjNUExdwD$UBVWh;37gfVzwv;Jk zjle<;K@y{*2oxBL6#Mcx<-++Mi}{o+>2UOD#K}n@Vu;!XM4c|Zpk^{Z!Y~D? z4V1)5M5F;@&;b}MO`}mNlpg%~q9#luvR=--9ASegMp+uWBnl?PQ79WR)EZ|paDI@l zl0s}rQ`J(rE~68)N{Qn}kVIYe2(6*DXP$j5^FkfIY@&QZ-Be;gQPL5%^P|HoE4(hHBFT&Hz5hNsxx%IES9#~wE!n)LO(2HKw#0Qq5=C@kq-8)WC#YGd0#>sX zbUo(e_>{q5fCI3xNQxD1LD{y1c|?D>!C<(?aw$b(;k2np3;hVIaZ1<;QvDBDny4dGW{F%@2w|?$xj8ALU*H^fBZA(6P zx05rOj>J=)ofHH*p_reFT5cG{j7Fy%Nop`aDW$$&A>40A<6Mv-{r3tSG=ic8#7QP< zHG9X$LUlnw-N15Mf%6phI5IMDsx9W+xUxkOwbTpCu%FP)BI&p3cO%Z7>k_Gy`?t?< z7-@>j(u^QTu@*d`GQjwe8FImmYcYW%$}$nwE2>5e$VGD+3W|0?)%y55N(%7ZtbW>? zRbY{u8K)u3<(xF_2*oB+3TUXE#k86L=h?{45yi3L+OWJ1Ynx zkt>#>Xx%E#U%D&`p8aHqZY_-~uu)4IX4tl&Y-&lWy<`>=+t21Dv)P=>m#@xqyxJc79j<&V} zjyS6^jTJA4@X-N|@x!a!FAvh?S~nH1dOgKX~V+w9Oxk4L|=IZt&ZXFmLd9OoLuJG>5clr3U0|Ewo z;fsgdelX$Y-2>ix_kcyCI9QaN%?cTXqhc+4EpXZoMV_3ovC_pkxBPAqX#J1+;NNOQ zEB$QXY>g4`Y<%}%ZAFyNf}E^cg#;+lBof|Q7%Ch_@}cwRhn(L!&;5J8k!?AvIeK(N zS&UiE4C6D${@x51EZ909qI82Il<>!z28E&1?TVWYbmSQdK`NnMq@XD+=pJj^J%V9_ z>(9`=f>u2u>pf&+&Ef2X-s+rg-wFBA1pA_ma5%b4)r<&2N5c_e0{h2H7PE6?VNM#I zLSt}FOW}AiU*H^`P82m4Z4yBf{dAj!bt+Y=?SgxV*c8u~T-o*65NF zxrjztYGc4@&qu2&x^W_{1)+lg?WZT$7~SoVuC38A1-UM87An(H*DawKyCb7Z+;*ty z66;#wpBYIs0-DBnL`;Pu(5P~Ujay98GFK&|C}9-zS=14Z6x$}E!7XYgdQ#dmHpthCJGP#7n<?h&LL1l5Q*5~~qmcHdfHJww7OwA98bIpt&bBF*%O`N9!KeM)*%@q)rm z7_P0-3oVVAvC<2typ-6$pzti!T4gx~CmSBt#QmGb^67X$6 zPOf#Jm(*3|kymHr;0&~p`|Z19BdW!MwzUE*w2fi0n9InLKxr}HG}hF$2P+&7V*+%d z@!UcMo^g&vk)&R{=F7i%NV@6><*_k)duLQ_NTd~?|LkLo56{>;JS58kPDeFOV&r|E zj0=`kh_gh$XXXPfibCrk1m#f|75a&AonD8Xl>xW!9f-GBU0Y>3T`(ChWj2s$0+P(K ze>mZ-cgD1?!?Lw3>Ip^DK{YY6`7yiah74Ca-1*>)a_KmK@gle8k7&(OLX$X5Ih>xd zs1N90*e0OGVML)u$_`;enksHR%6RS0oV9$1{ix*SqNM9`BIRgQL7Y`|gNm{`qO;p4 z>0P2IEU(=>BJ50P+bN+osKFX9A5N(cmbl&uIvJ28ElyglTVYE}4Fp3H_XNq(VL?+X zzVHhVSj`nW$f=rCa;vFzlx9v)DsG(L=Jul**Dq{waC9Q!aG(Raos@33j|#MaElCy$ zxl@&;Z}m0}yXRBp^Haj8$4UmvF|dTBY1v(M1j&?Nd+95jE@89l-=D*w(UNZjVb6mf zo~SB@0pPn{p>-9lXvtNAkTtCmZMwE~0^>Vs(kv816vIeUk=X(WLr;3Oe%iOMVFeIK zS8^-GQgC1;fAn4gi-}8Yl~F7$R@ba-DL(n)bzcAGF?)}un6_q+^w~H+;SJETIWR=Ci zbG-S=19Y~>tksl}qT5ekR?*ZC7>5stT3CspoX(l6h}aitG1lT)(;+w|VWbSKG6k+^ zx%>8rc`qd$wmdj1SZfr{RGcoRM0Z*)ZQP)qEJ(9Hm$x@~aC%0P<*cssuqX}=k7XkU zo1Sc{10`IwZWgn%xy{k>8ESovPkiDjE{zS-DxlL@@~I~jb(t`(Bce`XI7{jyb4c$|9zkSkOf@F5m4qEC3^8tj4j&SC=H#yBTHNFO zaKOfn;nI1_W4k#|T;Ar1Yfp0hxl4TJb8Fn_w4{xuyOEO*hD6O{^wb71&~X7lsyJz3 zwk(L8mcpab&Ld5s7EmN1#m);-9k9By&J&Mc=2K5R$?n!R<7ol&mT6;XW9Wq;Y6?f1`g$yN zmaAJCTbrw#ovfoKj=pIL6kvUOHUU&sAxmpMYG~79M!qXF3Sv=5LM3u|HI0_`2|t^* z^|HKJK^3t`NN0@)dBAwaM5v@TWUX)04uV9eRm#66&N%rUI`Xe&49y}S2x^{seurm2 zT-7q;?%gv!{>fd=pWER1Pi}}{%kA4EPA7B1yyg9OPI%?(cer?|!ywNnnGxBDr~&7Q)sUluib&_= z-43_!++%yYi_#UL^#o64Vm|)D3hTp+FMsI~#zsVeL5asVBFdqa!q^%`LKbMuazUK+ z2~;5H(r~3uY9{fHSfOrh_mrbC`xUSF*7CFI@(4{c(?a(%Dh=n zI!Bv!pqo(`N4geLnG9?|+DoWgBVnIHS-|~X@Ic}T@P#lUYr(dZWkC=JBzewgGNY^; zHa9jTX`M|f>DE*sBpu7Wy<@)q@&N>jlaXbA-w_4_sp?;QqRaU!0mZ!H8!z4G=(Hy9 zIg|~cjq!|X>)pQ&2-A!(PO)ei%$s*QQoo6_n3Ks8okpBHA2T!q?!1=rjSo&yYby*Q zIG0t}St%jXxu;@l2Obzu4o)e{`2P z-e+?VvDM3IiEuGE>vgBIG{;#O4EiLA&tdYkCCwa{u3X~M)m0uG)&!mFMEzAJwxmf7 zHgqENpQQ=TG@cnPqL)U-qF7U*Y>lQBd0=ZwX%_S#P~genP2&rz7UCfAQHO+oR_4fJ zYoT*t3~``{wIWo=QpSkbqcx3`AC`+sj7YkATW@Us|56IrhkGTr-#;bK6~ncJuYUbD zM+YTuy?MktcaJ$7Ejd1_IXP-YO1YRDPEQ+x)R1*D33H33VKgaubTC1o!tb{SQUCy1 zmZEUJ$t#Manw;dJ6wDU$Iaw6S-&Bhhv}3T6pmZay-^N|K{QzSpY;SI(^eT66KV)>K z1z|ZIH3TXYK|X7zT)wf%@zI2;^r3(f{b%hxP*q{;0vB6!?A1@BG^E?@QWs}zUtDFa zXL`n`mV6EH0+!;mXmYaHF&_sxUav5bL8ums@}r)Magp~9JXi>6id z20MJ=Yws{#Oi8;Lty`c>Kx>qrEu@j0#I+6p>wI40jAJodNS#d%1XKE^NF-kpra_*E zQnLcDo{F`;0Wj8hva1nxzSiEeX=%U%m1S~>>L4P|a)H_%PK>B-YmC&8BGNPx<7#S4 zH}8Q9**`5H&$)PRoglQ_zIDcM*pbis@#l6}-^h9AjZ?PHt@FmW?xF_^R)z^>1B8w& zk0|TX{hkTo6v`_cDkZH8X&i$JNn254AxE2y^35|iO65bf) zjKwrYWB9~{=P_!J*T?U&$a-vTtfLP5n0IHW%TO%A&W^;R+Hxo&kXy}&YC7XMENHp| zdS}}V(FDt-aJFKr$aRWy6}CZYGNJB}#VJ)el?|F`O&T>ATQX}6=}IIaU~i?%3+)Az zdVn4VG^dt>xBbl5qCyq2IK)2#m zWt@paz=cf4+WX$2B=tH9&IJS(kem>35Qq_Zx|2NEwq){4gHcMD^I;fDAywOw*0zMw z#vV&AlvP%^eP@R)_xJ8`;mSJKFIqO&20T5#$m+VIEhFCh;4NYM_wtkwN1m?9ELvOF z;4Dp1G3cKoieml!9`X3m*Z1!{I1G~%rz_F4o-Y-+C4)>8*dB}dLc*uUggC3{WGiHe zX^k62w#}=pjoUZ(r5)k?^&wHL7$28@ zRAAfEjg`Q?)nqAk3}-3=49Ylkk}-R5h>n(U4RqRNcQfKrH=(H~Xak)Npr&*?TZ|?> z`gzQ=7mNtK+c2hxYNDm4of@X`l9mp!(dgFUB0m?@*}zj#VuubaO<56TPK+mran`*Z|iIF{TUk5^Bq=Iwa{t1kQr7=o*ZbTAZJ2T3Cq|0v&lRIhjJ23(_^6 zJGxz;*DOs*Q6rN!l$Fj^LE(ID2pWg?T+ZO9iF?g!WAROS^3)0)_&QRk3sBy$f>2`? z3onuq1#AX}Zmg+X2`wm5UDCRiWmOU? z*je8o(6PYNqfvpaL!tJcj`m19L*lR}gwQLOF3?Q_zV+TijERV}Wi~lLbvDtHf{pbK zc@Dj9!u?w_dc7eRE^cvr{D`Vrk|h~hX({x9=T=2Muhe5zMB<>TE#V5}uA(T{80K5_ zh7)$5Y&bkF2+EvZlu`r@>)in+QS9$mbc&MRAY)W4nYUwHJ|pUOm=%_`F~TXUlp%B$ zuLue#KYk?Eu}q=XVMr_!22#ql!8R3FuUu!c50eKcQedtZ6`@`yOiJQ@#LD(Iql1Q8 zS_T>ktNQEPY(|Rr4j)ljOB8QX7YSKj3w3E(7#40$)x=mbw6he8S{m?_4bUL$Y^_4j zf|B{snqf?s#8_i6&T|Uu##3>k5U>uowP_Mxx1UK4~*|wHRDoH^hR*l|?# zG3jBCn%ne;1H!muww!QoeUqXZ6NjrT3q_Dd*xE9gP32X;r!!K2w-Jw8E8`k#lvRNW z6+z(p+|wk%V)5eg0jjJCwBpj`3na1P>}*P$SdK^glu1UO=Q6z3g#jC@>vZB2rxZ8u zJmlen8Mq6)@Zx2@{F`s{($^=fZLCNLBOyoWN7{Q?Bs}c}gx2FCK*bEZ5jJQksx7W< zZZc1fB@T}m^6>o;NdZj+kv&8gF@tc0x>RJUWY)|Hngz8s*f_&gV~XM!9d?NFF0Nh5 zN!rL567BOJ4MCjJG_A0ctID9`grv7Z5cWaODe5KNIFk3fyK`PD`=jQBG#L=bW7=v- zF{y9{NHoJ$O_p11(?#12Z0&H_n4*w&3lZQb=QGNAhuv;h+Ra6lI8H?4u4yWfi}InC z!8t`7Mpy$zdyj$hDxq2{G0diI0Vm(bdBSp0im^png9C@cVNv3Wpl!Xu83O=HLy{=* zNOf&d3Rz*O{XANfzGn1gW$xL{2!@7E~@IGSqCbI)DECK12#x9{@VA3n$BE4#e( z>V2m5gnFU5e|tn(VJ;Lekpyd6C}qN(!&nF3167`XXu~RsDhL9lg$iOEfiEP= z=`mZ(IGarf(pc#34$J=j5##X$ts&1c8LsQ%oSp5IXRfb`5k1_Suy=66!v|A>php<@ z8J*S)1|3#bS9tVr!TTQ^36aq%>%%zhwQ!ckQQC&8ong!z9c!FwIX-%bIa-jPXri@? z6iFh(r_!)XOP6Ic#JHUCc)@hOPuK}iojzePC0HKNu7It9WsS{OICUA8nxLM1yJByR z=-m-$+R#!og&YFPMHGt(+WDbj@4fXZRWV}FiCNoRBVI`$G^EN(bKvx3#&W)p3Vocc zuv|i0oU)NG8RUv2>k+30r)GYFcMMTj&`A@f(}v^8oM4zr%Yc;=Ts(_!5AbqQV%Px2 zInR9yG(jBWLSbaf=RtvI*()JrHj9!Z2uK4ZZpBHV+Ms3jQ|k%73ILo1rI1{9eXUQo z+wmMo2i}MjI7?s~0$Y)&M#BD1ERVl2|Hf^8 z<>zm5`~4%D(g2FQAF!C$+_}9+-c1GiM;WXwNs>V5SmGwX57Qac@AUn!(_1krP-&)c zjixADj0lb!OzT8Sw!7LTiZf{?PSc!TH)UClF|J~LeTSoyBR>ArP>_}J*@W5+1X0`G z?V|$2>PC-x>G<-O-ex>$Sy@}dx>ge6DDY#<>()w$)GFyckvc}F8>6hj)dfX2;_7kC zXmY^W@5^DZfvs;7c2c6Xkj6!jG=zhIJXz)Btfa{mYSy4n?@&JZBo~K$dfM@zUgc;p zM7ula_6V1)c?|%I)K1V28-fj`KqzApvo*k;QAs{{Lbe&xNgC!=N!aPK_vn3=do@jz zQrS7mDZ(@o&YX1_GzIHP%=${7!vjm8_i<`Y5S%0L!nm0;TL#oM92F&FDq5E0Sx8a) zUK<>eODH_A&06Of3DW*uNghO`d_$xtoEqXNl&Hd4e1~nCdge#Cq%oCG{*CpXNHih5 z2LilCgbXS6Qo{-_I~+#Jqiz_fL)o^G_KtjzQ5&QPF3;Nin=fXZ9v|?z=bq#5 z9=%D`z{Mwf{J!iNVg*pr7|q^+VP!oQU4(bUGEmC?UhqJ&R#!UP zRny9(<~UbGiQmkQ7Ule$%C;`i+7f95%9R`;i$K*x+OW00&T`q|>)*Q1>Q=;CHnkHGm;b_9a$y8de zEBBD0Mml2+39EyYOXqXW?XHuieJ)+SL=rApGzG&;Tdc+z)#-u)&1g9i&RManB{7ea zSWfe%tyz{n(u#S$lTQVpm;2%_C2BNuhpLauYCn-M7ut@RRHbt!BC>ncF| z;whG@EUB%bvaNg%$TgG_f!1`pU0Iz@F5YWCFXj0Gs;ZI3#;vV&X==+mA#tJvS?Jqc@VJ8&z%TrXtZ@ za4nb?YaCVM&>^g>cR+as7C%G@s=CD*4Malw@8_2Nqa}w2Exj=2?Cc(Ur(<4w?GZ1% zbeFGx<6TK`AA91WKMfnXu^#oY4JfG>iP6L{B6y)y?AZ_6ktDv;)U9@DGEKXSXb`~e># znZ_GcSrb@8v22-)3oqA8DhI)`NU+V2kPfB^J(?9lpffbF?O0iH~LMtX|?9UpwW&{)D;-2;vZeiuq{C;lnW}2NUwN z1I`IesoPD&80)&gI`iFopzk(x1HaO!|71%=(hYD15q8kvYeChVs+xOW)eoB zHD|MuU}9Kv5mRTVw5F&UNlMY7bAv*aVw#Av zNdr1TyByVa&@RA*#*ZSwU_*;h4RtlApuN@4j6&VR)zbRgl8AaD*RKT`?<=ZU3Woks)6CI*Qbye7AEbUPhZS69WmCuz!XIK(+V zVqSxzX^bo}2;~68fTB<{?8hSVkwgJ$5|hS}L?j}M<`F;~jxdhA2Di2HT&-4MD)<)Uh_)V7L-98`tu@4l#7;AsU~|NWw0P;Cn(QRo~%z zb>qr~t>eAfi%Ky1f@M+q6UrIsyI)=1q~BYSzI1CXbyd>ZMkIh6(+~u3b~fSWtr1lf zP#0tF9Ny*j(U?b%Mm&1BPnduC zAj!_W`KBA!b*764Mh1HstxlxSDfJtdV1tftFRZMJHx+%_tMC}$q zk0X$ z@~rPYH6dZpAx~EYd8;ci2LC&!plnE0NZ2?iYP7Xz>FEi@aFQfuZEamzD2j3^S{7O> z+4O$%8wn|!rtx)Bi(eiBm#@O%){59!5?c|w8rM`T7X{Y(yazm>;k}NC)QFez(VP)L zA_-G|zDUwIQ1IDX_uACm(c?IhPiISEyVgFYJ@>ojGH zvwROjxZmz|WWD(8m1|kH@?Vy9)oUxmd}dk9N@-t6(~!k%iU!Ks%6P}X`BNyAMy!~& z1w}-ZD$eF->^)Sx{pyVQH~BN2g*qYi;tBT^xqG@>pHN?V+&eK^s0A&)cz z?FPP}X{SCUi6Sf-3Qe&r$vQFhX@!Xg1aU_ix`G%P&a|<%i!nWPvcdXpPbNo%IwM#p zPc7-j#92o=ENcs`R-i*^St!dw67IUGeJumT0F|{^7eJgyMLfU|(c;V!8_ua!i^3Aw z5N)OJk~Zx_a*nk7IOAEwWTAQY!6{$-<}F&)q1VlYA&}=OgKm#6mTh*1Y=hG+=qfu>3;K0v9wI5CV?&Mtt!yeYc ze70b|n2Mt4Y`Wy=XhK;wd^>alK)zZlu(mfKrSLio4(%)f7Cg`dD7ja7&_O(rF}~Jj ztRloRT@=z|_PHN=jI;SMZ@zZSd|Xm56;C~WovT;2gfjHO2X`@bz}1f&K}W*V?w8~G%Re5ZZd3CNbxYJ6;3!?OLC=9!T}7xDS?WUq>r%k6$zhapL_A)g~fLnmgeSNZ1q6YhVoM_el&o*ZK$4Oz-;VsU|^ zX_vzAPLh;*X*qxXiomr;hexcguaT{8gRRmP9Jx#*G2JLBfR# zUB2|2H#s;yq2K9JEmJS8Y&~zXn-5qnEhn=E(E|M*!r1D!8q#{N^SP$tb7?mxumx$H z;1mKqmxUu2k@Z;E0R3)G)l9KyNi_R?$I5ET*=QykG>u|hHDPPT$;Q8TZ;#WHsc1yc z=Z3SdpR#>n$Y(#b%4^?vop;|GlMNDqKPC5xGytu!;^C|aT37-gWeaN? z9jau3wVEgyGA-U`B~zRgb14!on=X^dl=B+_NuCfK)Rfhd%5?}dBA$T?&}a|eYV}bO z7pJAAqiLr8bqO)@e*-Nj0|v`{GJ)lixYre7$3$0Htz}H+AuV70+I`-8zeP6)n^>=b zUP2rVSnc)*qY-h(if2xe9-U5~`RRmqDPuuI7j97+PL66ire(e~U}_?xNHU*ecm-LZ zb_-JGL>$D4_tAL@AOJ*?_$Ilku2E>f%IDU8Xc|f*-Vl_`F!DS(M;Lw-#(h+KGAcmF zAx6eIYJBsZ{^M>q_7`=4L$3D5ib5CEVZc3&j z$6;1;?s7<7G`#lmnCVC}T#vw%U}LoMh+M1#(l8_rA~2RXh($iCt^>4ebVDg1p{AZf zQy&ubmrRe>S?MkyEulzAvo+4fH9?v)SdD1+OUk*STPSAT4yQpu9S685#WcQ?Qais% zwDQEvrY+>8rZxggo5)DWroxgY)uih^Too{Onzn_>QHee4ayFfFuPli2l#Q!i6CjFW z(Vec#N(>Sk3m>qX5vxt`?^dXrsHPE1(@`CNocw=y&wO3AxV+9~O zf#2>D27DC=K#Vbrgi$ZyqzwVww+*jt#Hf!?rjjpRIe(7B2Z!8x?>5(O?67w20;W|0 z7&Y|~8|yvR*Uoe6&O?I8kz^^(8oIq08^xU6pQ8f4du7t^CjVJ9^($McdiC^R_Jet^ z2eu;X39pMJ>_DwBRn7k4l%NPv!xc7*6)tae zP(35uytWQ0CM9*DM2dJeTS)H0tieSSI+14W;t(6G(Cc^E-Hu2s-N~<5Y^dBhSyF!%E(BoWi7P2ouHG zsODt9Ku3m22rHqlS0yIIH4?&z$E60XTa2x#e7B>tIx7{StRg^TRExzCH5sb)oUzfP zIqke8_FR9g)1nNd7Zq!!B(2_~om<&NT4lHAZO9(qXARX{IJ znccn5=C0wwYR>Gi;laLRrb~)w#%!!{XbG>Is-jkkEQp0Nq=Cj*Ox;M{;;)^P2E!!I z#5))n>u5iMv`woi%Ua;*Eb}_zpa2Qaq!_9Iw68%aXR)oHHxvd6XN=HmRN%W|TT@|` zlRh7N})9gVUV`1+TR zxqt777oJS$M3o?Y*KQ2?>7Tm6%P-w$QC4z8Xcct}Ug_V-jm9UqI08%?kR)p|Bj#{_ zN!)L_@nV-W(JYtyGC6IgBN|nTo?6xRSA{x?MH{0!U$7(wr~VJ1Z=pmk@CsMa)M?F3rB5g zTpMCtK^PsgnJ0uw3z|7C!8QS^%{VhXH$}TIzDVxgh`nL-5{(v)en`_TqWpn}$ z?%yLB48%~hjD(Z|sd(^KfCNnxdIraIHj%?3OZ&oJFt!o|4~(BL6bC*SQ-;=*#DSKB zGK%nQY>jv!loihuBq7#9;GCa^rxj_WXq`WlJc<_y0a@DD11nO!Nj4-#|ONPvs9nN3e<^HWRUjD|IB=1wTB~AfKNr+d>rlJ6)({)s+S^&U(UvePf zM>=@x-CKWSeRb`hs>>$Jl29OHXQ6B@Y2`>XSbrtKAt&8-9${m$9*%v0ThK+6y>;IpWFZR>+2$C!ZMd%`YF&%Le?j|IO!k|Mfe({^~8d>sy4@ z_P*k~>>J+|*Rwa0b{)e_ZKl$B;R%&A7Fq{cDqj+l-bQNbB^8)ox|tcEkfs!Ny# zq)9>0GZ-~uwaq!18BS+4I?m9_61qsH=%o=d*25a-X)anxt3YcZN1>7)g>iCi-n)B; zJEK|}($X-X?7*@%bd!)O@6aS`l*>KJ0$P`%f*j>qLg&yz(`f|04an%`nNmrSFo;TO&rs9E&F@!o0#7}?-t1(W2@^gY3<*>?2X@SO> zmN1Bg%G8(&qzG)~Z<-<@&n(YAn{zfcObc+i<>Jl;t{Snj0t*)~p3Y={70ViJBNCmH zbULJcC3A!nN_}5J1O+&7cOM>3Zd|$a+Qa$LXS->F14`4%F*2; zj?+1-odgqErl}&BEhwv)S=|zw0wJIqMV5Q?&zS~rlr40+tGGCodRVkkCz_&ZNu6)D!&=GzT3a)(3r4npWP>P! zvvDbq?WLz3FMa)(_3l+pADwaivgPW<9=GPuNmHzH8 zKhr7$7mCq@VIrCa^R^VQB#>$1T3FC=tg!+>h*8M(shlN@EPmb(SdTPNDk~sC5GnGw z7BHr;j<#+gw&JB)5%IV3bv7yZ093T%~Po{ab=C_6X!l#`zkc8bHdL$x3)>^Woc<#{cYTZ z0*k_u(;IAa>-|HNI}>=h8(5~JCB5xk5z!mXC)~R==3sw8eIeja{nJl#bo4eq|2J-O z@!B>~uBpn}pKwu59JV+cAYU6o9Vs51RK6v7DX|`-#e`1QMThIsuoV)D zSfJA-%hG~OitEs#1x+hQOQwyMF3@l>qX}D_jtN^Ubb19CNty$!X>oXJexxk6m~uKl zh5iO+(Fh~mwvKXH5@i9VuCcZ7zWn6!G(*LSgrYj@NFLcpuT4mtcs^XyR?vntv=nV2 zz(w8}li7ra_r_eiutuPkgi0}*8e$i4_2Le%z4VCAa3Ee`SzDZFjDH(2FwOu@cz#V| zJtJF%_@MxZ9>Zd>ln|_{mg04kb^!ADC{`?s3TFZEZ49({{WpbY88n_y8-UU3aCUZvQxSQP zqS}T*H)l9pWj?PzPSH$=vQ{=upq-FCby(tn zOXt?PFi4o#4pBbfSHAF7nxIb<#;7nr>4-Q-COBYx5*>HCR4rn-1S#6p&}v~CXj@@) zfXf54cEmQ8$F*uCaD6ws$kF{N&ph5^dv}#$uB4tbua*o~wuysd7Ii^f1vu3L5ypa5 ztsHtf4Cr-xQr|1fQfgaOsjx;#Jq6{bXB%Tkbu2)FaSc%n)*_w*aWZ~|!kET~b3q_K z4C08osZfA&NFFOPS_(h6)oITs3>~J0wPByfp3M2umu_?acFvO@izycsU;d5zJUBXH z=jxCw>kF9_996lXi9&jD0!<^F!O78_JWbs3Wd87b6vBPyAzN9N7-Ni}WJ{4VvN9Xg zIXF3+GFw)3@(7%W4$6spcyKH=BiS6T7C3vA0i)xVx`IJxK%iII+U-(R6V~I9y6W-G zZ``NT9ioC-5HjTw0y&v0tZ(q5t(^!ThLIN5fO0vmFeJL>_y2*XdH<#NdDLxr@#CK+ zTCI5HE8pTY3FvKgL?Q_j)6YV-haw+>JAJ@tJY~@D3MzEGoC(X?rHKzUlvm%$dVM)L zE90=nqMW8FOB9+=B_yup>h6GX<>-W(z0riIlhf(uG_w}3`2-aZhY9s;LSs_Wq(>P0 z>b$a%=yf9@QH*x2(1Xfai#MrRvaBo-1CGv?%$E(@=eKy{l>-^ga&$Uj?ffM!Y;5rG z;Gr-wqQFrZgCEgS<7$I-5jODig3>G#Pt#P=el3SZl4R1_P&Z2f6{3Y7Bvy+z0~`S8 z031FlaVVTO79199lz7zEAh}h{t*y?HzOdN{4sv&S~leicAiH)zx*D)~WSjzIHrXJp3LQ(0BGu zQK(H-S6+%JRD_-q?|D;&A&7H=D3srs#?Z|=qBAg?&FH2nI#L8}#OYB>RW7-5b&GB< zl#_h4tZ3SR?Twsso2!(S;^DnHQD$grNMl13BUOJ)i*<^+hA8sA`QV6xE^WI>(orlH z6QXI&lb3QfE|%Oqxq}@jp8w?Mi2Dmpf8iyrxmA{J>DvhMKFd&XRs*gDTZ2~Kuv#OZ zwQ8JbdPimL6*+Z;HVu&jZ5>u?Y!irgshcr%ozR#BN99`t0FW&y<69m!a-3Y}KqP@^@N8DI9 z+mY_3Q9l$-__bs)jqT;tAe~6W9A`^pGgEj zQC74LtVW>fvNE(aWRW3Io?c@Na{rW)X?WuOoU-^@8W=3j_XNpEmM{Ir`#gRl=a2l6 zC-|Fxkh9n(Bq%(SXpC~$&o-c)#P*cYW8i>L$`)>EX{;xbp z3-HS$!{ApZ@6tAks%laA@#M}nIORkaxveU!YcVu(vQ)*Aswm0RL|ph_&}TU4No>2l zyN(W8CbK2C?wv3>HJlzAUU~VDJNFN`c(qS$W;AsGF2%t|J^qVD;geJ6v1{uZAjCN% zXikY{T-UTFl|fV|`$sq%Gn=IxoPi#sRRed6G-9ItdbDWk_^Mw-fVdzd}bT>uW>WBA3I+ln30{ zRNyRg9nt6r0)^2^5V1vDQMZjCrhq`p{)X2?u>n=fJwi8<_vD>a>NibO)9=T0dl`8a zOJUUb+aU}I$p|&r2C(1&0PTCJaNxr`pBH!*cWdSEl{3U~NZl%a@fYrkV$gHX@9^-Z z<_M3@vLC~qwjPRg8ilxff5DycBkq*~g&+8}Tu?6!)_4Nu zvaThm9wuvC=;9Fez*Tz8mHrovPKq;T^NKse_r?MXe zcMv*`0|&8msfOaYmC&C61vvcF24lSU5y@YLQve#Rk^9-{cewdr!q;EENq?Xhj~p11CNKwVh+-Af{+W}6;iVhJOMal+pdl~Vt1;k!ZN!tZuM7vCOpuThRD zaX9NgXdG%;(I90yZUhcR7`KL^glI7*=|zMpV>%g$9IH|}YPKY>DJm!#j~#~(r)b8a zd2oDM^H=}Mukwfg=nKqGTbT(H^iyFnYZOtUaS|372sK)C&Ker0JzoulEh3S9b%n#$ zfhfT&>;$*4q>FXwduifOLc*e15~Qn8BZ>Pm)i|wbC8SUivM%QZ4GvOYS+MfEK<2L6 z7SB@lngR0MCP0j-s#>Z@(T_WXD(0k^@yt$#?cM-e1x&{^>EImWNyx(w?vn*M{lr7V zoisJkZ5XCNPp7InK<1|U!f+I6Ify_DVpbM&DR3HT$M-cLBjSf4i=v{_Nu=vnbRke!45fti$m2r| zS^ zsw|DAL$Xyh%d#X*BZ-L-^~Oi+fXNKr2<&ut3>To4j zqbjH392>@QbrW8DbB1M8av;+#e9}%t(;yx!O(Jj+wP-06zGf4I^m<*2iK2=H`3%Sl`%y2DJ6kT9KfA-#bwe z_Z{Y>I;FI;)}l~8v=(Dw+ zvZ$9*yeyhIr5j<@9#JwT>?S0`O=)#dF|?7DdPga6cS674Cyu-(fC*A&b%u-9aXKQ7 zQch36)ipcohGjLQ&UQGR&bZiFaL*70oc_Q?V zM~}v|P0IeGBf6<#ZP1r${BoQyI&ggMQ#;&vyvHy7((BAhLvOf2*(@X!s+*G5)lz=~ z0|d|(SQ|hPqjZf9K@($STAZVkL}Jv_3w`QqYxH;fm~u%gsM35Vld5=PQICx2hn4c5CPKmD5k>p;7bPkv&D&;HOg zZrwX$SuOeDpE$?kPi(MzE~i+`x%1v#sVS!2id*;JWNl-U_01ieOQ_qFygOiPXBUTv z>g(?*mFIU0=c>9!D}`(2lVU9*x{;y$hAaIz6NQH_jj0(=3KHoERYK8tv_D~G&}A`N zGM?02xRO!tkGXvD0>KR{G>@-+`8IESs~}1ZDkunou2lS+c8;}(!lrTtKVG8&VSoo< zz4Vl+t@JPjO~`UNl1)9nw!z@qHfDd$XfmfuC6H@SDRkRleBlDG3y?t&kS7r!Y-L}w z(D+(N7)mH3h>|fxksyS!xg9luE-|VVB&=Lm@*ts9DQ#Wj!V(qsWOH>gC++bf!cvsf zjUf)5pSS8D5mnOwwjUpVvZ}Z`vLK-5i0_ChU%|%Hp2TP_> zY!pzNmNc!saaRVbK(r18+EXY8m1ls11GEJN3JoSi6HDvM{rd+TKAgy`rzq}l=hmqV z6)UeR6}9GcG(!a$N`L>jaKFv88MhA~@!TTi>8GBgB&4evOg$&B`aJQ_ z@YOqK1fp-(pz)(kTp&4}Ac`JL5J(Lz;dlfXyy4)>1~>wT*Psji*vQJ&ELvaxQF5CU zh_?f)1DPQeYKO(scpv~|>bd+3Wu)LfS=Rj87jEXTv3r`T`7hik7ZWt_hh_@`_7C62;($#T96k(!F1s0 z4>}C!}$m}|o_wqjtmwzvo&M9nS9Jj4}K%Gyzf znn%Y4L1DO&XvXu3cGjZSa~>TXa%M8JP6s=g3(Gk+8dX~=gTS2_Gu6@_i7wUNZTsT{Mip)=?bxQZw$nU{|H6GxNY=VJUSrAS0u;!9_2vaIgDE@%M3Y{6u!gJ(aCfCP^}4D z>np-MXxm!irYk2RS`#d_l$5+2_kMU{C<;&}=uSDFqw87QgEn=fs z>F3;h_YohwbH-lUh|u$qt0 zlrV^suq4lX_!W10(jMF{3n5Il%9FYx$%pWi8bXW_bxn!o<>nLNcU^=7P)2xW&Nak3 zq$(_@GY>8}sS^bxSVB|b+L$1+lM3%izQ`Kp`*Yn zirR)i5W#@)Y>5Hk5XP-To0Pr%C0a$&>HEfe_j&i$iBOR)ZS|n7gf-&CU_+uHMT;;< z4d8oubVLwjGU=@@y|=g4$yYw=I_0C*_CSkDBrX6vVN~GtqHyAooS>``&IVFb0>6~P ze@d+cD#td0s^w{yypyo^pyHj^&iLdfKE~6ZSmEN8mKUDf;K?Vp`1q$j#?s_Gd{j~| zLMG#dgwZHpM!0n88iUofkH(vP7fq$;e_SD4)tJ5ofBHC|3{Iz0$v2WPB+|YQ$aCQ$ z#*+y*Z{DY_0+h9EZ>N0X)0dbW#e}-cg>zjdXC?O^&N)6kqLWu#z24#46GNu+ImgFK zHns!dwvA8co(>Wu5_iV|WJDMbtI9?-2H~Q$I2LA!RiWfzLud2E@x+nPle;$s3LV zjQm?6_t$@~!1LS;frrr~985xpBPTgx82iCq-k_vARXlvtRPuQLJB4Y%7=@$4xe|w> zaZ{>#!jn&2_z+O&vYbrVKhWGi-6IVvHdb2cAzR(bS>5ai*plUmpp{N3X3Hg$MS*cn z{hpc9J)0MSb}en-^^`FdWf8KY4I-BFinev)8LWXY4%yh+APl9CUc~5c-#cM-J0wbB z@30|X(OkGN6kW2(yyC65Px3Rf!$ zsjP0ndZ^TDBOWD*4JOEOu|-vyljE5Py!Ue_b%bS6K$HtyjWuA!t9pKz&-H{RA9^7` zl<$24eAs~kBZ7qxIgL|^bZcfYB5_p~3!lRz3C5Nzt6FMga$=j>4<=TT!2Nj`i=m<{ zvD2RGCjdoEuand5WT0chAQ5&!ptNw&@+@JtSkgKlK53m(&EWWG%KFN6!nTjWayC7~ z=>_L5>>v)q1md+VHP-P_Teri?1~01uhY=)G5V^+EDm(+hILf-FEGs!YOVfB^5(g+G z%(ey(gA4MeaDrU=aMVi#w_a`5bKrbZ%#z7Za6qc zua^_+lFfdOYG(AamTT8~m>Mx+v_ZuI3z;rxz6)vVf6OBun__;;sDQvhVp_UsEZWzj zS&1_Z%{;&=Ewp`8IYC^G9u=Iw*u~MZI7xWyvGbh22xq4i@83S-gZKB@-W<}*9S$iJ zpyP)ckj*Uq=`u7YD$_?Fa>j9Wf>l7JC`1woxtV#M$<0Iz29ak1?FajIy5R8WR1mRfO2CTF zb=6pk>KNJpQ*|gN1$m>eaYz(^t(<@?N-3IVL7YLfnPF@|LkF8R1k4Fc4VlJgvWo+3sKFF~LF<^d zuKdA}N1$Ur#Y0)Jcr66wzy(+wq4vaNbbv+EHZ`k*E-!v^lMn73^6S6!kk9?#B|i52 zHgCRuk5LnFJhDtiH3vtAFf6!m5orwxl@o}4GM$qI8KKhd_lyU+c6s-FRU0Dfz*?Nr zLVK^umJmgfc$6%QI%K^S8BKEe`U<@~B+-hGeXP&*iy`&6&WOQ!z}j#?6hs))fU1EAQ)?^>92QK9vK<;pN((>AJy{SX z;OY#V!^IKPI^txR;G$I&0mb|T?J9JTQ;H0%!w%m?24rh$UwG^bOYXZ{{DM&&;7;92gkQz+16gKyRgDEa7*xocGZb_>Rr&YnZ zsAStzEL=qpCB#IiKzri>6k2$I=#nrNEd)QoPN&2=!!-fMMOd9+q70)FkrLJ_7s9Ty zk>ooTJW}eZO;R%gaDZpm_}uB+vc6JW^~yocvURRYr@zS;e*Ga;sp*C(hx@1e(yzS7 zYp)$~I#!qE)ezBG>t)HzKdq;f4m+j%X-l;QE1y@M18bnCG9il zWT>{F4I>6gf@vz!ZbBS{ET$zks)hE`7|WxhBW#`XxBu3w3|7|ovw!Bg^n27z#C&ncK>3o1Inbz_q-&1CW%fftT9l|iQgPW#dQp%%)Hye>G;yjI$8e%TnFBWA1-6fIVb zf-ims4NDQ-wzs+lE#UKqFkXNJh$3AYmEQrV@SaNC&jV0u$m{={mt$CloJG z;KO0Gc$yH4!8naEMqWcXKNX@eo@|Q_qzHTKgClxF#ee-@{!#wR|KHcxI~?=m$8Yeb z|EU+5oLN@ZbJjK^9zB>#e(eIwz55}hncfD}X2>2tE|@XCJ0(d;gt=$x_HwbZVQb~PbNHrNNZC+K#;xD6>85~wqVNll&& zv96Ll#}En%mL{uYI;FJ*P?4owoH0VkR5~CFQowqRYi+^AvSFiG3%T^l^Sh+|gxaOp zLeVZ4Y;3O39MuBL>!2ZM8tSUTBVtbE$*N*5b)mWf=M+^_f^niD?zBRq#J~i?HZAE& zk8U2xHL9vga>pde{2W!;$X;-ywOYD`ZBt>5ll)R%*IA(owZ1D4+vdFU#vOk2^XK^s zf8j^@i+}Ak{`0^5I+ri5OKZQ*S{C=g zjH~LpFWjiW$Z1j+B_^q*A0!e9riiAfQ5zkCFvl^cx2iB!VF_4_kJ#w#Qk#Txmv<=D zn1emX*~D=EVxO|7lFjan3Nb985a&NxR^!9DSw&l6!FJPh=T}}unDQqM~+s$+5 z$)f;!aLQ05dk1Jr*~HF4A#DX3jBhWuszpiLLWprC0gfzA1!U;tedfhTv?+S!kWd*a zQ_{May9XuL9y1Jkn)#z8S+dPuF(V&_oJ;47mlahNqI4K zjTFdT+u)QEt%k;0K@g)PMv0u5k-uwOgA$;p{b(Gf!Z2}-(2rtky;pY@;|x~nM@~>i z%32umc}FpsYf$yQTrxk4f>B!s|Ggv1{4aH(fztiFN%_;rWgr}d|;5UEcW&Y!Tf69;l%vBzHa*KPnXM{%gESnqk<7_BU295FRrI&I0io1+&Y)x<%^u$askpJ`SuKS96OXJS^!c{8NvbJ2<1c%v?7QVZ5y$uTS{l8 zV7VL@tgm*7x(TzU!|}M};7GF@4$$?CsN3blcDdGFa%EWZ(#X;#U926^Vgcuw7EMc2 z`}fc~Z$Nn`@B~_+Fez!4bl_`QiHt_^H8|}D3b!IF<_CW%YEVvETs()e!fC({JC?^N zonlbv(23zG6%%**eDmcaR{J3cjq7Z!ZZOC;C|U(nGG{2zT0EX{A;oeo zG?XyVv|d_P&>p1~*igclPCI7>jZr1t-11DOm~JF=`U!C!kYtvl<5N1>25SsC7*EMM zeF;UXrl6@y&lGMf#cYladk?&N(HsdMKTQmBL(JPRWu1 z#j+9^F;vKGHld%#QdS6)h+M65c4D}=+aZb0nA$VnDZ4D_Cw>e`l!r8h_9$Es0EpfJ zDv*M&mC#Fh@k8gp7t!T@DB;K{tmLMefDk~ImL@rkqXl1Zm5|Li2M(NuFmMD~%SvKJ zZ7tSl6e$kHuxD9YU*{`de|8(3?Jl#-Ymq4UJdmviP-iyKE2D9TvQ6t;Ebofhl?nuOD{hV^`vC>(RL9FgKZ0ZIel38V;M(I6mz{LHr>gz`0x zW8n%~Yd;#`B{``BudN*sgwEIPR3N!d8(IL`${UeImIr7;g?264kD6q3w&3Zfx_siZ zoBZvct9bw3i1k&)=RSLmm0>JxFApEi_{!uSNk8D=zT&YbG^MIADy1yvGPF60BK>>D z0|i>wPC3AzC`JlwjU<0NyF(s4nhAv@iAy3Cpfe>?lm&`+N)WNU{l-SuQAROKrgOvO=_}33bc7 z*W)+$rtF=RL{`(adjxe&mSt4)gru|0`f9*LFL?Ce7~&3tVTaT54C|$WsT(Vm?JS9D zYhRW3KW{t|Wx*(D0*Apu+t5~qv8y@X>xvB1v|0+7(gp}GY(8&jOz!1yG1LNs*TT1p z!-(awB1wHXMB_Pm&MxKj&+`t>DJc#{p(0H?-usj_ZCgsKNC;wxSvME2Sr!fBiDkMK z(H*W(KR6LC-LzbgZLHAEw|H=Lh>I%^A-CR>N0CN>Jiaa+aSVaKhXCK!GB5`95%{e> z^2`drd887j#gM}=lsQB=htlBi#_ze6poHVtvNDYL?B{Oq&Yfd!-Kuae-RIg90|vv0 zt<{LNen`6rSRZyUEP3OVnoh6ebDzFS)tqoRt5HD;#L|uexZeu~qyT!|`1a{|LQwg+ zq)O@~8375S(Uc&-)8I`F-e#E1r=(dT2Oz1pkqnbfK zVDM~4)Q$Pp%lqs-IHkXl;%w`Cbwm)uVB#@B9$`v{v3|y862&aqM$|};7YTO`0$%Jl zbcPY5GGso>7z{QzVNTNP(lm-mFh%zu2`d61C!kSKB}zm_a%brnFtx$p@bS8mJfXD~ zv?Icz3@B|xA!uIZoXz2}wjd^C7OtY&F>wU01sX#WEU>1a*Bg)~eJqM)Wk4b7p`IrL z)(I0qXi+{Vii8hXR0W-;#R0|%%|>RCR;}f*iMS9YQdUTnrLYyZ$01j;j2keVF@Fo&B7Qh!t+jGc5r^wBM=ZLT2@t;;zhL% z&yDR7M0Qi9@*_QCsm7KXjFwH_v$aTek?q zmZE4_TVLbugApg=6WTIl>)Z;9(t?U;)dC&&n9WXEj4E#IrcBBSsfvh*C~bqa3Kh3t z!A=7nzwQ_g175mYb8pg68cSVSrt?~)mlnp5_c{d1c{!=V&?X9%Cb&p}XPtWrJ`Uxa z535k%XWe2Q04S>jrY)Pp}a4!lvN|%E0UQGKSf3k_ z>x5LeijD91#q-|k#u4O75W}KaP#Hs@`>3eLo9~Y~+&iV8TCQF`&z*NqxpSxD=Diax zU)rW~ZN_%G5e;zG>xN7W0;Ju_TIq*R8L<=OG-PQY$bcVMrK2EH^r0 zb(k=j3h~i*OgiL5Iz2m*keR_vq__?<^-dm7$o#aNCL%Kj;xAaVn4WO3>EG8{cP!pw~syWfZU~7k3v;?K2 z6GI#t9F|2@$Vu21Emlw?P1$%ZS>5>Nw&fC*QA?g9hS#)~Bmmn2N_vxos-z;&A<|l4 zo0@s!uvI~KJ!Ua7A`l-35Qlh3+O>kjHR8n@ERMQuX%$iwjw9uVO}R$IBEu*WfTF4j zjEg0g5;CR^NMg-mIcL6@K76zJk&PGD)?RIjUTMF=^BFFB9ZI%mo<)wXL zqCENBIX?fRS9$lP+dMoe>GvbhM&|U2(2Vg7g1={SpsH>{YZQLwi>JMWVOLNaYmLCT zegsGj){j73Uth(kT7DJ=UB319J)U~ina(2w_?g~#nyTxKsvnUE49h|UtZ^8Cz z$a2!>?0Ct>W{;15;yQ1>ae|982Afwnp5(a1N!aq<{eb0ilg7f)9BDG!TC?m;Q_dEK zvQ_kA1x}$8g%?P7bg~v}T94xOBAiR4ZNSEkbGao8LwZRn&@$Gd9TJB3uc&>C zd2D@6C9#;K^}_u@LOSTM(e*i+aY~ecVuae{=Z3}+iVlb4kjWzB?&%m8Bm`Z}X__*~ zGG5&7p#v*y2d5NHax){zy&?ca3sAhG1Aho;gdD0=C?|asIh;V%0tATn6qdZydPE%u ziF!i~p+Yqli((jdAWvj9CpBOA zjkjp$F?%PDt*r$e`MfPiOGZo>1kvvq13DQ^b0?=SIAzgjaDl|_N?V328BOD4W>DmN zLKvOQ=ng}0j;ey4-7BIfx%Xhk=(yprCkC7y9-(Z)h4X8)jb`<8A)oxzD&P3cJ6yST zk)Qp8*Vucw&(ZOLB+pLum`*f7ur8FM*KZfZ;W>=6v>N1Pg4u>HMG?DUrI}11+5hji_j=j8<=#dyI3s#nqQ`XNN1?2z~N^2I@@WBy; zwqZEz)7XrYSs`s18yyXGg$-6Q76K9f^Wz=;ns5a zh2X?+#2~z&iopVbj=ZtNpyPnDF=8C9_2DlbMHC}b+8<(8`8JM4Q=mdE?chqrSz44J zdD^q<^_@_8YT2G}dG{iN zH01VT526~=_R(s94hzoC7GMLMu9!|rEUi%W4-clyW{o7UxPX>IAXBd$V7#o8wnPR@ zWWgC3&K1TB4?!DC94eG3iq25RqMHa6C>*{p2oWx5q#3Yno#+iH9T0aSq9jBcMO75E zI06-cawu!SLE!w{P|uSK2?Vh#Jppy>Xw{M+au7s>wk60CNzChIfi@07!ZNow*Z9ug#)%Qfz+^m;_t)*F z5+YCQ0+i&6av#G8Jn=IG1IlU|Yp9wfZ4FlWaP`G!Q=Wcdm9KyGfSd16Id`SvoQ;^@ zGi+ZBIDci8dk|tbf(vJjEtHN?KpF&I+|V|(%6e{= z(IUO-gpC_uTP@*T7#X2htgZL>^T3IjK&2|Ju{FF$U7e_`QW_= zJlH!Uv`_Mh7q9U1f9D;(@P#|Xi6!fW;2Ph>X&gpZ80CDYBME3rs~kzx<*{c596lN| zpDf6_AxbB>(1O-jz)2UYFdGCA0gG*YG1j^TI~RsDM$y<-yu*CF5JOZUV&FoIw1ICi z*8mPb8@46QV=ioMp`6BP$KlB-#iAjSyuc9#eg=*Kf+Qe_lqA{KLS>YMyyq?qg-^Dq z7P9G24rZR*N{PV&F|DsrN{t2Q>n+9xSe9J9wo0hAkWxpB0x7jbp@eFlm`O&+A*73JuaR*C&SF9(?Ym} zfk)%OS(>nsP*~d-YXYiz3a+7P5oB_>n|ryiCghnHFY&>9Q{H;}1D<*68c$riz`Q&n z3_OT&b~IytHfLqnB~QSamR7|~W>Z|CpJr0XOQQgV z!Qd0V#iFHYOX3J|{c(~K5M!h}F`%5+yztC@N@jlzxEox_;+7nWw3@0 zYc5>eB+XWN@Zju21KQ?K{n;0J<(*?*d-)!nG?EQ~13%ojBe_HzYafP10jklIvpG9k zYac%ThH$wKPUgJ&%{w>=A3~Ig3GiOVm`VzQ+IfDTvx=f@q*?EoXLgv(D&Bo-PsZWr zVHYf@6e-$P3!g(Jofxe`-!Nudmdl#q;|Z6p_c=HibMH|@6l6G*6!@e{?;y@Pq*+Si zT4{S%wZ#BQrfJlYZl>v_vCMdN6gU@%M-gr9#w(_xYh06vhXq9(M}ji0uXlLz$#rhN zf6CEYXN0OJVW*A^*hJXub=ja2C3Pj`X*J?mx`UR-pW0zMEjc@C#ekwX5Ja$*f^aJb zV!Ko#_EA&|lq!jWo*#8-N_wju9IZ^;Zkmud@z1Yu)TQr!by#}%EcbnAKxNd%ZWN&Y21su0co$t$#KntTdk}+ zH%}#>RAD1x?``8m1K`=`Hu>?NxzKvDV(^DU+xqEo!<7!(yP3$A zwREVIp|Ax}*yCWY;jPzCc>Ap}rzedtMXZ6vqT%>x&gwAbCw}S~u06iV(ddlEWMFb^ z6Ul(Jm%s6Vx4w0ZrDgkKPjs?FJ10vu`^P;B(?exll3rJ4BIT**I>=`<#!)XDjt`Eg zmP?Ga&3EuL>L0fOso(kEpp2{1ASOusX!D3b1VxfCp)5)cMpM$*>lA1O#(=33oOle4 zc3sAcC22q4!J`S&MZ=B9J3RBuDnIl?m(Z=(TDW|1z#sb)pT_ExU--KZs0+A$zJsD= zx^SG09V9WI{LB_V`O}yA)Tf`~^rV%x<*Kqg^~`mie Oe{0THf8!Ci-ao~WVw9pP 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 TZWAeB00000NkvXXu0mjffXX&J literal 99432 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%0AM@w`&rOP{ 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

b`2(1J9ZB9unv4#Fx`45ekn z!B{y(kx>+eB#jW#!mT(so{dz3G>$1SSi%8e5L$r&%basVrG?QpmeweplmgU|p%8N{ z$AINv>jEqA$Z|zCkCDotkYq&}aN<{d3Sm=Z1?fB^GX~Fr!1pi|q(zQ40z;0J7KJK9 zPC$Y>_tGec(irkNuI=JUA0ZXCE6U?{Rsxe9EitymtWc1bM@JAuaY~UJ99IGvN=bxt z5GbSsOIsA$V7WGqr732HW&ymWCYgOVvql*BlJ2U1fAgXdJaey737!HC&7BB0xXQQlbGP6jGGR zhSK-1(ZzW))rll}aTcyQZ0+@3)Y=IxTtSv1ejKoO8 zNv}^F>QA@C~*-=K6twc2Jj zizv0U);WpJ!V((6xxiH080BI)E@?5MyHw-S>ME0A&SVmkBq^36F=EMypvR z^uVZsLb!~l6O1MQMg{)27WB_s?7x%(`iW;g0|0>UB8oi6@&jB~;yV@sg`&LhMNvUs zB)CrL`EWfS3l~)s*set~%Xsz9MbtwslFXRKZ7;sL?HXBT zIPT5(@VzaJ5olo}l#4~V%P5uNc^=Y~`aYutagyWuf;)FEFrKD7x_`)cJR!8Jph5Uh zXh|BCcPOvoB86QV#&p4ap3%JFv3|AA@$rbAgB-^SFbI-lR(9zPxIqO!@X1Yq<9dip zqW~|E(&C#p7c4r zKfo3>v{pz*f%Z^^AWL$DCvfc28ETBC(1vQG;KuC>OeP6~(}LM7!f`ExkQ7CMG6i|A zDCUA%-6u{Xgh+6#T8X+;3AJ_wL%~^ZLY~{WUitYFLza~8S7Q`G5S+I}98{r6)0kSP z%2KV0NL>En4|jRKea79-y~M^9n|t>@K$(CbY%ot|OrivZ#VjoloYynEAC|o}JV3&9Q@(GV0p78wX5S95jj)Uug(iY>%obGZ+XTfDQ9+&5R%fbo* z#!<$5_a5`%CtKWqaL8z45Vpbf3M|_uR}z86#-$opZ!8gXLYzt!>Dste3%^#Qcb2jD zykPHn!Pc`r+GvEGBdvlWH;hLScVF)EjbFb;QA9+sEThGWlr)78?mgq(_n+~>hdWHB z8eu7r!(43pI`g+J6yTZ<#ae8 zR{^LHojV*KMcjLLkNY2=p($9tRHIU_VCxxyFF87^5vv*^sN&VCEU&Z){W8}K z**xd$_>^=$M`@k^13S$xUCJYV*1v@^sc*Sho+8gBj%O1DKA!E7#0kg43Eg&sERAu6 zgHoVU4-=MNdR@J%n-C-;NQE;PR!AJ9jqukALt1fAoiY zI38TRSYbFzm_!Dh8EQ3`H{V`jQ)@`>-dH6mRyf$1U<$*lFE8OZb#}K0 z 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 literal 4687 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 R83_OY002ovPDHLkV1ge$?Y{s3 diff --git a/manual/images/fts-button.png b/manual/images/fts-button.png index c32eb109bf51c5068ad11bc2670886b21d3e44bf..0dbb68f91ff9a55f3dad6da9182311aa0da16b32 100644 GIT binary patch literal 6297 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 KE0rlc3;7S8t-nwJ diff --git a/manual/images/python_template_example.png b/manual/images/python_template_example.png index d525ac42b6d50fa0abe7d93bcfd1cf04067246ca..ff62222b8ee7aac3b5d6b6a0a45ae6846d2fc99a 100644 GIT binary patch literal 10614 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 diff --git a/manual/images/search.png b/manual/images/search.png index d3d48ca74d48e727ee8c49b7e07de38891210fc5..1e69b67cff942af501ed10a1993706d5785ac24a 100644 GIT binary patch literal 16791 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 YM8(EJiQg~K$r^^JkfdPV2kmeF3!VQ)mH+?% literal 16815 zcmcJ%cUV*1w=Nn#KaGk673oA&1e7kF&_tw)5Rl$`?*ve)7y+r$d+)u4UJZ!Sd+)u4 z-aB^%{O#xLbN4y-ugmk8tgJTYnsbbIyyG2n=?#|>#l1&*4+H|?ii^FI1%Ym=fIv5l z?)(GnL9atafUmn&V#>B45abv3-;FRj2pI?@s4f0ZQ0~*{dK^yS6GfNddV_Rt{}9P^ z@4KHLeiy2&k~q;}`5-$g9=#$NV%$NaUfD;|D@Ata!OPoZeg!WyDWzFH>I4URdH2CQ z=r9jSn%E#5cgvhV6VK(Gc8BSIB!4d`sPwdjX(B?SEG2&8+`bFrW`F49HsxABMc%$~ z+!;UQ>TsB0YhVA-spFL5>4Q7}{^S1Ush?S<@TJq3%hitucfLKmzx))~jXetc4taEa zQX=y2;eQc<*T>wy{O8z%Zb4SH?R>E7vF>|{GZa2W-J$BxChWpep!ic0&t}Hv^(`KV zyal@*-2_{>O^=LGaIsqd!LQ#6@;IU3FS46zA+wJ=LLUmeJA8P3wwxNczMpl;M35}%j1B8T8wmkKF6{kw(~%LX))6U&TCtiQrn8r`>tI{jl~B#h3r{tcdk;KmW6UhH& zZwY*o^mkl6naEFm`%2(CY35V9nOA8QK{V$Jc-|D}ed|9ic0>b0U+K=d&`D2n zhMW;WUtG-|+cO88chVD*dsxRnft_!@GW0D~xeQrnIqg9(Qn zo4H|yoMEsOuKRr}UQ{k7)=a z9km zT2)f7i0a&8q!BUg54p0+w*(D1rbJGc{aRB9xCcem%#4HE>`{U1ndQYX!;kY{A2i1+ z_Ioss*J(m=G){vGE_N*Ww$sgRFSmv^nPOjcmXQtW`#+BU8fMgYmQgIdDfwp{LE87K z&sy|P`QESwES@I9C~b&;i6UMTzy2Q`zT?wp*m+2LBslVuY5c-yO1)4tq{;tvg9G0d zs=UMeq_#lguqfl=T=dChR-Wc@lq!Ul=qzpQ$tCB<_m{Ja$BMPPs2R~^avk$~%~7YJ zNrSm&dPOx5$JgAj$Pp8kuaOZ+a`@LS_f6(r>n%-(T_G0-^pjeNNB5?-4^CYT#$s*I z31d}9=`o9LtDeZI(}1qKulhtm9in@^qL|dIvEMw+St|NoqICp==$~qN6!@|9_J3Gv z8*xm6c~t$cofw{1I9Hc(!VVkuqKIy}J9QjzI>p9apthAT{>iFwA2n2SN;kE80FGlD z>ZrDqSw5cSK@ADU+tjpv@?&xl5W1B5AcopwPCaC7UKoEA_saONHe_$>Ec%Gx4frnB zHZphgwrQRmi)i|6G2^qJ4RB&U6#>b1Nl(X&HE~?qo51 zY$o-(4AQ>D91@?-mD9&F{eaa~-W>f&pA)fgP`8fkuCmA8yx`!lGOHN&DY=b?@FV0R zB0D3KjvIDIy$_856&9%{C=iA~3iaROTS^X^4)U%cCWtkB@Wc&GPgVOF*eI7`dpn+|s~Y(RQTyDHAIpo4&`e?^8#AaSHSd+F3bI~I(w3116Ajc4ExcdT z;m|XhDzX?c`^88)=Ogjz(2S#sQNDfc`36P4OFxXdbAVZ{{H?LFtc8fZ4rF$n^W^7C z6{G$57yROSgG#G4vQfjF+~v<^#Nxb%$7J0W_r2u?pVX_-ykAlqHctL-&=~-q+3htv z;A}m8qB51a?NEoBR*NXmo+NrYoWQMl6yTE37Rw{x#o~sVji}i&eb7ofKG`(RuX%qU zwUu|bopngl?Yv+#lbGNK!j$BqvE?q|W%QV;?>qoqqhraC*p!RtH^AiQH@H zlqZg?83@#!`ih3K$Od*EWodtw3~9B-T~pOmqya@{^YT;5)?DWxNH(Uq(ywWq%!I6r zDSmf2j}aZ?z!OtF=l*MA#a%fy6dCviHlF=+qvNEn1W~RaG8?VTV&)e9(T4*Xq&?FHjHjn0trAn7>41Aoouq`Hq$-bfV!v2jau!^m+8t zvsF!e^$m`X#qQ3JhW7~aqzy3WQ-sPz;MqZyyOh4?*H;hn*$Z=~s&91WFWb}v3R906 z=uukxguH<{KO?-Gbo;r^t=RG6_SCW{yOA1G!6;wlqFDu=N|-s*h~2S2?bLv*<>|Td zFpWp8ij_);+PO~{4^NZkm+@qe&exp=os|}|ry;wynm4=PhV~n$<2n#wHx!!b^F`$Y z{F1%AK!J{9%4|{^saQDS(O`L<{JEmJuW^4EIrJOjYT|2~rGQ5gbGCz-@ofD>PZDgG zzBa;^PeM|Wu+9gggm#6Q3||}AvygAiV`AtnB;jMB{er-AwM9;)Tg;Cw4yoCjvm~dy zi?Qb}8Y#n%T0gq`t1$#g`YUbzLG!aQj^cZInX>P!M=piEvJQ?3LRI4)2Q8Sm$(3Pj zNagEtglgCK4YT-L)(5`!t?r5M8q#dyz~xGcCWecjH!M|olb&dP2|g!T9qx2ZP;E|H zwp6$Xj^HNhB`OZH6n6a}^3~FLj;wsbKIX*orTf*aeSk`y>B1bXn8$u3JZqPKIWoNC z(ax46ND8Gl^G&pxpn&d;(MhurVG^qXa!nR>C}LIZDtM~yO|5T?*NSI!l=+JtzNB&g z^eGb_1c$8HTSH`mKREoke0@MlLQoD{VXpW8V5jby(rRZ{SY8{As8CF6RdVV5J+h7e zm~h?soWpF)z>Sc1=tDnq#@OyDY8hwBebd&I`|){)kLFH;t;~^!S(2xRn+2ucRe$B7 zkEdRSPclRJM$*AUZyRPQ!g7!sjrKbzr2nvN7i4i&%Ubo9AsEN58=+ZWhNj^%%}P}f z^63d+H;(I1X-6T;Ak-1MyNAIkel<|m>1>)iajBi=g?Es=|P zLb-nW<+fuS$DBVh198c6)i}(U4|Y_|htOgha>Xx08k}tJLLWP+E_XHXzmoNtHtulj z;nfzud*x3`9p<_k9(kKKJ(Y~d=hmI-#~1V|NORW&-%j;HVGlRdWJC>fT5Mur*|Cep z(UK=58QIMa?Z2n9M&|k~=ZC9una1IBq^_~C@-fUv(vLLiG^{KmQPs`9@SSQZr>|{W zDTL`0>L_=+csF%F*uPtZDgPTJpNo9F3LTp}vhvPi*pXSh+%}{nj@mDv4{SafNEIaVbCApfKwzW~EDeL%?p1ZN}u*Q1OEy3uC5n7q7?Fq_!s85Am-Fe z(~7F#91IY~fLPx=J|26*x#0TuDg;1GfN&3R6svy_fggWBfDCl{-=A5c*Sn_sQ;+~j z;kse9+JmJlu^_Jf<)^@g6e#wD8eyfat!-f7IdoL*wL00qgVPY=5 z8$>o*7;nfwuscHO$U`;7>WwPQ+vneK%kq&DaVGz4t5a21N0vvil*bY}=Z-wDGDNym z97ZTm$a8j-I^g>LLVN)4*46UmTs}Bt#ZKk@Fvujn?=)Wkfs#T172j@JPWOJkq6qY{4+l5V*JLxM&yf4CRcjUu}?<|&QDR|FBc&^J?8)+IN7$MA9M)H~ z9_a@BRC9{Jm^X-p&FIb=gH{I&Tb(w4`Rbzl}fd zMvHt1COtY|Ll|%AwNU=}>_WhmG^orREd00lVK9Fi-_*m|)jV6<|FWb^w%6^9W{LZq~I(=S?X_RTPer{_j8 znKmWIX#0kXdk=Srt_qF|Y^+(&}8~jBT75_4ADK+JAe}FBfv<1h6jMV6~jMAUV<;#SY1Y zwpf+Dc+OuH#QdImBrz;W$?D8HaUAa7Cv-;mSV1y7SaFvhlbG{mb~%={Gl()Gsbv;1 zR#mIdqY>B9+cB1=_(AuNL$1}$b6&78*!GkgQM~50C9x+zbJxxb5^StWsZ$1`7%()9E5*^?u%-_$ABVL_o*Np)}TO2rWrR znJjYovc|pYs3=WE9#nkKL~=8ZMoQwZl6Rxu+??PAzXkiAaQBD*d|_arxsU+2R?DV< zDLLok)O%eF<(i!PVND!afDE{ABE*8A^=T!4C0pQ6)}g-ps-B#z-OYk|wB`3+s*V3W zOr~d;#PMSV`lE5J4PH9|M0fqjF^3Uk#m))Ad`2j4^G$7u9NNBL+q)aD4upilgqUm- zDNO>PO?a**EYdAMh60+ac0Ia@7_CL|231nW+k^cAjd=)18s+A!*jE~cWy5acU9^g^ zT7)+FQPTX2a`ar7@fuuvIQX1MFt;UpekASa;~b$rb4`9IO$ct?D<9vX<)jtnhFF73AOJAZW(Mx=#hoS z*9EVV_oLxDG}(V(FQwo4oSPxChQs?h4#SiJk2!^MfdB*Q{`;zH0DKuM>+h@7ujBX;1wd!BX}t<(z;+904Hw4lK;{ z=XP~Ae<>&>7a6yKk9F{W5rsamPmAqQmW^5|ok3M}w&}MMr7sTMa%;Ddn#6fhiwC!{ zIpe?(Is{OizMw_^ccgSfY)v|C3imrU64f6yode;){0r6yKp^H`l;rYLX65_XxR4Au z0T5mikylae|G0%c5x51m$*WL<#X9W0zCa)pefy)s^JZPXKtk%PsJJOQa&-nm6a0s~ zN2ZrOaj}mG>Ze!8-*J5je5ME8e91E7y=DmaLZ2ifT}Xqkf?c8m%;-)M6>{Z8)@voB zDw*bx1WrBFJR`?O8Rf5VG>V>|-i&M-Igu8sR)T z|1!lkbS3t4DQ?Gc`dxbAHv8BUE}`IKm*BpV3rQGW1Y~1OmaKli`RS!gsb(YEk`e^+ z%ZF6e>8yKazc}{S(pijoHS)|OUJel|K*`Y0(O<>p&Q2NE0bW|w5=`)`g3t~XdADcO zfJ(C@xV_3lP@m0C&9TabRwkmbsYh_^*SK@*iq^J_ee0kI7}?kA2LYm%`c5T3q$UZN zEaq&OQ9ze5Zf_Nl^l_43;*y@%{IqqkKIIsXu|=DzP;aZFXb z2UD7cDeKi(V+i>yUi!WAe4LFAcM*wKP!P3%7La7k{(Bl+fjAU<$yt35cA(F#T7Gvh z(#W~QHgt%riF=rjWFcanQvL~GO{y^BPl}55G(5^19XgaTj_m zelk&g4&H5KlUf7Bl`oK0tx}SW1U&LlLok_+t{()3$`2nDyQfdpZ2#--c&xH|5m#Ta z%{Xmn9`CFYKJ7;qP9E?&(tz(gADGcM;2!i$G`+oKGwHJs2oWwy9~x?ln&taL+mBw> zviyR21$}JGOy5ncPC$!y?Il9FNRJfcte=T0ERs&t$%x{7~SCkT&gZxdt9L}KXGfAJn;?D9cXU{`pNq#m0DZN6{Xu(~;0WRKeD~;A7 zm**XPx%iZ%So1Sh8v-K9Q>=>hC3{s%^hu2=O~?Ul-~~)FM0pQm-6$3Ad+p*37_C2U ztX*BH&SmRzZ`4qbYm(WGzQ9a>q42$S@st$gh7OsdfQHHo!q65+EAL0Ov}&<7Fz!*3 z;5K?1)u~PmKQ7M8=+a-YswOL*T>B4gZSL(WQi;QU36#B~BNab)a({tH?)0dSUHvY`wDovDWPmGqMMKNYE7eL%0g0DdPK45y&0iK!*J`A-+h5QO5+b^ zK&Ee`==(Xebm_ZE+Hp!7H1CdLIE%)$`_T&v3%Y*-paQOESg{X5YUd??sBBlh?gKG7 zf6yc~TDJHe^|?#ukTwUIKXx~ysbN}6P-y(5;--yFl4l7!Brt%%!b3nbuIzdLa8e&S zBf<5gKkFkM9i6GEDF(P>H+O%n{Dii20>8`Na*rShbWeE6Yy;b_MX zh|g`pmu%i;=H1c?!#;0HyA+HN@besfJG=eo*iM1%6677m82NaXy#ulsl1Wu|6I*nx zsSw?~jEbq_;2UHR6Wq&6VGq*h@-3fLm@>NAwud82>7aCnvRfm9Ia8@NUQ%G`Sgxdt z^@b^0z{(@wSI_tra>6tpZM_oHv?G?Z%V>5N+FwApgh>o?skNv@1n-{Ybq8vNAyne( zl`@|i_ZCq(wm6}b#PlP0oM9Dpb)EmY*NDZ+Q@O`jd&7#6J=f~#|FQ)HKkTFf;^|+z z0w~{Af`TYiMC^!$ol|(Pyd4zky$R4{b}tAtIx1=e@UB;@*`JQKv=N1`NbcvW`2kkQ ze8gB$QE|iz0u9pxe+&#sNFQRn@jp_dO;Of&BpI7d`qs^o2N2xdzG?G#| zQ(}P5JYTK;ML+?1^G^NQX$o@ic~#$j^GCmz);bEJzg_2Y%?ufO$Y`u01jBcTvSkcv zI+WKHbLA6Ds|b`+D`jmHi%#y?j=g`HtFP9dgAI^CnAEyNblc;kwdp_C%n*CL9J|7aa{yIcGKd3i~U%z_~I!XElQ zk%6j3lNOAmhZZ0WUH}mG4?rZKfNVn%jH2x0I=^Sh7C^nKHQk}3oD_R@ZgS_x^Lqjl zj~r2-%HBDBJS-I@PN_})?TLV|wbW=p#i4Q>nT2w9udGLlnc5fZ6NtNmk8I3G2H{FE zP+bCcunpHide@47&GOcp>J+5enJrd&6(tjB6xRb5H&G|znVH0@ui??uyQYr}7Y4B7 zgEd0cd{4vmhls)&Nct0_W37)V$aDwea+B2N@s6VEmr@doM~P*>x3}-E+gO({b`ASs zQgY0~qZL#$?_wFivKEyfxNTW$RiP}XltbCg7&R+=2R$!v_gOqVZhKV7?5-TG3J zaLxnESYUSm_UCBUJ!!59wq>9bhezhhJVPn;Xr$DHi-OOiv33RIE8kZWb&(Cks$$@C zV}08(M_9q@mKft$R}C2n>bgBMLJoe5wE_ z2eU!>Uu(0q(h*?I?O`vPqbW0P!Nn;TnM6Uo7Q(pkrtoPeW-H0UklaE8YS_Wfao(AYw!UGNg-?saCf-G^r-qTSpg(=zC zh2%0Ev{EV+TZzAm3-xrDDPeHhQ-_ddsX7|Miiuq7d3#&@A51Gywn$v)qxe{Bz_3*#!c7ZGqg zncG#jpx+)&Z_PB~;NYn3-2jm2}lsLCxfgP5C@i@?mgO9pY_25Jx$IJ#6l}JS4l*=^rBm;YWqDa&pb=m3N@M)GeTWy zMA~@^8O1I(yDGdw3#74xh1a~*=&S-4o2mfr1>j;wm=m9X#D}&sW`9A|a>oRjCO9|(M|2-v`YpaWZH^p}n`I_80~pdLYKaGFr- z0L@vxr1g0ZI^Pd+Ldedg;c<2HvQ~|`)KK(I59_nx$g`&!JE5AkLO(iqd0vvsX7gVm zyY=tfO8LD8?LKtYUEP{)sWOMRcr;RLVJvI{0Co>m{-Vf+vf{AfQM%3^WNpJ=&ye~A zIfcZ^QM=oC5OU{z_l|j9LVN|I)t76Du0iD*oOZ3DLJo`a#7G?8$YFO*-?pJY0O5)E zejG3V4t$!3%-Vk!GolbnFZ*0*Lyen-KY%0T2TBZ37r#k`yVkI=CqeGu9&xiv_I}G& z57}*QB~W=$_Rm!lPAN!AX?=4mB^qCp`A2Z0gkEb%CBCIp6om?$rtr$)TJRkhVOcJE~`bEc&HA{%~*FpFKopL$@{wv>lP>2a#Af zVYpX*-40fbf*zwjh)3gg#+vIiB(&T8hN^a>4Mf0AHs>c^D(+W?hBdj>OFI)Pt9-aJ zYhBCDF9#rvj?Wxj#D(|?(8smyB*Ul#i}oJDBDEx89lB5U*LB((P_^LrTAX@iwf6(9 z@l*i^;l%*V3JRHKb~!O9JEMjRgt7!bWR1fuG{}MYg3Ybr!ccONBkwCt1*P8=`3_CxA0!$ zM+NDI^oiF7b1l{U>v$K)W^@1@nT{Xy<$KlJ199-et*^Ktuls+63fHbvb<_P(#yo!S zHSfGiA4|Wgr5*Mbz!qaEIzQSK+1NU^9Y201+wVQ@0XR-eO=7BNV}L4f|G5_)g@%&S zbTS@jnpr@gAV2WDN6^ii<2`$ETz825vuc|FlgajRlX-zirF01Z6vywiExo8@C6E%+ z-#B_tGiLrcyp!&-kP^_cI(mD9Pl&bv&0I`IvFR<*S~BEhPBPqB?{;6P)3GQZlU0$r z^avIZ43b-F)eR?16F_=y{y-a&cx($`M@*|^WpOz%1L+6x$$QBuY`P!&>H9dF+ck&% z@?=?SnxxdzRH+LRZqGE6Nk}or`b67I5WK$hAngxH-Vm3Ms&>aTb=*RKGpVA#eX3Aw z#`6WvxTf8&faL%fb?$?0ZWp+$$phb)yAIAGKe$ z0D%npEor_xN$9NmKIkXty!*Z!5uhmb-y$L+N&tgEF&-WsX=zkfD9leU3kETN^6kF? z@U*vhGE-FfMFo3*W*&@Ujav%~mO5dbnnSbn{3M`1Yvaj6sEsc*k z1Awpqz}wHCn8YWCKF~i(J?my=J}PFcA$CsI{#-UNWo=5;t`0H*RU2u@U21TpOsgn;^;nHZUQcky0w55g_aEoBE6Ee?)pC_NJYQ-= zC+}xtBMUME`TL)$1JZhHJq`%rsZTh!t~lpd#&~WnyVD#+I1ueHkI8m2F$;KY;xHvS zpNJ$>pjAF(wL{AzP0Vsew}=~7TsYI1h0J=q;48zfZ>3{dr-&6tmbCz3bLYM;&17u< z>Bh9e>i5pT$Ow5}DFbgmijW1}4-GzPk`d}PTp5pF*Q%B%`EY)rQQ)d4NP-Xz@>n_4n#V^;o!Ml>z<)6$BSNbwns=r!80)Ki!3W6MH~iO61h zz1-J!rh_OWMB&yg905Vfbet3V^QwudNYayGD7$@Gt0U*TT&%8xjGADcUq$AxD~5~+!R^Xs=>Hc$Y+?>* z2}N;CT&`hPxqga5e#qUfMh{eUzAB~u_WM^Be&=!thjJQ81VEmbJ{YFzlaRFgQ?mz! zyhYGy?9uI5I zvOg>9LmiPK(__;GDLXeP>b6inY4(Ts*cd#NBd0q&Kol}JLWZ*ak|df)wq+!>^Lnj+ zu3aXN%dekpbeUFUAYDHI4)7(ZAARF|q$Ws3Q16|qL*eoWx~8bA;mi?OSv8}=E}v#) z?a@N|JW>jh;pD%g@&ww>S9?VQxXd6%=aZVZMqz4}guV)Te39XumR0}E-u*xZQ=)C` z9Bf5o_QnMotL=Q&O=PN9ux{A4&MDls)-5RP?A-O3bC2Fy!*Um{UqWB=m;}9mXQ)PU zCx@TXc_bZbtM;RVN$iUij7Rr6W@Y1^*3PKrD;!j}c%s6xY!w=QecOiw+G15&_y<5w z4wM)FBD{*#ir#>$Q<3a<+wWe5+#W$KEIwel8gyP?`|q|@g`L#{IkQAWw?<~ZDN}wT zlKFdkd%IN=81z6!Q4C?lhJY6&^=2v!t$F~pMVq6o84r(Jj3>alSnRT=dn26C`wU61 zl@Arhx*JsnE%!dqaKFI(!_%k%b6{#)*Hl5|)YiBAf7`@?&JPUMQMQi_)Y>yhHk zL?3az7cGt(*Y`ayb@27&!Q@7%?X9VQ`mCAL@C;gl6Pz<-=0MPS5lxxCnB1gs*y;k* zfw9;gHy~)vlsumnba0Bt-ZSr4^lpw)6^!F!>(YgvnEbrhTyX**CS>N1vxnpy*bf7f zIb@5R7Wl8qx83MEKgeww={V7S=bNTB2xg^P8{tqOWk#*{EGPE6>-;ax0JMIFX|U0% z?x*sP(GICWKD^!d!M=Qhy4@hv*xmfR*6yGjt%r4i++D|?!>J#?P$-Dn=pA$Cwj?kY zSruy9AfWs!zYqb|$23p6J?c&0(Hk{@314|!r_AeAHjnu!`_O&6lAb1orLya3*|veT zIJD#mIU?Qdd;?ebYP6QM9NZkN9uIA4$v?V|Q2$N)L5JH}nVb99%L6$AW6}mns9Fr_ z=LvZt;OcB=7xrJfS1o)Y4b!XBJt?AqMpj!~oP4of3;}{$oX~{o0Q3M5)1(E?*?_p7 zgKTSS1Eji-mnPgm1soJ7GNC=-qA+3Zl!?UbkG9=BIMV9MVRLfLwXamD=H;jyt>h)E zXYE58vO47f00`=KQ0Yd$bBKK~*2TQ4<@(h?wFJg(U%5{u*uXNp!h1C&%RiSB3nT`} zpk@bK7JF}`HAjH_61WsyW@oh{+Y!egzwIyh?sKT=N$?Y++yXG9=e7NOTS#Ba*;<-0 zjdG}bri3fqS!fSjQzbd3QqDbMRl%{S!4-G+^pDz)7Y^>>D=+FMwBKEim)S?~$0qOl zOfs2b*`!Yh`4kpeZr=khaRUE30jWV;rEh?VyzxOn>Sbr0qcBhMDuOjF^(kX8VV zvydZJal?UykXC|D7kl^`#pVbaD>Pk|l|XCRlHJK}aV!ZgBvOIv( zNen0LKFK;~;f^{WVL;;Z#B+ilz=%ae!C{<6WoxE=%qoR}Yk@m|4uVnAdaiPbdlVZ7d>ZbHav)AB$$cy(yJ$7v=piq&uca??uelpj$f*srgAr zgYXT7GULaO^!e+WSh}@d2M|;e9K=cw!A`|mKNyL1E+=-=R~H`(W$W8A3rNoW!WKUe z4@58{Xahtyizl0Z+S3uK3uhy#i-2IUrXX=2vP~&?AAHl9A2J&4K_{1&>@K#pu@!#CR#Rf% zFtm7t0hCt++}1j474{XPf)rKRo+XjmwEh`5x=ilp{<5uO#}4j_ZY16JwfFI?^NUTU z{QW0Sd28D`i(oLsrMIMbPf1S<+%*2p^IJ4i7=|}l*;!U%vHLp(MfqgO_J2w>Z+_S@ zho`nwX^`tyZP154RO8i!d3Px&nm+a4)u&w~Fr1&{DygVwfMHsXqE^WbY*jf|_HV+J zxTY0w>Ti;)^7`M;Ph>*PkYoXaOqy@Qc2}l70kJ(|2Xte!e6;75aMaG zJ6Rtdlc1SD&^04P7lWkqa&N_QJS4zp(NsFA<)usP_q$Y7h6sKaA*i^FXNUR)i|&|G zO(oCX!_Z5;^9^a5nYG!uF0Ptj0l~a6!Vl`UK0)vQ5`M5eZN^nBY3*TW55ChVOhm-_ z>tj2Xwt!&6K=>2AaZ3QTXwY;#Yk(*$tk8k_(atXnt6=`FnugMo=c9V(BYH!61Ud2l zqx=-=L)k<}b2BiK;w^K4vH=C=3FB0GcP-W;*3mHOBM1oGkaG0O@BwB-^btH$Ptk z3lkfO8>s(8C2xG1{(AKzRbW-r@}pJ$oQ*Qz>=Hi=?Vl&78;S}u(Zp3moz@1z{UwVYp&mk22SBo)HHop4$`gYv}`OyM$;QROQ zv0?*rRi+z>=4}wMd4Jf|?L>(7p>tAza|Bd9x{{=>aCl5K%@k%`FGk;m%W!GXTl1%5y@ zoBp}1rHB0ZzQv8fD>6EB<@2ObtTfF7C}kXxlql)GYX|9b#O!%-F-r~7PK}U`if*g( z>tNKM)W#SCw6dTLzddb{qk)B#0O8=T%_TNKn4e@&q}g?1>XcaJ#Eui7h4!NRZ!%E$ zH3^almFPSA=plQac_!)0$^zhoAXYb_6kZezc*PwSyp$#cds!+qQO?py>~Ppjbm+mv_RN9>|N{865WkX-8R5`=j4fd@>O00T)RpT0O3#!0e}u2 z;&G)uF}K2&`AKL3IF9ktlf{YW4AbJF?Fo@(@d#7NNCSz~gioQtx8~c9*rAnn{zSPQ zCjLLj@K@637cg4kUP^pRGXhC&J0`N(32D#2P0o#6QG5{%K=c>dEWPb^huTu@)!uK6 ztQ0nVNOX)o_GX$tX_;TK(p6%GC(nu4f3%yUrl$5Ok#$BXWfpfzEXTn=NbIBF-};@d zEsB?CI4L3QHeuGyt0D{WEhsq$S+4l>=j+4ES{NKPW{&Hs(DGBi9lh7r_J&nt{cf|e zvX0*cY5kH>1fT$@V5vjYI}@Z;mpc2t1#L%#(X7#oY9w|x=-LUB^Qs|Vw? zPyu3Iy_SOkK7L(v1B8idsTW&IzpADEx797!rvKY1Z|na%bG>r;{~vg3k07zy6)~wp z{$ILd^9=-o>l4ZOHSoE(x@we$K3R^0Xj~pE zYB+wD`)Wc@gaZO)(gKrS;CDZ}Xz$G*#1_$Jt@BD+gy@UR-!?@+Gug#%mlqE;(NRx~ zM8TR>pR!H_+3|tie=)&Kzo!%u1B=#xRfYu9B#0O_vYp`_SBmHk?33}o%|mFJ4fjF2 zt+S?xPW1}8*klFDfsc;D>v=3$LQi>A&uyxR5pDg5f`n~Lhk z-SroWZ5dLP;CJS|41NbZ1H|Q#vd5l|iFQym_4>jZ`#OAWYmDcpr@61jS-hjUGsd}W zBC)Q`zlicr`G&-8QqIk_cl&az*p%)K*j9Yp^Sihw%uF9CKUlvpkwP zojRh#3z*otEQ@B0tO!n$FnHH!`CPL^c261nfl}_?QIs>0Uk;P83cdF$4Gf~+O{gr^+H%$!Oi5o%cbZP}**1=zs648yt8<+rqL!aGy;!3O zdsydYEZdT7H#~cQTiQ{bmpKtLpzi#7Vz|~tsCej^9IFs)SPqZZb&2A#$wBjYgzF{L z`KUu6Lk%uQ#9o+rqYB^t&rR>@^=2cw_0*>I7`L??EH_!Lv74=|8QEtv1tO_D#)-#o5dcGW-N)_^3ILuBV6 zB_0os&IxJOEeyEroa9_i|8bixcU$A+Kd(DHZvxj}QVQ7bZ(dgTIByd1N8U+pJ1fI2 z{ZO5osU8Eo-_&79wp#Ch9*~Ijq8&cqeNh+boeiGQr& z#}wWF0+Pf>9s^~^KikWcz?!G$^~Wi+FPY?ENAX}wHD&d?X?aEdy~PC0ljZfr7^*49+3#`Aj0fn-ar+F00kYrrq3#1ht_dSiCBxVe z+-)=Ms;d}{+G-KAD>&C9v^^}<9eW~kDQ|HYIck2Sv$H}O5|iBgC;4^LQQ!QQ9uH6I z-%f%Mh4(q1L<0VBvwl~F;5pG5hOB`xay6JrB&y%2ekJh%khM=;jMY!rbzs7dUlBQP zbqHKGw1m*wto#s&C!W!CJq+8eKdcTujykbFq#hvbdt|r!0$So|WHClfVED@5(SS76 zd1GxfiU_c}y5fER-_1*<335R^VPg|}VK3ZArAWq3#GIQcIWy|zj>10}OY8=A{(C*w zv;|1IIIYj0k3GFCUH2*Kr92LKKh5p5+RQuym#uG)CPNhZ&l`@wZ3>uWnB3pa$Y2)E!g9V7b0?I30f z@x1i2-;F}ejSqDl-gP8>+3&O$;O)8!%b|^+S{d@ zay&3w_=_6=kNFO{2m7>Ixe#Kgk^7%OHwvq$sX3dyb7iZ*g@krne0cSHd8|yXm=RQ0 ztZP!kGt#HLyUG8FwO|N2JUNj=BaC!7ZUNQE zo%W6nD3_I%Tbc}R`|D?piNe=hV-;II#VV!O{NjJ&8rb9iBMHGCcq42V`1k)KU@o4y Zyh(yG7RFc_k7BP87nXXL|5oek{{v{be1ZS~ diff --git a/manual/images/search_button.png b/manual/images/search_button.png index fb1bd298ff071fa453c444c500c8eac1cba4988e..4a4de6741196b88a78a8c57e665a30d6d22f5f6f 100644 GIT binary patch delta 1782 zcmVK92 zDj>q{vU~4&+wPxT`aa{kByj726XJ6dP&XiYC`OM9s;X>04_&h`l4VFIG?xyDQmqpNv@ z2Lici%9AO=q9kEennZnmf6)2~et(FKi&rNmVP^COjOr97EnWWPf(u(-zq-D>aeaAH z!7Gh(i|WG|-G5+F0z$0I8Jk!RDET+=m+$v1EUGQoclo)myTRALd~xCC+Ka9CVyM|e zauChd*=FzUdn5{Yx5Bev)46&1)hnH@)n%?+XAO8>nQKL9ZE{}aQ`^s1*7rxj%{~6V zG>(kR4YJq;A)Jb}-|cPg^vX;x&T{;C&+O6ze?N1{lYcf_wW|W5jB_gtfFUG_qNvEy zfRDHJ1in!+zkFNjffHSojR8Yc*FHV@_pc-aEKG|m75ET@lWyM|zw>0y$HPPZu1|e5 zpi3r6o{FhKh;DGm4;>Q#!;*+4Rh0z^_YHB%C7VM_eatawQrx9Zc5Hu2PMXF|K|}(A z3q>n_^MA>`Jqu=tBGZjP5K%&Pof(D>Stf)LYEmkS2#0n`xfLCfBnit{l~qlZH3h4h zDM?zKI?PpH5Y-)?W08Ue4+T~AHyje9r$BD#K_eJ2Abe`E6O++Ty8GSk?n@1y ze0poZk~Ana45|}A1_IzZr7U2unehT-@ocjXXnz?Ws{@;gd;95quP$0SVVs6;2%#Wo z5;AyqE$J5El1q<|7#s={?66x7cYFdtoGThmD!t4X6C;q$ltr*?g;}7 zgBW9#b@X_LkZ>q#!ymE=PvyK&wPttCvYmC#gDnrP14uOw6~p!eElal5KDX=K@~x*= z6rD=>(r=#0Ju%ik*%&x7NtpB27qd?+-+z4S*)1Qgd#MI0=37UuTX7KnfL??I1mo44 z-gpfG0pcc>(4BtohBfm6KnO!90)`@BjKgGOeyYdg$5;&WA;G*q!a@KuN;|&UaUPnO zkx>!_zu$;1MGYZjp*V2GEXDxF3q&av&q)XnCW(?MBFcGU;^c~|P5{U<4o3|}DSsD5 zi|y2hoih{d)>jDxVTWk4U5d)b3WNheo;xPNh9=9V2rIC0F)7J$y<_0$@piySlE}FU zj25XQjY#|+!WzT@|_>QK-C+|h?2_lpOz}4a} z{NX>>Etd3v5%dRWP`6wMH#i}qeqZaAJB|dUFhA9x=0{cC!(M&S(phmfMZ^k*DpfQ^ zQ*oTC!iucbbg;9B=48x^qs+u%ser+!PmvV`kB$C@wxDTy+cpsB)$F~ZjFxFFeV@+2F&>t&G~gtxrzX*+a+!BIigfEsHW zmQ+QJQ?aUwih>lGAv|@OW6|PS;8SNfII^h0WfgFWtY9?`b^~zv_I)8^uDT{;l9jgb zEoGrJyjDEDt(Rz;LQUye#b+U*m?=R12xqX5DteDEUwdG7EB35<>HBPj)STV89)-`}`l ze`CS7n^KG2VL(u4v2QYj{EsI)BNSWp4PRo>73ZNF+YYwBaIk&*_d8~8{@5R|s408I zqhmNs)12|uj-=;XA1k>wx8$0muyxt~n^C|cAAWg+si9-E^;WR$R YFWeM^K<}>(L;wH)07*qoM6N<$f}$I66#xJL delta 1805 zcmV+o2lDuZ4x0{;BYyx1a7bBm000XU000XU0RWnu7ytkU%}GQ-R7l6QR(ougR~bLI z_xfGhLMf$P>9)!u?XuYsj6h(RXdHo<&6${N*)r!P5_i#PWSVR-QPE)H)c@4MVwTLy zAMBzy9CN`9h6|1|q=VIV6jn;V()Me=+j}`TpYy&SUp{67n197LCnxWF-gD0Ld!FNt5n^7hyZ0nT zDCZGPP;o(dJ{@~{wV8krxuOAfW&2Vqg2=(4k_v)PuW1Ql0f;=qxq{*_M7L0m|sREh>;hzhYSI#W@@gV0Q>+{*HcVtvP^^NXL{jSo8V1!{|5;cAEL*qAf5F(>E z2(jQIiewx|wMcL-xzIuog45%HfGcy>lroDsmg72>Yct2yl)9d?pqxsGU*FZ6l92%_ zg7E%-RyY@VhZPsN5<*0=2xI7F%;5_OuBALPK7T)5C=-f1m*+aWT4~}^8fRfP!pwDo zST1it|DNQp3{^fgn3YP`kKA|I#6>Y*j)*xE38!jSbD=oboX<{|-aPPbcRSnh(3(fS zG?=mU(L*DZQW;5b4w_Vua}Xz?7?yLTs8EbsbF`jC;6a){yI-1n@{ea-6nA7%9EPG6 z^M5eVJf10(&Wz0*c<%F?H{RGtzF+Mg{@V6G53F0BYspdD2JFx*-z_X)Y`G6S*!%GM zmc^ywb35KH6iOK;!*U>_PznBzmruQR@PUo%O-vz-kQ!E!`%mpT{grR;UAJKsVwUB4 zUe=xut<`<`r@w#8W!!3TSQ|1R*TG>ZF@K~aQV{O<&n(;Dn$rmgDxNj=>@HB22~!)sBypGcE?ngxE2P3m41VKC>#pE-@yT2v20f zxX*6wo0_UJMoj*iQ0w`UOeW~Xr6P5uE<;IA3944(rlCt{l2V}6Wx}wiayJ_=TYucz z+ZJ$13DpEcW^{J8ys&>1#vwQ<>Qs4v6Y%ofzR~5KIrB-uAb5ydlH|cnm8jvh8J{X> z&SKZHv{6KGoUB-pKYeQEH@n|~-;_f1H8hs+ZP$~B%jI}!n-65bhzLa5a`xzCXm)Eh z&`6?3|8Q~L_G1@jcrIrvKm#DbEPo~ggI!;J_#Q|v$eyj`A zXT{9Se2Xp*)(YDz3dK~$k<44eS(6r#Rw zWqmwdmi3c2a*;&r_Q%FSmVFX~*g;lCAz+7hqevh}M(lJw`oicoCh!3;Kv7+bGTY8L znpj@Ooe8b1OP_zDClxyrQh%8o!((^14^ME{wS}gq&-3%Me0++7Sh~IuOWSd@y;(UGdPlaO_Du{2V2%=H{YDucPxDO0>6K2 z&xzk}+P1m-@NjjuiU6ij8;G@&w2WhU4)yGFbK=OU+P&LuIPtrUcYod9adafwHxxic z>+f+*1;tHT)F^7Blf18s;Y*}>@zBi9U!N{6L@U~W37`=;DVaKq@tMhRaA3(#9_}CL z%cPU%OQKlQH*{JbESZa3<)G-Rdi6Ji`g`F!KYypc*N2=+Yx4Mo=uf{`+t=;Ysn<05 z3QWFq^-u86Zo&Hi;Lc07|1vt_;d`c^-=cwzG4FTd%f vL!mcEf{brOt5`uB`kiaMVB`Py_5DBKWq~RVN}eto<8R<$$}gb)+PTge}IeSf56^7~#v?-3jIT%@S1 zmqa^G7|#SpK>eXNI`Ft5hF+g6`fl?G`a7Py!RxA7hmOMz_NbSd(}8-CZ=srS3=)IM zue(kRy))4Sdjo;}QDv|w$MOEVJZz78xiwQzM^vkGZ9$#x4FOL6eG@3Zubm`Pf1=2n zPGF}OL!!TZD1T4g-@&$Q|Hfo~@4MZDI>BomYHyb}ky+vv`S-39fIGNs8h?gT4|E6| zCv}tj@;!-iWdr;Ed(6Ml4(zXKsJ%b-26}($75R53GMv6{4~xN48HBmE?^hl{w)~!X z)@>ats`JuRG4$3Xf%eGOcU3{{{fXB_ijrcaFv(ZLHh-3W)DZc9O%&;eIz<00kEP6F zHBW_LEPkJ_5YC|ygG%C4=glzJ{_wN-DnRd0`055 z&@Unxe&~L|PdeTPPy^l&oXtfh^IaKta$OAa^+Nezm~J$`_cTms&+3o*;r>c|V82Ld zOcjB4fO@kxlnQo;!M_qLng-JI4Uu`?O^|7DZ+}m+gue$Cx!I=C;)unGE9nn@U=D{( z_7#fednlT3ZU?A8qr8(+ob1cyC8!Il9$bd$2Rq7+HACnJ{h{B|W9%RKN&a>_9%|pu zy@Axd9hTEhRd&@Bo-W8=zw@E~!s~~#*_BgyRpeA*P9I92cu ze!^e!J0|`2jSEytq`Uim;|Y;sq&P`HNKbI);V5!Lta@^rhn&A8be!iPS?OwZZilp|1qA-b8a3VFNbNo zH11&Ypx*9@aWv9=RHoJDCC_;wtyJ1YynMw}uzE_xabfX7;|?_s>Tf;WYO@65vq_y| zxp&owAY)t*R%Y0!!MK|{r*?bd$n&sOceM-Sr18d@exsr}#@~O3$L#pwt4BP~&VMm; zSYCDg!Nc<@#@mvVil9cZ6iq*3w5Kqi&L-U};oVVHk(TADG|w1FLDU@Ncs(Ms5kz7A z`?E=mv!%&DbsEOX1nH9<4S)Q+5UF#Ny3DX$*OpWkVh^OQP|H_TblC*pFyH;_)WD)n zph`6)TekM%~IhT>{lc-BI}S8*%GJ}4ZO3D$zLVO)s%btPBC(5g9UQr z#MWY{(hi?>JG_QtOZg-y8_dJd(7OB0&+?Uztq_^;OmTVNzpXNSPA~XQ@qecD>-UL~ zW19;d^q&>Ecjt||nN8yjHVrDxp9V!loNrE;AD^hL{Q8BW)+_tU47SUyy{o#>`Gd5o z>0~<{WIeBuvj?mV>D+tstJ>iN)Ul}@!}uEtl|TkbN!~~Sv?pt8aM^bYgQEz{Jlo0T zxxfFi0CmL-E?2Pf|5<3&M}N7B>fuk8a+$K@D14}s@9I``Qq*Nv&Em4$8?y^n!}6JI zi{lnRwMngkR&{HN#t#k+fl3X9vH!^P813rRqx^=D=`DfK%QMdv=G5%UE;01M1EWXW ziEW%7DKJCt&$l+3@mWFDH=vbmcJ(Zgd$Ry}Aas3^9irJhaVzX>aDRu#4t2J<&4j+ z)@;ir{a9y_T~Vagzkk6UX#`XkDU7c#a$58k_o&Lfc}wB)J@OHK-KMjwJiDfgRmpDP zsZMcu|9YBBuOA!KS7&h>Rz|Vo9m{5T;~@_`gpSr9vYow^^TauXR~=`48t79aDqNES+xc&|6SHSYVamvjtV# z%`ZT`!z#dRNl60hsQAU zj8IFU(hijmyHp%;%k2xx)u!zDH%~_Mp)N3DyySdwntyHajj6qWVw#FJ6)Ih}0lqoC zXnu#vmgHsMrt&ur>Ov!myfXW@skQ{Dybs&HP@DH%D!Ms^%`H5)IY$z#dY~dpz>Teq z)x2;0QM9>8K~jvAP>)VBPn#6-B4tdIIoFTc{17E_R1Td(`4E1-^yacsc)9 z)U>`2jrQ!yVQK@YOWe_hJhy6{+5qbF?g;)_L4O6sX?YtY`AGr(yRu!Q6ytu({z5hS zb3Rmh_T(caP`{WKg8)z%4kfq-A(Gp244CzI>VHH2miW3~yC?~^5c8QXDQ74RNydA*0+dmw8JglB&R3&Y9qd)z%bz_P(vG)1R6`p~NDALYifKynxA`t#uBhQtJ4C+cV=+8DY+GFK z!Xf@#scrd6miQCad{w?1t%ho7&=jylDSs$6(4l1pYlQXOZi&i1SCqp+X7zg0OqrRY_KfKbZ|N_9h3r+N~cYwQlh^< zCHngMS-pLIBH7!^`X(v$O_Fu#(l>QW)lqGzHqpN73;m*h<%>Eo`bj7SUkbb=+J8fm zk83ojFbK*@q77kGxpU{aFZ7ANt&@-}2G7t-U{?cO0$E62K^?Gy8LkFW zHChd#9(4+!1&;=GR)eM>96xbVe1CQPgy{L|xadiIC64tZ#F3*t;_%U9;?R+!;?UtE z;@}}u2M!)ClR5@F*rOimA3fH?ZK2%+3H_WndCCH>fGgPW6DM1Nt`~9*z~JZt)VL0` z^wL&Qsl!y4v5H0&i3+5OJ{5TwTtWiGz!~0Q6WIO(2gMir4~V_{_KQ9HzJCzA_v{n9 z;(NsB@!ev_=W(%P=dN;5`OaOt*)G0&uh_kJAJ<1aXbR^ke2su4e zirPw06zIXD#L3)q*emj;7E_KC{t~vGyT5|dw7b7dxpatif`1H2ZgnO9E-l&T zM1MTOhtp;vT@AEG3qp7XGNv2cxtVvDy^pKSyk0q zQR%Cy3M(p%BPPcclH>F{VM9f&qt%KmM2_M;Do=n?y!1pgLK>jB))zoU;fM4^t%#bk b`SO48f2MHN#_q2G015yANkvXXu0mjfihHAQ literal 4035 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 Q`&k2OO4^Fm^44Mh1Ea2yX8-^I diff --git a/manual/plugin_examples/interface_demo/images/icon.png b/manual/plugin_examples/interface_demo/images/icon.png index 26db5241afe732cb3d831cb896c9e1e7b3c4536a..9f7a8992f1abd7dfed41615bba9b73b346db9c15 100644 GIT binary patch delta 3345 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 b`SO48f2MHN#_q2G015yANkvXXu0mjfihHAQ literal 4035 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 Q`&k2OO4^Fm^44Mh1Ea2yX8-^I diff --git a/manual/resources/logo.png b/manual/resources/logo.png index 81adc85c63d0b620d74ed671d097e42a4ce2453d..9547b965dfd3b8e6a0de96933f647598cab6abd1 100644 GIT binary patch literal 16984 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 UU3b~5rv)VA>FVdQ&MBb@04!YDmjD0& literal 17440 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 J6VU;d@gEXdOsN0> diff --git a/recipes/icons/20minutes.png b/recipes/icons/20minutes.png index 81e23871ccdc96c75f9931771e0debd144462634..8be97a89979c1a594747a62e748db81330eea680 100644 GIT binary patch delta 2237 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 M07*qoM6N<$f?H8naR2}S delta 211 zcmV;^04)FZ0rvrrEPn(B1QZq&BqAgnw62l-21LcU8f`j4z zudTu3NyLTSk&JICq=?jd&pB`4Ll>K;XADIa#vC3S$iX2q15CM&uwb7ohS+4PSlKwn z1%(nO8*;+zfa?lX-;Ef>jq4?J3weZwNOsY+og2w*KY*J{^?z^uhd&FF2O4*e`(^+D N002ovPDHLkV1f@KSn>b> diff --git a/recipes/icons/7x7.png b/recipes/icons/7x7.png index de3b51b3568ae782cb4e90ee4bd6139105c917cf..905155f01261ad6cd24c42fa05dcd9575ea7164c 100644 GIT binary patch delta 960 zcmV;x13&zS3#$o`BLV^hkzo^m{`K>?`J;mYC-?~{q>@V{paEJqlxcm zKJRTs?`c5sfL!&ajsElS|N8mwY((^$egFIU`O3Wh`S|$2umAr2@Of1BubTh)_w}QS z__(F@rHt=!O82;=?{G=?wxj;{_5JSY^q+gDiuQSWL(`Np>Qxu^B0kn@&# z``Xd_-`DuWwDOE>`qat)`uO_L#QWUT`q$0(zO3?wWcts;_OP6j@c|fQ^OAG^^6&3+ zP5$)p{NUL3w4wj__x|_x?`=fyXFUAo;P|&@<-r+$lW+pJe*vvYL_t(| zUhR}cj~hW4M7`+@o0(~unVFe+!_3Ug%*>26`IBqUILWNs7oT#-yi=*9C$&D6+F;1a zh*gFxk`b%_xu6TWVEK!nHfj9|Avf!Mwp4E$jRA0rWNYW;y3F<9f^~e_Xf#$l2TU)u!s}?%Y&Lf^g6qnrTxnB3*b$MW zv6;ng59O>4^T5I)Iey~gnL0wM&Yiz-SLyFR03JR~7oJs1z)^v`^0DljPuX83_S^Sl zZwmmfHp(>G>w$vgOYJ83!#%wy+Ntyd1;F4CMHtRUNvV?7<>Qx(e~C$@7N>%6mQ$vY zJ1fg`Wollt4R9`PQL=q2q=1zoRP*4ES=3xs32X?d`4k7{G-Z75BH-X5H6K0#K)$FP z_>u%%7AV3MfV-ML{qU3tQxN#G3-lDA4ejBL2qeP*G!M)nGjbL^V;$a{^qRiV8 ze#}29R=d!8KC;JVM$3!vd-qv={Qd)PDS}x1uJru}wfh*4*L)`AOQFM2HyXJ_)J-00004k zg3z#OQHcb?Zm|edEh=iU5d~41x)c{&80~kn+$1;m=H8}Hed^13I5WTbpL2evb5WT? z^(r~AOAn0+1>jtj10h%d0B}j!5*q`6Y&8IdB>-4#t-?0|>}3Kl#|I#w0f0{=Cx^EO z0x*xhEjpeB(}IxnAebHq|E)n#vu1^nJG9>?qRI+XdDz&~KpG9$_z2A5Xp04;QE&R?3>1f=>Ke*y zG&CK68Nt|{t0eMXTg=zj<>P-&;D5;gbD4?uX(@-1S6M@~jh}|57Ic&@14fC~rn7svNgqTNqb;q({ZWR7x%KGYS6k;^5-%Ph`SIq_^SYzE;qGu^ZWh_M9g4%S8w0g! znp?WzbSbHW`s2B}YKgvHW;}m}GMh+~QC}-1W}i}+$=KV7KfR9>a48%oY_}^&KwB#f zCyU^oNNtG#Z9R(Qa1iNMR8?tcE-|(pvxVE-+GLPcg0xyGihBO=$#|{LybE$bOOA>R z@8Sw|M{NH=m$GA6^w;(uJ9)df2KUSdfc85(JT!jG%@jW0Z4uLl>04{Zi&%eDyiBsV zYvD*~7ho@E&Hsw-u|!ft+gKy}%zMqu?Cguk2QDsyj`k%b$NkGk4sk0gTr-!{SKHCl z3P!82dY2o+gYHzvX#nDna1T_&e1AO%!!SkuN>e%yV}gsXpRW$y>i-gUJkFGRHDJ;{GFSKvs5YMq2*=1B3pjpbY?Q*4FTzF#f@R05tw~X8-^I diff --git a/recipes/icons/TheMITPressReader.png b/recipes/icons/TheMITPressReader.png index 1660ab53b593ee8dbacff36ad9c664580229ae98..465a234fc9697de63e1d246e81dd7181fc5e6929 100644 GIT binary patch delta 455 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 diff --git a/recipes/icons/abc_py.png b/recipes/icons/abc_py.png index e6138769ccc3d34d847be1ff48e5f3a6a2954f57..d5d9c7f8e1bbe91112f9505f50d09a72d16d1392 100644 GIT binary patch delta 2289 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 XYXtxcOeBf`015yANkvXXu0mjft4EI1 delta 277 zcmV+w0qXv=0=NQ@EPwy|(eVG#|Lf8J+R^{f+R^{^(f0q*<^SK&|IyU{|J||w&yoMX zvH!8Z|D}=tk*5E_asP{P|E4kjm=XVRjQ?>_|50)OS26!FSpPi{@GJn3Q2>wtQm`NZ zumB;D8UT<08*l&>O#4al0001rNklbF diff --git a/recipes/icons/adnkronos.png b/recipes/icons/adnkronos.png index d783965df57ecd6bcd3ce28e51935d95a7e93f89..67fa0f8a7aabc28cf6f6d8f8df397ab9ab3d8fb1 100644 GIT binary patch delta 495 zcmV?0lQBp&Q0AnYe0>?k7Z9~tZ_B>(TM zA{^@>8|xt&>?I%TAQ|f;9PB3|>?k4Y9Tw~%8tf?~>?I!S9T)2!7waAv>?R-VD<$nP zDFhYJ;gbpikbi7RL_t(|UZu)odm{l9K;c{4wr$(CZFB8Sv3H2L?Ha$e*e7i|bDN}3 z&)4(9(~F;6Ndtg@&drT=TRY1sK{{snbXd`vdnpq*Z^d*DjU00jn_^q4L<$^?W zQ7qEx9Jv>XYVEzm;X_pG+48kdAh zfc(z_5CsD{P|Y`zBO|b>ZW=U l0KmO^_Wqcu^@qoG3EqL5dY&JJ2><{907*qoLmC^E9~tW)8S5Y#>meHJAsg!=9P1+->mwcP zAsXx<8|)$+>?0lQBp&P~9_%F_>?R-VCLru5A?zn2>?k4ZC?f1BBkU<8>?$PeD<$nP zDUMOY<&z2mkbi_pL_t(I%cauwQyW1P!0|_s7B8i^7k4l2QrxY$JB8w2q?b!VNG`w~ z+~eSHC6s|AcaH?7-(PlS_cJ>?Zx-Oc3+@_;GA-`alN005%?wApqMZjA)zn9TOvY#t zGwnh1QWf&+z5sP6w`h)A^d@!kfq?w#XMl#k7FyJ1lYcsB$$$iBn@H`@p++T_bfaD* zFEoe*H}1reI)3{Sm0Z*ok=lPwj|5k@(=%*-(lXA?eh|+mN@wvR%>^M$LWQaFldh>Tl}mO4te`r*ot}50l&`B zK1c1W-#^}}!MAW^+a7g+L3|q*`5b_xT^6aVnN&m4NYH{T83*8@AO&XvfUsw@Bi002ov JPDHLkV1mAh`Jey* diff --git a/recipes/icons/adventuregamers.png b/recipes/icons/adventuregamers.png index 1d548e6291068f95affdb4c86d4b8bd27b28958a..37bebf1c7aad9354adf0d6ce5d6d8f7172b28c6a 100644 GIT binary patch delta 264 zcmV+j0r&o}0;>X$EPo_33L!2DA1nzQCkPQ31r{F$^2=WGsxb133h-D26deZdN(As{ zB=B$}@^=IsDF{AT5*Q)}HB1jCHVYCO1@UJD@p~f~BnKif3L!5FFGCJ0I}AHi5e*Xr zrN4)g0001qNkl;W3RKZe=#0bLVpYhB$^J;wEl4}h)5WM zz_N=NV-WQ5Hpd8&lBZ;xQZ+S($k}tQ=v7w6$}o6EAvOS-E*CcxqIV2bcCN)sab+om zTKjkieM5{}Z`Ao_s$I9u9jFg_g%L+d2s#Gvq~&)ci!h6-yia~P@C7Jx4Huhy+86); O002ovPDHLkU;%=8;9W=n delta 269 zcmV+o0rLK<0^Zx#Z!z1F@!#-%q(zME=F`<6g^(!H)YN@~@$f*Z}hBUyx5RONc|%YhFi0rk;65AhDk(0RCr$P%Eu1E zFc<{kg@oFB?_~_U{{teh{M!mw#3^%GY4z#fmBH*i&N;y%d^H0nz&ivCzW~4_0_p{@ z=I1- delta 214 zcmV;{04e|e0+|DlBoYa5NLh0L01FZT01FZU(%pXiks*2$4Gj&+LqEzxK*~fw%0xk{ zvryKNt~Gy*Nkl1)%W(_FM+wU}5VVKvgg?pmnYd?6^xaaG(>f6hKcB3?1mn ztpEp5_E>G=fwBX(4xro5BtfeH0t3gq(Uo=rDh?uG96^mv0@O?&Rp#fs0d&9^XDOCR Q=Kufz07*qoM6N<$f{=z+SO5S3 diff --git a/recipes/icons/ajiajin.png b/recipes/icons/ajiajin.png index 850549d75c63ecca7fddacaf6dd3acf2a6df3c1f..3a9f3a26e733c367f6b557dae022c40110506a97 100644 GIT binary patch delta 9 QcmbQs*ugkKb)r==01rX~3jhEB delta 27 gcmeBRoXa>tRj|M#vY3H^TL^?1FWs&CGf^=e0Aa)j?*IS* diff --git a/recipes/icons/aksiyon_derigisi.png b/recipes/icons/aksiyon_derigisi.png index 6edbd48ad629b186f81985a5c8d0b1665ac87636..3af2487f3282c2a064d3f716b4b74ed139053937 100644 GIT binary patch delta 9 Qcmcb?c$jg5%0$cg023$!y8r+H delta 28 hcmX@ic!P0*iV$akM`SSr1Gf+eGhVt|_h+KwA^>q42&n)7 diff --git a/recipes/icons/al_masry_alyoum_arabic.png b/recipes/icons/al_masry_alyoum_arabic.png index 92f21891cf11c34843452be815881a89cc332bd4..0bc59e7f234366423e6dd1375677ed351804d1eb 100644 GIT binary patch delta 1314 zcmV+-1>O4N3bYE4BYy<@NklSAsY@7hb#(-prQHEc6L_gX_`W7z18pcx0jZd zLK0_ZXFSd1*$vI8=3rFd-Q8Ut4u|~o=~F&EJ^huLsSH*4b9V#_GLy|S|6lu)pCc$whm-~PrgdsoFM zIJg+%#zqihp&{kK&dv@VKYon;{R12w9c>wG2L#AqMgff(!hCB6ce)W4Z#QsLGXVwG zaQFQW@ZP--@$tr|c<|r>*4EZA7!0-!4-X$zP60EJYkx*FB3zHPG}FsbhPN>y5)#ae zMx!C~Ry#&JZpz%0#Hg_!udlBwM*!sI%bB4Kl8Hvyx}BEbZ7k0c+Bmfvgy{#0jjt#_lHs zYe;YdRew0Smc7M55ws&vX+nY`GZKLmg9xD!155zCM6B77DdG|dQB}Ya1c5R|&;S$* zL0waTfpM-e$rX}J3&SkuTDI)d<}iCE;irOUL^hUR=0E`gB1M>;A#?m@r3CZsdI0Iv zpf>DF6UHvB*%H{^a0>X&PqQ3JMtC*KWqvQt9Dh^CI&6$EID2MXb-Ja><; zuCB1KuuwTA87r7eM}k`poTB%MtZNbWWrjYe2m?xAiM=Vxfe)hqnq=$Tn- z7JqQ0PEJnlY;JDejacR+4KmS;%wVE5v>f7EU~skG98kmO_wUyQ!2swyKR-vi)0V|Gk=ezzK7***sDsz91zL0Y9$+Lz4`d4I zL?dTW13rG(Xy&bEf_L2RPo6xnuB(-ILx0F8Tf|^!+s~1l00^J~g?cts>n-*w>&!0e z;CP1jHsSW%93m&LX1M{!IU8u?nhj9O$b;=>%|_Ed^6S+LQ|8mZAEWZ#L^YP=romxn z5Sq=V3y#uR7r^^d1V9|O5eZuh?cmme9`M;(p-aJ&Bt?>@;(Jp5-oJJ0Hk{dYUw;BF zB-b*^68YD(HMH9?TCG_8>%pravouZRizi8nv1!O8E(!N8s0kXCQzH-tX^wV7NsvaQ z!*{=zJiKyw>YAN%*aKYp7)guf1L4&;lkyM83M^98I@?C$@GHoc07#Q!bW)2t-LCC5 zN1${Sf7a{ZoBNB>h`DX!m1c_5XMcanUoD_o^Zr>M>uan2Wn*hBy;N>S>vwL6?;9Qj z_AevCC+~K!VE}i8>?U5Wl3$Qi)eEBD?Jl^uq5L*bG#p;zpOYh>q0h25uS)(`zmJ8v zjw9zX*LDQNuN+BK?d8iWDaWiq12?pdjg7nC|M25aU}#GXx-7fXT4{s?BpdL-{vW^p Y3*jv?<1N?dumAu607*qoM6N<$g2IP`Bme*a delta 1361 zcmV-X1+MzE3gZfpBYy=dNklMu{5hGXx6QiAku(DDt z9zKp?I2ykffPYLR?MK?5QY(z6Kp}umrvo=PWe_=l-c1O<{`L>d%y^JURNKE;j^OL( zx_~z#C=l8cM@L6wn5Rf7gxDv~9&T-I9asQ(-;57XBLG1_?xWw8d3<&VSRsdj;;!yD zKKfXZvfXSzP!lj3jo|k7cE8u_z3FzlKZqiBpezrB2Y-(fsf65*V|dA6Yc-Hst)}i9 z%{nyefx73AoSdB0x3{-_VYx1=2nkz2Gn`6GaJ$t|^Ccr8ZY1OtCME;R$Z7zfd&v{h zml6To0OTahnBL03JdVP0HYhntrX;)kW}^-bvK&33wCZfh0kGmEQyi-p=6akoGnGIY zrG6(<<$pANzTogmy_P^o7eGoviXC0dP|Q3ZRZ5lv%&y6)o+-l|4C_^?Op1BxH6$@6 z*&I_J)gI}1fSyh$PSeDPFqR5ooS00Jk zB3%!7AgB^C4|wth-yi`xrXdv7uB068Ou!608m^Ki2Xy6P4@8MaGxY#Z)WyM3c6)OJ zSCjNyh-tlkeRp@4A8r~@j6P7&QOY$2>5>pL9gAqkcBo-x_`vIuBi9W`hk*Vot3Y&F%xu5z=XJ3AFU&nD{d z-8(heNTUu=?Pm_eK16f|aQtJV9%BX3hJV8$DR1S{Pft$^OVRXO_28CGFhZH}S*Q$1 zk7ce|KDgG{qqEYnp6g7}*X>?)Y$IvR@0UMOVO>rDy)7U-oh^UW0;NPpj< zC6w>OFrpqXDvFM=mdD)Oyk$)8ft2B>8U}q#>c1<}fZe37ra|Yr>H&riCCj*r=8lJf z)F?b*6dJrJ&H~e+!ZeLL*H6bg{ew*ozyPtT5fZ+baQXW8~8Ht#D1ma;5 ztMHE|+}C-zK=UxsW!3G1(h!h0!hfhx&a&w_|Np_F^HcBhI`CPi1>f$iC?X~qjn{(+ z{(AeT>S}Wcnb!VQAGW)j#bJ}h7_)E|+)A%oa|Lfw5MCAng~WqTU$tQ8RjbHrG|~wS z(&-KweY-%oY&Vpn(SVrV;*S`O9^v1MGy8Bwr<@}=hcCe2{XQ%$w5;?~mP*U)H|qUQ9i{S3 z7ytST06+$tJrCg39e~5v0Dmxa_!D5?1o-s~fPDnGmK=S5>>%)!_a6*B5ALFW2^O>) zAS-BS54D@8eowPt4rABRo@%!8m(Wk&22jqUBMEQN`%-|BZGJP6`F;&DGG>jRO- zWYt(TnqZr@#mpwNTEsG0(_$+vfZDMJHN{5$@f=JmljGW%k2siDeA&+BWOFAg0T!=H z)8Nw)Hdyn5?S9_(9>$qlq7i4y<0{iaU7iinAjty}6Is}QF81@bu1lrUGifwR+Ebx8n_LSoNQ=@>sAZUR z7K<0ba5haM7t-=zcmYi!W11R=Y_i0u;80m7#Wxn|wh*Cv!K*v;eB#+nRSi$<2E&7{ zpL16(NW*M4tk(8)LB{k-j$)%e9!Fk?Z3P7n``sYoafU;6Ez@o}bS6|$R(MB*B7Kth z1MXTAN+onYTM6wbp>nEpuc|c06jb5L@v)AGl9Xo(TXE=rvGe);9?wL$FSo?KZ%+tk w!7x633LJx~wlj-S4NfE+Dz~n(h2tGpQCxDjjuW5$Vchz+-#@;b+@7od1Kfu+hX4Qo diff --git a/recipes/icons/albert_mohler.png b/recipes/icons/albert_mohler.png index 6df02e433005212d47d456da37bc41a3c3258579..7096af8eafcba8ffd3b66752063c53c0e8889dee 100644 GIT binary patch delta 1410 zcmV-|1%3L%3$zQ6Eq_Ot08p6#Ql9`P0F~n?)l{R%&dB6e!iQV308)*$6+85H;HXJ=_dI z-U>(F%8lW=U4P>aLgVA+>Clwv)S~I>>h{`@`uh0#`~Lpqmj3Ck{_dUr?yi!22A*sX zo}YQ21~e5;f}ds*pJ{ZTnRq{MqoGD0p_y?)VRRc*hf5epqNISM0542rYo(NIrVKPs zc9f>1d#6Mp06&ALl?11q0H|(wSbCpZgs-a^GF@+ZtAC()UTsaAsU5TYK4JezNKxxs(QZ7*@k$8hY2dcqhE)ASi%%M z!mD&3Nq=_62sVgv2Z)Y+id7TGz^=#5*2n=liwPu*Z48YYAIiIx%F^78b_dJ0gv`8- z%-i0M0x{3YxzN_pj|eS~C?wJeIFFWs)4q+6FG3|%eFQ#t)vs8QUJ}*b;?@T<*2I;P z9V(JqXe?KH+O%4eC_&q}YLj$-+~(siCsZ+7fg~|vpW?k?;|4dCFCyc?ZR5&-m6mUFS0vmsvc7FEVp7#I$_x1Gn>d5-mf1G&=5Hx4|;iCNEoc+>m z{p+Tjm~NdFFrAKDosMe$^RfQ>y#MTz|L(T`@sFM>K8xLPC;$Ke0d!JMQvg8b*k%9# z0%A!-K~#8Nm6KDHBS9EOo3V|tZQHi3wQbwBZQHhO+qSo=lb+0EXC8m{p3@Is)jfYz zCto9wAK&#$Fcvg2zNilgU5F}@?5n>28C^NkUo+^$e;E>P1#VJW!#pn+dj+Y1~V z1l^CE@amxjyicWtn@>P=@+OyZdvo`Aa)U@w)0bLp=?k^7AX9&-f`e5mSv`ViYMNUY zu33wbS3Si>LZKEgO4P9`ENbQCs4#movC$p24G{_#EEEm3l}O%hkVphP@to;G;g}qH zLPbjpr4l>TD9BwZb+?sD#t)zP4g>Z0d{!@%N5h{UZ!?Q8;8$(o3%dD?!@vUS7_Apx zKRd<5Fv4c056^!jCB=j{VSOM0`q0`h1kv+z1_K91;Ev2}lFZI*7eRml%;Z#r=9juL zRfy#WyySB4KX-AMSg!MsYJSg!l|h?poX(g(*9`NAb?FypWe^^HY!41W6H2t_HM+_% z*^$$b8WdkqsA%W>xbSaC-E6%U=hDG1+|dfDmyl4`0kv2D0<|*X)c5VS Qr2qf`07*qoM6N<$f~+m0SO5S3 delta 1425 zcmV;C1#bGZ3&RVLEq^+F06U5RJ(B=Gg8)B`07H`iN0-XBAF@8&roJFkm1_b|qDPCoEblS9&ieR54nCF=C%G zTaGnccQ;LLJ7t(VXPZB7qe5YHMI%j17)VTHYfpBRR%&cmdVil=gs)w1d0ua6Vuh(= ziLPdmwP+7MY<6jIUtBpjf^DF}@NxzNKxxs(QZ7*}o1vzpsVBqhG+KWx^CZ z!mD({rhfy*2sXy2TgLz~$EH`uz^=#5*2n=l$*Tj&uye`}Kgzq6%F^7+pa#pfgv`8- z%-i12t60y;xzN_p(WV8`14hybIMV|<)4q+<*W1(#MAZOE)vs99vS8KT;?@T<*2IZR5&-C~d>>FSfr0vmt!+K~3$p7#I$_x1Gn>d5-mfBO3P`uqO-;iCNEoc+>m z{p+Uwy#MTz|L(T`@sI!i|KNJUQvd(}0d!JMQvg8b*k%9# z0&z)1K~y-)V_*P+-@l;X=l7pKff&a7%m4vjuS8{nLBk<1m;m8L-FklyRq(@snVka& zSmkd&1_5m*5RaWX+zG*kHl zSb+gM4-Z(uU1SA3+aT_0>w1|+9khBmVcNtzDGzC1oQYUX* zJaf>+V&j>MH?FJkpeTO`7Z4H>lv;h{$dRLw-uw3-IdVWvP)JCi45r}QsSSq@p9q@2 zWqNl_dU8$U^et=scO5>w?cQmag8LtVZf|Xgcg$}qDr&3p^_y4?^3A8?Fa>v>1GyDb zkF1ESi}dJ>TySK5#+(2DKfXSVu3-Psqf3??J-Q!h(~N*o!^2*>8LHsjOKC1{ zZeBj~X<5N`cENvHlbwWlfw3tzdnr@_!yhwFVEpn5yPdy$0wo!Cm9rIr5z%K2*+9ufZ+9$0!P74qKug$_ zKK#FQ7Lw$+6xPjTKmxC>DRA*1rAaQydCM74z_)X%oSb1W1?*f3^OiB7fmc_JS>+)4 zLWfCf9fmvu!@Wldy0<~{PfTr(^)X-pTc2HBx&Fa}J&Sg)No2qdcHG=uTReMBX)FUy fuylESGpYyxPrPVNiXs_E00000NkvXXu0mjfdj_8M diff --git a/recipes/icons/alt_om_herning.png b/recipes/icons/alt_om_herning.png index e10faded09d1078b46250277efef55c24b46bf05..f4aec357b51ac8903a22f2bf803d4056f789af5f 100644 GIT binary patch delta 37 tcmca9*v>manTe@jV_pdpOU^+J@5wKj?rwg;%)-pc;OXk;vd$@?2>|vy4B7wy delta 643 zcmZ{hPiPZC6vkgt|CM5k6=@X@TSPCK-I=AE><*$NB(_0Q(im%d>t=VdS?p$)?8Mky zQp7(ev7!f&c<~~L6sk8pRq(2UAc%Tb(X$|h_!5K!!NX&C^M2p=J!Wq1{kU)aNC9>@ z$y^d(^Y+NLxf1{)VPPf(8*dJO!$z`Z3ps%1F@Uve0Kf5S?JGc?0DNBtVD|wgg7%B~ zlK{~$f3BE8U^BTGy^}w%yVL2Yqgz{dA4b1O5GkFVNxvKY@V1S_a4@?R0*vhMRsNRXF5D*I<9p|ipz$E$*|Jfb1c$DNnTKTI!_u?lyciU4Y;V^F(} z%!Fw5js8LxU0YPT%=@QPdyqLekd<>iE>;5$y|t8}>YRSIFxNBc`{as90o61^BUGpA zuUnmQM8*bu&bv(xB$c}NC^+>4XA1+f3xzC{-4%`xpmFgHA|URotI{DMzmt+56CTR|00EmxL_t(|UX{|<(&Hcu1%F^2ntMqte=qO^C-uM+MlPUrT#h46E4qy8wle^Ik=7>U@iv(7JyqRc!LMywG7-L07F@# z;|lO70W$)?wItDh!E3vlC?bHj9ncOj0Pg#V#$e1YbwL8q=MzlO0v1oFb$@hR~6^=os$&@x8AVIrQ1jU}0ru>znQ290KL zrghE?!WaSPa1V5>WnN-H%7c@D)4HC~s25NPsIB#Tc7G&WHhgaZH2!xY4-ELiuq3%L zT6XN@Qx>fQIPeVt+MjSV+TW`+9u)u<;0U~I6&|<`swHEkj>M2xnif!VZ~?r?N>OZN z+4CE5oXQHclRrI0V1nX+t>sW0JTOXC5^E>ll^k{Y6nN7y2y+Cf(21juC5Y;P7#smI t&N(Heb0mMq060$#QR8auPh}>x_&-~FdEF!Hp{@V`002ovPDHLkV1jgJ1l|Au delta 576 zcmV-G0>Ayt1mOgbEPpjID?>jyP)tZ(T2^alWO;UShJ=8Vk&dCBoTnlnuB@uOy12iS zlEbK{$;igiAtBW-FW1)8*gZYkOH18ZS>R`9<8^i9z$qMtE=(3x$^Px z^vB2d)z$gqK7s2hw{s##9tN;G87XYn4=pX?ezWblhpfCOQS1F50r1-G=mKn|u*Dj1r?9~) zaHgumM(5coDfHcc=oi#BZ4b-|U zGSK^sEHuc5_mr5Yn421Fdwf-BI)Xy22*CA>`Xtxu%(jP(0P=wfkm?$yf4B`od0fU4 zeMnVOB-c~jI14VOq*>dD0y)CQOeFf0qX>&dG!k+0m{Q72=)@zHS8$HR?FBUDc2 zvq_!CS>JZoyL--=`8b&)?JAZXJ5PDxFO7C|W@o!863(s|5`aP3HLV=`KNJZBj8eE_>^@m70S95yZ{xX+t(<7-rRMl_ zh81orO|W-%jG~|8>CWwh%0&UYOJ_Ng>EvH+{cNbu@yV%IiEudFwSz*con7Su2a0cy zGa<4P0S93;+kef$%^&5Zd_SUG5(tNMz?UZf!5wpF_}<13Q*qNM5pJwbQOI;qb5f*D zz^Qou`P3SY7EU<39Ab7agc0Do>Dw2~VXk#cZF^UwSyK7akrD=1`9!i!LIJWl3%0I|!r z&yQP4lL`uRZkowd+Z6|SqWSDQM;WYMvL5J&s_dUW$);M-&bz}3ExK;W0Lv3lU|9pC zbwF0v$eNI{lSWVg)@Tb(iYAl|G7tv~92Z{<@2byoTTDDsF4KOF882)1Ddg%V6d)}T z-hRA*gKBCl(JqU@%a_tL9t zLycntKh}FIAPiR~xo7&#_}Lz!MGgjKYn#&{RevXCy%A?k$xY*kSm_JtcH2pH$r^E8P_~&n2t8qmbzzxHek%)(&%C>e5(wa8?4byN@Iu?W8Bcd$ zM1Ngz#Ujuhl;fJuurVHE*)CqrZ^RR0vv!TK-819203EaXZD>{XPoed}}}T zu#0ZjW4dl2h;UOr+x#k9!i$V#*Fe#5F%C46TMq*3WdS$eN}CTD0j2C%nScnYwUw_P zyoZnP*o`q_N#sqP*_z?SAN+z~=P>&|_ZPx0hu1YS)vmovKVG&R%#D@U(EEhSk!7F z<+7dXi&IkCe?Y2VykDeLO8$R&Ee!2EI{b{x4Q{vR+c?b;URfBWkZO(U!;et;#(y_i z_|`+1vxTNQLqn+cb^wAuK1cPDLjXA4UATMq;M{!|#0nCIEPVGzM6VrB)<3itZrTdQ ztb7p>1O&f-minWQ5j=Z@1Q2J7nVUoA=aEtwedaCn#94p_38%Lg00|?^sWHsV3;-&Z z#p&t-kyXBsxNh<@uo>0U4S;!jl7HHvUs8YKNmM!oKx+S805)U&);R9=VG<<9YqX8y zfWOj3v={>J&{k9?1ArbojTxUnA3uSFAuT`qG3n2JCW-&@F~aBnW&_5z9g90fa%b;39xD&XX{>1_DZ1xsz_dwiF9h z%+#euFFTw1ad&KQdgSyO^nZBLmdf{RnHp_bsUR1pK&e#{$Yk(_n+A+djsik-^cA9i zzX3q%Bm2mF>OKHW;hepgg5N!rko0}B4}Jx2$1sQx9Qjjh;{Pt`5pPKYX41Cr!@s|P z1a+draErdylVQANu3Jyf-{~xD7#Px&z9|ImC U3LWYp)&Kwi07*qoM6N<$f?eDWDgXcg delta 2002 zcmV;@2QB#K4BrosBYyw{XF*Lt006O%3;baP0000WV@Og>004R>004l5008;`004mK z004C`008P>0026e000+ooVrmw00009a7bBm000XU000XU0RWnu7ytkO2XskIMF-*u z1qvGo)*9{F9{{irtej*Y`cV;lCUiQy1Zoa}@5Hj>nHy zZpinXjhxA8$R#=L5QMF6Nl5X}!V0hXXtMS|gS98Jr_9A$zL|XOwwc4@ARYx^%Ij^t z;1%{h2F*AGgQQP-r3S+is*vTKj^mD@7?H4#&kdSU4S(CHG7L-H&-V?b@A$ygm>jkl zFVOiB@s)h8$Rn9NVXYs4QNA-RqgQD)CPsXS;E?q&U@)Z2Fm@LTJzmEv@q2scgn6Tw zov;#ZUA&CE z@M?U84u4=s;sLClG7n+FIj|vbm`3m53N1w2U>Nfe%%is>&37sOr0_izfLYIC#Q+#N zQaC(MDAozeE`)M6T2IGT;dQIng@r-5=@)E2}P7YCeh`5KifOoZyEkF zwbSo``=++2vzv;Ojv+h=Q~fewNtQ50OyP@k;eXpcOCADnjVQ!^=VJ0&NH>(US01HiMNG=Xig)k39 z1+7JjUlt!v4l5vl9T*k22a&;Rc?IbOV2Q%wrOkY&$YQLd(qf7q1*qo97#p|-AG^fE zK7XnNp5dF}7n+ai36WGCrMgNA4$0FGpv?gmLm>%kFprc~O^n9i#Dk9oupk$+KEX

G+}T*C`>hOl1JF93gFeddd z2G6R&koYRh4_Xedr0qzJ%7KTd2*s{RD43KYR7zqs#wF~*cODV&i!S72W=wEsKYwyS z%w8n+E)bupMbix#8m~)n>AD2X&3Dmo^$(P$=3z_0QCzJ58NdE=2iLFN#HsI_apB!; z)H(!Uz4tt9&OL;aKm3li%hynU=>|$G>##mD3zbx+_e^c|14x|Q(RJl2{`u*V%And< zfF6lNm{6&J_ICc*eeXWxQSneNSbqe_<-&fprqS@{u4Bj1YCD17um5dmx(!l1L0&gVdL{(^9}wGFM&i4c#o z?C0+RwsEW(q?4xU0J?A9f^u&adSo*6w8^EKSI<~;$ikx0)%+WJRBAzg-8sm7f}v?_dNM%z zKmjOYRfmu202JA|TzLzHMt|lPiq5ljJQPC0DWF)B3(dvGX9U2i!OOP7TUJCr=8 zvVbtOFUdmt(iLb;NQQFTP9327*a_PItUN%jmIX(kyQM`qf9p1+o<2}){6MErS-ucfy&JN2^@vIKN+ zTi6TZ2NINv`FKIt#YR>ScTqOS*39hs#{awjhW{pj<~fS$Z^_NjJZe6b(f|MeC3Hnt zbYx+4WjbSWWnpw>05UK!I4v+aEig4yF)%tZH###hD=;!TFn=(@ZoU=(001R)MObuX zVRU6WZEs|0W_bWIFflkSFgYzSHB>P$Ix;spGchYLGCD9Yvoi*&0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUyS9(-fbW&k=AaHVTW@&6?Aar?fWguyA zbYlPjc%0+%3`+=cRwyXSPs_|n6Cgx@G{a;ABePT>%h=S& k#LUDT#0SfONT5nC0O}VJbn-$ql>h($07*qoM6N<$f}*m9E&u=k diff --git a/recipes/icons/amspec.png b/recipes/icons/amspec.png index 4ae255b4c149e6f0febe136c89cd70a0a03c50b2..fc211014df9ec30ec0876069d46c9e7091961b33 100644 GIT binary patch delta 408 zcmV;J0cZZb1G)o{EPs_08lo{braC{yfrQSImC%@*(wm;^)70wF(X&@vwOU`cU1Gaw zZM|%8$AX5;kCK`nCeM|ao+vJ%E;QDprq`#b*s84Bt*_+7#*z*dlMxs1+}-o#<)t@0 z=EuqC&Cah+Q_6{r)1IN!prX2FYL5sHr#(WxZ*#e0XXnbyn|~rH!F749NlxzB+MOjU z@ZaE(3ljJ2?f32O#eRXZR9U}rcAqOTw_s(*L_t(|US-ckj>Aw8MbVF7W@d+(8yWV$rr5IGQj{}$ zcTy_Qi@`smHGe)DouSVEEO5h1WmkDeax#Q~Q=EOgy-fPO+ZH7fs?YRxX#_7S=NAv5 zvNttTaA`+6rw>hdl4AGfK$5<~aKsOkC1-Qh7He+`l(CR~2A90q4t)F;sF0T0GXpJE zgOZq?NR+RomD~i(sVh8vN@}++<=*`aR(9!GD)b82Z@7&$+-{AA*<@fCE_wDZb z^Yr`m_5S?)|NsBv3u#mU005~;L_t(|0cF5}5`i!P2H+nRG$Se$G?0Y?&?MmfpNUTA zI3H3ue6Ymq%zxgHYCjbG@FQI{YDAZiT6ivv`l1Nx?Jjw^LSBCV?koR+9#VBW1(Xwy_heXvo{qG_yDuoYKIH|0;m z-WkuF`M9&$5QMY4b7#)n`F`g+mtExP7srJONs%|94_*|!@_+1}*kbs}-s3`Rp=qwo zgg!hXg%6Rm!*2=D**n4-h9L<6MUbFffZ2)_zoX|EtD$NOlZFA3hPF#c2`ifeq>fo7 z2`M%Mk^l)9R1Yh1Q&#-;)KN(A=o}O-L9>07K*7s~fbOH31EuF-SPIe-GnW#ZYnR{F zHEh(O%^*h-jDJA}AXssjjT!#lv3il8T1e{!UE?0E0s z7uPWce2fmu`?%Ahx(fgLj~6+Z|8C< zGrtr%P+X^F1ogGC=Eit=Sy8&LMeq*AYA&$Gb2(GT_dib0WwK8{xo>}}j+Q~7X!q2K zWM^C4`hT9DSupnlfc|Jps*IK2%Mx4;9Zqsy3EshjL8x0Z^Mvg0qpH=Pvv*0u_q^xN z^wn1Huq|hCw20flV>IF~E-hcaI!R>m(bO1cC^>TgTg~Fq16sx_%Y{}yPu+h1;x$SZ z7e`(`o37YVVs8XM7{LyzsllaQeS z!+)!*jnM=D6#52xt7?@KPn zOo-8wY$z)&e)IgLKW=4+xVt`P8T}>qY!ABEPdS#_WZGWx1qEY{aS1v5CtV-!O)Kx|zexfBlbz@FT z^A~mQovS6qQLXXQCp$9!crbC{5C*i`ln5 z<8*Q7w*ML+~7es44hZ|jF@3rR*3W5;gFz03Pb_M(tqRX z11cOC$3XGI9dd4*3Gkt7F@U*mGC~!&ac95^{~y2vkU+U=aOI3A@^uwpj)wvvjc`eT zcofLMfrYXIR0ui1Ac3=R1DSAKuS=YFbPs|MMWAF*Dc~7ePC#QQT)4;JIcWiu3rNEU z`~c#(x`2mUf$>l#>^lW_Ej*71jV?NePTGYhAgx`eu>=eT90=_EzChKb+%5kB8%^tS T_mQ-{00000NkvXXu0mjfU{6dC delta 1316 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 DFDVU2 diff --git a/recipes/icons/ap.png b/recipes/icons/ap.png index 996abb662182b28de9675f845f1cdaaa3432a133..db931efe45eee52da1d5203aba8b3c244d9aba39 100644 GIT binary patch delta 505 zcmVs|NsAMYis-a`~3X;W@csw2L~`PFi1#9Vq#(e0RaL6 z0&Hw-+}zy${{AQ^C^a=TcXxMqczE62-QM2b;Naj02nY=g4Ss%pfq{X7f`T?SHaIvq zJUl!?LP7=x21!XtQc_Y>R8&@0R#;eAUteDi4i02wWMyS#5D*Y&7iVV@5)u;=6BHB_ z6%`d078V>F9400vk=t4fCnqNZ0|P25Dl021lR*I(e=IC41qB5%GBW=%E;KYWW}x~W z00004bW%=Jzu)2Fc{U5800037Nkl7l3gKX^x;?x)n;EjUKC;(gLGyt1m_$URq zdMSWGf2e8==sMVF9^lSe;Ee%c^+SVx?i#n(f-UN%PT;EuJagYH7$T^Pf}a2!aia$K z<3L0%5zJZO9o};D25b|z$cF@wHQ?zQtTAOkh~P}hc22zpk6>a|YT!=*(=ybTA($_K zhMr4lzz%ip9I93gUia_Q!Cj0H94|n3r$EVNL-~`yRC?KU-424qa5(B9e*pmj0s;a90|NvE1O){J1_lNP2L}iU z2n`Jl4h{|w5D*d)5)%^>6ciK{6%`g1791QLCMG5)CnqQR8&@0R#;eAUteEhVq#=uWMyS#W@ct* z7iVW{Yin$5Y+9?6?CkXP^!fSu`}_O+{QUm@{{J#A|Ns9)e0qHV000DZQchF9-{InUHVdKv009k2 zL_t(IPu0-JQUXB~M&VHp*orx#V#bK#V8onrz=W8U!TbKN;G(++D5}PVE6%RFzRmrA z9Y+GIajFR<0J0(?nNV0YL|S8$yIu*%w*jn)h-9!uZ;RjvfWdM=@0Sne^v{z46PJf8 zEkN3Wd^<#dv;ngsvJXsKa11Or!cYnL1O_aa2Xb*BR|MBU+yJiwVzLdaTadr%2bK&t z0aoQL48sO|)B`kU4M+laB4fa_2JZlFYR~N8bwZCMXQIG{51OVP?UJX!;=NvCM1${( z*Mf)z3&72$2E5oS#0Op%bQgA9*BuA8D#00$5)qLHV6+mXlv3yV*$1HmrBtE}1Xd-W hwk|m5=a2pmegSaf7AaP7!w3KX002ovPDHLkV1fl^-njq( diff --git a/recipes/icons/appledaily_tw.png b/recipes/icons/appledaily_tw.png index 20f81bdc0112bd6999a5005cf228927287fd6756..fa26911e1cc755e505051a08758110e3d2a52574 100644 GIT binary patch delta 252 zcmV3f%W+WIpIh6dVa;!C&usv_ zagkFn5&*_@0LOFlKSlFJOY=)n1ju!fl{0@M$a4ML-T(Xe|NQ(o%X?a@zk&b&00DGT zPE!Ct=GbNc004VQL_t(|UR}sl4#O}21kqhPNs*bEnc@ByYgbmQxbJTU(ydhh*an=I zl`?DzPC7NYB{Mc}@pCzMs?EY{kjO%wOMYx}-1XXh$kmXGdQlsKm5E4fHHMIrnesGC z9T*dNV{IPv#xd3-4aNk2BZS=f1845F9QGl{oI&1v^9Mi2#DQ@D00003f%W+WIpIh6dVa;!C&uw(j zY>`ti67n@X@;E>9KSlFJOY=)n^G`#Ol{0_+*xLQu-T(Xe|NQ*_|NoLe+bRG600DGT zPE!Ct=GbNc004eTL_t&-8BM_j0>Us51i-~LxXTtO?p8|v|1s`p8<>HbblU(-ACaL^ zEtn=E7b~WCAxL zu-0b+VsAq_5Z92-9D;uW2>y6bFFJ^<8AIZ7_PF2*>H~VR6bIQ;1s?za002ovPDHLk FV1magb2R_} diff --git a/recipes/icons/ara_info.png b/recipes/icons/ara_info.png index 821ad0ecdb4d3adbc3159a5364f3fc36f389438c..32e0edc0f161cab1e541345cc524ff3561cb0001 100644 GIT binary patch delta 448 zcmV;x0YCo11h@o{EMx!w|Nj2|{QUg={r%+R1q4x1QBhJ;!^6XzoSYjP7gJMHR#sL_OiWEpO%V|h z6ciK}7Z(^86_JlE3<(Jcl$4YS2?-}BCy~f7e<&y@F)=a4#l+9?6?CdxchyM1zm9k6X2aov(mB3ej8qx15ferGL^67 zLS&v`cQ3l_3(KkFjm}8i4a`cpQ3jW?ryPvP+Er@qE~u5-d1h-d){Oq99`47b?X`vC zI%R#Rhxacs>Lmh0bDH>ldi)tyjCqggLPq^OztNx;9Dv63xdY2pHIW9;^^vAuuc~@U qV*n!fRzaoOMPg*;8^inwmY9EulNDP(VvBwN000087Zn;B z8XFoHCnqN;C@3*8F)}hTG&D3gH#ImoI6OQ&K0P}>KR-Y~Kt)AGMn*df5E}Q!^6YH#l^|V$;!&g%gf8;+9?6?CkCB?eg;S^Yioc^z`-h_4fMz`TYO-xB&XR2Kw1)`uh6&`}_RYTKwvw z{QUg={r&#wnEw9$|Nq?o|MCC-|KuorBme*a%}GQ-R2Ufr!HFUQQ5c5duY@eg&WJ2! ze_u!TokWafWN$df&wKyZ;CTc97~7{f0|5YzbuD0q0Kll-MO{G?6X2aoIOsT}cN{Ow zfv0}`n5#6iAu>-?y^NpBVIy~Q(iw}Fxi%@g%=}($fW9T!gwj3TDNR$nFP!ZL+EmNN z+v}=yeza3RrlC>I@clzUC1Nm?*V^C5OZ)Gl0?d0%Lz?CDi%Cu40JNg^3)s(Siv&PV z{c0T<%xE~l07NvVl+wiq3;>z=tFUs5836nPvw=hvm9T~$00000NkvXXu0mjf6Y|~9 diff --git a/recipes/icons/arbetaren.png b/recipes/icons/arbetaren.png index ca26f7f8cf2c501144c4b813f31a143a60e37605..eca81fb24666ac766f6af721202e094adadc1a5d 100644 GIT binary patch delta 3238 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 Y4+qM;rqkG#qW}N^07*qoM6N<$g6b$XtN;K2 delta 3285 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 T4;Vt100000NkvXXu0mjfd8aeg diff --git a/recipes/icons/arcadia.png b/recipes/icons/arcadia.png index 0c4d6f9bcea2382c0228bf6ef1de3e11bdafd0ab..94fe254a60f3e35667f6e48d37c343f0bd6fbacc 100644 GIT binary patch delta 485 zcmV`uzWOtpEQ0|8%SWZm9o*wEuIe|C7G|%isUZ z;Q!C!|Ip+A)aC!C$^WU#|EkOXuFn6j&;PN||FY5lj=TTG+%5lhtpCQ`|F_fsy4C-_ z*#E%U|H9h;mcak$@&Av!|B$@@cdh?XFs|LgPrn8E+>_Wz&7 z|MvR-pvM3B`v3U*|DneJqR0QE$p56s|E0#gRZVc9XKm}%QOR38~G00000NkvXXu0mjfA@MHj delta 515 zcmV+e0{s1*1iJ)~B!3BTNLh0L01FZT01FZU(%pXi0002?P)t-sZKwZkr~huK|8J=O zaH;=sssD1S|8uJUbF2S!tN(SZ|8}hZcdh?@o`2Y6$|M&X;`1=3&`~Ug-|N8v@{Qdv_{{R2~|GWWO zz5oCK>q$gGR5(w~&$mJXQ4m1Ug9s{S0dvk-%n@@=`2PQAECh9N7YiG2Idi5M;g|eN zus*QY5Ny(X+W?$5V=Wmxz`PjCd8w(6fFKfqGj_uAj;L7Qk- zb>O9=w#jr4!&V(A!R*C-C4>Kp*MrFYwY!Uj!WIbEWXD)2{qU1T^UH;2ogd0eO)H0+;{*002ovPDHLk FV1m|zGtmG5 diff --git a/recipes/icons/arcamax.png b/recipes/icons/arcamax.png index c9f7de7415e1e4f287b63f345da4fcade350d09a..bb8b6b843bc9449c8570d2a8fb05e88c1b2f6a03 100644 GIT binary patch delta 1781 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 delta 22 ecmZ3(zmtE0GB*Qbage(c!@6@aFE=W7vjYH7wg+|q diff --git a/recipes/icons/ars_technica.png b/recipes/icons/ars_technica.png index a50955d48b06a325f6609409b22af18739b61672..9042426fc7c81cb0ee32b855ce25fe071967967b 100644 GIT binary patch delta 225 zcmV<703QE`0)+yQEPwz1|NY~|_nb2Lqe1wmNczEk`oVww;J^Oy;{NgD_LCs@l_UGk znf%qL`MGWU+_(C`eEsUw_^MF(v0nMNY5w@}_mw33$BJ>jjxGQI0Ch=3K~#8NWsgS= z!$1tgsO`<&DgS?I<`fuM-pGqam?$ZZ5mPatP;*B>&sD+di&{Ugs*rh*jnaA03km$@ z34Z(B0cnKQS#amFuPf34_il&VSk4Qn!uwjjPI4I12(7hU!L^1o0iJ-kfHa+*hE3C_ b=cf06U?2oyl8T#800000NkvXXu0mjfn(2K= delta 227 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 F0RU*|*mM8@ diff --git a/recipes/icons/atlantic.png b/recipes/icons/atlantic.png index 639d377e761223b02640a078d3aea9ccbfcc60ab..436307c3100f783021ac54c29294288d5464da27 100644 GIT binary patch delta 191 zcmV;w06_od0pkIXEPubh=AWP0KtQwK-|~Qf%wS-@@9+5_Af|tR%n%Tt&(G|SkJC_4 zxsQ+1<>lpYaK-=tnO1T6`Tzg`Xh}ptRCr!>&PfgfF$e|G0z7qh#`|A3K@pONxOP1K z1w8+>!}nB&2gizvF4&-+Gb)fLY~a%o6;XH%*q}5>17^P!N;rpSC5a?R9cH3?pbsDR tt$+mj@b}CG7FZE;vl~d@*dCWQ_yO{R6iocx3dsNf002ovPDHLkV1mP7ShoNG delta 194 zcmV;z06qWX0ph($ diff --git a/recipes/icons/atlantic_com.png b/recipes/icons/atlantic_com.png index ced037ba721252fda81db49c328b291013a79daf..6b622f7f9a7604e97c6b96f9ccee53a3c3a879bc 100644 GIT binary patch delta 704 zcmV;x0zdtP2i^vdBS-kz?~ai0g@^Eoi|~^R0T)91+1mTx;O%~X z?r?PE008fZitTA@?P_i302TVw)cV@m`rO_6-QD`$-|cO0?QU=FZgA~mC1@hEnTC`=Vm>j-OjD4` z9mb0io`@Y)1LI5Xr&OYpRj>lIJiZu-TK|#`rhCB+1mTx z;QQg?`{U&M<>mb0;{4;|{N&{P=jZ(B>HO*G{Os)g>g)aM>;3HP{q64k?(Y5W@BQ!Z z{qytw@bLcf^Zxhu{{H^|`1t?$`TzO(|M~j=`uhL+`~Um<|NQ*_{r&&`{{Mgf|2-m= zzW@LMXh}ptR5;6((^Xf)KoAAsu!FT&ad&rjcXxMpcXx^uCv*QV4_H!~a>&!3-7j}$ z_neU=n4tNYAW3Fh%w`*smHz?2WS(%|d1scO%7-zOPQaQ##u(8!WGkSRM4J_W$26j> z7tJ0Rm!6KcfkssmYA{E7JuQD~0vR14zIy6XHKFdIs4v(-lxrn0juQ?eh{A7_LIoC* zP$FFHcQrwKGkS$VjB0}HDNK{AXjFD7DbBB=c5vnANO6y_43-mtmsoQ0I8fA3f}Tpq zXc7i7uK{v~@rof~5G@t~9%44|gcW;dO1A_U>j4F2#ihAA9R;2K2nBx_NwgHyDg=;UoGjBmmKCkogU>Jh^$S ze%JT{#zAlQi6bzsC;f$;O~&%G3doqrtf|f)fsD<(s`|Qy;@aDPj~+9B3@-Ec5&)#X b#{V4v_Y3r1#0_8o000R9NkvXXu0mjf%($K= diff --git a/recipes/icons/auto.png b/recipes/icons/auto.png index 27e0c017d5d94f5486ecd221f0405495221a81b3..10679e87ba084c183191adbc41567404acf22c6b 100644 GIT binary patch delta 1848 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 diff --git a/recipes/icons/auto_prove.png b/recipes/icons/auto_prove.png index 27e0c017d5d94f5486ecd221f0405495221a81b3..10679e87ba084c183191adbc41567404acf22c6b 100644 GIT binary patch delta 1848 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 diff --git a/recipes/icons/automatiseringgids.png b/recipes/icons/automatiseringgids.png index 7998cacc896020f92ad0cd158fd8c01f8f38eaa9..6f2b464441c43c60f7c8aa46916d9965f5d529b6 100644 GIT binary patch delta 3579 zcmVI}{?75<>4D2gkQji7Rh$CQ*o6SuRnb^F;;iK=2X1%|>r8}E6=egw_fki-v zY1^uZ3Q8k^P97JB@{=wf-C$Dn&Z!1cZ|4hnkcP*P&)|(S%`0cztfve`1c+$tN0inA zU&t8+nSX#lNr|c2)sD=8NK_VO5mhNKEF!E@uBmK&nAH?aM;H6y{$rirf35pR3!S@P z-}vJBH1DS+PnmCu30)dlJ<|>MuyaYVG>Vta-baI?E`5&3qL3 zffKzyczxqfkCfRwdv>TAQZ#1Msf?p+7cFB&sedA}q;#Wb+J6K~x7xj>cyXy8u$3?t zQkpD}!q*oze)q@g|8%COs`Unqp&JJ@!s(6 zH+q8@Q$pjbZg(XyY;(o(zL2JnylG?wJB!E~ql9rKWy@oApjMcA(XPqSk#4b>lURPW zUw^L7O(lISYL&62T-nOD?ez=3=Z(&9zj^^KT1>OILaL-c$15KwSU-gG!;ytFL@M`c_W6>K5}wZ7mV48w3hRpr}PmB$%RX zUrRoj4*?|ZA;i#lRdb%;udprBM%WdRI=2PWvQYbPhXF&iP&QmQH-nCtqH zNEC8j@%iQ9Q}4lSBcdjxrl7d1p5D3gq9eHb&FRJw`y?;)4 zX^=Ds8w43bgR)7a9S!`yVK}-Gj2r4Qn4l4}s-;*bis(pKLErAlv@Zx8k#8a@!s%YI ziB%_i00@yFRTFC2l>wsB6QW82QgHxedQ2e3>Y#!SeJ3IqCjh{f55#SmfTR;cK=gCy2cCJS|KDre7mSGn1%E9h0xH@jGfz>Nl8eN{HDQATCqPwDVrK^p1WsWr z=5>4(wZ;QLj06~z8bsYFhuiVlv;D)XgN{PWam~$^5hiP5BSezY)~l)$5JZ;JjVeT_ zb^tCN3icue;W~8_P1TY2pH^y2IOTSDwRrLT2oW>Hro(bkqRV288SZNN8HY_{aiXmTW5lv=A_Il`y{PZ%D%#D~!8rv*SRTd*xGSR(tN9W~s1Y&; zs;Qw8JG9cauA0K{y}JI;sp0XBVi+oTV3iN!eZ7?KD;3dVjQ-mt5zGC@Hkywt?R)Cn z{->8VKUX}xJn`7s&%JaZ8h=EYi;C=QMgMG}`{}38z-ueLF)d5t6Vae?Os<7+&{zX* zO?=Afro!-Tq&@B$3emql-o5>$m0J!i|I7=^pM7!VuK#VH?vKnx98`(oZYvBd(i`m>44wK3klcwv2ybnO{xUbs=r#zgF}4@4x%S zM~_2Fx3;P7L(SXCCM7x+sjsO951-mN-3w`DeEO9G4Pp`{)xPv?PpVQERqGeN^z!=j zBk%mmzZ`|_LAIz^wSSlMaxQDRu8w47B!i&i3XfwK_I2=$&R1StXN{61J(Y`eQ`?Wp zS~ZTW>ED0%6H9xac<+nfdkfyz}*wVR(?zWIff&Fkj>)-V`sQ2x?2+@|JkrToKKRZ_U8%Zd2r5y6h}Ioaxc_MXl7An;Z{iPk2dssIrAYE*?N;eQ&;>Z?@a>9>w`7usR_x?Ss6y@qK` zk_Iph%)FNCo9o4QPxbEn;e|a-KYy#8@?njs9u}Q9k^0^@da68%DqGo@(wMkpyuN)%*T69fr;R;$wEGL+Jp%h4 zI`-}3{XxOiLD?6luc!;%xOpPC{rk$yWAFX!q4nia=tl00q*IiDzLv9!-m}o&Yba@0P zHsb4@(c3-fN15`}A}m1X9A=b8A14GT42UCvb{^-l?00^ub;sV;o|Yp?tv*6M#3kfn zo8(j+5g?>?5e>M+`dIkk$u3^=@Nu|wCTAU3jXXJ4QPXV^G#xoYu}6v!Ku|m)vVTSb zM-V~1^Xdq5j-3K7Z~E)!{8f`~?-cB5X1g=k-Ex<-8uL!4GiZ5d`GKZDX6!3u+W&Y zNOQ3li3u$!T4|LN1q%u(IRK`BLX07)U7op>Jg$cUD>(#2ZczhOh=`Ps7=INS5EVTt zBg-ris9Nhln4&7#IIdoWr2quhFr3DypcYn14i_6o9Iy3M76IZhxF703jkrplYC# zf)NOi>1j3f8Z#p@q7zUBhxVSafJ1opM0;_q@Sohdb0k*DN3?bfPW8t%p-w&nB`**e)PA9x; zxYX2+r9w49K_#?(R&BU>A(GRb;&1-v?3W)~Y&m-L*KWLQx^#(!Xa?}gne{(;_9rKL z7{Q5E{38Ul{7KLttlc8cP5G;5Ti5PveQbC0>YbAZ<|Z$lb~`8hOrznP5nKH-^^3SN z3}@PLVQJ$(&#WCjGi(nBcV0d7tvheLbgC(e{|{0yJwP4*uPOil002ovPDHLkV1oOz B#c2Ql literal 3627 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 diff --git a/recipes/icons/autosport.png b/recipes/icons/autosport.png index 1a5ed8895c09a4f9707ce6de57a7a35690c8bef5..645d55c0631331c5d80fa0f20e0f1925b213db2f 100644 GIT binary patch delta 491 zcmV z+q}ok{{Q~~0|o#8|NsB}{`&Wkp%nu~M?{glHZuwg3GnvzK}kyh0R-~&@1d!z@%H@M z;pf}p=<)de8zdtiC?~|o%kla9yTio@5)>^mHj_62GaKOI-O$_LqOh@Tczi!iP74+q zB``FXqNYMeOOufSP6O=i=#%9Eauxgi__)T&)#K*O*V}7$dH@R#{{Q=vfdUl+y1coQ znF1;U93C5!#sVY*6&Dnf?E)=-?)3Bk0|zubKjZ7}=E2E^aZp)^3U%veQ|Nq0cZ;qbc42&!cOsor6uKD=w z&#i~gZ$EnR@y9=D6=iu1bz3*rtM~5y{Qv*s&)>TbALS4dVc_7Mc-L6HwynE=#{89= zc7FWz|HuFT7jEBQv|(dzMUB0$AGes4s)5P$MaxcIy**i*(T}aBvwzR=vy*EWizd%! z64m|p|No9-C$Bz!cINu+?6N8bZhi)4R$*EB=I-7*51%}K{^G-zufKl%`TZN{!2gq_ znY5Vy|NlGLgGq(?->>hJ^OH=O_Q%{3dth4MUI977~7C#NK|FtTwxOLk$^VcNku?^FZpkA-*6*9eHkI5aGflt0jOoJVI* zfV;zs^a4hXh!YbIG`-oNd3{;B(Z{BPS#t9-@7%dA_E9G>raj=pRQ8*)4)q7laEn#w zu?j!X=P-hZD3$!U?9ES?l+2t-29Zxv`X9>vR?7ExdU}Dc)I$ztaD0e0swn~ B4`ToT diff --git a/recipes/icons/avantaje.png b/recipes/icons/avantaje.png index 6dc40aa6fe605fdbc9f3476c13dba851ff219a37..be3e00908d55eb6765c9af5672e4b8f865f98e33 100644 GIT binary patch delta 669 zcmV;O0%HBl1-%83EPv(zc;x_e<^X!-0Cwa6ard>@_NmeALYwL>k?0?Z=p2aX6@=#v zfaV5#{`&m=^7!Qdcm449`N-n<#o*)sZR#+R`{3^DIhX1(l>6B0`qk;_DUSa2`uyke z=oyCR3V!ARdh0!z^`_A8Vyy05sqR>(?NX%r(B|nRjPiuO@_&K6@qoMM5`yOuf$@5{ z^qI-#1$q4B@b|ge_OjOH0(kr0?fcy9{{H>{{{HPqp7D3L@N~8CakTis-sc&G``zvJ zqRsS{$M&w(_N>zPs?zEK~#8Ntvc14}##R*X(tE$aAV?5# z@^XhhA$EspYB~UXaB_`~4_Rpb43*0Ot;)$r zOFQZT^^X*LgTyA%p145JRO|sKeoI=~N(jCx)_Gh6nY6eR_DwP4{2>3G8ZIcNPPJMC zY3=EvTlgE$SajeP!LnH<`!>7cIBpoPoqZeng>Q+F?%Rh`KFVGZu0qfuOmWP0LTW^p0fVEV4>r6RiAChh*Eu9OyEg=?Wz5_x_+A~I!9E-Ix z>E}Hl*6IkUCKwX2`-bV00000NkvXXu0mjf DDqnpW delta 684 zcmV;d0#p6H1GS;O z^8DoR`{3^T-tGI{?fcy9``hgM*z5Y$>H5&-`OW3|%j5aT;`qhj_`%-yx!U)&*!Hs4 z_O8|TtkU z@`1eZfV=T}xbb(l@N~8CakTJkvhQlJ?_#X(T&eC@r|nXt?MR;NLYwP7nd>>1>Nb_? zGL-5tlIksy=_!usB#h`EiRc`N=oyCS6@=#*h3682=MjPD4S?qge&z;z<^_4?1AFEH zdgcIn<^Xu*0)Ker0C(j8cI5zc00!U@B6& zX_Z=9SZQTs42NCiPo;*na7ZeNJEP^; zW+|hFXX(oJ$Z=dEDxb~JEm1qGX}XEXtLZ5^7_h!R4u4ULA!U{X3K_(zK`byJLu^Y9 zkcvS}F~H7>V5c0Y6vRjmVHgqYf`FBSxMm?>T(Cn9{3(c9!v)+GY~#&yl0gjDecKaE z+0LaVDQ2NyVny#d2vS?mw>`Tn>r-=<=M>^~cis}OoJuie*_^km?b(^}8RqSJ#G6S) zYS|HUQh$onVjo2Jirii}I&ryaHq!g5+){lcBP6%uAyQ2f{&iCL$|1V#=%lad+y3M| z|I$AZC==X2GXVF%f3`W9P!EAMHvAMVQy+kxE4sNA*g~BEsHX2+TKw_d1vKw_MfGQw zdd$aum#mw`3i*-Oe%C4Okn0i^{ev7aB6^tG&?QQ%Wqj|mns3!lW=E?k|5jfgeOHg@ SBX1D^0000a)@PuvM>W@fk$L90|Va?5N4dJ%_qH4k%tKYdRzxN diff --git a/recipes/icons/baltimore_sun.png b/recipes/icons/baltimore_sun.png index f5866765625c9efe99ad4f90ce5457e29749544b..4b63e75771cba73bfd596cbfa028e53b1fe81c22 100644 GIT binary patch delta 683 zcmV;c0#yCx1(Gs&a{%0`G2X8};kHfY!D;5gYXAcV?9!2>cnYV0AJmu! z?bVs5fg$s*2lL{&sDU5)^5OjV?EU%h(2oG%v`F;j!1b~c_kZlt)RzJIzApLl-{ZVo zIq4&bv!*`*}&;kNJFrSRLR@!YD{oe|=>R^qx>+NLPlrzoa>ANJ|V_Ug;q zrz_^eZRf>u)R+eP^5E&pe(K79{P^zJp&b9qPXE$h-K{qN=aK*DlmF_N(2W3N=*9E^ z00EOpL_t(|UVm-Sg_7hT5C&jcAnqA=U-xr&m-l~~gUL;0H{`EW(KKI?Y6Rbp^U=h) z->LzI&RH*xlTq|Dhc*xbFPC55e<)7rIA&#mFb}sE&(3acASj4Bjs-Ya6HbvC)$U0f zfB{`N4jSro*0(JnrOki9GQG5cJSMu@;a=pU77%E#VSnIP2F&`>0x%;jdXQ_NdR=?W zyh|pt4oD8AVhbQ7v`@icj%}b|REbR#V-1*8Kmt{FF$1Bg9 zOx(#_e{)w1g%HCB@pY=6Sxn{cHyRukVL(+2;UxP*zs_MNOxdXH)S$k_&Oc3DEMdN^ RB3S?c002ovPDHLkV1h7TXtMwS delta 710 zcmV;%0y+K51?B~iEPnt41_TEOqH_SGcnYR{7N&n6r+^-(fFGxUA*g{L(2W4lj{wt^ z0MwTO)R+d;mdJrX&WG#JjqJ~i?9!3#)tT?xrSRLR@!YEM-mdem2lL^!^WwSm<-ql_687oI_Ug;` z?9%zYEcw1J`SRcT^5FXN;r#dP{P^zu`SAbCPXE$h|Ko@M=aK*DlmF_N|Ns96G?b(O z00FK^L_t(I%YTKE4SUi+499PUT0k=C&8z2WEaK1&v|0*kkxkc45S^Ou|7py%ycF8! z@&EKCm;5fdq!p&D;E`2Oc8Q&ZVQ@N(<767Wlu!qRU>1uHZ{JOB=qfh40)#SL-#tD( zI|F}0)DNl|{3T%zxs>jHssmt5cR_&pQTxkRH9$(M4S!%%dQk<`713oEb3#sQ0H1-C z0rT7fvyrNnm=P00kZkMWpxUM!kkzaY%7khc-vbCqPSC`_K3oPgObcQa`7*2Vh86&efxi#jPB0r=$HP8U(a`FBeU)KLbAWBa8T0ric62#x?5 zz3<_sUw`w1o2>#L0k;$3A0nv$Z&pTaQ(O>@jsZ2G0O>3!&cY5~y<@f&pQalnPm$sK100000NkvXXu0mjf1wd+z delta 277 zcmV+w0qXv#0>lE4IDau?p1{xH@ALTI=kO9sj{-l3ag)3vSC*i+(t)4D(A()zd#$j; z+K{WuVTZKEODAst000AYQchDq05GgId9nZi010qNS#tmY3labT3lag+-G2N4005s! zL_t(I%hl7%5`!QRL{an(Anh~B|Nm!mruIN#cA;=Ib89SZq<*HQf2XE_sHcLdYbg(d` z>*MU~^YriZ zs)nnph^>TAhUVMm(7n;dugCQ8^mRLUc072;ugGU8YG)^Epm(C*(%|mr?(gXDbvk#I zX_&{b$o%^J{QLZEFmTPd&YN+aidl?=P=|6hb!aJThgFG)R*H#Ni*q-1bUAk1&E0fA zId_?Do88aensA)0imq=ma`o}`_VV`k^Y@{7qHi;D;nd;c*5l*X(qoh5KorN}9_2xgdAT`g1BOt>~5_g(UOg zs(DOxUA8zmNE4v3fkxDorw4(4YN*Vvfku38EZ;AItAic9IOY9+k>bX-017*p2ifth z0WQoOH1qpsfmYiC2E5-GP^baGjC=)xACFY9`6N4g?V;rtiW3H=+YsbY6_oJRqylP3 zu&jaBEs7NbKriLZAs7^UueSEyFvS7@?PCI%5%{Ps4n91Uy8uCn*nKveO#pD3b)ZV@ zBZtHBOaSN{=b#9G!J>A_`Y#lF8%AO0pcp}+26FfsLD0kJA%ffQRnVkZt=950z}_(6 z{)7N566kx!z-vGN2SFp)a5E5M06sIYpn~xC%1i9%H*m1SK74RupEq<@{@m9E0DnG> zQe7@c0IN|%-6UuL3SUKn(p=C37&UpRzMI!TfJ6{|HC0PA;LdpoqEtdMLFW29Yw~#R nPi1`s*~M5wNMiE2CB=|`M@e!(S5=~+00000NkvXXu0mjfp1RwS delta 918 zcmV;H18Mx*2Y?8WTz_ODXJjL1Wh7{3C240SYG)^EXDDiDC~IgbYiTNMYAbDPENyEn zZfh-WZ7^_dF>!A)a&I$oa5HmoG;?w`b#pg$bUAi(Id^qBcXc~>c071@J$ZRSe0xNG zd_;bIMt^@vfqzMYf=z^jPlba|hJ;UsgiwcvRf&jJiiuZ?iho&*j$e?GW0aL>mz8Ok zm1&rkYM7X8nwf2znsA((ah;rUo}6=@ophg{cA=klp`dr7p?RXCe5Is)rlfwRrGKZU zfvBf~si=gisf4Pjg{!KCtEz^ptB9?wimtGZv9XY{vXQg1leDvxwY#3Yyq~?jqQAbP zzrUow#;?c6uYbtLu*k`>%FMRS&A86byU@_R(b2xr(!bNwz|_>j)z!n+*T&e|%G=w^ z+uO|C+|AwG&fVS4-QCaL-q7FQ(%|9L;o{cggwU@ z>*DL{G1OG^78HS^X~KX@AUNW^fC4D_4V=f_VV`k^Y{4m`1$tv z`}zC)`uzO+{Qdm>{r&y^{r>*`{{Q~}|NsAx??4-q3IZU1!AV3xR5;6HU|?Wo#R*s# zfPi-%49u!Z4)^v7Ni3fM7vf`NV1u#S!qkMJKqa6NCd$RY31LhLl@vw-V!jg~;yh3V zb^5|+K&KK}L5YGe29U`^D5%3CFDxuw22(Ia4=kXr4FSq96`H+J1tDN=9NcgHX$BA# z9uNhs5cg+)%|n2x)?gu#8n6P00VoRQHNcDjDwwGXS5RbOVq$9UI~%A%3oIr#6{w(6 z7_Pt*!fJsl5Y7WCNJ1!R*99@6f%45z!C0VzU?@8xBO}uY#3-3JZ<-BM&;zKz3q62J z=1sJOvF(8hpcV@Y2?`2A70jF9futY^%1-I&>9K-;DwsFd79!{lRFHshWPuWh;W7uP zA`c=M4OCE!P~Ze%bpaJLL73S<1=AJb3R*(^{QUe=K|xso7L%F`3Tj_0K|L=LEanW3 zi+WK^1%*;zv2sXs`+zy|aM?Mvf#P5xM`(KJ0|&gQu^9vy%EJ@?ZRS4k$B1-G3?>#d)|H*|LOVz3| zNL462j5P+p`_H{JAe(-9fmW7iX)!Dg5(xxth?u#IdL<$M<4**Uk`}F}aIKm?3z5V#eG z1^|Wda75ifpkk+s02sRa0wqgvBNnkV_ntO*gx9`~WPAO7O znw6e^c08qB5CQ~R)4h85?dIHfKU}^3^G^VZMO&{Y*#rmxHfEP_nRkf{fxQinbaJ zDi4ewJr3uLwOIw(QA)YsbNlv<9C+3xi7}?j0wVZUYg@T8^4d56-9n{YR{lF z>Ieb`uh;ZY1%gysA(Z<1m#$5I`^8BZ<7ASggCHOP__w|h@_7QlfGW658U&;Y Z{sR}xxz#)PC0zgj002ovPDHLkV1oKR;{5;s delta 1062 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 WF*(hnlns6W0000o97aMz zOi4-lt0pTaC{j;Q`l~1UtuFemE>uxc`m;RxvpxE_Nn~PT`+vAddw6+&eSQ1;`+$Cb zr=_N_uCKws!TtRG$j8Ui($oI`8KMlL`N&1!FKBmq?zw`(Gq zAQKj{2Qq??*fm=BmgEi@Sy4B7usMW}rT1jXTq6?5F-#7v*iRwj@DTGTNgg3hNf06Z zbW)^~Z%MKUIhSN%$)1<|N_ zSHS!CKjgzx9YM&*`|N<4l?v&y`6?i7rao3ceQr0000#`et)N>rm(KB!N9@D$H&vs)7jYA=(1-ovwEg`3{_ofR_v`-n?*94k{`~p={rmp@{{Q~||NsBn+-i3K00B8kL_t(Y z$L-eHR>Ck42H+@mz+K$e0*bhSAW|v#zyEW2k(QEVCTWhh{FiCJGs#IasY;`xHpI=- zGzXB4>yj_5r+*tJGuV0dlUH!@kst8siR2ejMgm#Au-qe^)78)iOUhZj(|X+XROF?2 z)*>@Ewo&%960+0Q4>E_$Btdj;mQiMrUE=Zy0LU$(zmg^KG0G^TNFtC+0KkwVL^aA3 zu<%|<86yUvi!Zmpq|Ws^tlpn@Ha{f)Cx4(E WF*(hnlns6W0000o97aMz zOi4-lt0pTaC{j;Q`l~1UtuFemE>uxc`m;RxvpxE_Nn~PT`+vAddw6+&eSQ1;`+$Cb zr=_N_uCKws!TtRG$j8Ui($oI`8KMlL`N&1!FKBmq?zw`(Gq zAQKj{2Qq??*fm=BmgEi@Sy4B7usMW}rT1jXTq6?5F-#7v*iRwj@DTGTNgg3hNf06Z zbW)^~Z%MKUIhSN%$)1<|N_ zSHS!CKjgzx9YM&*`|N<4l?v&y`6?i7rao3ceQr0000#`et)N>rm(KB!N9@D$H&vs)7jYA=(1-ovwEg`3{_ofR_v`-n?*94k{`~p={rmp@{{Q~||NsBn+-i3K00B8kL_t(Y z$L-eHR>Ck42H+@mz+K$e0*bhSAW|v#zyEW2k(QEVCTWhh{FiCJGs#IasY;`xHpI=- zGzXB4>yj_5r+*tJGuV0dlUH!@kst8siR2ejMgm#Au-qe^)78)iOUhZj(|X+XROF?2 z)*>@Ewo&%960+0Q4>E_$Btdj;mQiMrUE=Zy0LU$(zmg^KG0G^TNFtC+0KkwVL^aA3 zu<%|<86yUvi!Zmpq|Ws^tlpn@Ha{f)Cx4(E WF*(hnlns6W0000V!Z delta 44 zcmbQtw1a7aijriBYeY$Kep*R+Vo@rCV@iHfs)A>3VtQ&&YGO)d;mK5+iHe^9ROS$2 diff --git a/recipes/icons/birmingham_evening_mail.png b/recipes/icons/birmingham_evening_mail.png index 7831ce575cd45c97ebb18c7da067f90167efb8a8..f96e8aa2ef0f1b8d4a5a869147a88aec7f90be97 100644 GIT binary patch delta 314 zcmV-A0mc4^1MmWnBs-8$OjJdiCWrt3|M>9t`}X?%`uzR-{r&v?>(u9*D2TRUr?_US zzH+g{e7D1Wx5tLO$%?+qj=-^2p^=Xsf1N6c`0@9nIFY41lc+(Ks6&_j{Qmy^{@JY2 z&zHv4qs-N%&Hw)Yt4W&RyV>Es+vCFB`t|wf&Ex3K_ZJC~g(bZMOEC!M^c$&pfu(s5)gj^}w8FaSV^=kxWV6re2IbQS}$ z?6Y-!zXRAd5`6Q00N2HUJny=`H_bE^;&Q=&tz{tu10IicJ)N#s#sC~=KiF;t0YH-c z1GJtd&hbEN&jW}e43IJmm8vR$^VzcY1ErdVb194Bb~6k@et|>v1~l~)4cVPK4gdfE M07*qoM6N<$f>hY6G5`Po delta 338 zcmV-Y0j>V<0*C{UBx?z9NLh0L01FZT01FZU(%pXi0001xP)t-snEs+vCFB=*{Ek&gARV=aF9?fB5kC`0@Ap_4)hu`u+O+{rmm>{Qds?{{H>`|Nj5~ z|Nj!T19$)c0G>%iK~y-)#nVko0$~sU;OBi=iCkIH5Tu~1hyMTnkq#vo3a++oesvK& zN+Z;q<}f_W3^Ro*QT$=Sqtcz7jmFA%Kp5Hjkn{}oP3;3sewra08N0U*T#tFsB8L{0 z6q1g0Wbcq1gQj&EKdLb{~JPr8$y8(ofR?+JsKwXc>=ZnH2s3yKG)`qLxUGTfEYl5k(D8T89;$?inDTx zvKc{uj-tVjqr#D-#2G<>m8iy+sK=M7$C#?fnXAVSIeg#d@ZaX~;^^`3^7jxqd}V;F z5juSoJ%159eRYkrc8;`nkF|Z2xqg+p7CwL(L4q1Vf)YD@lBLCyro@w`#gwPT5juPr zKz})0lR;vbLt~glWtp0j8UY=DP;Q`6Z=h0ep;>mNU3sSV`270(|NH&_{r>-BeybT& zU8ev50Jup+K~#8NbX+-esT5WSlM*)Ybbhh0000@d=EK%5IKAiI(!j2eGxl-5<7hpJbn~B zeiS`_6+M3zK7bcLe-}T17(jm*K!F)Rff+%789{*=L4q1Vf*L}B8bX2_L4g}Wfg3`C z8$*E`LxLMagB(MG97BU8ONc2>iZWJ@HCT~0Sdlhak~dkAH<6GFV>w)tL1LIgW0*x{ znNV(^QE#A9aG_atrCoWZV}7e;fUI$fvT=&Da*DEbjkI=-w0DoSeU!O=mAZhIyN;s4 zkE6noq{Nb?#gnGQlcvR#r^S`1#g(YWmZ-;$!v2wNM)P0TrOTmS)KONs=O?H?Az;$!F!&hgZ!s6 z^?kp6F3yMExM> diff --git a/recipes/icons/biz_portal.png b/recipes/icons/biz_portal.png index 928b32596b77065f8a9a5a6327b8d758f5d56910..26a840740bb6f95d4bf6b0d706e5dbfa85b33c13 100644 GIT binary patch delta 1035 zcmV+m1oZou2$cws8Gi!+002a!ipBr{0q;;uR7C&)0RR90C@3iY{{AN?Cm`c{QN8|EFK;p{r&wSA|fIpBO@avRaI6N78nKw1_=oX4-XId`S}Y_ zB3W5k6ciRdK0le6n)LMa?Ck8p!NJqh)9C2vz`(${xw#Y+6pW0G>+9>~<>d(p47CeK92}~us>H;^BO@cm z#>OozEU>V!seh@dy1KeAFEGBozVh<%EiEtd^7CV3Wc&O3nwpx{*4E+S;fsrl@bK_U zOG}H3j5anlYHDksprF0Iz4rF@SXfwziHUuEex042T3TC0MMc-w*wob2-{0WAzP}O@ z4=^w?&CSg`JUpMDpqQALGcz@_v$OpC`#Cu}H#avRAb%cFQBmUJ;$B{0($doS`1d_M zJ|`z7Y;0{_US3B>NZ{b%&(F_USy~bj6#Dx5WMpMZN=s5wQpd;0x3{-eR#@id<^TW& z>gwv>-`{L(Y`?$1$jHb?M@PlQ#*K}R8X6c~U0%n>$J*N33=9mCl9H8`mCMV_rKP3B z#KXzS%72fKkYk)CPXGV_0d!JMQvg8b*k%9#0n|xEK~#8NrITZmBtZ~{Gpnl2v9-2s z@6onx+qP}|(5sp5y_k)S>AQ$~zE$=U*%ev&0@Tc7qy(%y2oSPVQWDh&f>=eo2!Qu@ zvJi+j;y($hgL^Nvq@YXOVQg*(pI`_aqg)Ynwtsw8t#7uVM=Q(ZMY1d}sOYK|Si^8@ zqaqaxQg{i@uzu1~>n1KkQqmW5_&@W4vQ*Qw+=g3CRKpX!B5a*XuxvWONq33`VLsBx zzJ|=peyWWJ-VFFN_VKzAKWAjLT4V(QV@aq{HzvYn9?u(4^=%KmtKb!<(RI33om z>wntPVba^1gDr?MoWueYw<)gRoay$?-W`;-!>-ERd-ldm?Ab3cN1z7|wjSc%FdRl~ z2X+LG9@DUSAgVi;#K>4GdFnLMGiN*U!nyMoE+V#Fs@I7tT)7&)2G`LvZ&c(3>*f$* z_no^EaRqws{)319M@Z?|W5gDCGKA*S(SK(t%l) zLG7iv=l0T`3u}!as37tsTD=qmMMY{Y)8-JpsK2B%)}oIO(qkv3h&s#9|S-+97f=z17eQ{2M1(+aPR{rr>3S(PfxK4 zyWNhV)qiS5l*h-%!Qgjnl+Xpf=G@a5{dNo_Tpfz*0#yX$!Ii+$>rr` zy?_#gogba!{(J}|7uyRq>V+@>J7y&ACDY)C84U}k29%s8D+5Eu*w$gj1v zwYm9iUtb?Kjt#+TZ*Q-xtc(aRnE=?>__DUP27fL3eA{tWs1qLTb_XFQ1hD7vxcHHu z|7dAxiO3-k2;iI`eaI>KkOAaZuYZPWRWm3U8yh1WmzR;EqQ^*DB0*tcAFx+WJZ+kB*Lv z#(!EKj~_3<6f(WIxM*m2SzfNMtgM9Q+-^@ib%<X@j7q#z4G-et&-->V*0b zy_5l+w$PA7?B($A5Sk;#c>8=l1U@}IP1ZOZ4iI=e9+Fk8L3!d|sB3O+4jrex{hdf8 zyjD@hRsmJi)tTWD_GHHGc4N4?xtY!cG=Cfp2Mr^0v+L~aMBSpZqHwypy1WcNquF6w z0|Nu8tUfL-e(LCW54TJNncB4&2}DMTDzGfJhDmWO*L0Fe+>rG1dw*~eSEVya{Nz2r zC-WWz|M9IN%NMWgT^(@}__~qHFP)9cFZ2rM*B9*UUEhBJ(RKs7k1TMb00000Ne4wv IM6N<$g49^$S^xk5 diff --git a/recipes/icons/blesk.png b/recipes/icons/blesk.png index d7ccc57a7a757cee74d8911a3b420a1616dcd914..75c7646bf6ed29e3300b7d3d0f15783071681b7a 100644 GIT binary patch delta 238 zcmV7S0#ULkqJ93v-xp*Lb zz~N1jTx_L{LX|rl15(5@7+(+=g-w780{;cgVh{+uu7Us|QfF6pFGLgWs)KAqEwa59 zAvl4My`V<`eEGPGLp+4SE5K12t6ok{Vbyemm>6+6#M8pnwZ?%s+&sUZ8AtwpI o4et$zi&dQg;ML6w{F+z!0XKsNX7$5WFaQ7m07*qoM6N<$f&m0?>i_@% delta 242 zcmVo8C z*8St;{_*ku|Nmw4P|N@T0G>%iK~xwSy}?m#!!Qg)!DmO2nN+s+{--S<2B??)&kWzR z>(4kg{%N|Ko$8S56fe0+F;q0%)N-Br2%J)*DFvn}?V$9;bZaGON;@XBjIB>C12^R6 zjyq+p2ZiqDu~9tag5s&-z=f78ev~fUTg!k@!VN6}O$n#eA(Sw?Qll%Kn5J}I`Up&@ sm>cG$qT`0#n_}v2b}HSBW8|008y% z^~J@-wzjt3-rkp&m-+em1qB6GR#rbhKfk}f4h{~?%*^oc@T8=qh=_(|%U zVq#)WPEK-iav&feYin!h=;$daDV3F#BNs_F0000MbW%=J*F62`F8ZCj{D@r%2TdIx z@cWa?#sGhC?jpYc00EgvL_t(|UWL==j;k;b1>kX9LdlZuLhq?%cklbZ+YQDLCtT^C zKaoZsgM!Y$e#nA&Y=<4S+A@#$<9Gl**#K206d3Z~LEz%GvNFT&(gtWw)kmB;dVw#P zsYzz=H2?(9L0#NUuBFH|0n~%#yn9~6S)VX$qwjxO@0bT2@Li|lqT;WFM=hx9KLNsO z0AW*ARrAw{LJu+7gU@CoxRgSOM;S}b4|>IVvj76>1%9RW=#)Rt6}kDF9-V?y!g*Yq4@F=K zZY}5i1|YE5tUo|qR+49T-3aExp5PIj4{4en;zSN#9V1;BIsh)abiYZ_y}{9i*#~{) zDru61=eW#u<(777@2Sc0dNO4||Es-yILPLprEjA+qQf6?rCajPBfIL@95|-F)?}l`t^qoACi)i z{QUf~v$J2kcrkVARQCcO$Nt%#K$A>Lg8YJk4*LK9`hT0hFZ6!R1+jPk>4@cI2eF{Q zS62SR@5!f+!TA3fUYIOSV_;xhCd$_L06M^O>_`y zEo#-hqHV8zs9T89f+tErU+1(|+$4|CiS;}4J$|fUR+BtdF*B59VQbb#`!ff&E@TgT z^-Za2%9fvg;z{qh4+yucVf&Eu-~(%ldCR%8o<|=q=8Vx;`Xf=$=w!)H7Hj4c-J5h? zOLLxT&tq6qeC^9ZkIe7h3V)b6*8N*``y|hTx@DPF_R}quxbG&b?_0#)wRF$y$MQ{i zy;C3Y)c@fYcwW;fR$S|Id4<@HD;%LA(y38*nfM=uTy~7GwSL3Sa8{?OX`>g5|AV*R z_W$1Xp|(Eo>XOpAmRbgJ&zUDqx^6W!pCdHk-fXoZv5Q=vvZtO6eX=brSxvp&yLHsyiqw;e{E_#W3Hc7 U(Z)aZKR`*z)78&qol`;+0MpYt2mk;8 diff --git a/recipes/icons/bloomberg.png b/recipes/icons/bloomberg.png index 820a6767c75d3913d96814417825df7771f0232f..018fb06371db5fd03a3ca58deb310d5b860f3439 100644 GIT binary patch delta 596 zcmV-a0;~PQ1)K$tERmEGk)Q{Wpa+ql2a%u$k)Q{Wpay?Oa7_RI{}mM#{{H>|008y% z^~J@-wzjt3-rkp&m-+em1qB6GR#rbhKfk}f4h{~?%*^oc@T8=qh=_(|%U zVq#)WPEK-iav&feYin!h=;$daDV3F#BNs_F0000MbW%=J*F62`F8ZCj{D@r%2TdIx z@cWa?#sGhC?jpYc00EgvL_t(|UWL==j;k;b1>kX9LdlZuLhq?%cklbZ+YQDLCtT^C zKaoZsgM!Y$e#nA&Y=<4S+A@#$<9Gl**#K206d3Z~LEz%GvNFT&(gtWw)kmB;dVw#P zsYzz=H2?(9L0#NUuBFH|0n~%#yn9~6S)VX$qwjxO@0bT2@Li|lqT;WFM=hx9KLNsO z0AW*ARrAw{LJu+7gU@CoxRgSOM;S}b4|>IVvj76>1%9RW=#)Rt6}kDF9-V?y!g*Yq4@F=K zZY}5i1|YE5tUo|qR+49T-3aExp5PIj4{4en;zSN#9V1;BIsh)abiYZ_y}{9i*#~{) zDru61=eW#u<(777@2Sc0dNO4||Es-yILPLprEjA+qQf6?rCajPBfIL@95|-F)?}l`t^qoACi)i z{QUf~v$J2kcrkVARQCcO$Nt%#K$A>Lg8YJk4*LK9`hT0hFZ6!R1+jPk>4@cI2eF{Q zS62SR@5!f+!TA3fUYIOSV_;xhCd$_L06M^O>_`y zEo#-hqHV8zs9T89f+tErU+1(|+$4|CiS;}4J$|fUR+BtdF*B59VQbb#`!ff&E@TgT z^-Za2%9fvg;z{qh4+yucVf&Eu-~(%ldCR%8o<|=q=8Vx;`Xf=$=w!)H7Hj4c-J5h? zOLLxT&tq6qeC^9ZkIe7h3V)b6*8N*``y|hTx@DPF_R}quxbG&b?_0#)wRF$y$MQ{i zy;C3Y)c@fYcwW;fR$S|Id4<@HD;%LA(y38*nfM=uTy~7GwSL3Sa8{?OX`>g5|AV*R z_W$1Xp|(Eo>XOpAmRbgJ&zUDqx^6W!pCdHk-fXoZv5Q=vvZtO6eX=brSxvp&yLHsyiqw;e{E_#W3Hc7 U(Z)aZKR`*z)78&qol`;+0MpYt2mk;8 diff --git a/recipes/icons/bq_prime.png b/recipes/icons/bq_prime.png index 71fc66b2f5deae159bd8bba304e12f39aaff0954..9997313519e63494a52852947de380523b5db967 100644 GIT binary patch delta 1428 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 IM6N<$f~&pUVgLXD delta 893 zcmV-@1A_e72jmBkK7WoGAB`6sj20Y?795Qj9*!6uo;g0OR$Hb^PoqOfp+7{RK0}>2 zJ(nvmkQ^eyf`#+v=C)4)WN7*G^Z4-a>C@D~fP|YeIQsSVyL5M{PgJsE zX4kH;{Q3E;Ra%pg0U=1jgNMzUoy3QV$&r)c!NZLf9k5blxk=d|+l#Psku{8nUAOxSq|2AwHJL4kQJ2*PYP37$3>c;&zc8qxgcMnf5Q53y> ze7t@A`~w2Pw_z59pkR3jLMSvWJR&j*MaRSfslW;#E}jUAghZT_Odur{NCk3A8u^}{ z0a=`>B$TD13Lra&P;MSi6Zm{Jp@Kp{-Jz(MP)R9OfT?AFgvvD))Fvyne|{>f@MM1(HW{mtzF$cz_QTW7u!EDID~XU@Otxb$Ql0wURgzGXibm|?fQmkXqB5=+lHMIgm(A#B?34&JUSM|velDQ#o77A z<<<4g*K2(SC;Rs9UM|0Xczk*`yu8+^;7@SAqo)Tz54kstF?|0Wy??{|U;IA+_UCX@ T%VmmE00000NkvXXu0mjf3Ea)G diff --git a/recipes/icons/breakingmad.png b/recipes/icons/breakingmad.png index 2b050bd28725a01da92e7e4e57c8cf0cab60c40d..b43d2574279d1f0fc6588b28e68b1210ba1eb8a8 100644 GIT binary patch delta 198 zcmV;%06G850=)u|e}Ab-L_t(|UVYC|4#OY}M8PFa3B{%O{->>_R9XpQ@9#Vz13=W4 zje>gzeAOZ^-6|ll4+nv!Zi>`>2wbKy2z?JG{V|ww`-^T^oRjYsT&J)6z0Uj>&C*|# z&Kn+m7=zXMNd|PD$6#@u$AHfBmVl14wf5Npy6@2C1x}nxqd%=dy??L=^m7olpyTl4 z;-HteSrUwES{jgpARaV#h2((%HS-FeXv8YO5BFs<`We!+_W%F@07*qoM6N<$g081u ACIA2c delta 212 zcmV;_04x8!0?PuBe}A`0L_t(I%YBo}7Q-M2M8{x_)mrEOzqX5Lsu5&+uRJ^eKr$au z9IiM3AkgA2F#uxoLQ;^ooEdvg!r)S=3JZS-b>Xv6<>IeDpM=g^mhU2*-h1CaG1UDO zQE#$ym-HrYUi2nAABLQT#>2=*q47xS!o`R79zuKXV~ih7xJ|bVM>a~30>DM91R0hI zW+4E+kfxAud`MeJ?oDola#n5y3$OdI3VF}7a(YvUcDzFbKs*0CzW_h?WitBT>I>)q O0000|930Zp(!IUC-{0TI$Hx>D6wuJn=H}*TXlOY( zIpyW$?(Xilx3{EO48my-&(F^f2>?t?O!tNoib@G3B_#|70DnkGNON;@C@3hqySv85 z#>K_O!otGT)YMs7Sx!z)&u|Z6VPWj-?4no<^z`(tt*xS>qWAar>FMcladCr#gW%xc zP*70t@bHt9laY~;pP!%8)6;^2f@x`K7#J8JARuODW`u-_@&d#Q$rg?dJJUl%3 z`1p^HkBf_ojem`ez`(#uOH0hm%+}V{Ha0djH8tJc-5MGi5)u;G+1Yw}dhc4G!vFvP z0d!JMQvg8b*k%9#0VPR9K~#8Nz0y^0>@X09;rYf8T4X3QGcz-D&-wp9q>du38fTmB zRUh9t(W}TKhnD-&(ZWzV8tzR;U5yT;qu-KRYd2yxvVVJ@c%GOnPy0Ptv;3*tk+2Iv zgZQpGBBULtKp6~xRREu~6^tQ-+w#n;0<&t6pcuD4UV=iw)OC*;b0)B5;h|9Z_@pKr zo=*f7T{J<}go_?yDKG6wHRYhNvbt7WhqtjQ1$7KU%%v?N`EvjUws%N$40iYS4-UZ& znu$ZWB6B=se>9Gxa7>ykemLey{#2itfX~ULn2W|Gg6IoZa2#C60!cS54`8(J?jIhR z*})SD`!?A?h@Ak>QlwosUS8vYq__7>EfcBz^Z#p-WD{+Pf6jg(T`eg|w$UH^0@IHp Uws-e0=l}o!07*qoM6N<$f@|Y80{{R3 delta 663 zcmV;I0%-k+1*8R#8Gix*008_L?V|ty00wkYPE!B?00000Zl(v300009a7bBm000XU z000XU0RWnu7ytkQL`g(JR7l6|mCwszQ5eVX>ej7WN*0oZjgmd3`~e!0Y=n@q5F4Z<3q^j-eCK_7PkOz*bFaqCW`DljZm09(`8?-&o^uYD z{Vdk~isyY?_lsixRr$P+`}Rrr?iQc-aZi`Xa|+>!-#-%1^Wv)%?}-gz=HKu}_H2W} zP%f8StyZJaD3wZuLZMhJvOjv7&1R)ii9{lTAjo6T0*Ax#Lz;TMo?frVDya+xIG@j_ z)9HMp>-Ea)qJK>j(8h*-Rih}%0N8A{duI=a1A68tC zErhjNtug@e$woaM5A6W`>-Acz)$%WrbA`Ml4YkNZw13;}^7(u=n+0{h-xGMXS_J|D zn3f%%d%&$I`p8f;8hzxr{T4tBYRqOcm&+wP2ls&OcAHM8Q>j!YlNk&K#6XhKK*D9G z0hJ1mZj;>YcJB)e0K)tIei_G&38(b7Ox0>NJk{xRFy!@m830>}#bV@^RMSSI5$Chh z>A(tI34d>Pkjv!=Kv4>XLXrR!M61=h*=)#hmQVB~lgW5Io=7BWwc7Pn1hCuflJflK zHoe~HzKUR)HrH;qr5**j*EKX6jm&1VQ~-s@WHL=AljU-`SS*(B3H{^o7$p!6hpBQN xc{_y<_4unPaRKJg>;L$>&wnWHzy6SZ0!@?-R}fOIMAQHP002ovPDHLkV1i}1K6wBD diff --git a/recipes/icons/bsi_news.png b/recipes/icons/bsi_news.png index 63d27910283882272055812e2657d56d170eaf95..495f0fd0144e8ca7dc570fa9e1f22fff264c3942 100644 GIT binary patch delta 1170 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 delta 756 zcmVFew3?Ck9B z?(XmJ@9^;O@$vEU^78if_V@Sq`1ttw`T6?#`uzL+{r&y^{{H`e|Nlj{w-^8b0eVS9 zK~y-)<HT(v;O=7%ejlNU>7 zs!w0P_Fw}<+Pw&^N)XyBfR6_hXcouUUtt9segjJ-)99)LjUfv)iv!ulh+Q*}?$s4l zAL2OghGlm9&p((+CMn$ibp3F7?-rm?rGcVUIzQjZ1)<7+H=jM3j;yfnMLr12i@>;w zs^@}0g9{m4GU?Qh3>teR-Ri$XRC(M=?hi>C??RRXqLHJc1u4)R;M;*DlT0?1!S)f= zY8=OLeDLtZ-8Bbj92poa-`NByE6pTHn$6^M1}Q-I{oCcGc3$N?10VzMuUr=(O?^un my$9U?T3X*5q1FDcd+{5Dbx}YR0*M0v0000gB#}8Cf9t!z$wX7wcY@rAk>Q!4x)CJs%+UY;|Fr-VT_WRJ00008 zbW%=J03t+HnX29H?0j%w0001ANkl9~_NklQYWX?aKKMq~zQl k7-h^Ykn#p-EdKFOO8@`> diff --git a/recipes/icons/calgary_herald.png b/recipes/icons/calgary_herald.png index 6fef06de60bc7056a71b9a402dc735611f769e2d..6dc33252bb97d22ef7b68234f73eec16b5befc79 100644 GIT binary patch delta 1063 zcmV+?1larX2)hW7BYy%hP)t-sG-#q-jKE%v!CsBPZkWfWz}&6H-%o(HpuF0iy4h-# z#Y}pwgs9JmtI>IW1rN7*9n#icZ-N)DIahu3;o5*#Y$9>U^Tif27Uc=knz2^n<6*}?5M)s-RJQ;ZKmJp@;Pj!PJgv{pvz;B!fuztnYY)QxY(Sz*i?hK zZ<)tggtMQ!+Mv7IaGJ-Vz1mrcy`#R{q`%x*iN0EizJI5{+^50abDYU^oXM%d+Nr|b zKX9jZp31Jp-*=zNMRlr0cdtcwud~P8w94Unpv!xq%eKqnw#?(X&EvVvC4#Xfu_#Q+UU*O>&@Kj&fM(J-06a+ z&tHzg(SP3U(%-6L5@=t%Yh^o;&Z>Q$% z_37{S>+$#P^7!=k`VkZ8e*gdh#Ysd#RCr#6&r{RvKoEf8bcbu(``NZ_)zd9F5W#7x`XQpcyOcR6q=IOLs24b^LYk&U(CJkWDh`N9|4IWVkFmC}7NT`8w zALTr~+JNqp&;&FmA!-4dr-WM#_S0PJ4FZ%}sDXM50bN0$W|p@}TM+QqMK}SCx$MqT z!0syf!0$GhR-H0^G#yjG+)9KRJm+QWdC1HBP>h@E@#@&)>wib4e<)x@10sT<7J`dC zS$~V58hXBXguaK@&fcC+E2O)K+nu(<9B>zP=-HvIE0M2mKEU zf-df+<3&a%w?q<>9b49XVhF%?!=43!SYYX;fWTdpcp-mdb|%joHax#i0DV66y+jHC zQ-S;%D<-fhlP!;PYfLV5EKRm7N{ zl}!Y?xETPiTo5wb8YbFmt)XH_R#cP~*ekhb>cc)~e*dG#%4|h)AOO~JH*k*uVDG`* zSd8n_z5#&cIbUxq`o{-#7ozLk{%ZUsmUEn($sos8YoDsmk}Cd z=~Q_JjWCi)8_8-K$!;FafzU_)rH|&)$8uZ7@><89w~pnvjTf}a3!r9O zjurq(lzex`Dm;Kl`kua@hdtqvY1jGSKG6oWz#a;=0W}#u7SG|imJ|j2x}6gj&JJuo z(uG)#7E!9ihzB76jP1F-Q{}dU+i~NiW2N)SW^eeJtXs{n+9lU3l^^O$>Wsau}06WI*yAvz7Iz#_KgqrbsiukEP=FC`)^=_WrpJn7;yaOUC<=Xeh1gF`Lwuymad Z003WX8(7-Y@>i$;cXJo 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 Hu0mjfu`sX2 delta 1008 zcmV*cPLMKC{ceYPIoC!dMQ$XD^q_fQ-Lc~f-F>lEL4ImRDXXhRe~;4d@fdlFGXK3 zNMkQicrRg*FjRgpR)R2BgfLiyFj$2!VUaLmk}+6?FD{;kV$QoN-S7tN@s~nZInz=R!nY`P+DbC za+p$CVp3jfR(6|Mc$`^zoM3N&V1A%sf1#7y0U-~9nze(5ii4ZAhKP%al%kUi0%-}s zXH3P<+sMSglYRnP7XN80|7kM+Y%~A={r~^||NsBVb@D)y?gAWt<4Ht8R5;6HU|?m& z16UatxGM{gK}u*qKvWi-TglD9lAi;u(A=;Ls9=vO$mwZ{>w)Yp7gPo2(}03En{CsRlGOX} zyn3};s2Ej2M8y@Lilf0QnwpcZzIt`P&J4{BN!qJGso>nYrH4WEbipe01QQZ@5|)}? zy_m&UhLI+F(|v0WLgkOO@|R#qv>E=!eJgkE+p@S*ShYF{YpPDLl$Vke5!1~nh(}hy zBvOgCKhQfI z930Hd%!Y=BqN1WnNl8HQyu3V9Q&S-!q2%P`>gsA|XJYvNJ&X4C@4rmLZYjy>+j#cVq#(y6%`2y3H$f&&&|zUwQAMj!-wzR zzyJOF_l%4TDJdy?dwYF-{W){yq@|_p-Mg2Sl{G3VYSX4oDJdz77cW**Q`@y`*Uz6n zmo8m;^XARt$B!2j6sW7K>*(nC`}?=HwiXr^e)#ZV^5n^xnVB&$F~Y*aM~)mhdh}>) zY%C8CkBN!NxpU_}efsq4*RSU0W^;4%W5oaO&Of z`ESDo5uSQ+tbQ;Veod0e-q9T&0A zoFS!WZ0vE&z<4^)b3pL&`PY|~>E(~#eW}?wYv=J#(*3Mb(>|TKqqBa|jpm5HY!&P&p;bb?%dtnK0j-R<=XRvu`datljpa-;@dGmU{ujF| zRtAb^>E%EF@VWj}mh!V#rhDWRr{0p*Xx*tNAQB>Yx#qWDjOn|3hZ)5_X*n9*_w+fw z_&Bfs?|HWUe0wWyepuDIqJnXSj*pZ`-S>U34^|U7 zHnZRDVSLQYqCMVRUmx@C$o2Mpx4Y)%CsFOAKf^0l@jm}AzDn*Rk6IMJ2B`G$boFyt I=akR{0Q5(~EdT%j literal 1305 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKptm- zM`SUO_5fqIli7Aa278I8uPggAP9afFp~LYk2Y{NoJY5_^JdQ7&>gy2_DA9I*=E@Sm zlxa$;UP@ax3hG`yb!Li~lZeEQ4yKEBhHJ|XHMj^h^`u@ownoftHdCtyhvFOm+fP(< zm!xd)O<*&0?p{<-6&2I}y|4JVn^k)8{^{>)-&sDN_q#Ue(T9`k`FJyf0*{IWyD0K% zEl~RT<^03ot-nkc&k&HaIdNvjp;y6AKTW8Ky&S&r%%4RIiYDJ&Z89&TGn&<@{)>}n z-Zv{>YvEdvq87DT8Ovr`nLTt(nbdb-N|w-lySFCSi{_f~O*rqiNpsmmMz$TNod1NK z3;Cn<$u#YD@r!4|;vU`6yXH?nVD8mxw8|#=`>Nx9cTO1;bYJCM9(c-KY03?+={8~u zqKe(lB(rpEy8l&ri*eSZw;zRZHQryCe9FUp^WG+ql7V&=tZ^a%Ci=9diM{*wi;|f=3Q3`sopRdqK%EXa* zzZk>wAoefbjKy4$dmW9`oYUYJ z9IzzDcDpe5%g4sP-U8JX+(&W_beLJMew62X^JJ#XqUj8wPd4;@opRw^u$*AR1UBvG zd&_jzp5E%$;*l=yzVfkx>6PLcx@(+LCZ^uhzH8;&TC7}XZdcd0!Xz^PRPglJgxKe6 z)*X3oyK(2c(Dm8HbHgJXr!K#?@Nd>t+r6J>e6w3vw{^$%@B7b$Pr8{o^{qTGp{ka+ zMwFx^mZVxG7o`Fz1|tI_BV7YaT|C zl1NH?Gjmf*DhpB>z^>9S$V<216y}F!j&BH16@#Ivv4yFrrKyPt8}E#{KqdT0<^*R} zr7{>eIZat)_Xa2xh9ngl6A(u~tg%nS{ZEsTwfjSQ1gEr6U<(-adupbkBt`@WQ1(gNDR;OXk;vd$@? F2>@RM@BRP) diff --git a/recipes/icons/caravan_magazine_hindi.png b/recipes/icons/caravan_magazine_hindi.png index 1cb7f5edc3f64569a89e3bd47267d9ec9b31dd6d..e2c7e251de9e0b44736b4e7ed37f514acac6ab3a 100644 GIT binary patch literal 1206 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0VA2lo32_B-hYet4Wc>gCKhQfI z930Hd%!Y=BqN1WnNl8HQyu3V9Q&S-!q2%P`>gsA|XJYvNJ&X4C@4rmLZYjy>+j#cVq#(y6%`2y3H$f&&&|zUwQAMj!-wzR zzyJOF_l%4TDJdy?dwYF-{W){yq@|_p-Mg2Sl{G3VYSX4oDJdz77cW**Q`@y`*Uz6n zmo8m;^XARt$B!2j6sW7K>*(nC`}?=HwiXr^e)#ZV^5n^xnVB&$F~Y*aM~)mhdh}>) zY%C8CkBN!NxpU_}efsq4*RSU0W^;4%W5oaO&Of z`ESDo5uSQ+tbQ;Veod0e-q9T&0A zoFS!WZ0vE&z<4^)b3pL&`PY|~>E(~#eW}?wYv=J#(*3Mb(>|TKqqBa|jpm5HY!&P&p;bb?%dtnK0j-R<=XRvu`datljpa-;@dGmU{ujF| zRtAb^>E%EF@VWj}mh!V#rhDWRr{0p*Xx*tNAQB>Yx#qWDjOn|3hZ)5_X*n9*_w+fw z_&Bfs?|HWUe0wWyepuDIqJnXSj*pZ`-S>U34^|U7 zHnZRDVSLQYqCMVRUmx@C$o2Mpx4Y)%CsFOAKf^0l@jm}AzDn*Rk6IMJ2B`G$boFyt I=akR{0Q5(~EdT%j literal 1305 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE0wix1Z>k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKptm- zM`SUO_5fqIli7Aa278I8uPggAP9afF5iOgAUxAvsJY5_^JdQ7&>gy2_DA9I*=E@Sm zlxa$;UP@ax3hG`yb!Li~lZeEQ4yKEBhHJ|XHMj^h^`u@ownoftHdCtyhvFOm+fP(< zm!xd)O<*&0?p{<-6&2I}y|4JVn^k)8{^{>)-&sDN_q#Ue(T9`k`FJyf0*{IWyD0K% zEl~RT<^03ot-nkc&k&HaIdNvjp;y6AKTW8Ky&S&r%%4RIiYDJ&Z89&TGn&<@{)>}n z-Zv{>YvEdvq87DT8Ovr`nLTt(nbdb-N|w-lySFCSi{_f~O*rqiNpsmmMz$TNod1NK z3;Cn<$u#YD@r!4|;vU`6yXH?nVD8mxw8|#=`>Nx9cTO1;bYJCM9(c-KY03?+={8~u zqKe(lB(rpEy8l&ri*eSZw;zRZHQryCe9FUp^WG+ql7V&=tZ^a%Ci=9diM{*wi;|f=3Q3`sopRdqK%EXa* zzZk>wAoefbjKy4$dmW9`oYUYJ z9IzzDcDpe5%g4sP-U8JX+(&W_beLJMew62X^JJ#XqUj8wPd4;@opRw^u$*AR1UBvG zd&_jzp5E%$;*l=yzVfkx>6PLcx@(+LCZ^uhzH8;&TC7}XZdcd0!Xz^PRPglJgxKe6 z)*X3oyK(2c(Dm8HbHgJXr!K#?@Nd>t+r6J>e6w3vw{^$%@B7b$Pr8{o^{qTGp{ka+ zMwFx^mZVxG7o`Fz1|tI_BV7YaT|C zl1NH?Gjmf*DhpB>z^>9S$V<216y}F!j&BH16@#Ivv4yFrrKyPt8}E#{KqdT0<^*R} zr7{>eIZat)_Xa2xh9ngl6A(u~tg%nS{ZEsTwfjSQ1gEr6U<(-adupbkBt`@WQ1(gNDR;OXk;vd$@? F2>{AJ@D>07 diff --git a/recipes/icons/catholic_news_agency.png b/recipes/icons/catholic_news_agency.png index 109e11fa3856f303881827e6385fc9b5ffc77339..bc1ee6ca6676373de373bad7b18b6b3063a0b93e 100644 GIT binary patch delta 443 zcmV;s0Yv`y1KtCWEq_Uj|NsB*$72{ljGV5{|Nr;*=Y%0ij44rswa3=I=l90X-p1$u z&e!MO=kMqD|D4zN|M%yuzSfkc#-+B-@9+09QH0*_|L4bRwcq!>&)z9XgzxwF@Av=r z*OaB#_xJCu_xJbz=e_^$z5nmV|M%Aa_uj_W=OIait>5>qFMp*@00001bW%=J06^y0 zW&i*I3rR#lRCr!}ljn|vFboFcLfKxg_g>!r0q?VRV~2olelmkC1(Hg(I|aLKng)&y zMl`9J<;0$Ol-xoSkz|mps@FRLGjUk8^!pA0kf2?9@e2=f;Ds*o0EYGvJ)N*E;k^h% zWUl`Oc);pDbbkls95ezN1$hL0W`8d^aGJ+NP4+_aLq_=bMiX%yLEaL~5x57VU}FLB zBkU)F4FYkh;DDfUKQ2BR3%K+%0=6j5L8au1jA5{rdIdA}=3lS@C|4t18C!e+f~5pE zL}Z>UI3lP=m+64Aia)P54+Yb=F17#w002ovPDHLkV1oQ9=y?DD delta 468 zcmV;_0W1FA1NQ@vEq_6b7)gvFNrWLuj44TkDN%$mQG}GH#+5>x$JVvq_r1^Fz32DF&)&w@=f>y%&e!MO=kMO{|L4bR=lB2b$7ApB_wV=j@Av=r z*Od3?g!k{Q_xJbz=e_^$z5nmV|M%Aa_ul{a=l}or|NsAj;(vQx00001bW%=J06^y0 zW&i*IBuPX;R5;6hliOOtFc5@y#tIZf6a0`*BW`huXb8^aU)sY>74Mab4zp=_mCG~+{O8|}_rW3#!v`2t5fN#D6ds*s(%SOtL#hQji&GdWM@X|e*q;TI;%CcKY`HujO48uH8m$7sWopv!?}PIzJQdy zwT7;H&R|8f;eQikXN{rZ4z!YwYPg~pyMx$pu4(!QbX%@zmTPnZqUU&2(;uLBkGz&; zR6@0PRl_Wuw1COVHMFW3Wa&yXH+7E&x3F!iJcGvYD?K=%byjZ;?m*`lYqL}rX{)C@ z;^PB64K9K;^;OwAOhEqJAOU_qM_WZFzyX~d#W69|2I>hDB_KH$)e322%2KS0gQW)_*>z W8o+#UH4VZ50000+ApX_y7O@I!DRv00001bW%=J06^y0W&i*Ihev3r<>?HdH4bHjlS}LTCD7U+17OmBq z7i2P=D`KR%s;b;I*Kwx+04y#d%uJ^E@r*+590Xtp*ho1*c_kD)+vh zga~34wNB{Ly!U?67O*G|jI0RHin zjEPRb`WerPgID!FQjto@^5xRA3uUviCgfaGh=Nb$j|rZp2&H8EF7JADJ_-Un<-bHa zI+Ad}Z#3MbXr1f(9t@BV1nA!f_;*#u`Jl^(qh-zh+|kj|2PnEzSbKlz^~pHzpZv@5 z@sfOh@PAr<9iit#?qCisEX3v`^0ml@c<%}pZ>Sf3fJ6I2y#AHc5Ru)C&;3)Ji9SRw z4nCtu*RfpG{W`WR8%;eYT?Dy0WF^iH6myaNF&(Lh*D*NY%>Zx{0KAs%OFYFFF~%cZONhxAD@RE^RdFf zVJ}vsT4=qidLM$K>kRVitjy~2Xiqjy^9BY zdsHg2|2%(L?{lcgSh%+Dp^9(=hN}1(%whoIiTLxo0P#lv{^xmC#mJcO`7QW(XQFUx zGIh0sZ1&*w-y{{r&+a^~_eqNUG_7y6?|*@ke#W3N;;xqzBiqJa0@GK9E$1{dG`Umk zolJul43wU%XimdAR($dig{bQ_NJR&+Hj~!Cy1FV&9S8d#z!0U> zMT6FPeDF9v-4gFzvBu)q0=UeYG{wcdwzk}|DKSZ zhW5AYJ!l_#h6X^l0?5sL>V?vX>_%ovHB!hO()dRQ4h^6WbPNP9)q{MR#@s#^+jj!g zFJqnKs^{@Dz=a#3FwR7$?Q@jG6#ILgs>n^l$cVBKE0jx?G2QV+=C+!64u9&}T!F9c z!t3pn$FqP_^k!f}wvj7xdeVFADCoAc5CcW=3DOgl?wEh_Z&UdLqvOxl96eULm&KLW3i7%15O)(wPhTd!Mf8CyYIU_NBq|DBwjn>L`Z6 z{v!G<2ZxfBqdFp?rdTC17=LNno4Xv%Z8q_4Gq2U|nYgPK1h9Xh=+D6L zv!6;Nn=q1h3{40XZ+BfYN?v4^#qNpvA~QsGm0^lEb0}+3g+&u4)qgD-G7|eEb2NZH zL;3=`Xd%!#8@?WFNIdP<^?`dK&4DJ< zKM6Z_$N=^>IATaD=W6=;c28rR#Da|6l=cm#X#o3~QY*yv3xB-y0Z{)`d+(sl9n3$G ztSb)QJ1@2`R5{BuY>d7}{trOETCW?1@N%~_ZJ@}O)+L0(`RLH~jPZ(KlSYn5Xb0T1 z$*9dPWDM^O}p6!vYb8;}(A zJI0pEJ=|5O%WW|g7De|J?oPB_j$5K1WsyfxQN_k%POujcEM{En8M99C(_W~&JIB%D z=3kTWLkurBNjeuvN5X4iYSF?h#HE86 zDs=Q(jfskavnZr{798c&66S1I{Z0}pgr3GM3U|ZiRB78H`qJ2Zi9ZlTh6)*#fPbQ{SPt5o2^Hc5{Jc2{K7sb53x=A^ zo&6JTH@0Ho->}OUejBq}W?d#K4(8C5EM~pngx!^?U(J0VZ}!yRrHvP2RR7^zg1%`^ zOKFnfhzC3`d@!V;YJWE`8#3(#;NO%c5A%JrHTO)Xpf->`w}i@dk|4M{4oloc^6?vI7)|^4|PSXW@T(YZ)$qFTbej~(nEChu>r9e*PFFfX4nI1vdxnrA=_(y!SJFYwQKf_EI66>^t_F;hLUNEu zEz?Tb2}6quFKJ*-x7E;Da!$uFjRG^La~$;iEDN!S^Ue z6v32?tS0XxJK_wIlo}nXS5s`K_=~sxl~9`n6Ez=nt8UI6r0*`<8Lpu+c=(=9oe{Gn z9aKIG2GG~R0fX*>c%^np6p$RPgDbWq{N%va(QK_Z&n;Co`uX&`101PrO;1~H=6|T* ztL09(lxZqolR2eENf=ev;~q{N>}36xeD|xZj6HAY}8v6sT7Ul z5$cUoL)`zKa6h5pfRU{_t0HCNSbt6ShP~wdEI-9@aDqG52E>hbiLT3zCKCavbrS!%9ZcdpJc`ege{l$0b zIbjW-cpWgCsS`Ns!i=&)Ry4BP35&!DxbUZFWMov+xt$R7D;gOYn?X9h|5g9M+!T3x zy&{pV6D}rsrwyD8%~988ly{pIjm(@7ER=Hrs%%9gBO}u)84^f6MI%G=V|Q*+Br-Iw j7u}#pBtda1h|#@u@&A_wZvO*m8phB0v*;SLI}8>9#Z~7J delta 3052 zcmVQqi&~OGT-rRFob|0THMQEkY}8twJl6ElcD5 z?t7E`Gnq6q$xMD|?mg$7&SaLh-+k{c-@Wf_BH5F0Wy-4EGnXxUhM8^$mhX zLXx(s3rZlf^iDw|F(v5lvN_7DK+s4^36VYtN@$ME3_)XpCFq>}QaT0Opn%a9A}B$u z0fLZ}z>Nsx)qf1O)hp=vxs)@UMXUBQn~j4e$m(n0BQgc`?&4~(=3O%`FwYUh8X%c`c3w4p z!JDRKf>=XNLiWRM_p8f;$jnJuGnahBuPJ}%iz#3iXMfg()&m8xhMa`lG`Gvm4+#9z zixCr)K`Ilo_i!_9c*yAkcqC;6GYw=S|L=qwZS2cVJ-PX@c%3(p<>CH*V+s&cBhK z&r27~L4SCiOwZ??V>Zi!NPUrCvpS^rE=2N1`NGf1X)mY8zvmpn>J9X>f3Y&H+MSBb z*9GYclCrG_qhwia=sDs7$fYJL<~t~vv-+1%q##~FNC6)t0M`?Mx1)SXyZBnf_<>bF z@lj9|SpAK3q)%vy)Eu9zT}8ekjl_(0`7ByzyMLVzMfhkb)N2TQ+idkAFmZx(on-1M z$G-v~H-%~q%nq&p#ZJO~1o}CdNPT)**9k8cwCX6HK|Ka!9sRjEo|Mt^tLVghIp{p@ zt%6h?rG0VxGb!mhlt9lD$ZLn}yzVEHbW4#%#rp)UI!Y!oZ^4en&gO7*$f0g#-;mFK z27f04q?}j6rGi>L%}p`v9Jcf$d=jj_9?BtSyEGk{bNp+6MR-b|JsyrZ6J|dS2j6V7 z8PuPrb+^AB9T_v1bv#QDu0kj)el?;P0`V;R^QQ#jWd!_h)5wY`VxpgKr03gXiCc}K ztL{OuhaTV0p)h^-j8ofRCdiLbItDtP&VS)&q*kV)+?0&fO{gUxe1+L^N>jZfD@8wo zFnD@r_T_@+7^G$4*Pa{L+5L_7b7bh5sZiS*$xQ9@l* zDKkzt`(bypy?4nPlDB^p6S0_?d7O6r9dObvl2WsRd@cpjPf#3(V5h?VSMH)<{(lDv z8uFrH^@0!*QG-9W6&DNl)(+*BngTQ5EN}FSC|Tu z10T@jPRot3{lmU$Y(Wdy5oz~LuK}S7k~ZiCVFiL zJ>H7;cqDLw-Z+HGHgH9*pY(|e9duifh@~U-RnQZ~>X_g8cMF1b5ID%%2|3OgdO^w! zl|#w)TYZ^V=vnXse3`_Wxk02`nFgoGDQ&awgEFTV+FvE;#{@Nk<}UL_H1Lu+YRQD= zFmC$In`1%Bp*tehO|h8UV1Kx1uO4+Yw_1<8&A3*(XX383AprFYL4O=VJO9t1rSTY) zbYRVNQbaqgGw39&u0-g8$_6#gZ87v-Dg2K?T+IO)FPeZAvgOAsaZKKiTo1EEQPu2B zsNOFvk~Ij(JD4Ui6(4m~Gqk+$EQ_7P`-@aB*j1P*+{{5+i*#7jAb*_QM!6|$`>i^e zfWDaP0%b&<>$KK)I|@Q|1y(Ci)$~4aLfZt%D&&YW=RBL6vnV=&b45d!V?~FBqfao; zgNCorbm0AP)cAa*@Aao+yC6J@S@FqMUzIiB?R@#K+U1nkU(;u5STKWog(Aq*Ebs-G z`mazu$cm`pGcK1<_kWJo5WqF+NS`D}X%s=n^`6GW>?J+eeLEV6a4Q4Ohl{)|q9=-%!*IbJ(&9M9Fy?;k>2SETb=gj=J*9wYb z5b*yX>`);ius4$;dO340rLQ0MG{)IT$iPiW-C&GDV84cQg?~tY8<#!^)W4PAJ7{u_ z`BxluMdnjeBmHJ+XBmSG($^IK6QHM6s)mtyd5nuTp+1jGmwA0?+8(L@4@V=Vl!NOrGTj)Tkm=06qf${G9TrfIpgMw}gosiiCupJWbRngFpzP`r z?D-u8_!7_)V1G_=`J>vu1HGguXA0Woz(o3J6wya(+T}}uuFLw`u`UpJ@CoDjj)t#L z*$G#IZ#CKEIw2ao$P_hMwR2!*+$`zCiMTF*~g^CcvCAQh$Acu)!Y8i0j}3pHcp34YoO zI`6JcMt%RL+phx&XBs>0X?6n~Iw2z3z?gr?{@a-~b|KRp>WW)N}Y4|Y}R zKP|R6GJgq&W@4_cagF*Im3!r#fuXOa*|qY76%x(PL(6 zBRxBavPZ&H$mq2);VufyqTuS8nd`bG)a5Sw9l=yEJ*_wR8#d>2fhW>g0{H`RQ$ep^ z4JAx^Q-Rq-7yyMKPcRj@Bj98B^_8zL4bGSN3x7a_so+rwl+A&8Bh%2MF$t z!+$(?QQaH{$B%2blP0=?1BTDRC>=yT=qo}t%VYa-Q`6JkT*TQUPsLFb`6#^#t@AFA zFyBI6z>~#n>%s6vlluA;A|Ib%T>?_i^_325haVVTJtLFC;VKlO7ePTzQQ0G*I4Dq( zt||nSLVS=&In#=^6POlfVsb4oImNpOoPRM-_7JsRf&n0W*^i6#YB^@rRk-qWHE!v^ zd=y6^_!>pOAlM)QtBL!_jyO{*%llA$H^o*of8o}@5NZ=3+|3u|rnotG`V1fK^p#N= zaK0C!&j?YH3@V=hA<$Ql0(#s9@nY^0OCT{?$EsUF__4#5(QNe>&n=Y{`my_+1AnJb zwydVjH*=)ntL{p;0bwd0gE>Wy5;Ll<#@!cl>?pS`!DKO^u1jy=QrR2@xyF}J;g z;-GL;<{33m&_K*BETMugc$ho3F$1ss4jgw>pxCdL$}hTT*ePK;qo?@1cpDV0FYb49 z@|4Ki5yr|dzH6KkmSV@tfZ0TqfLRx602#8Nk=#j`#Y)I$wV;uZpqtK}1YlDTG!haN zgJgXFlR(@Qd3~oKk*pG~19>L}oCL*D*J=QFn+1);lwfAEUx@T^KLGk^fYXyloD6R`)U~gUc|1|=){{v|n#?MxL*?6-s3>E?VanddT diff --git a/recipes/icons/cesky_rozhlas_6.png b/recipes/icons/cesky_rozhlas_6.png index 316fdac18c867bd2cadd24282db6cb0bc9a5fad5..00c62b6454f936558e71c16d13a725b3e353eb71 100644 GIT binary patch delta 1163 zcmV;61a$l239t!}HGc$UNklP(@p62-+`plIXZ(ZF_yXm#@fogmW&fMg)t~bI>!i8U1v(-KCnDsnElWAi z<iruAFfe$V~udV0o)4}_tMM+d?i5hm4uPPxlNKv*uKW-r+sdvrwx zoD3b0W%ZY9?D=M&q9Lb)2qb_200&Tza{}{+6GpCJV2>r3jOpF@qWo=G-LIBSr@%W> z@Bumm2}Ixol&ak0Qd+$BIA)IufLft$TfXV?I&}^~0)GHOq7w-^03slJWIF0*Gwg3h zo)TR5^w%%AdUf9af%TANXn`OCIRx%R5JEvXu69{UTIzbPWm}s#93BzWb#MFK)x3ZC zZuq0s8)70ZD3Ifh9B+hZj+|(yvCK8uZ9UggQhoQyD$EL>f|KZ0wLaTzwAm#xEh_>E zHT>hdi+{kATuoF{JEd+?yQnRXe>9sXPN!3EZ@=nZmolX=I+FqyDaZ;$I7JW{oX-0; zoyPh2quqT!9q;%EW}gc<7=AdU!?(MwR~x!;Jl;b90TIx3L39zQ1tiolKeUJM&UgQu zrs?Uj175tizP`Cxtyf5)#7{};Qb}E2+)v|VCVx_q+B7}*;b2d8QLW_KFX`qm-=<|H z2^;{S06`?WM40(Fn;qVP@yps2VCdQIZ`#%G$NI|i;K{PgU4&30ggh@|w!F3BW=hZh zef;rT8xH_fo=%YI`^~(2?wgm>>KWZ!<6`c(@P2z-K1D9M^yP5<`7PzCp zJxe4Z`J(B3@#R$Wl#`qZcv>(`7!OQ;&+9u_ks^Zw5(tqAfJAO&Yj~rY`{e$MZ*r}b z{GJ}jIFFFQGvM#RaW|WuJejrc2d_7lu78~sNgxzR0E&ztTz~+wV-4v0AXz@$Pt*9A zVAtpEFxcv=DZaLDLu%kixPQbhS`)uw_9r6Ykl ztV51Kf{Zx?4h~iz#>uEY^rauJs$Ir${CR=EnQyxPNfv ztj)Tl#LV)=1n0An#=6lt5fA`KkjR;BP+FTVm*(bQ6pZ6!=Fe`q zzFtM#tYB#DnC5TwrpNvM!gsgf?2%kg+J doA|HsA1@0o?KE&Y_y7O^07*qoL56OcQ#I^*(rWh_cyS-D>Rpo~vBk`)d1VQR?x>2XA z&Z$!a{|`~1{LjF{<4=8Gwtx7`=F2x}xlV3wMhxH>#DJ-(PJd&S{gv&1k?V^(Q14YE zpJvPVE2T%a>wf#q+tWY)wcDO2Z`cGgfJ1Oson$;*AFnT_tM`1(h85oP=61&;fktSLX%n|ZKZ!r{D$tFZj}(^y&?|?e+bZ&aTK}!O_qPJhaE21 zQ9Xd64R1aQX;z~{>fU`|RA zL~r&8X=Yens-Oz>a`+^eRhqHey#P=ay=>PfwL;`ki z$B-t~{ZK`Wn9W+PH~J|1(^ip6KgF}%cpj(D%x-52!yF7e5+W|5!zi-5ygykF-SnYd zpP#bY{;{2WHU4866>YR!`lVLAYvzjpqY(Fp=D%=ErCXP1FeL+3%u!8dM8ixctB0db`;uyk zrqusvWfn+%Pua5ALYLw!b}$3O?(8%|Lw}{AEfs;i)cRzL!c%5fNGYWrNKaM$F%oD3 zR8>(_Me1i)X6gHlmNSW`N=LL?fzHdoJYnE!v$;Z2qyc^JL`nOrT8Eba)_q!SHqw1n z{c8zJVgqF+d4@5ChB-K-<%XV4#V(_OhKDQWYemcf)U%@!{@11Fw&hUlT#cu)KaqLBFD(c z^lSx(gAj+ZT%E6Tj>TpU=AR;)33o8u@3ylUL{ehq}9{4kpQq00000NkvXXu0mjfQ{7fK diff --git a/recipes/icons/cherta.png b/recipes/icons/cherta.png index 72044c1019a5c58af2a12dc1fd4a3aa94720e252..301f59723051c32b574a719ac739aa1b50f23dfd 100644 GIT binary patch delta 483 zcmV<90UZ9n1)l|wB#}8;Gynhp`1tt#{{9se6-GuzWo2dP=;-O`>FVn0?d|RE?(X;Z z_bV$a`T6<${QNF1E;ThZk;N)EGBPqJCnq~QJL~J~L_|a!931cO@9^;O_4W1k_V!3f zNK8yjP*70%`ubH>Ra#nFA|fIIj(~KNCIKpcPf0{URCr#6lh?AcFc?KY1wlAMh|&|V zcTerTm%qIK`AQgOe38GwBzg7fl0Z48xyj2BP2AyWY!&4^Q=K|HhOv9glgxuB$2ry4a zgq+m~7;K&{2pNp4f&(E>vjrjFmv>PdO>X`dLI45fT)z-TpM44;1Sb>Xt4-30f(@AF zOwW(MHjjT#PD=s?M9VBc=1V#+2_|S}bQ?j?L3Z~Et^{E16A;^@zgzHL_|hLMo36VOiWBrP*7D>Ra#nFWo2cNuqrp`=;-O`>FVn0>+9?7 z?d|UF?(gsK@bK{U_4W4l_V@Sq`1ttw`T6?#`uzO-{{H^||Nl@A=`)iN0V;o5Nkl2iWF6os#fC{ze2du*$$GcKb61goA(XX+&Ks$?Gl!PH=CY0ew770WY}(5eD=>1%Ma;HHQOV zBH&1%k_B@ls>yK#H#<$S?RmGGK5$aOVV2rqk2KF+^ix@{ZFD zOz(_h97G?aE)Yx5u_{(eVV`thj%+n3C8Js1_mD%a5#jpa4E1TJP_G+Kr)D>r}v=qF{NnkZ4x&V6-3)F%;Edq%j5Ha zVb%BZb7nROIi#PP+TD75hFe~0h>Me@rmSmi zc)-EMOHN#}w7Fr772p5>0I*3!K~#8N&C1&6L-B-byO^ei%wb31PEVLthed*OHkA;U0B#LToBp4AKVQw2!L54T_d0000< KMNUMnLSTXlL3>vK delta 266 zcmV+l0rmc<0<8j&EPp2~J~}>4OHN!{UTSM?czS$>h>Me#n4qPmtg^JZz`@1M&e7W1 z-RI}&^Yiro|Nk*E_~ZZp0JTX(K~y-)&62@xg)j^RHz5fIOg#VpwLPG!5`?_o+P%)m zURjp@X_li%bwtvdrDQ0EJ*+V|BoFaj~pR{1PP599*!CxjvF9$004IY0NlI0iV_&&#m0jK z355s^g$NFX3J-@35{eTTiW3=&6&ia00DJ%de*ge~00WWZEq`PH0NuR2W&i+?9wU(; zB#s##hz}Hc004*%6ptMtfdB=MAtR6-BjU!#=FHCK&d%u3(dg0A?AO@u-{6D=3ZzI& ziV_!d004CW0BHaKga!L1|nVFfHY5!Y@;WPz7esUZD{qs2?S59IHX#fCg003W}aixnD-78{Kj9*!9vjyM`0jvFA3BPNd=A&(s) zj~yb9AtR6-Baj{=ksu_IASIC^C6y~LoHsk2IX<91LZVJkqe)DpNK27)Et3!d8-Lup zyxqLK;>E_|#>eK&&gRa}=+V*W(bDYK*ze!q_3G;P@9+5X^85Gq{QCO;|NqyKFV_G7 z0M1E7K~xwS1;K?v0#Ou2(QD5LHYlhV2;y_b?Xw$9z;49O^Z&o#S_Tl0HB&JFU;ycc z{(3wB7~n(cz0dnO2NhPn}5CEaA3gj?Hs`rL<1QI Z`~!!5HiM+-OG^L%002ovPDHLkV1mN$#e)C< diff --git a/recipes/icons/china_times.png b/recipes/icons/china_times.png index 6f64696f8b0dcd3a3b00adaf3e3836038c777e35..db890308a85a019a804ab70f2ba25f8456ebbdcc 100644 GIT binary patch delta 457 zcmV;)0XF`M1%w5VEPwz1|HUUD!yp;MAsWOb9mFLb#U>xa9~jV2Ny8o&%|$=J6AQ#5 z8^$Oh{`>mQNJ7p@L&PH+&{0e3v99a0vF^US{`&gEA{)dZ8q7aA%tAcPLp{wzKEM(S z!yy^I4+qanMbA!1z!na`7Z1cD8o?M5!5I<6BOJse9l{zC!U7r-kX!x$TKX_QB1`p9>NZ( z3X@L(Jb%eEEy_1B{`~y^{{G52Gl|Qjn*aa+&PhZ;RCr!pkHeZoK^Q~-R9&KN+hg0d zZQFkOnd#e${`#zvvr34W2*F699H`RutILnPDWnq5DA94)>{-9~C#1faM5sYs$Jdk^ zZi_M6AI8f&%J^Q-h6LK10PfE{UAUXATY*=k!+I8MTRV>CaZ;o)Y$?QD7>TEhRrK_> zcb|S*h$o+$)H(MxzHe?Zon|CB9xr?V@brdfej#lcB00000NkvXXu0mjfW6jvM delta 463 zcmV;=0WkiA1&RfbEPuWa2fq>uz!3_-5(~f+3&0i*z!wj}7!ko45yBc0!Wt988xz7D z6v7-8!W|XD9TvhK7sDPG!yg#KAQ{6U8N(qO!y+5RAsWOY8pI+S#3LKTBOJse9mFLa z#3dfZB_72lAH^pi#wa1iD<#J)CC4o%$1fm={QUp^{{R2~&Z(DU0002gNklL5zXVt*RKYoS(P1x6;hgpr50ho1vVW zpbkucM0cGMQGpUsf)Y}K#>U2tEi@{u79qw%*@OHNPC)@o|u=MnV6jbMR@>5dH_dyn3tUZNqm@=oHNHOS^xk5 zQFKyHQvfO2k|+?_wMqg39)3jSwFsKx$RH@R+u6)s$JXBo2q^Uk&|RUDrMj#fqmh7R z-3=-N!-ZlD-{iI4De}(b4kgdz)-D86qM=P1rlzKqOn(JC&-iix005pzL_t(|UY*TF zasxpSMA4KqVrFJ$W->EXbr0$O2l05}3+OF>L`LE&UQfFd=()qGXwV0XKdMR;)5;*|a1lzHY+(Msd@E?YMVWgU4Cni&C zGMlrp#5`(EwAt?Vf)7W@r!#PKtLgfka;M1)3rR#lR5*>*Qq4=#aTxzR&wekPxiJR4xmZRP ztWppr4GPweMKp_EL?rU$J&1@-U3Rduc=S*g50O|MOqz!pg?|nzsLe2ggw&ifgDsZs z?f3isz8%WB4*dasPR|1mpEn+0$Ah+pcs$NC)r04k*9-kWisjvYuY3B*jmU#n@6sd8 z1SXP-s;#>>*&%_u9s(* z*Q0PSASEJ(0&u*e^2NIA{N+#kc5y7@4R9#O~GC9}WGd%vWP^q;7 zfUY>Qx}szhrr_kAvllYyxlhatBK#ua1tCNy5rK%Z?H$2mY#7HkPMv5w=XGjn*b-IK z@)ygMZer6CnyRYuuQY61x`hy(QUb)7-{bRjhr{8b0szkj`@fut$GfO>=+auDbj86W zTlad$nVX9qkNY8*|GWoK(46}7K%iwR6bk**#SX@A=xEJ=vV=Sv00000NkvXXu0mjf D#={=y diff --git a/recipes/icons/chosun.png b/recipes/icons/chosun.png index 41356b5b3955bcc9e59027a22e25500d9040c52f..00b7b242f5fd4254d809a15f8ed3fa0e6fc9406d 100644 GIT binary patch delta 294 zcmV+>0onfH0^I_TEPwz1|Ia`^+_ABvGBW=B{Qv&`#~~l)$H(WNpX#fs>#wfv+S<2Z zVDR1D^32SoI5^QqMbl4C&Xbdv8XBG`D2xFC*k)wes;Z6!1>Jgh-hF)DwYA^5x#7IL zFM_D?D+8T`H`y^f66T@{{8*TH8agQ zIJthK4FCWDb4f%&RCr#U%*74^F$@4vPm<*=Gcz;(|EF~~J*4WX>RfC+$(HF?zk*73 zrKf{g_lzF}4tlU}&D}}$s2ox-6APu)b stz*Hgv0av9d1v|u9Phemmz(lmFRX(S$SS5mv;Y7A07*qoM6N<$g2V}zcK`qY delta 298 zcmV+_0oDH90^tIXEPspv0geR)kqQcv5D=Ie8lEU9qcSq3I5@XpV7X*u#TggH8yUwS z9>*ac$b^K-Ei245GtD_T&XbeRKt9n&Mbl4C*k)wes;b#wfv+S>5l-SW)L^X29B>FM_D?D+8T`H`y^fByXZ{{8*`{{H{} z|F3>YV*mgEcS%G+R2Wxd;6?)sL<)!~YpV$%1bCzj%uE&e5CYO>W;)`$2rXjzrh1~t zW+<7N$%B}Df+7My0R{$DGcztA#j0RpEXx1_>Skt=EDUUNW@cubU;#-WOGQBoND4E7 w1=uyrz`&57nE?XWAZ2uojWi`V7#YX|00Kx4lF^rj$N&HU07*qoM6N<$f|ALNH2?qr diff --git a/recipes/icons/chronicle_higher_ed.png b/recipes/icons/chronicle_higher_ed.png index 930ce9647b3fa2f59a723663b6f16ac09b10a93d..6c09bc7e62762e7441549621f79794a1e99f5f64 100644 GIT binary patch delta 939 zcmV;c162H&3H%68e2 zPyj1U05enoI9U4o`~Wpo04z=bElvP4Q~v(`13Fp&G*tjCPXH!K{QUg@F;e~h{sK5y z1Ugy#{r#V_za~|1+2Q65LSX&<{)?ot05w(%JzM}TP!U980e?4FM{kD2(bxbiO$0ky z`~3U>C`g`H$h|}HT0VqlpM`Q#zSOF+Y9Zzek zy~s9Tc?vmMyvx(Z)7lb2U;F(10x?nmCrO5(ury$KC|7Z_!_DUG?;uKN1v^`gr?qQ~ zoen!%>hJM9W`BMdN@rDfjrjTcYloTvIavifTdKLm12$GFSaaLs=j-qC7DQr7a*0}e zky3Sx20U8;CP)A=dTd>B`Pls?F7F+PeV&y$c2o9X84-W{jKYowl$DYl}C3Z#{PM)aevbsPt;W zf2-DQfVS<74t1Q;&SsacKsO@;Fi-a$K+j%WCnQM-(fjo6hyDVf`0@}KG}su@{16Nc zA2HIG22^V_#$aq@e8vZv zVGia}k|jo;m!V64=EIEzmTI>jxqJ~)SZrwaz!EIQvcCgStnk_bp_Qx9h$f-QTq0J5 zdaSQj?EpfH(TEKjJ&#iI3VGH!6rIO~Qht$tB?(cq$ferLW#aa(@T=Dl z$MqZK5~uv8bnEt=yUwhslKkHN2MFNdqsLF4J}Zdgxtw!7!58%9tJiPd#sT16)%y=0 zbG0N$i1SZ{pTB(l_Weif=Py?a&PlLB_M3(g^DvUIzg|sCFvmaFZ<<6|2muJnJ^%m! N07*qoM6N;tV1lT8#J>Ol delta 967 zcmV;&133Kr2$%_wEPnwwSOPg%0XJ6zIavfcS^zaw05eknFHit5Q2;bl0y$X&I#~cM zP5>!O2t8aCM`S2haVl7ICRK10Mq~{_U<5c=04+}dGgJULSO6?dK52oIs<_kL;`;mi z|NsB{{QcSC=Ci}ij;FP2i=7xsX8}1`12$FwEKW*tiNny?{eS)b+T!Pnq_Sm(nq7dD zWrmq(iJXzCx5(7n{Qdn$Z-xOhR0cIwowUE?>g`H$hyX560VqlUC`$k;OaUlM9Zzek zy~s9Tc?vmMyvx(Z)7lb2Uj#Z?0x?nmCrO5(ury$KpR>RI{{H6d?;uKN1v^^hJM9W`BMFD@|2*jrjTcYloTuCP@W6TdKLm05w(%JzU%4=j-qC7DQqIE>K#0 zky3Sx`TF~^!OH+JQuq1#Yl)lzI9Jfx;8b{y5kz9`@$@)jeD3n}{r&wnV0!%g{YY>047M2@d1s0WNJ)G00060NklTZR5EK#?5fu~X$ljJr=Yod=kDZTvhI}R;D@wpHWg*zH z+46GoIbl4w;)g4LHxC#t`AELPP*5OWC|`tML9u*^22KS*rDgKv@)c-~L{?E*1@vaM z6fQ?Hu=42wO{l45W5l6=MZ8YFzF5A2uMtJK2`SVhnt=h)(%Ob>1WS7dQowh1`F6|q zMD_N;!%@6{f*rz9fngKndRhmTYqJtm1I%vl4Ox!5_Z ptt;6WS*PM2&b=%wQyGcR?f~3hZKCfkUNZmy002ovPDHLkV1kG7zq$Yb diff --git a/recipes/icons/cicero.png b/recipes/icons/cicero.png index 80ad3aafac23774c86ca720445cfff8884457ab0..ed0088658eaaec5424b7302220dbf31e50e6f4a3 100644 GIT binary patch delta 20 bcmaFMbDV2}^2QcvR!#;_S3j3^P6+ delta 314 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 delta 1107 zcmV-Z1g!hZ207Iv<5TWO{8tgiOjH^O#F`Ju>9 zoyKghQ$9KN)>juxub-~mdocFthvT1p>`c^`%f0#a#r_vZ*YDkb<-+-spM2zJQX6+4 zXaY8!z0{~KM}HxJnQluHtwz18&3cj5v4l6>Y0Y-K<9-srZ_TYDkQ*+Q&z{{{Ub*w* zrP24^eg5rt>I;kYxrOmFXBMwsd-%)b#24psM~<%Dz3;4LCYm$771KN72d9!5D{?>V z6to6W#h4IiI%gqU&NiJj;_^xdaeVOz0Em+~M6ss$!hc9=aIo8`FWKwNjWVoldM099u_5P+i6OthcA>gzi+Q3kL&H4|>uiAak`=qyM8Ii|dG z*`f#D6n~p!)wXWgMn(ic)f$hs4QPKTlLP&kQb{07*KaywS!)ddA`lRu00KEkf;gwt zyp0}5JrI22nl&1viF2&>%SS}A!$*j?Q?0B`&&2ujZgw%b3=rS}1*MG0ViX3%Ltap~ zw&F~JIn8SS4a8-EIC0j-n!MW|fSTeKY}6ECLVrk*8m6 zEW=w%OU<>lWPktQ*voOhq`i278RmeDPo`2Z$=X>GJ z_Le3d3lfSbL7ZX*AggqW*xzN8xv{u3H-CBAPb6Nw@MX4C`s@1jmD^LK6aZGL`p0a0 zeZ4PVIDC2nBI^BgEwO_^&6?|DL+YoA|j>%?Vs=y1*Jed?D;2xWQy1syD~N1 zYc|^(8;>R@uYCJmV__*#Eclu9>eS5Kua|Wy)!p2z%>KFd`yGmZTWE(e2HYMth)hUC z1Yp`ek7WmWI7dotpUlkItFlX`h~5J*ut$BHk{u-7KiMQADD_18Un+5V55CC<2LBg> ZzX6%Q1!RlFg9ZQq002ovPDHLkV1iR$6iomC diff --git a/recipes/icons/ciperchile.png b/recipes/icons/ciperchile.png index cecc160ace6de1e3076cfe6c400b736567e77be4..213d69e9b60baed46f523b32285b6243ae49ff4a 100644 GIT binary patch delta 1130 zcmV-w1eN>R3egIXEPn+XCn>i2)B6u3y3`sk|uG;GvnfI^)iU zxyvk$r<0nah?J#`VzjnJz0I=6E3w8a%eXBIHb)vUCkrz~8ZI;oGD8V2J{Tho>jVvAm12m1MogERzZWMFzFYEWX7olT`sH58BO%#n6|r z$Sm!nO~bw|lZyc`9?{#Cv&bu;wkyh)Spf_Z;?s;rUMWReCWNftX%vOw!%yKzXIX9AJ+^Jz zwr$(CZCk%-a&=8~WzT#4tbMg17ys>G9kre{l}4O84NBvo>_r6 z`@jXF9>H5%#PQt~nBNCpqKpMf$7rG}EAU(&crV1Z4!IXs{&-`51y=Qe*J^c6!I~F- z`hDub3f$TcVipGcwcYExq^b{mXuK~$e{OX;?bc)kKAQo1W27k%|InW5v_~uOejg~* zd9Zomrx`LA-0vKAUyeO!=DCC?kpsj(Yx+v?FFzl2trMW1-pJ}$G zSERQmmbXdmjyCg*Fz1gvnYTKiwkyh)S%kM@j;E8FqllEHjbgO6MZL{TxYRVh>o>jV zny)Cbm1Huu;*$yiMFyH;E^};DlT`sH4;*ZXM_wsKTP7G_c}Q6wlZyc`9w0S1A2T*9 zEGG*#M;b9F3o}F-E;I`=LkTWE7$YT<*#RVD2OcU19V!PMDF+=W1|2B{9V!PLCk7lT z1{^2_94Q4HC36iTIrM591Sqt*HXBtC*dOF<(l zT8M~9AQB*00K2n0GxufgJp#M_!mqpM_vFcQ*;UKCt_nKE)ia+@@+udtrz~O9v-6RE zHTvZ(JiH<=DOBRVjGB@6)@)?_tSuN@=**Js@QCTf4Vn)3( z`0HQ!>nA)u2~1U*kL*WP6%c2*uD}{H1^U`GNB2I$a}$7?NR!won+6iTjj;o>rY_*h zZtvs8?9y+6Yz9@<2=Bs^}3h%-t`65QfBxCTjW3-l}f^M0H|L~ zr%kx^e(r}j0dI=db^=TMXy`iq`gURv9UNfX{U`zX3mR69;Mh zfeo@m0$Sc>*b3>WUh@uqL1ih;+lM>WZg=wK2K8|i0=wi@nc;Exo0Bwnu$P8^GfY_9 zdY$zBzy0blK8*C}e$%gzb@j}u&42mPhS#qiz>PDMB`q>jLPrf(Xtl0q>c`LTIsj8; zA_7;Rq+D2GioOxkCr$X#&qq(#C^A`zmYYSORkvV?bV#BB z4wYFp1o(pijY$+GjGDA;@GAm;EHI|fFf%2;45XETRjnlvJJGFK_4ejM% z^RWNyVe{u3{K)AoEq4g)oX!xcoL7Y@?!A2T?ac!adf&!vYX#|99u&LKf*ttKp9+Yh lZwf3mLqKo)md7Yt_8rNHU*|6;3YGu>002ovPDHLkV1nGQ^8f$< diff --git a/recipes/icons/cityavisen_dk.png b/recipes/icons/cityavisen_dk.png index 05f107580a440e778ae50dddd11075d6fd1379df..c531c3367664939381070bac54b85e49bfb4ebe8 100644 GIT binary patch delta 234 zcmVM�r>*QkDu>>!={w1_*OkNph?qdt(3-6zJ7FpnYOUzu*Cz zPigi+AqgH*$p^;-s;Uq4@jM+H@T3*qfove{Ks8WrFomQUKv&Rl1IxGoE8s1#O}sz} kmKLQ7fVReOKFlAy0lf$XIWmX@yZ`_I07*qoM6N<$f`)Wy-2eap delta 273 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 delta 1984 zcmV;x2S50?56usdBYyw^b5ch_0Itp)=>Px+heNhv zNNd|u!2K zfE57&i89{j`Srt#C?aZ4KP2}#{}0c&pZj-T>%JZV9ciK?UvyM8D!=ibXX88aCjGAfOGL+Y(SSsNgo9{6bgk-oI zTp}>+F0fHC(0)N2rXMXw20J4TUolYgIbHpt49{O&C=eu=)~sXRD!}}0A;yE;!iO7s z733Iu7174G5~CoH10ZX1FdZ@v`HdBW<_9t1m^;4+n}5%*BW5GJt{}c(qyuA3C2jo+ zY3FkXW1stoPJJ05r&WN#-}rvq`ETFS#d00p7TeLyVLhE(T`>)d$J|DX^qYW;u%i4y zEqdzdc+d*jGm#;OgXkQZNcT_|`h?ihS7SpT_r>&dvZ1Zd{a0CH;VS|9P2WD$WV|!) zeC|NkwSU(1a9N76^Jk2WTufbE1#VGExW{MkY=jq|XUCnB=&9z@@2zORcPjm3mNMX= z9cICf^!?77cDwHw+|eAMtj-K?T(TDOyihB0 zP^<314INMqfXj^-V~6Q%Z#sYBOvkAk zdDq$o37f{A{A2*s!bCVa5&hYX=+AG4W0RpE9)PIgG~~ow`o^xNU#KG=gxF%P+eACv z)7AiXw*-jbbo+S$$S6x>^b%y?B4qwd;vSwe09@Jz$+J*%_o3$Qhol*(XB`1ZD!+sT z&woWc7a<;B@~-b}`e=5(E+tR51UO8|I}TSEIoA$rO$-5XnH);JPQ|?}D9uJavkoO= zIXwCSDsxaWtx+;_;t0LtmTd%?q2V#qmK&u15$eiNi&IfDtWeHwK*>J}wPoze zJ&r_<<(CH*(oMG)(Woz1%$5K$&9YR!w#4I965QMYhsK~LOhZXqf^zQ!G*lY^lz)0q z8la&D^gp5$rJ!W2Kuwwjhd+U9-uUJw(II*%9rk{OdC(l*^Y+3hsLqg*+E!dJVr#(J zvieFxz*Xj=KV^q{BM>UCLUkV0J^+2)b1N%#Q2P)b=fk7xsJA20XFH)jy4mQ22F4`0 z(_yzQANa4M$MzuF_&s@67g`44uzzK-{<$~*V*seSX#mL4p_X5Us$6*bz_4gDfKsT= zL#?=qdOH&3j05VU>j0FLK4#>mVA|R5pr7M5dN_q+q-g?ZnT95F4^EO^Hf#tx1$iED zXcTI^1xkt~`hs|a$(vkI-*l}nOhQTh8Z~Jq#Qz2Iz1gxS9T{VbOt9wt`G1?}VjG17 zHtQi;7NTLUr$m11RO%Rb5|j#*!ec1sHld^~MJ+l3b!CP)s4au1WiJF_3QC4G>P1(S zk`z!L;TCop>AeWkDNb~>J&uGnc2U~bl;RPt88TvH!lK&8mCZBiQ5NJKK)s)i{$8@d z=QRZ=#VM%8sgQR7%ClZd!+$!=)+Qs4_mF)U0JLv7jHddWm(Kz&%g}#B+@5D2h4;ZT z>`%N(>77g{yNGhy4kc{~R9-g}qKsvzSvF91k&2RY94Y@d;l)|lUp~T=6HauCK4;jq zuG!+PAIE>)a#CjOIwgawV+TqnE2O&((tQ>ClCM24#mB!uy%q7i6n~$02HofFb$75=ej3|1{ph4@M6 zvz^eNcY(NZPXmcKhiHGlb)LO;~XTc}mJaMv5C2EZmX5t-~l=Q+M~v2mfN$0pw2?nhgn zif0YUo14;cZV58ZEkP!FCd$Z72{L-~_tJCW{-J{wgkE@9Ud={L63$WQn!?QO$WR#`mT(}#Pu8|i}zqU%a=}5+%cIHK-Yh28Lo*l8ys;> zJ`66D4}yxC>CFIk@$!4SV=~-6QTi{7m4T~{NVi#`7DHAL*008Qgl=I5U?Z3a_XlT}J zYu#XA-BVNBN=n*AMc6+-@VdIxC@9Y&BI}!*%@Pv)|NrQYj_8Pp<%ERu+S>Ng((caA z{{8*v#>U=QSk+ou{rUO&`T56CQ0BC>%0)%*udm{)tiv@m%YQ5^;-{zVqN4iq^S~b; z#uym;>+8Y|4f*Be*pH9%gwR6q}NJHoXKnY z0005PNkl%Fe=PZEEX(je_fz-S zHVk7A!?tm`fq#q9-qZ(%aWu_t15GQ4q9qH5g&(-<8ixJAmwSLs!18qWc6TG0Vr8@OXAh=l8KKKwV6%+NMov&@nIZ}v z<}8sj86*(;WnLulul*=~$YFp~q8gW{*R$l>azfv`7=HoP(Tj&=_9QzP=~SIE0LUCH ztyJf{44GA6uok=Ck~cCT9wp-%LiRzZhkVA;LF!RNX_4vE%p9G*THW3@Ak;_nG$Q(y z5*AeQjS)-ohio5^YL;T1D1Frfo3Nx$i47Bh z+L4Wf)dAhSl(bq>^GdcjS~FMKFmgx~mZFg`Un-3JYcVdqAbVHHgn9*N?J(jv9l@W@ zr^P|tTA1|@e-fg`^&=-+rM5kIs(bET>)bEud>~J5yYqa0hJUS}egjRdEuaG{2@U`N N002ovPDHLkV1g3EVAKEr delta 690 zcmV;j0!{tL1=0nOEPwp}|Ni~`{rUO&`T6?u^Y`=f`|IoV>gxIB<@4j?_uk&~+S>Ng z((caA^UBKT#>VZxzwo-c@3ge$w6yQ9ui~t%;-{zVqN3oWr0biT-kY21l$7C=l<1C* z*pH9sh=}EcgyegB<8*Y_d3oVzXx3|M-C$teSXk9sTHRAq)_+t~+e%8;N=nC2P})UB z%0)%kKR?wrHp4YF%PcI^C@9Y&BG4Tjz#kvR7#PhG62c7)%LoX`0RhAS0KNbKm)3W1 z0005aNklvF0v6hPsiTI&^|-bz!1q=3>?BnnZ?PTv2m8q}dqAK{!X{_G*& zB$EO3KlfLwKYzAuyNB9idw6>@w8!0g!M6L3(`uj-EFo z>il$f9a2FVk-#6i`G%y-MW~-J8gbG_U0wv@beAonfUZJH$p~|Q?6IVW92VJb%3o0T zScZ+9fy8E4kizx3N9H8=keh{m;FVv!PWdC-IZy(67k{qb21MW}aWgiwbBc=>2BGyJ zCs5!@uPrlL34~FFeG$4m3Lsy}^Ykbh=_y9l&OjqS6=fjiyxLZb0+2}UANKSi+34l% zZd*k&0EPh|Gg7R$f?x9WI$B$(r3@0ojfB*f4|1s(fxXr2sdi9$qL%x!^wS49lmZtj zey-(V7lYkS_NVL2_>W74yhhSmkNI*1Cb&NWRD%?;u5-pAzitJhmhi7EHYv5A9 zAv8KeAzHl+wHUlCzW6rzPx#07*qoM6N<$f;V?<761SM diff --git a/recipes/icons/columbusdispatch.png b/recipes/icons/columbusdispatch.png index 9b7d8c6fd424ec1c784dd456d773e1227c096346..295746a5ba44ea6ac2c222bbe1ea121d90a40dd2 100644 GIT binary patch delta 593 zcmV-X0z;hhU+GU>mq~e zAcE^2f$N0A@OQZF6>s1ZY~BxI%WA0T3tGwtXXd@x__x>jf`636C4lE5edio|=Qe}j z!O7v->ij#8>CNN%oWbiAa^(?l<-ytSi^A?hmFqr}?r*R2U8V35cI0ll@epg(6>i>% z$ng+q(+_6Qjn4BmQ=K-C?u5GZ235=kTh$0*+zeyZ4QSmBYTz)6>n@1vEs5(9Uf&E& z+6zb721D8hK9ec|7Jq!T^P{cJD0|;1eB&g9>!GjFMPsNegXJViqZxne+UN9}+xFk> z`V)NX@bdTn|NqnG`=849%i{Vk<799E0004WQchCSkW8A%Nxrzkt?lye{#wcJrrl zHw6gP*ls-VCJ3TS0^cQim`Oa^p+3gaOFJ6zT_C@fw$zk|rMlO#?M*ZRPS?zW(Y@V2J~_YtdoI6jn1`o8 f?-ZvDz>M`5c0C|We5b7L00000NkvXXu0mjf_SHT^ delta 603 zcmV-h0;K(f1&#%fEPo_Pqcu~VMPsN#WUo_jvUG;Fa)rQxl*FU0&7rT+vAo#9$>G7- z@4eahx7Ycx)Ay~>_@dPEo7?uE%J-bX>y^Rli^A@S$nlNN^Mu0igu3*6wexql?QXj9 zZ?E%esOWI0=3=GpU8V3@sO?vw@I#gBK9lY{kLf&%z;qit8?j>n(}v zCx+`Lh3h1Q>mq~eAcE^2f$JH6>lS_M6MX9ocUBnJqz6)B)235=kTh$0*+zeyZ4QSmBYTyTF<_Bct2U_A0Uf&E& z+6zb721D8hK9ec|7JnRa-XM42D0|;1eB&j6=OTUQ9DC<1gXK1Z;L+CS+UNAy>ipmB z`sVTc@bdTn|NqnG`_1F}%i{XGZs?r=0004WQchCZ zCRR=cIACX0RM2#AVNHW@(^wSwfSDx=P=kv_qJgrYs$o(_UXrmhHv^lKsa`UFwvw7|Mt-7^6$=B4gR#DeV5X9i zY8FtyJcfbU(KIDaj8{ubGd<7Hz&x6P&DA#6K~_jhGgU}5F3yLAAEz)p7!@bkA$2nq|2;(~e3 p-OE29B!UGb5A`1h3ky4h3jiu-Dels<*h>Ha002ovPDHLkV1fcpFOC2J diff --git a/recipes/icons/computerworld_dk.png b/recipes/icons/computerworld_dk.png index 4b439afcec1c7c80b0c3030a4e9b2658afff73c4..a715bc0327795c04141750ba332a779606d4307c 100644 GIT binary patch delta 488 zcmV+dOLQwqdn!qF zD@u4PN_i?wdM!zKElPGaR(~o@btz1AIaYv0V~Iv&ibrIIN(*9yQ*e@0ag!)Zb&<9? z11L>*k@7r$DM@%KN_ExQmAOLX${_ww`j{Qv(aOm-_w zb^iYU{{Q_aOnCqO|0+y!|Nr|eNOvhqc|Jir5C8xG=t)FDRCrzu%rk;SQ4j#Y*3;M6 zT-&y7+t&Tlov@Mu4z!L{71vNz5ETQrIfSrI)p$UEOca76LXP^0T*D2(cgVF*R&{pO zh=a=zaHD8W(sCR0`1?3xmSA4aPG$GV?;ACDEoTEPp3Vb|*}DC`)xHO?N3tcqvMCDN1)KOL!?uc_~bEDNJ@MNp&hp zc`8eEDob`MOL!_waw<%DDolDRO?4_wcPmMID@t}NN_Zod^Y!xc_ww`j{Qv*`{{Q~| z{{H^|{{Q{{|NsB}|Ns8~|Nr~{|NZ~}|9(z_UH||9^hrcPR2Ufr!DmClKnwuTIH{&b zB3fERYEi+BdvDx(3+}2^&-3#Czxp1qkC{nJFqGDcXD>EC+E%U;C`|Q^}ouPN%mxU zS7hozkAgJ?s+1Sx@#5>L3esUQ3ko#shXk;=l=nP6EDRZ^T&|@0000-`@lZuOrNk~P6gM-@K-?g>2 zQBY8TeR*bPWjj1QZEbGs?(q>68XX}iBOxJQUR)U)A|fR$za0~wprEX)tXo%6$Hm5U zYhh6w7^xNxv=$A}(bIAl6NN!JYf3`(`TUv|4y&G%VLv*jG=D2Zub+Yd00H|+L_t(| zUZs&&lG`v0KmpcX)SJ7v!cG8v}0Bs>r~UQj(Oi1O*ydW%q*qC4HtA9Ds+g3 z4xOW$9ZCROtRQHjfErDdpzu-(h4A)oV81_6s`r;G=70KM7Q1c2ZGT<^e0xIbXaQ!o zdJFv@Hy+qa7hho}TVc_8LBr+fiVx1zFl7VFhVqtpk+EFF5E z7-t$d=Og1hrEzSV!Bfb)q2+8C5*rDi{4lgMwslUtG7OB6x@*R^0+0P|Z96RsII>mqW@sM5lUi|T*&(beRT$`#r~~yq+>M8T zhlAHmO+SUFvJ3sI@T4QgZWri?G669huG8)FJWUgvUmTgL@KN~|egi4DK7I?VWE21Z002ov JPDHLkV1k}cJ~aRU delta 836 zcmV-K1H1gY1=|LYB!2{FK}|sb0I`n?{9y$E0004VQb$4nuFf3k00009a7bBm000XT z000XT0n*)m`~Uy|dr(YNMe+6d>Tu~etgoA_H+~2jewoy<}fPHx*At7{YVSi?3Wjj1QsTK~J77lG~ zZoeH9pP-!* z>WwNas8h6dnskg#t*c(K>$(^_8+)_}i%h~~lSQNzi+}EUuiKXQJcQmFih)B<2Ui+* zT+#2S(IZ;gbV+{kr~q8$7(o*m^wLBDGB4Bz=iuevz|lYISFwL5*;4Lhb^Ffz{+*`C zPhOei02aT#F{P;nyZGI~p*V)U%XtNe)bU+U?&YH5kTjz!f54V)%F_h`bwy9XJ1niI z3({|xCV%BIgvKDDj{Oa2SGVABk_llcWf`zX*t)sQW`eTYsF-{7k_4iR<-xKHp*_LAbu&?yuuG z!s~-^e2n+^k5vuC^>n+B^J#|h;Pl0OyMNGMfN{9axA)UL&v1HXE`qnhkKhykzc6?s z>Es0f0038dR9JLUVRs;Ka&Km7Y-J#Hd2nSQX>fF7004NLjlk@Iz;zcJ zp}Pu0XQ4l5cZr3eGgjCU4JzFxQa}e1k?T0ngXiNoX_#{M&70ks6+X5OQrW{jvBp36 z*#9HKkpH`1&jw;EB9zp6quZ;-+{{FLVpq1Gz^Ho0kw1_se}BNo`5?O-*_IwwkdBdH zB3^C=M41L@R^Sx@9?3&Qgtf((;EcyJCdiD5og&dCBrSj%&DF7pR_fFQdSswLGxtQq ztrAUu8?qs3GE9qPD^D{(C&gpxB_mQDNgjCIWgGi0D==g|kfU#)ZdhP~^_MuMInaOw zmax7F{gG`tV1G%n#iuT+HL|4(UK+QcvCTt?R8E0yqZJd5hkl;if~~nHe07C7o!v|N z){O#RJ*-Vpp=%Sp#RY2oxDeh@q%PR}9V0}R0jWIw06H(>fcyw0O~K}>Z6tz_+kLQk vgF_B#aP9#(T*JDk&!DKyDmTmC_zu4nzw{~`Lrlm3015yANkvXXu0mjf5%{{8 delta 407 zcmV;I0cigE1AqjObANbAL_t(2Q)N;+Oaf65e2AeYG4@u}m>6mk6MCDgjljGE`E(Z> zp}Pu0XQ2i<5(`5|te_(rRJu!~fDR-g*KvTry6-@vVY{81-Pt#DLa5q0&|*h>;fnC{pR1^8RaqJSb?0@@e$3;J;DBE(r>LG38 zkbzLPu?Di#kMIOu);;PphhKR0<%nR9MpFjJ3<_V0=n9gSK!Fz4i3sE>#7nwlAW2i_ zRD_CY8UrONGi^MGh;TDOQy@bY)2!#H(AtuhGu>gC@_kHT!1F+yWS%0Pt9tk)3tAl)j%F+QZ4HcoY%tM;2xCNFCuRcMJZcKESmniV#Ja|QlI_mg04EgzNkd>e>b-a^Q1}O_AO~B$-%e7FM z-0FeFYb%pg40e?rwAV4XL4EuUY9q?cu>buI-xa^~DiH42tcw5u002ovPDHLkV1o4( BzF+_V diff --git a/recipes/icons/corriere_della_sera_en.png b/recipes/icons/corriere_della_sera_en.png index eb31bb08b0aeb40bda289f5c93680736b605bdfd..90df7f4d163762a49fe4740e15dc64de550208d9 100644 GIT binary patch delta 261 zcmV+g0s8)#0+s@h8Gi!+001a04^sdD0BKN6R7L;){{R302n+}S1OUChy%-!A@9XdX z`~ON!N>Wi$&d<(cVPi2cF_e~+IXF4@_xI=H=W}s$*3;JB+ukWGDW#^Rt*Whzj*PXo zwfFP)t*))_@9(v;wSNf}9()l10Db^yNklheO+*h;sD|>V+s*uiVs{=x*{LG2!QF@Z!HWjK%`;jX$d@00000 LNkvXXu0mjfTC8^1 delta 265 zcmV+k0rviu0+|Ak8Gix*005AYXf^-<0N_bPK~#9!WB891{QiIL{JHn9-vgP?A3xu_ zZ!Zvi|Nb4wMb|K8+7t#fzyhVQXh=>>X21zRD$zBdso>z@fU`AqG|@FIUA7ciHN*pk z28IxBabYpK21^S|xN0w7FNh$J0TH=&`5Hn4rhD$)z6TMMkbjW?It+*q${^JD&)+fq zhY1iG)~;F$R}C~3p9V}1^z`*W1h;P68Wb1=L~GZrMc2T!z*vG_Wi$&d<(cVPi2cF_e~+IXF4@_xI=H=W}s$*3;JB+ukWGDW#^Rt*Whzj*PXo zwfFP)t*))_@9(v;wSNf}9()l10Db^yNklheO+*h;sD|>V+s*uiVs{=x*{LG2!QF@Z!HWjK%`;jX$d@00000 LNkvXXu0mjfTC8^1 delta 265 zcmV+k0rviu0+|Ak8Gix*005AYXf^-<0N_bPK~#9!WB891{QiIL{JHn9-vgP?A3xu_ zZ!Zvi|Nb4wMb|K8+7t#fzyhVQXh=>>X21zRD$zBdso>z@fU`AqG|@FIUA7ciHN*pk z28IxBabYpK21^S|xN0w7FNh$J0TH=&`5Hn4rhD$)z6TMMkbjW?It+*q${^JD&)+fq zhY1iG)~;F$R}C~3p9V}1^z`*W1h;P68Wb1=L~GZrMc2T!z*vG_QP_)@$&SjukU?_@r#t_HAUqnH|kPf?r(bSZh7;X zq2@G1^{KG-x4ibZz4*n-`qbCxLs9HtZ2RKm>|kyB)Y#}|m@EJQ0BlJ_K~xx(osT&J zgFq0&teL%+dx7NtFHJ$05?7Ksi@)hrh#nsfll&HwlmJqkRZ%+1`UC))CC#9vK2Ebx z6_Feux))Ls9BcU+Pj{>|ku{U~TPgdG2p|?|p~yikVBlvB1VW>mEf0FoE&peZ~~ z@)6XK44|1srw4V#FrbI)ki#ug)iUr3=D;kGrTUr9GkDRgMR26Xl~VLODvuXmejgwb UUlSYSQvd(}07*qoM6N<$g8N5rg8%>k diff --git a/recipes/icons/cosmopolitan.png b/recipes/icons/cosmopolitan.png index 9bb8a68a2fb08786a064685a515136227c257a77..27d192b35192bcbf22f2aead4bdb80deccc0ac42 100644 GIT binary patch delta 402 zcmV;D0d4++1pWh%EII%G|GGm;z*AYnTw>6Fgx;m7;H$3i*V*vc+4SM!{Q3F)`}_X< z{r>&^{{H@=0SVuztHN4eqy-L z{G|#KzD-oVPE@4|6Wybx@YdJ)@bLQb^8EPt+@PbbAt~Obso9sC+n%EE*4Vc>K)F9g zs1X_S-{8)8eb0MG&2%ruH*ie;u|8Kx9obQrd9fy wQ2czU3KS;w9140KHU0JTDZm0aS^dGL2k=1?CYQiln*aa+07*qoM6N<$f)=#lP5=M^ delta 407 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 diff --git a/recipes/icons/cotidianul.png b/recipes/icons/cotidianul.png index df652d9400e33c05f009fc292703311dff6df5dc..d3cfcf6451c8dbdbf95064232fa474858cac6d92 100644 GIT binary patch delta 364 zcmV-y0h9j!0{jAy8Gi!+001a04^sdD0G3cpR7LIp0Pz3-?EnDm008d*0P6q%@&Ew; z|NrU$0RR5~{{R2~|NZ^k-2VRj?G6n4xxxPa{_PPx#p-DtRR2Ufr!3AOjF$@I3o{?mG z;lfN8X8ix>QUw?{gK!9lHY_&-7Qo056vzP^#e zd6`f9d8ryAE@h^EnOt2{L;~C_oeC8#0Wr4ymH;42K;2DqiUblcx}W;P5ah57AcM+$7v1t?A|yFWma+;QCLY#g7hv-90|zgeCANu{S3oyF;+ z5uGJ744Ux|m)Jxu7q1MpDaU8RV*3GF!8`4K{+OLGkF)XI+c({Ealf|h(HUiumsjb*B&{N zK_NLbuP=*}vuOF@(a-H2*2d+zk3Lxo&2Qf(7(VC_8o;h8%YU<@&`M=_an(NDQ~62O zk58^B>ZG$;tWdj3d+*XA5MC2iLvC&BePjRt N002ovPDHLkV1k6(q)7k( diff --git a/recipes/icons/counterpunch.png b/recipes/icons/counterpunch.png index 6f95b98e536a5289ebdbf549ab273ee832b1c15e..dd576b049ae4627c4625f99815b0a6f5f9612f2f 100644 GIT binary patch delta 879 zcmV-#1CadT2d4*+BYy$HP)t-s00030|NqVa0RH~|(jqI;E;Y{p0RR5}D=RA%6&3yc z{W3B#(>OiTJ3#Kn$N&BPq@<)GA|lWL0MHd2(=|Ko?d|;c_Z%D?)H^`o;oiF48M9 zZ*OnXGBqwPE|XFLCx3Q!c64-f>gnn1zQ63hz~PaTtE;QxmzS)ptUWzF-G71o_V?V| z+u)Ryy1KgV$H?x=%HZGM?7+e7z{2do!_+c3CnqP=Jwnt(NaB~7h~o1E0t)YV8# z)k;p)Oj6ZOQ18mi@9*!tyu9||;`ip}_xASq=;-NPbr&wmvaKtMps%F6!x{LdmH z%>V#>eSOo@)6z9N?83wB!^7>v#O=ey?Zn0H#mCb%IPS&8?#0HwzP`V|zeGetPEJlx zP*Cyk@bdEV^YZfa+S>Hn+<$+6fPjF4f`XNmm6w;7p`oGs^z{4t`=g_yS65fpU}D>H zblY=v{`~y@{eS(?930RxGSL7W(G?sW$<+n`00E#$L_t(|UWL7T8a=yeQ$|>9Ma&0r3eh`>@fvd7d31`1HB)wW z-3p7*95_b}kg|&sG{&GpJOH+fJz_8qc(N`B?f_K`hVlXGf(TijL;-FCj!NFvnQi8T=AUL_lxJv~4`Ktx1DPEJlxP*7J_S88f%ZftE{Z7x_`R5yu7@=zP`V|zreu2 z%F4>i007MZ0L}ma&d$!y007Sw70)6f&;S6?6&%nU9MCc{(EuFL6&%qbC(;}z(jqI; zCoa+}GSV(J(lRyDGC0yTJJU5d(=|KOI6c!lK-1IH)G|2KJ3!PuLexY^)YR0~NKDmA zPSs3O)lN{?U@>Cba&+5sb=%t7+}qpTe}UkXmEhms;gOT!m6zd`m*L^z;+L1=mzd(0 znd6$A>FMmgzwE!j?7+e7z{2do!|cMs?83wB!^7>v#O=ey?Zn0H#mDXK z?e4|J?#0IL#>ei*$nMI@@5;;X@9**O@bdEV^YZfa+S>Hn-1YVK_Tl39=H~bI_W0=N z`ReNX^z{4t`~3I!{QUg=_V@k${r>y>{`~y@{r&#_{{Mgd{r~>{|NsB%7sCSp00G5G zL_t(IPleM5SJO}w2k=|43|TToMggV3BM8cfgw;YBvO$!+MTRm|W|<-&C|LA0_xpO2 zG-+B&y(cFp<9GfidGB7p{}iZa?oo>?*RoeD*#x-GjSID89dJCd+ibSn{Pth;@O0h1 zO?7zwM^As?So#1g01yEgXNZHv&ly!&dC!HC0rY++7OYkaL|UUD2XS0**4Igm4vBUD zRueo}2v&fkM{yL9aE7By5^GzkD?|r7K(7jH5Di%z-^YT*vV!lJQ&_&f8W0?8&HyoB zN6wQW66OiG*dF34Yh_abz{67wu!XomU^)&Yl7E;Z-+O_7AQfgsFc&2ax``1^W8{0000< KMNUMnLSTY#cjVXr diff --git a/recipes/icons/countryfile.png b/recipes/icons/countryfile.png index 179ef0a33582f6dd26186e3bd0c166f572f56fc5..95cc9b452f98bfea1cc3e3457582d8e51b7e15b5 100644 GIT binary patch delta 917 zcmV;G18V%k2cQR#8Gi!+002a!ipBr{0i;k&R7Eq2ssI1~{{H?qi>m$p{`~&_Ig6?_ zi>fw@svLo%{{H_Sfuk6IqA-c5IgF|nf1)LXrTqQ=JdLY0i>W=0s~Ui#EQhA>_4oe& z|4WpxFNmn|_V^lrqQckVey_uky2~7ZqX2rIFNml2`ul~m#($c>&ozpvAcCZex5y`j zrLWA{X`{R8@$~ol{C}^+Yo)wRm9ZFrq8@^zDu$+ctie{Av|pdMox#xc`1*#l$AYoN zI*hAElCOxh$S8%TzRuR<>sy?*`~3VZho{5X;;qZqZKb@9xXN^@zs1_(cB;Uy z%h@o9sJPPID21ic;^~jM$|HlNrNz{cxy$`hWcL_xMMXuY|M3d#%ENu*CBB z`LN8{>hbmK^7XRL+D?|S9)YBCslFnEq(YFbK8~y`hNk51^7P;r82|tQw@E}nRCr#U zQ)QFmFcgcP?Qog7%goG7$IQ&k%#43}&)MBJ*=D9Qr;mxOXG?mD8(J_Lk4J-=;t!3j zgsnuv3V#QfFNN*3r@Ch553hGc?IiR0Z)KLc;h?*7&8lIrL@@<`{{w($`;A=;W=sMR z0!$O22xxfF!_a3(dWZui2Z#WU-1KqHBd`GhiJm4%2ON^Gc2qN7#$4}$NA`j@Q#%vko9G>W58_8L`V^}$AI^`XCxt+!oq zs(+z7TVXZJu!>t@-M!}u>;3~LZhH7A^Y}^1iTkNLZiRh+zFnB67myHm>6#$pdi9zJ zFlnc9Z{B(;r>Mp|PigUfknOJhha8aRLhv;3vDaX=i_`zi$wuf3(E rz3=zHALhTjfv}O!8{XxuyZ)Qp{6Py{1rEy>00000NkvXXu0mjfP1*|3 delta 952 zcmV;p14sOz2g3)D8Gix*008_L?V|ty00wkYPE!B?00000Zl(v300009a7bBm000Bn z000Bn0d7ZQH2?quV@X6oR7l6oR_jYsVHiJuM2N(YFro-WDO8LK>O%!xK1R@o`V>U; zr6Q=P&FOTyX_grxl{nqi%eGwJ63xrna-wCH7xF`5fk~@P=YQthR=@4lhYU@)<`LlqD7{l8sqmZLuqUb72X71>eo$0RTRvP+7)k@d=kglZCnaB2%gX3lKv60K3Ez zH?-ZU`H_~0ya6KU^XxpUTypaJuZ0Y$2CLbj$O4NDsrs9Jf!#o)dEWb;t0jgCS}nL7 zD{msMNq?X08~+N!uqTO~`X^>sImov5(dlF!fRO(vFFtEsm7OwRQ@8;^fDxh$JH5W3 z8?S`k=BXKOK!gG8&R1Snb`hs!g8*-(c`OJZUjvZqb_i?rw#f*08l9OFh++d^VVAi6 zmad1k9=UA+vdb0q;V8FnBdceiA zcp~{f0@$F6w%y3v?xrNrj)w=DpA9t8rUQ*HRoIa#)O4yym#Sy&PEM-$ki-u_lAYS} ziJR<@LFr_NV#iH(H?=*f;<(99qP$$w&PmsSMUS_n)rwqwszRSDr!1x?ZdG z^?!rYu?o(LYuOvM;OK1=@e2;?OBbq5o>0jQuf{P;rqk^?S&>-DB$j}a6tpB-^zqzl z>Pxif(`}2$Fo4py$}S|VFEsptb=^7L%cySu*0#`%7mBc9X!3`{#cjOiovxR|Q|F!< zH_~`ft)nVgq(H-aP1z-DvsU+vV1U5y(J`_{ETt80v~{GL-^@_+D+o5f3Jk$kHNRWZ azW)WA+@wsL78b(*0000 z<{z8y;Q#M>8eE_qX8<^G^cgno`t38{JNy3i`LJ)zRHN<|r#n`^?Pk)LehUDh3H*wO zoy$Td!ZSbl=1qYAB|Cw8fE~aPEi(7riQ4|w&j#l{{D^L&53PP0Wd^JpzVS}y%Iss5 z!Wz0uC{ny}a(@IT)UW`c9e_dzfvaLTzDZR}LISXPf+jy&D*jJeknavSblWv}+hgKP zlr>Rny!uRsV1NW4&^Q=fQ5cFm@4 zRTs;P?jz+4}8`Y}|PinSZI^9~X>XSiyHkUgS{Eu9ryg zG%?0^+Mn?oXDd?z(>D%>y4izWeBBkJ9Os!2onX z157A2Ozs%}J~N^ht-FGEJxVay;LmHn%%OXCL5jPvI7giOttfcXAk=g)^yLQf_TM4& z!KyrEi+@dst_tR6m*Qtp{9|Rzm1S6yff@i{Ee%9KIzTPN$b^VnT8J?L6BYQYBa1Y84!Bf~}i|}NINq;X#cdEiU^M1m2Y;L$K5UZ?x`mjkj z+j#a|HNSTmz_x{#!GVau@Qwc8K`BHjA6o`Yt@#4`AA)Z@|0j+fxx$y$E0R+l&z;`S zx1xu+vUz~vk|5q|!yRQ7{?)~9^CTj2nyIaO5DONQqy6NKD&>ye+-#z6xd9Xq@OV^X zPk*#thaiSsjWS!%+0uK3`lUTS1g+2j9Wp7Ku-Q$m6eElzN%#S@SWU=X?EoGD&~*rFF3c;+Cr!prMe#Ks41X~7 z5G5m&XVSC^5<-Qhflvx8&|(3a;}g1o2GRps3SF0=rO;&r)XlDCFX3$fG6WfMpi9v0 zS7;^cIG#WV5J3~6`K0m=*-QYQKwp9mK!VgIW>z6uKKNjUO^7tGl2IyJnlK1&cC|CG z5(S0^sxF}!<$68O)zS*0nnDvm(|?Diq*l(-Xr(}^C1cX_p{{7on=DS}@LUTl2tBCJ zvSU61mC zvxQb5Zg#aZFa;nb&;l_hD1Q`UOjhG4TExk7E&NWbRC4}p-KUcZBkXKrSn399|+FO^l@e(hp)v^+$i~Pu0uM9;sA;HaovA_}Q>`3`0L=%|K4PsT(`mES zyojl62*D5@zLdCH9ABFFNRYgM2Fn3Fz~NP4FwRVi@I=VicsEBDSAz<$6p-{0odNyY zG`*BTY3LSE)?q^9`h1Rq#SIi}h13+SFixQ{KrKj7ml8klu770h+N;?{WjR1L1s$E> zxICyjeB@4>jRTkYYT_&?1E~Sk5H)LXxjx8czYAoDaDXl!wz;fWHN(R_pOV#^1dgD~ zblBEcWWcOaOlC;0i$1V>{sOxf&MybpyXGXX-8#w>T~{uCIKA^R&ZanZwV!>JZGggB zQtiQdjg5T^jDN@l@FbBqP>_Tl>Ri1r$X{yrP?ULeGhjYQ@ZQ80j?ZMsf2sGe9Xq?n zR(C~MHJD`C8fL;dL<~iK}j?XsDqe_F;_@%G3cP|NOaGoZ1kO(S%2qu zPIPWNZq=%Iu_(S50MLF50EGZTI-EH!SgVfz^X{*2W+?t+-Q^M$5HpIgVkf6sIglZS z1X>MflzT`zWke%HOPW^*#1D@oAKpCOIe0v6FQ8~2owgAm1hG~XX_{c^FPCb(Jf6G# zK~{$MuFIEq?8w-IzelDu55g?f#+sh1i;0mF13P-(>+8)N^fd$J_{7{QN_5da@VWnv cC7*Nt4KY^Ye>YLW)&Kwi07*qoM6N<$g2Hn|-T(jq delta 2343 zcmV+?3E1|H60s7HBYyw^b5ch_0Itp)=>Px->q$gGR9M5sms@OH)fI-nz0Ym#Gaio} zd*Z}#F3vq9O%Mq|K}A4-hE}QyRiUCSl&Yxe1*s3M5Kp~4v{F?bs2V+(K}unAa1i_8OeqBp+&^M%*m|A=0z zAESO8Z3iqLx_|m+_h9xhN?{GNO(;^ldUzNwG;rq1KqoXp2--S^r);W55)y#RH)-%yIALB2O&@9mf24WA1qqVzs0X}S*LV93D5(H;A?ZC`yTd)r5a?hFgZ3M`qKpx)Cn zcL{`$fW!|Cy3GrZ$ezj6ZTd`kY??R}+seH#3YY;sZ?6qx*N290@%h-cuSek*_^#{)N69MS~ zwGb;4B38B#rvy}Bg__`=;(O-l(xSK4yO*9&QXG+P^SCYBvjL7Y7Gd}$JmbqB4zJN` zhnM{f!c|P2iL>R7UvhPL5r1^vVrc*CoN_ZbF@MeO;qB}lxDTyD@HKAPAv~PnqMxHT zRbj+_knl~HtL+7d#m-CSkWDz*czUFo-#H6l!}JT_LBwL{>ij=JDMV|9D+9)set}&N z!`GjEh5h@^bMFdGa?I!Z$9M6q=sTQWJHSv$5btwgbD8OX^l-O*7!f(n*!t~=X@`qP z=6{p7s+7C>a+8VvIb5KDfXAZ-+oSb51Toxdl*xk0y1w%~*t|;d{$Z94jS%lmaOt%? zFP+`aDUqWmJPDqrQH`Mv&!aDx;z(;17n)xrpDQrl$kTPL$X_-+%xe#PwGh+ri(8uz z0SJB*2Mig2ln^r*N?V+rj0j~jl3o_K#=&Y6pVuJ0 zyvj0rl9SUzyc0ZtTriH2XyaEJ6cb%sOedckQOVgZ0JZ{oLxY(z!Ea&NHDs=a4i#nD zN3))Sn!t&asdYy{fp9@-0|JZ26^GKr1g+2l6EZ26u-Qwk6eElzNvHr?EFom;Tz>$M z0GK9(wKhy?%7<-6k3~_20t;+2M9T=}i8QT(givD`AhZStj97r-DZ+Ngs?rBq8q<_u zq%dXV#t?X9u1R=1fDA!KJm?YhstT=S9nTjC0U~H33`HvMkm*_=XbVIDrqhOGn{;c6^!zH;Le=1`o-GFeT4p|LE1Zb`&thhiba#6&k$Q$hq9u4Lrc8jfC>M@ckjqdOT8jLl`k zf1*1JkEnnygJ>!w<~X1o3=J4at8NAu|%*gY4(JHV7_08IgPgXwE=&RxqQ5ttfC`-rYKL)mc_ z#>$|KPH6%KbwRsfGJoZ!X_E#CW}wLG3c09H+s>h79AN6Y)+_*k=R^P`Jw(4^Fjr)C zt#V&$_i2vyIB^b(~Fsw!mw%L+90Lxy5C5aE|VT;u&or4WJsL zCoRs^7jjPZfDAjcVNgM;&4RNNESOj0uiiZbo}kC}SkYg^EmbKdGjuPHe(CP1)6i1D z=9>X_Er1%ER#b>{u?Est%ReD{@Om*(?!WdopbRHp}6VX%67n&A`E;7cNLpdbm4 zn_R9gW|trnUZYzTStiD~&|0_a zK;r2qmL$vErA~ns2n(17J!W!zW{6|{01#p-Afpg#Y=83oH6D_m5ECH^gB9L6b{HS9 z;P&hjv1Q%6qSC}k#fT=OvrEv>kq%L4(q&EYFJr^Y;o{7hdHIib((=|=0g!DYRGZDTCg`H@+3G(rvEt6}9_rq3(5Y4NV^NeJ+|>2Ax&Vy; zLb|+boPTx}AN>1WUt7zf_)p9>XQ_ag(3~oEbF7sE8Fp;gYCxkz(kmkxAx6?ZPauA@ zFZq?Vxs|%cP#mwq4XCt?&Q< N002ovPDHLkV1n?kU4Q@p diff --git a/recipes/icons/crikey.png b/recipes/icons/crikey.png index 5a8aad885025fde93dd7debcec48dd13d6b665ce..7fb77affdf04438e9fb74ada45ec5909875b5b6e 100644 GIT binary patch delta 743 zcmVaFrl$W!M*qLR|1dEBEiM1}`2Ww(|IpC?Y;6ByWB)EL|NZ^{ zJ3IgH@BfdF|Fg6IQd0ld*Z(;=|K{fZb94VHD*tY7|C5vduCD+0fA{}>e*b)Y|0yZ| zyuAPM@&EAf|C^it{{H`BV*kg-|1vWFF){z==l?%H|6N`Gl$8Iux&K&L|G2pS_4WU4 zZU0P6|NHy@PEP;y^#5O9|FyOM+S>o4qyOUK|6X4I)6@TCWdHj5|LN)fn3(^dp#OS$ z|8H;ql9K=6;QyDG2mf?*@;EyzlNJI-e*w5jL_t(|UTxB6Q`Am;fe{=Gl?Hg;P=gZ!#UftR|B85sy%WQUs!)|lB%E_$4?P0}J zSw%+GHMMNusPj^hs(Lo@HIQ<_&dRPj`5V>y`RCcagV96r>~~@$nM(vn@;0%wfAc{U zq!t)1Ruj}PEJZ}In+(keRMg7Rr^09r-AfqaX84o8dwpW~zJTZk+ATySazB$_(EK`d zg3JEKkeEVWE2mP11X|kCxgwiDlzLoWM{5atg;@1+0g814=m0^c!!uM&8DEd5BSz~}-)SD2(gQ8!C7KO^I3 z;(R>vJju}0OL>icw$R;2O8)?xbOoqLxLIQZ{aToe0xd-mt*w*HLaptCL0u0HUcV6{ ZYn%bzP7V;!&ppq5@4okZ?tAwCK;g>4nnk2gdC*h4^9MW4>qg3 zynsXku^1W};Mp_y@BvKq?j6j|!ri+dlYzH4WMshH96WyxA`$rdLR}q{l|e@b6c$2B z2{bi9Z7sC6!qO5Ljlg0-L78b7V`6fLIHyTYHDC}6NZQ3$rIS!1s)H2d%@2Ss;VG68zd5#o`%=2 zVSOD7>FITeHY}aNEJB6Mc+6w;kYGMJ({wXSY2>L4gv{T39JA47D=(9YRWPE%u?sky z4c4W5--Sqo%(@u#&^Sk@^glGoOqnBgw$1Rw@;;tEue* z96|2>xU+la)y~de0q24PhmXS3r~aG9i|QE%kw{gYIOKEiNLW2rE)=e*nN`%VZ@L3x zT_5P2e>X~hzqeg08<)2$5P04k!@0+!wdl%RhccMd= z-A1O(w6tNd1M&TS`ndVRAz?dnj9PNalYQ~U!C$RI2a^P{^adO;vs2G==@wOId(4x> zs%iS8dUNVb7iNTMW4mmp*)v(&uE?s!j~ojwxIkL%p=@&qpPg8_qU@U^1nK+pl!H7= zmrwfgl-+ZlLHO7Zy`4s&EYR(nbJ8qHI}(!m!!#`0>T)gxJH=cay)$8ZX&kp;qiI`< zZQQ7MWUdd6Dz!aeMG9`nHIGdix~JM?TX5V85^vWvw(bgcU(&p1x{g|0^>9{40;kOr zCoG`57gti*|HKn`A)Z=~=IhQ=f4beqyECW}RX*ayw%j+B^~M=eF$SCVT|C|;Vy||7 zoiOhB<%3GW8@N{J5?Dd(4cYGQnZ`_BQ_Y$l{JA1L zL6O2$2xxq{z$D0z>__n-A2D?tg-Rp))2L)`GMPpu`xFrj{{hk^DQT(a{~urv{gP?| zoIWO;l%y)MxN-r?%F6Oh6ALr=T&YsvE0LcmTMgQ8+KSJPOHi=5SwyK^lA0zG5EbX8 z0%Dp}!AD40KEHv-$-edQc>MLiAY*}rFs|0Vd~17Gd#3JFj6IRAKHQUnacznt4or^s j=?C@BdhCEYvMS0KtvC=A8)0AkV2VJD@ECeS7$@&vLJbJs diff --git a/recipes/icons/cronica.png b/recipes/icons/cronica.png index 4bdebe0fa06960b9f8ae37da7c7bfab61c97bccd..81ea8ee6ffd3061d9c17e090aedf2108180a0a6a 100644 GIT binary patch literal 627 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GXl47_9?*LR^8|y9|mz^dHJlxXPe? zmqF&9Im3DI(cK^?3e zqy{Jm76(ZIje_X~SpgBh198Cr|Np*x{dMd9|38233mJZ1y#CkShrjPV`v33WhyJFRQk`2+DXFnfLGekNbiK&)nl5N?YFN*ZY3#?E8-CkCg2{&07BF z*~`BlKLfq+>-wEX8ZMt^uXtkU^VBBvKCjN_r5oQ?b$^(&;69Hw$g|8UZ}OWy%~^R% zxseYTe7Yq;e!&bse=y!I=gG`bnDAHm&lIKXxq1H{sQo*0?&riOk9p0%elgQObyhq5 z+j6&%opC=bf2IO8?(%eT45^6AJ#o2;IZ?#zVZFfZm2c~+C1!8Uef)2|!ta@W=ReI` zt9p#fXVt_BQayjS`?opw{ogu&@ul_G>f$Df=;%CtbnL=CuUm>sc=|P}_`iJl^XYCD zGpAf;@bdegg{>}}3z+|4qLyH&-|1~v4R`nI@UL_@D$B6@`1bO6q5e6(DZFjfzf1mi z*zljXw(RNmKXCZ4b?KEWUr(>jX1HwJboSgq)BOrE-QEQ{>p7-x;##vdUqfcy*|oXr arIp!R6h6-1>0br(1%s!npUXO@geCx5YcCN1 delta 669 zcmV;O0%HC11gZs)8Gi-<001BJ|6u?C010qNS#tmY4#WTe4#WYKD-Ig~000tnMObuS zabIa;Z%=Y*XGCRibZ7uGGS%X^>;M1)K1oDDR5*>Ll21rfVHCxGU*l-0{S(Ags(GUg zqEJbY)pCe}76ol0U9=E_APQ+AxNp@$P>TL&86~Aq;X;svpnnKLgIa{4)tjdlLsEu1 zGmhiTywgHw7>rqU;4Z$)z2}~LzXSgXvoYb|^9j=?I0abiH6bv=X;NJQWxzU&+{X<&l?3EjhDLObT8%s7;!p0u}C;-?*x(z5Q*jr8I zNFUz18X`RdEZusHDcQoxyN`G(%JEc|XJ!_9ckgQFd#H7NcWc@yYt-r&N0PMEGoYF3 zqjKXblBD?SF}cy0!Y41t#?r5g9a^2V6FJAT+=b3wWq&YTbwtk2gIXSat5thOkw-5y z8wkoNYgF{vh}`A4!og-YmmrZ0&8~I3u0qB2nO)by_%(O_ic;Y@r57U#2b*PMC9*dF zK1n*X_AiF=JJ(t4eu$@R2StYuP}*_^Z@?ls72;>Gh2*4-fr%UizV`W!8_J7RN()_m za!MO=lz;i!vp$%hp>#9x$&#}ts<*bD!@JK1K>GI|R^E&gx%ZT$J)3K&A4v-9>zImU zjpX2Kaxt%f;yfhk^c z`Q&u0w+`x{00003bW%=J09wPiPvQyy002)(L_t(|UUkez4uCKW0KvGX(f)0%h)1|EC?4QeLRDF%&u=iQ+keW1f=JLv_=twgxERV9f~NaYuBE;&B{5=%oc a!qN>b&IA>p;NU?30000s3Yo delta 174 zcmV;f08#(e0oVbMGJocOvgCBE;%cSfU!UlTx$K|C?yAi3w9@mx+4t1t`QPmO>hk^c z`Tzg_VoIbs00003bW%=J09wPiPvQyy002=*L_t(|+I7uQ5`Z8GMN!2x4Jh`%+6IRS z%ISWdkN47A^B@~|`3qoB2T`A5z=9};Lpf2=LEA5SK^HC&HYefQA{0IO1DKOC9>^`0 cR3O6A4X?8W1N<@RzW@LL07*qoM6N<$g0+TJ-~a#s diff --git a/recipes/icons/cumhuriyet.png b/recipes/icons/cumhuriyet.png index 63c4eac267425860f359615bf7b176bf0471bad4..1de5b5d305acaf998166e91e969dc3e7ce16d30d 100644 GIT binary patch delta 799 zcmV+)1K|Ad2HysdEPwG6D)AL8|NsB-3nTFfA@K|(@eL&L4JGjpC-D#`@e(QU1|IPU zAn_C{@CYFLw!QEK9q|w-@ewHT5h?KsBJc$r@eU>OAvg0WKK5U4`I4OR4krB0)BfV+ z{_gPq`~3Q>wD1QY{Kd=jH%j$QU-1kh{K(Dn8!_@5GV&ia@ihk^@*+6$BRKLUJM$<# z@C6?El%MoEPV_rY^g&bfK~(Y@G4)Pg^;Tx}T59%OZ1!Gm@B|!@{v?0#5-RZp9{HA_ z`I@BpoTmDrs`{$4`m3|?4<_*wEBwpQ{LInu7A^hH)BV)g{npz3*W3Ns-2L0%{@>#9 z7%u+f=Kkd7{^scZ>g@6vFaGcG{_yht@$>)n_W$|$|N8s#88H9+{Qv&`|Nj5+2Os>+ z((^J#{_E}j?CtY4N%4OOA^gY9^C>^{LRIk#BKxkk`>(e1EJ65xi}XfV`>(hAwY>2L z9{ae!^DaaCzQp{z!}yGs`Hq?X;N$+`@W}p;2OfX%v@$>W@ct)?qOzn>ISrm({6Nb_P??;pX9MMQy_o;C;0|ZMN}w7Ob0+t z;G^Dk|C#wT8|nRrsb>Ik(uX%k$jq@?L~+mtAa0(zKt!gSX-EM0`eh8+6Z<+xfIx87 zEF|M=@uVqQumu3E8s(kQ!ctgPIJAM#Nm~Wc!&Q$_e;e$iJ%WdJoz)$NUBoK{*|^|i zF(aGd*c+OBrmufUuWy&LayB3!lR*5bgZBlFU+w#%f%AIW2>^ak1GkmDBXD19U@z!) z0`vI022Sr8#~#Ewq-DV62&h&atS@mEpcqI1@BXGPCdadF@>2h+S^~(Q2a%qMF_8!J zfb+u$I%xL`77)B8hFI>Z;xJd;x1#eOTY@zY#ea8^k-J}oZ;J`@m9Pk7k@C6<41s?DQ9`FYs@CYFB1s?GR9`Oes@dqIB2q5tY zA@KF)@eCyK4J7dmCGid=@eU^O4<_*sC-D#`@enBS5h(EyDe)31 z@e(TW6Dsi&EAbR7@f9rb7A^4@F7g>K@)3r*@*y|!A~^CRIPxVs z^C&&@DL(TlKl3a>^Daa4FGKS&Me{O7^EFBHH%jz6PV_rY^g&bfK~(fYRrE$!^-W*( zPG9v_X7yTX_FQcCUT*eZZ;{|6fB1ik_>7nNj+yz7n)#BP`IMjemZ15Xr1_kt`k|`& zswY8ueSTIxBIoc`?kINxWN0q#QeO&{Kd=s$Ibl6&HT&I{LIn(&C>kN z)BVrW{nXg~*4q8o+x^+x{oCLE-{St@)n_W$|$|N8s?`~3g>{Qv&`|Nj5~|Nn^*6+-|30g6dPK~y-)V^AUh z7^tj3oSl`OkDZlGOaZF`em-N5u-N3da4$1KJ`4rQl05baOIbLV<#C3RtWv$Wl{T%hJE3 ztELl^@v6{CL~|TDS<6W5)y$rSyI$o7}=Zr4j}nTZBclT zD2Sni18XQqK`;Z(pyp2n%C}ny<5Hj?l><~zt1E?5fpjiVL5+?ye=Y@zg6Tj7trj9U z-M|_Fa-=U4P6d2UAO*!Lk_d4=5oII=vTCIu6;T{8XG-z-*s&m|2Tnh5P)EwMN+~ER zNHQyjcQv>$%OMoV$!397^jvYiQ=)&V8 z4sD5-EPQ!k^UDLv$|;a|&a2x82Ad%x=29u6bU81^&?hwCMj)3?e}QCPSStIjMLnI& zXQ*a5%yEq0T5;`IGdVXRlU;vatjsRA>4VhI)t&A^+%rC2qMCbahbcWg2M(HF(`9() z(7hxLLA3rD#ZoQyqj~=P6myDB2U;Gu3_&%oxeMQ|K||3BK&ncMcMEujU$`rF-WBIg tb8^xTyP5Q%MrTAPftH zu!Ug$|JqV-0h)HEK7nN{S-`ga7rS%rD`?ZzFls)Y;L?Vue#K*3;<6K-SG+aYf8$d{ zQtBLIR52hdPH1^TD}o9aRNBVD4o=Z5o;G2NS(O_gO5Bh=XV2Cajr5>XNJ>jfSrCWv zO^Y3}vK5klYO#Pk4hT$H1FFa4r z;`ENRLmX}*>GYg7Sh}kZVA#LRWbyW)>avIl>a|ziw~8=4H1Z$DVmR(cX3BKI+@d9! z)$?svN$4BO!MP(<*{YT1YELmd{l|*wYnXF|Vf|evzD~IKHI-jlk@Iz;zcJ zp}Pu0XQ4l5cZr3eGgjCU4JzFxQa}e1k?T0ngXiNoX_#{M&70ks6+X5OQrW{jvBp36 z*#9HKkpH`1&jw;EB9zp6quZ;-+{{FLVpq1Gz^Ho0kw1_se}BNo`5?O-*_IwwkdBdH zB3^C=M41L@R^Sx@9?3&Qgtf((;EcyJCdiD5og&dCBrSj%&DF7pR_fFQdSswLGxtQq ztrAUu8?qs3GE9qPD^D{(C&gpxB_mQDNgjCIWgGi0D==g|kfU#)ZdhP~^_MuMInaOw zmax7F{gG`tV1G%n#iuT+HL|4(UK+QcvCTt?R8E0yqZJd5hkl;if~~nHe07C7o!v|N z){O#RJ*-Vpp=%Sp#RY2oxDeh@q%PR}9V0}R0jWIw06H(>fcyw0O~K}>Z6tz_+kLQk vgF_B#aP9#(T*JDk&!DKyDmTmC_zu4nzw{~`Lrlm3015yANkvXXu0mjf5%{{8 delta 407 zcmV;I0cigE1AqjObANbAL_t(2Q)N;+Oaf65e2AeYG4@u}m>6mk6MCDgjljGE`E(Z> zp}Pu0XQ2i<5(`5|te_(rRJu!~fDR-g*KvTry6-@vVY{81-Pt#DLa5q0&|*h>;fnC{pR1^8RaqJSb?0@@e$3;J;DBE(r>LG38 zkbzLPu?Di#kMIOu);;PphhKR0<%nR9MpFjJ3<_V0=n9gSK!Fz4i3sE>#7nwlAW2i_ zRD_CY8UrONGi^MGh;TDOQy@bY)2!#H(AtuhGu>gC@_kHT!1F+yWS%0Pt9tk)3tAl)j%F+QZ4HcoY%tM;2xCNFCuRcMJZcKESmniV#Ja|QlI_mg04EgzNkd>e>b-a^Q1}O_AO~B$-%e7FM z-0FeFYb%pg40e?rwAV4XL4EuUY9q?cu>buI-xa^~DiH42tcw5u002ovPDHLkV1o4( BzF+_V diff --git a/recipes/icons/cyberpresse.png b/recipes/icons/cyberpresse.png index b7971473e3827ada4e940568f07e2ab6745c6cf6..506a9817d50b07ef9bc3055e5c1d9b9e45403c6d 100644 GIT binary patch delta 702 zcmV;v0zv)D2Dk>0EPwz1|LPnM>Kzd38xHCj4C)#U=?ehq3;^j71L_?S?nOKQ`}+U> z{^=40>K6*?7z^nR0O<|_=@0_x5CiEF1noO8?ngfGURCjBT>kO#|N8pr69)hL`~Up= z|NQ*_{r%|_2kI3F>KO~|KQ!tb5dQi3>K_v79~0{#6ze4z>yf=1H9a%!J~QeT2<}2T z>J|y^MLh0CJMKn2?ngc85CrZ>KJH6E?@U4OQAqDoO7B!k?^H|a3ILNa0X7W({ru?^ z2IvC->K+o4e*qPL=??<__xI@(2>Qmt=@A6z0RZY74e1U6=@SL$0s!d~2=HB0=mr4r zUsvrnEa?mZ?L0BPZ5$O#8?KUg$S5EoCzWT($`o+Qi`uge@3GrfD@nl==Ixy%5 z0Q$|!``6X`*wy^n*}M809smFVVM#ZPH&kf}3Gf_m4ag)9!ob%Yct5!UJ*h|H1~B)5 z7Bz6Nj2L)-41EW$xUg)%;-{Iv{P@$uN`Q{N-%TgL#p@`s1|z$5?rp@;=Pdy=*VE?* zFk*gt2{1-JZXk~Tl>)N@F`ONJL!t`vk}6sQAel&FGi`pT2dQe6Qg#0Z0Ee@ePG$n1 zpSfjOmP9EF7J@&tO6f5(H~b4ug+f24+g`3Bf{sUN=Uzb-&7Wl;n;isg>l_S6YX}ny zff47a!V3;`40-q%OrRxfYFI<$4iE*kyI0O<+<=?ehq3;^j20qG3@=?(zt4gu*7 z0_hI|=@0_x5CiED1nCh2=@A6!5(McI1?dw7=@SO&6A0-P2I&+B=@ba+6$t7U2K6&>7Ygba3+fpQ>KP2`8V%|j4eA>X>KqU191!Xq5b7Nf>IEJV>K+o2o+w#1EA2Nd z?K&{+J234$G3`Av?L9N?J~QnHY2O{q61k@bLce@c!}f{`dF(`T73(`v3a+|NHv?`}_a= z`~Up>|NZ>`{r&&`{{R2~P$ttK0004WNklI-h1zb6p()Z+g674SoGHSCMP*h-i!3Bj{76J{{k?^D0zS|CWL(L6~J08 zma^o5!)nSm7?>)8Q-CF&{=u&5_`bTS*EcmgBQ)n0k_12%ZBGDa%;ycB+J-2|c{m=U zaSFTuoX3G%zYAha3`Hb>^*r*k!L8qcu+kuj@35eM2(1R#np?e+m&;0&26(e_S|0&k zdu&)6OqMnpPapy#<}^Ss6qnqB6!uutfN9))C>PwgSfmBQ@O=LU(I9M4Jf{r+V$m2J z+0_qqQC7zj!q+AMz};H86RE&yrje#;ilY>;>0cCFR1H!$T>F;;fu)ts>^%s9H>1GJ z@|^l-V@aR2@8H)R$Y8=&pBjiHkRoz|FfRiU#!Xl?QK+K*Mb)q`H+%;i%MXnT!%75S zi@js8Z=k{XeI{AP%*11IAbXM3;?x-0M36GH_+S26zvTm*{M#5rF4`{u0000)Dby4)zDlhky@o5MVho&Y?@QG zREwaqP|Uk-4@Ffa9a8Oima^+AcD-V(XY)#RzJI^(?#njq-(FxNOABUmkAVckASfhI zOkn6oBnyTyaD5;Mf+_W~u) zNg^m@*1y5Vd_aH}%OAQCksOBP2nfQkJOs8EMq0sTAuq#9EPG&O`>QT?-GWFK4=Zt1rz=6p>R=izm#PyVNoqwm+&UHze8 zPOkR7VHR+MnDyJ}3t?cF%fyf{QSMBh;4qKJIJp_Us?GFW|2y2YGU{ErOl?FTmp%&$ z{E|@_9beWqZt;dN>`L@!hc-PVrDRw?6_IWUN`Q4y)O)fM?mjXm zV*j8wIb`IF7hjpXdZWi%`1O-(mJASmNafeFK1!34UJ*m$*4}P5|KfXhc<$My+s3}( zWOIFdU~R!68rjQX8%@?sO*cg&&HbycrVmCJgY9373-%3X9zH!j{_kY@HiOiptKH3N zIIe!b$FYa@9^V!(b$(6@^>?3g>e1eO#LyB?>Q7GZCQ6ewCPog{6fs*UBUQOB-AP); z_#pQ_abuNH!oM=JrkP9EuAcF?{(d3CxJufKXef+o=Z^>e_5(o(82J zsc2NyHKA>50{>UDJc5&&{bKy7FT8tMODh~|Qm=P!!4>TUOJKAJi zE|RgtwWi&hrJeqlSJOI~p8X$xn6Q5D&XxaC*r$s48d_Cs=MlwoMz45w$~o%S*M%{f zHPgxr?!@HuWCOQr^9i5I@K~{qnK9Gk?L6Q{r|~~gxtAR}VIz+nxWnvedZ)-<`Kf{$ gDf_V^rmAB3&0=}xNs9Y`R7}_+@K}&vqfd0!{{$VyiU0rr delta 1134 zcmWNMdr;B`0LLADQ#3}VX6n&H{hd#2<%%=SM_zhLM9!Rf&O>QroiRmcx;X^jOQd{} zl`-24Z6WDW%1f}&xE^Z0GFwTurd`w6G}G1@tMUE&d*AQp(?>40t|G*hF|4=P@Y^uXdc;+Rm>z=4G9cnmaS@iwM! zKqi8!3~~{)jbMd??t$!BEDl1Jg)t>$>EOj;w?DEupx8ihQjC&x5H2uwL0=CC8>mE} zkTB2=J_p;}peO^M3Bvr{1=A#wVqv@ig$PNJFbzV;fiw#PO2magT?c+V=o=tSgCY-F zGiW+?ZiZ&Kk1K9^5O^3b#4pdMqp^$*j24UXm zg0UBhBA7-YG?HxYEP8?+5DJwXy1sygVK5f_PVKG^fcci&oAklLbm?}`i9 zmtR}C-)@(~o%7qB$&%(@C}qs!?xBX!NLqTPT~yc67czRceFV+5Xps@TI&OO3S&_@ZViGCJR7o~;MiTV*TE%JWF76VyVFzyoeSXw{D@;bi! zzNgr^lsnY)NUjxM7W{o(D;=TgoJcGS@sBaKUoUO=dHtr9)d}=EjZa?_Mai1#|Tby zR@?r0vMNk`=&PFb@7lP=>%RZ&e;q7flV?6E$wO_7r~MD+GOKg_=tt%y5`Sy&YQKH? zEM9~A`vszLc9?%*YfA!4R_<*@>$nt9*WEL8@kQW9_lnuiBfhM=L|1OowKy;BASHTz z;4XKZTeX+}pyFu=exEoMlim=jEK#3-HdVT2_7o9unCZ)mT0U00weZS|Uj_`LimH_{|xGpEh9pQcH7kOpZd6ws+OOH(qChAKy>?Npsb{HhfM#6Z-Ef jk#%=PYwm#%ZfD@DP-CfyCTF)7TbL6a!QR~(o>KHbkn7{I diff --git a/recipes/icons/dagens_industri.png b/recipes/icons/dagens_industri.png index dcb1d3676d4092cec3dd0c2b411c668c6df43334..5885f9b81fb4c6bcbd83381747a43722ffed433b 100644 GIT binary patch delta 517 zcmV+g0{Z>31g8X$EPtpJ5b@dA`R(ob@9+Qq{;(+~|NsBdf`GL(G5G82v@$QK5)Qvn zPQzYX#bIB^XJp80Y3sBPh8#l`Bz#mshe&3ARpcy`Zxde41(&whKfH8HR!BkvkrHZs_ilh~D%*_M^s zoSLyKC%8a8su>me?(M4^7y9z@`||Sp_4WMr_5Akss~i~8g@fIqpx>#e;H#?OtgGR# zuH?0}oL0002jNklHsG233_Uw|JQh@Jj5lMgL~M<3u3!&aY6_00000NkvXX Hu0mjf=gA)( delta 529 zcmV+s0`C2%1hNE>EPtmF4X6?hs1pyU6cDKu5vmy#s~Z=q92l$|8LS^1tsoq(BOkCQ zBd{qZu`4ICEGe`yFSIo=wKXxdHZrz1G`Bl9xIjI;N=Ls@PQzYX#bIB^XJp80Y07VH z%5ZJUaBj?Ybj^2l&3Jate0tA)d(VD*(Sm@|g@e|Pjo6fu*ngFi*p-yomX+F^n%tnD z-J+o1si@$qs^F}v;jgabwY226wdcLN>A}J2!@}vr!|KJw>c++E$j9u<%k0d{?as~a z(a`SF(eKmJ@6^-q*4FXZ*YVld^WNR{jrKQ0M8wa{zqe~`A7I*h_rL2RYOY`> z6|hwsZQx(-9SmQ>bD;>PqN)HJpB~W(54q|;a9tDai+^XwznaQHbb!9{G2TEFA>WxK zNs=t3feTpf2CHe{5Vk9TJvgnO%LKbnh=EM759bxY5gb$iuPe+3x3G{6TApDj8;rnH zYZ>U8fXBi79jr&=Jna8S{}2CrqO?h~si4>&20&g_4@k*d!ZbOs*c3f{{H{@{Qda+{cp6%2!5l?-G40 zxy=%Tr?lAP%jD|~fu;QZ{(Za8qtD$akFU|^?#|`ywb|pO&)vk}=~kz}=kxZT&D)#H z+IF|h;qUZ}!xq$Yw#$pd)SAlK1bm^U(canX@yFuolZgQvNUqi4e7nxr>+tgU`t$kv z3V)>5>hQkZ=gj5o8i=af?DE|0@K%2Pg^!FQxs>9#u^!fYx z{rwY!r<3dfA%BX()IglLE|ReffThmn?OUnBM4q{c!qY;Wx#IBjGnKQw-RJlF{1%3& zQl-Ag;_IQ#+-9%FG?ldEw<9Y600Cf0L_t(|Ue%FflWZ{el^4W@1SL{N*8*5*j7;WmVSnZ|bTb;X3KqO8?Q6Yqu ztW>3?!=nt;WM&z&0g^fw#k_p<%7VfoILKl|Q=b4t01_=UU3o<%994S7Z=gnxytb|$ z`iFr9Xn$-%7dIOyf}mU5Sb%nu8I@h|cC)324e0GdLkF#YfE5@VBEph$Sb^aYD+a7% zs%U_R0gY_}1Cus>Brs)?O{03o?wH5G!lHc%ebn;Gs?!C)#^x5niEa1p9tQT6Zu*;~ z4h|!?Nm`DCc!kF#>$*wD$tjxDnI#H1f2XlLmpE5iFHL)Ie0QF3McqSy`{2h4sDsXs l-yd=YUjyOl&F2EKpf7`=O#D@l`~Ls{002ovPDHLkV1f;j(fI%X delta 1065 zcmV+^1lIfT2I>fqBYyw{XF*Lt006O%3;baP00001b5ch_0Itp)=>Px#AY({UO#lFT zCIA3{ga82g0001h=l}q9FaQARU;qF*m;eA5aGbhPJOBUz%TP>IMF4uB0(+qifu#|H zrVN0k1bm_of~63HrU`$e7KNyv&D+P~>aNw{G?laie4$&Z!hgNp=f&abZL`S(d!ZbO zs?6l<|NsB|{{DTt&$Zd({r>)p!_)+Pp{CK^41lB~jIG$~@XY1x8i=Yuo4D%q_Y;Jt zio(>h*yHQ;_u}yMGnKPwu*diN{LSUHPiv_4@l%roRP!qK?GX{{H{@ z{Qda+{cp6%2!DQ~6@{q4-ssxw@$2>Ydb!OKgQv9ENKeIlZPz0mvp{(QU6P^7-{`1Gb*g`u+Xj@AH$I0U>|L;_K$} z_CTDtE|RhM{QS=5?JbhAM4q{c!qY;Wxl^UTOrpF6e4G00009a7bBm0000;0000;07l7cJ^%m#Xh}ptR5;6HU>FjBk%<`&SQvo< ztZZ;TI|q=@$;Ax^JiH7HeEfd`aK50BFav{#s2CiGOYku;OCtDE(lV$D#AM|d+0zPUCk&bZvf&NN&yKYV-r&|pp-dC#KMxz3hYs9 z8xYsl4&+LE2S*^K;RI6c>;er5hyrz2AmQfj;R&R?ybUoG_;`azUq63;AQ=#dML|$7 z$l{PdXECwRuy8C2A|j(eg3&)QEot-kcd@5QZg(6^w_Z~NJ&kD$fu>7 zpeo>mC}7RVgeb_$;6qlB9jToI63)%bFMud0EXpee1yo6CS$PEmLuFMpNV!-|ZCyP? zL4$E^D45~g*n}LL8Zm#(QjjQY(ZCwr8u_hlV14Z|C<;0{Lm{B6yT`RRG_<#`e?k{j z$ax}A0mr1t5HQ7hYWcLulc$wWojwIBG^4*_NMt+!%t~<*w~;G50000bbVXQnWMOn= zI%9HWVRU5xGB7bVEio}IFga8)G&(gnIx#aVFfckWFr@;`+5lky03~!qSaf7zbY(hi zZ)9m^c>ppnF*YqRF)c7TR53Ik_` zKlS6~u^uY@`uqR>{ORq04J>_2T5nU}^N;;M9bQ z<+Zu8AuRs;`{ua2_T=T*ke1GKd%#Ik=exhSGClL#-L@_{yE{d&8Ybeaum1e}v40;b z)Psq!A1lyye#l~K#$05xB{7gW!6ChVeC?)EMnGRY>~`Io&7U`bVYxkNkth+MQZE+3c1xK9 zuI#h`E;j^rukY<0g1{%w^XGj8{D1dAZz(`<7F-{M5MUVs;1LKr2*9I**gq#waqlaZZuB;9Dc!2i^hP|{xS6X2aak5A&@TPiB=1U01E&B07*qo IM6N<$g1(Lw;Q#;t delta 769 zcmZ3_vW9Jfay>*>~=&v);Cwqy6hh@6Vqfuisp0?mS;oeWj)A z%l(JHK7Kmj8GIrk^Y5=;U+z8FZfJA8XVRaq-wydloG-08+KR#Hp^3JUJ zcV;hmuyob8r_X-AeYe-y=ZoY02QT&>*rcfQ@AvNuWp)4l{(ZJ>*H&$_Z%>|XH?+Re z+;t)$W4D#dMmf!m3fenO?avige|_}ifLHJiBiqln?|yy!bmCfx`V&c6$6``8s~S$4 z5_uIAhDqM;E&@|G@#+9MoCO|{#X#BvjNMLV+W{F*JY5_^EKVmUC@>iq87^iP3rk_m zc_Z_pVRHBEcJXx${qYRk0wO}k);l;jxJ+qaGAs=H!^NVa!l^M~Qk%wviBl(sH*jcb zX=-b)Ug1&MRmjes_-3s(;>bdX$<#mJP{%p znZ^n1KSV1|R8(;~iB$Zq__ZlPgY*Q;ACKMNwjggJ0TGi45}rr5hW>!C8<`)MX5lF!N|bST-U%@*T^8m(9p`r z+{(Z}+rR)wPI~-y6^e%3{FKbJO57SuAMXnl1L}|jSrMF{R#Ki=l*-_klAn~S;F+74 bo*I;zm{M7IG8LFi85lfW{an^LB{Ts5%RNnc diff --git a/recipes/icons/daily_telegraph.png b/recipes/icons/daily_telegraph.png index 16cdae9b71d740a530ccc1bef300fb36ec150591..c55e9c054e011eef21910ec80d1f0c7fdab4b13a 100644 GIT binary patch delta 331 zcmV-R0krC}7X^FL^gIGQQ006d0L_t(|UOkLOmc&35!{iT@q_3dA`oo1rc()e+th-Uh*Ksv_7;*V}6km@< zyBN3kwiR&<7qQ}WOPh1fK|~LpHkJ*iMY$buh9e3tN@2GOnFM&6P%TbYa05ef#9l4j zi+e&kt*Yo1R61d*ls6=(CNO!7Bc&bc! zt5JNdUV^b=gtKUgwP}d7X^FLwWlk;0(c;R|C}7|NsAxyn)gH006m3L_t&-89l*QLIOb)MA7@+(A8tk zIir}t2x3mP|24^%=b(_NH6laGBC;%vMla*x)AQ?aywQm0?D^k*Bz5}UcdO_2?ec}c z=aY8Rk8^osU&4)dg3i|AT5x6$VBugp30hRTcAr30Le$EM-B(ILloaY&+E+wEA_byn z_akaVffBUp*@KE|N2Dgov3*Swv`HapbZYmf2uNy_oLPLZKUQW@E0h`QZ@-^kKkvIJ gAVH;QqHPlC4>e^K)r09yl>h($07*qoM6N<$f(#9`r~m)} diff --git a/recipes/icons/dainik_bhaskar.png b/recipes/icons/dainik_bhaskar.png index 40de95e4ded8ebea8bf8a76dec4b89d989bf7e39..ae3b2b38ce3942ae41631739c3ffe3844e2f6b1e 100644 GIT binary patch delta 1402 zcmV-=1%>*(3!V#*HGc(DNklTLB5q+%cuI`p(+44EEJ+qTzJQHB|QxG7* zZ1VsA!EO%h2FbvVZ|iJ{-9?6ELx7OXjMv0NfowtVy{clfY4GJC01JeXGel5kU_gPk z#Y(ZZuo{=v;w$580*3?a_GG6-m7^?T%nUrxC>9MCE6i4QVSi;hxAQM+WS=pfTpw0) zvWK$~AM|5dF`(5*!ck_CPL9`MY!Z z{WtQ*-^lkD`JXTHzg!gGzbiiew)priOyuK*{OO(i;jMi4THYVa`M&J$qLQy>P*phT zjMCnG+c1Y{ZdWt7yo$>O3C6rAj*?!9!lWQ!SPo(`29oFNA&BV-)cm7teX5i7oB z?fGJL_9MFigej0zIp@SosYw~YKt^2>F`GpD;?dd28ATpRG6pB>nISP07dcY^GZ_Q- zEPs>PO29{WfJ{e9Sq37oiN1*dli+-Wf4O;VDLM~zF`CU20&MpUSz0$=ySj1eH~xGl zzUAG2VZO0gH;5!_XdAsc2WncZ+rW;=5|i46dCK$!o#=#^Vq8U@h-CxY!dg(Ejwky< z@8BAxjpjMEzm@n+(TrZ#W(?fGLY42xk_emn8Ix3ut@Yuk2 z^@XMsnPXh61FyS$bb;qTlOSLhHLL||ViIObhkKDxm75|nl9?=7L&}B>3~g$&Ie+Xp zfB}P5gW&q-Tlcps{l`uFulx3Prjs>P!9Y51janr#06ZC!GbHnpBO~*rLUnt=d(^@E zRDZg)KVCIIT(&=5>!-){;~ZTXcvDTZjXFoI>;4O7!v>usym8y_*5;+Lt(&j>^~C-2 z-u-aZ{CK5*zX|MmH&tt)*b-S0)MuRs+97yz0Uvz1PlcwC0AV*mgE07*qo IM6N<$f@a;fT>t<8 delta 1434 zcmV;L1!el43%v`FHGcpJa7bBm000Bn000Bn0d7ZQH2?qwT}ebiR7l5_mVHm;MiRz< zUDfWf=ZzU)VQ0CZ>`52)q`+(mn^z`(rQ;~sbS{o#SYDJ)#6Wm$5&>Zzw* z4KIrWbvPwcoFlg=1ZWzzf!gB(0lfc)4(vC+4f`B)-5{sP$bUN&8Pup$++^@rQ!_7S0mVUX$d;F^& z6po`lqptff((^<8c&zUa^l%{kf^H@%q=XM_w#~d^x(Z+C{C*eyGvmrJul%B-@-G`G z3Ql``H+E-Z{eS+Q`|eQB59Qr~jC#^@bgYOWCU%k9%cf>l#+y-mH`J4%jtBnBy}O+H zJD%CFPqDu%n2h;&s-NDwi%E7imgBzka_M%+1S?_;DG?G)&ZyyVAg9B4GK?of8FX>t zLvRiLY2@u6%oLoD-N#e?`-kGkGx=d+-}NQW#28da5q}^hFsQ*8ah7gkRLH0o54zD= z3^dV$i^0zyZ0rpT9LEFqmm~e*)P0)BUr*$8DE*FzAtX{FL5%yfFn~b}#^5ZOiW)+c zUC1gQwmX(vS1$HGXiNRc(48OY#RvD}S@vmSCkNVf(n4-gw+Uwq1`G%QF#(96Vlr#g z7$2y7Du1{5AXO7WK%HZd@nNigJIO94*~LW9k93%eK#0VamPg=_9-a|mBtc53kf{-9 zVl3;l_H62~sY2bLg=5%t=f`^SKD(IM?~n9opdGtcA%Zb`;QPWcPmUml1S(iTP1*%h zdQkb*d+>lk)8=rJhB9coAqNWU$6pMN4LB491OoKcrik3mkigDTc4xuw_9 zp3~fk+CBBJRQ;0+#(*IiM8s+AcIbELcgQWOh*g{=w-kWSWVpAc|N}u(Efet(X7bf&DL;(#dcZ ztAD6q6>G#9Y4c1n6;&~Y_^PHS@h}OKNFb(FD%K)`8mu6~zII|FrWeBd=8TGo5Q%B8 z6AK@%1H@{_x{g&%Xh?||kzGUGPnDIeiJsHDxd^NA$O8l zDY7&yOgp?(;7ON$?5U47QzC*fSS7)xj$aq?^G*2my7_$DTrKM9lCnkvAUIVqIDbpW zN|Bk4HO?9ANyW?gRe!GpoT!nw!G{4_9x2v$I2>}I~fSTZg zhyjuqlPS(7mtn=cL{ys#DN)t&W*I);`k$|MKV3GTufvyFJzMW5vp%xf>_Uvv#P(lQ zjT%~$Bom+ay0&P16W7~xy@>z1i+?{|?S8%rzutr|voKu-?+H(5a^px&{p;%pr+=kecn@t5oH&)e|ZG|cAD_ONGb%eo@q!PSv< z?XMSlHq!S8ay*b;{t$Txk;=zK#q}~?&Ew@PT+YLn`*67k)0Ow%*o!>)ihmINs&R`l z+$^{ryUCEFp$-airU= og0F~kC^!WO0sq>{{#5=8&lBd(?<(0qlK=n!07*qoM6N<$g5z4y00000 diff --git a/recipes/icons/datasport.png b/recipes/icons/datasport.png index a1fbc460f8183fcbca56b3208b96b5d9b852293e..72c44a775dd89545f5d346dda53c5bdf414da479 100644 GIT binary patch delta 2992 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 z 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 diff --git a/recipes/icons/daum_net.png b/recipes/icons/daum_net.png index f2e5feeb958771e3265a2f46e99b9989f790e5fd..73d4a92ac9af508ca763855aa32753232b9134a6 100644 GIT binary patch delta 1572 zcmV+<2HW|S4aN+RHGc*BNklKM()A`Tw5hIrnfc!cbaIf#JWb=zolX|8MD1Zx2A-G&0C1 zzwrIrKk$HcE(5{1RnCY&Bs~#k!a$T5Lq&$e^;ceeZtcqR7tcREQlvZQb_FOHPIKt4 z2M@h>#aYO>O%@}7!7xRUqQK;ttU|l|p;OuY>(u8~pL^=bvrqnh#O%tTEff13a*h^4 z5ar;r7ug(oh<{y*CWYCcE(eoS583L;!tw_{^7*fS^UQb799>?LblvDZ0Ysn>6(AFe zXAx(nEh$QhK@4#!9m_(DE+xZEci(gGw;q4^*zsj&3S$odB$kfSlfoh%dr=gTh@+BD zL2+OVgym=j6U^avAA0D~FRrZIQ5bqRfPymh5FLfVrGIbV9fNoUK~y|RbWjw<6p2?p z$L{dE_C50Gr|wu<9G=Gx07J=DTjPh3RFxT%Dkl;%@OVIiBXWv7qjaaxRTNU_-hb!* zFMsv8bHMy2L2Uz)hL|7$WWcSCkSH2L&m1X*V!$iT&4S9&P_~lJ!{g9Fe|IuJlCoaA)0I$0fi zowg(%A?^y$3|hn8cOICstzxIstcFhWAb;#%e{D9Vi`1AfPYi8D^VATFa zz23S9?>~BSWifScOtJvl0*GA$GjI9z;T?!KKLZ`z#F-R(%qf#c4#Pus&f1+nBY!uX zJ9%vB@vq!9#V*VE+*WZ`hfSHC(Y$zN`p5I20gRagSUFJ)9Z~}JkOERb_D65quPG1% z?3+;`;kiGv5;;bXti)H4;kiZ^8m^bS0nzrfe8uTL9rBY?L76EEZRKoPPB z41&SzzxSonK#tgvbY+?Qy6UG3A%CaM+_ejJSB191nW7fJVeQ^{wfXt4-S|C*xf2E3 zIFsWHI70G30g^-VtehA|OXw&W!k&XkZChSlWt5*)*b-X6m=a*6e|c7)duF|9z?k{p zgWC6#Fq?SOcd*HGkjznQQbo z{+k=b0tgb_r7*)-jsxUG$R4rB>?m1gOS$J{xH6@K0dSC0&M`|0yMJ8cZ~VYqT`K_R z<^Wq8G_H-YBMD6o&;wftj+CTW!oC2M5mGr|qG>**b5CB{~4v!axTY zK>6ay150EhSH+g;p)mssj=2v?z#BK`7c;9XJw4IuM*VP((ZEz)loF zI#7dlP;4lgkG2sb)Fv(MBepU1z1(}w*=z5;mV^6V`jWi(fq%FL=M2ta{npps>rVgx zq%?)#zqF>qfd8+hlv4WFzfkHr9fnUm{M}nW@L*PTFbDwPP6TErGA3uL%qCPL4^l6_ z@$w66=U+T^;<*I{fU4dMFx6r9#c#cM@O`VA;k5OE00IL<5N^UPa1}1XIZzfFT>p0I zwKFd~`}Fare}A*U?8p#_sa5^pu}7x29BNfzfSFr^8LX05f&ztnB+ulTJu_vdoD%Q9 z?Y6t`Sv`96&NnWc|NEtXq%O7Et^SlTUhj2THq>BW({0~G_VHJ$kl59 z{=4>m>3_@jszM~)2(W}EFhL8{0&1WtsG-ebTTi|bZ6n%7Xd98nUAsaI#*_oAa0#qI zO5g&^pZMhAkKBJ~*jA`;6%f!O7Bk?00Y+#)|IuTY-#RbMZjBg#b=}ha4<5Vk_sJ(gd{Ohm0blv2gJ#*^x?|=K1 zuRnV0!QE7IlpI+D<-lX0;X|v-hYqc*t*u|%WVc`~c#U3zmw*ycg6kwLrIfm^OWSO^ zvU2JVXMg&`A5)vfkRy+Q#>hEIhK)z=pby`>3qaQa0LvzzCNIfLbWN_wHKIf)ys2ty zO@Fu9Y<+z??Vf$+%(>HN-nDG(W0WyUA9)nYySv%mJ>mK$M{CJS^paeoYjmA>p11sfCuh5u#3i0=M(Fb^~vWc%p%WTy`aG6hTJMq%YTzL zS{n)GT!BhnAu8-CB@sy}T@7m5rOWI3>5m;bwz^{d+e>ktY!ECy;=} z!oCW~6B}FYi49UC0I~&GD!tbc+~5UNA%(nr=L-*kl|`AALlzk)ahxt!w=3SymM49z zvqhaF5pV6V5O!knJsf2Ib;c=*bqV= zX)ZJh^+1I;a|z4s%%8h&KcQWEC;WHL!K4md>8;MGRY<|YyA-04T-mZnW*@}{iG$c6 z7yyNvdvhR&{`FP)+V_SFYk%4$Y#)nvGU#;HTF+9*6jUG?RXHd;GmRpd!@!nd1+h2` zZZLzl5Zk#|(^r4eo;$1Ci*xN&(T1S&N$Xh(I1?4HLNb{`GEqS&WYuE@j9`EZ_2gRl<*8xex?yMU x|FiZ5x__g6H-#>yHxD!Q{!jlMz~awH`E39I002ovPDHLkV1gqK8At#C diff --git a/recipes/icons/dawn.png b/recipes/icons/dawn.png index d98906611577598faa5615612923e17ddae7944e..d71c86ad2322b43b118b9165edbf578676e1e611 100644 GIT binary patch delta 996 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 diff --git a/recipes/icons/degentenaar.png b/recipes/icons/degentenaar.png index dea820bbd4ec302ac727a454467f35e4ac016b24..96ef1448296686a5c0c6df74f1e0dea88fc90616 100644 GIT binary patch delta 359 zcmV-t0hs=%1DgYoEJZkz%>V!Ys?q6$yx$Xl!bY3WPM^^ngvFrC=BLl-C5gwz;PBGs z^Xc>X{{H?fjLD6`;xdrRHj>K+dA~c9%?NqG4t&9paTR|Oe!?t`$&SL}lf>hg$K{#G z9>fy1uT>b%?TzufP|-|z%?zaNIi-R$-vhsN~z{Pp?#_xkv@ z{r>(1cfSUBzo?-|0RR91t4TybRCr#^)5Q)0F$_h~mkcr#nVA{>|H~?+ooJ*q^>*&c zi?gz2)Ifh!7GN5my#5dfj673R#EJXozz65u_1$Vy&c)tgEzX*B24t&89e!>!e!V`bO7JLYlgvAm%|@HhPM^_{aTR}ryx)z%;*P@Nlf>hg$K{#G z<)F*vr_boB(dn+!>b%?TzufP|-|)uZ@Y3e<-R$=1^ZE4o{Pp?#_xkv@ z{r>*`{{H{}|8jfRvH$=8xJg7oR5(w~(#H7nQS|Ch0V7$h8K zhq$}3j=X;u8yN`w3-I-$`5+H?I?6X-%V*&%2^9_8egdBB0^m9!05)x)q88?KIk3Iu>*D1hSb6ZqzzDgY$avDRAa?i={p4?r&awbUI`lvl*4 SLGAbe0000-=&)Fu3z=dJtKbg z@mEKe<-y5f;rt}iTB4^g5cPpQ1i0N0g%UywAw`S_Pj%bRjDLLZ-t@uFM4i{&S<#$a zR)rhtTq~)D04+cb&=L*gF$7`?0R1y`69|ByP~?as5E^0)Pt-Q9F=}qLHd?)`+M`8% z@V0PG4`V9@dVt*O`AF|7G!i9HxM?9mMCU0~&Q)$W>By0Y>4Bi`WTvy+lGUoE(1lR? z00E-94*c*FlYjmE-sFdSvz>9SBm$%^1OX`wQ9>X`Zg9t^=U=|Q{G@eSq8I2< zkPvCh5Tk?yDM4$H*jB~)@zJ4G)3R3MX_jYNTWbxXWG0b!UDtPQSJzeB9-h5DZ~F!n z7~u3o6MrB@B1bMV5tL-_I2h$H8ZUb~JbLx{^XH#D`_o^)JUDyvrtcc$bSYQ}p{c7k zFJB(~<m==Q+m#$%uSi*WaCCy3#06AT zI)7(n@{yU}fxUNN@1C7bm1e#WTe<)tC8{hpcXr|KU6@TwX0`;i)|Q5wI&tovA-LXX zBVZ&|XASTLG6GTl+dCnYR3mHV19+v36y8IV=8#S}Dv^a+|64;((EBtwN+r4uhNh^X zD0-`hzNbI|!gdjXydPXQlyy@Pn0#~oM1PSUAfiw}h$w{CLEQqgmNgVf%L*1HEDHB_ z;Vu@gC|vIbB4%72vgZO#7DZw*RU-B16%8SS;JyIo7!{YMG^IO#VGdN#c+hKho*zNiE2A&T~x;!xPOb~ zBb+=_w-#8j$%lT3!&)AevYNG8=0eplmb=pJ5RiNlu8*1n09-f1O|}`F#^5Ht{&=03 zTIJOZlj%k!ZEvkW6m0*3XG5{gWzV|Gx<;`OLWqK-0NWV0ElD?7>Vp5+wG{$f(>CaE zt=lY7uBUXvgb@A*_J1v)ZUqp4Cx7zZ_udEZ8Nl&CM6pZ!_XUDNG+J3>tTjSP23W1F zQU;}h+YJ!~%OvH@LX2LbmY54}5!R^jD4$HmLTX56vTT~!v5{7aT(ICeU8M9xlS$}3 zu^LZ~eUwk0+JSxGpliBfwYt2xXv?BcR<4%IqNv)g;}i->vXs$(d2}T$^0yxt=FK(Q=|OpO5o7Ob>gf5>HJmY<bmbTAnjtCf=JLA-iS5JfyuH}D=j*qOdN9~S)sXD`8*2sl_< P00000NkvXXu0mjfKve_w delta 1623 zcmV-d2B`V14Cf4xBYyw}bW%=J00000003^L2ax~(010qNS#tmY1K0on1K0s>M`JYr z00sw1L_t(Y$F)^Wj~q!6eesc5Royewp1~ixULi2BLY(2mjU#`LU$qEW4oFyHwb;ve zZO?c-U)|MJm6Z{}p?i#1cx5eiU6*0TvJqkfx+bj_=|njT3p_rQ z2oV4>kG_6q7v~Q0C0Ea1Vov(PoK$=PnW~|+<$RS^|-f&<1}Sd6tG zN}cM^kK4^=-G7`z5u0f)j>=Ss3X?GxCL=Z`CsHth8O$Jo2vUOZ*sm{MoIY>#aI@9r zVcWGSHGfT$nFWBmm1&x%aoTSCaeV&f?Zr3`tjIu0U6ztgY0xrKDnww0 z3WUMC41~b~4{+qREvw^KBYipCE-ug0Hy1zuX@B1}>EzxDcd~Q0?RNXG^EZF}>YJ}G zH&@GhSF49tl94H^ln4le87v^)l}MnB>Y$J|gHBGD2Up{!Y))}`@%!cVucs+R@Et%z zOx4bY`A-}BV=q-2K5Pa_;1FiG?@Mx*fglNz00CtPBk8&5`FxDgQEj7{i4(t5F8~Bl zNq^0o<;hEPELUi{jv3(~zzG&e3SkHFyZ!sS zgBbzL1}20fpco6bBi22Jibg>W3uFVhyuHTdb!pl%m+BsP=S5*=br|gCrra8uIoAb1 z2nQ1am@1ltWk!<}n*ldHF4h^7nG0j%Y-R(Wg4j|m!Ear--B4*?Q57-XC z&=;6tm@v)?A`n2dpUj8=2n;aocR$n)_J^533?eWg0t^J-Mca8KGen>QRj7aov=hXR zXF|+GME}Ji+L@Tho-hGW?C-P-C4UUr-8Xilt3=$N>P@cWG{mYlwTwN@W?^A9NQEGj? z9{zT=ezBP_15t;5gaht{34iA3r{aD)&bOuNLggsys!IbPUCKV9=?nodi!Uz~5GZk}l&s{pox=f-^jt?4l^XRAAbV-@a z11umy?eOUV50A^VKC=pxB-{+<;5p3e_#8f-4%vG+kukz3A9Qk9+g+^LtD%zP;IAj#w7!%tR=e*?a0 VflSYGU!ed1002ovPDHLkV1l!%{iOf^ diff --git a/recipes/icons/democracy_now.png b/recipes/icons/democracy_now.png index 3c88dce431db00d035bb36833ad3680898032fd5..7871d4249eb4224d893078d2646bf7b82b74c907 100644 GIT binary patch delta 388 zcmV-~0ek+Q1eyepEL8sf{{8>{{r&y_{r>*^{r>*^|NsB}{{Q{|{PFSe{QUg<{{8*^ z`~Lp_|NZ~{{r)>aLOw=DKSf4fW@ck*YG`n9Z*_KYcX)GpdV+|E0|W&D0RfSKR09eP z4UyL;e+v!{{Qmn75)u#-6BipB7#$rN9v~$uD<>>0EHN_v{rmp?{xLT=H9R~yKSBQg z{r>;|IY2==K|=rj{Qv&{IzdAdBSaJc006#8L_t&-83n-uVgpbRMbUR>KH{WyQ`@#} zd;ja`IdZG*Oq2=<06?+WSZkK}0RRx^VR`xhe**weC{}tOc8LH0ipfTAHga%)vS*od z`Z!|-03gqiryg(sKp%yW+FG5xV*n6t)sFj>t%bw=qpR$7Ii&9X`Jwj7xoB`9$khS8 z-RgRL@r0KkfG+h(>Rc~pOrOLsC`p$iOJ|L`$$8FWlC-_auZN&ODoja|?pEC@L3pao i9)A6`e80aA6W||PMiNCvY}4vg{h5= zrM;u2gQJzBla-_M#2C4W4I#`QKYpBeUA_Jf5d8c7=ilGIe}4n<-@pI<{`>#;|DV7A z|NZ*|fvkKb(h7#4)+ zpZ|VOfPq0|(fYEuCn*f7r;8ph%|9;7;4qE(Z*c-c!@g`yzaHuB{tOCdw1aKc!`b=E z`{i%WGV0i@``df+?COILV<%{HOT7s?{_CM}yeETk)>M<)xKlgoI5Gn#-3srETkSjj z-f&O9eboFyt I=akR{0Ka~*y8r+H diff --git a/recipes/icons/denik.cz.png b/recipes/icons/denik.cz.png index 092a51aba2696b6a08fe8a497109fdeb148034a7..b0869a95af7c947191a1c087b07a58465ba962b8 100644 GIT binary patch delta 982 zcmV;{11bFL3f~HlEPowYp&wbG|Ns9OR-hYJpA1Tv4o#Z!_WBo6ofT7`7Eqf6Ntzf@ zng&9Z8C0GcRGa`mmjgPu_OqcWY`}Ftz z5I zp%_}B3OAA(MJ|*JI+F`LkpnxH99*UgMwT8(nI1}%06dc)JCGkhi~uu{3__6rHj@NE zpbSl%vseK>0XYpslEGw<=-j|Njp{jq~H; z06dWM_5Aep_W(ne5Ko^7Y!uu%~7D3AFklvQ70)N?O0v^bS=&nH8ze0jyG)_32}OV z%al0!1VaD|0Fc0B8c5!g{Z*lebUGltu-h+9s9K2*lzxx*p@A>=cCTEwVw2!PzeZAk z`{MF`wy<;6zIB*(mNwzzfsSEic&#WpLJX}A&#UYWQnogae!v0`73W9K9Mu}h+ zs=()ocYGlNGYxvxVTs(0Ac z)&fCH$&8&c$b6Gq+7OU?M^@k_3 zNb(L!&u`D^KCsKU8;=osnQ&JwA<;#E(o;dW{s?*Z5469q91op8ivR!s07*qoM6N<$ Ef}usw7XSbN delta 994 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 diff --git a/recipes/icons/descopera.png b/recipes/icons/descopera.png index 8b40c88c212744241d0b3cdd74f6c00fba431ccb..52b7c9ed350c35d32ff6dcb98b190fde09e2436c 100644 GIT binary patch delta 632 zcmV-;0*C#o1)K$tFMkj|RvlJxAYXnWXo(g>S{PDr3o}U{T6G#)c@&nCaF;e`bWmt=Yj}BkjC+ZVb%>aTqJN~C%AsvAXnz`Mlj7K_ z_u94A*xBFU;O6G#_V)Mr`ub*RPi%mObZlI5by#_LQc!`6iIo1t`RL71VWSY1O* zhL}cELoQ2Hs;i_=jGZuAXJ3$-*u$6Fw1Porc|v7z&a#G6Zg*X9cwl^h&arsKsC>Go ze20W~jEsR{jDL)Hn2ma)o|mMApr4Jppl!gkjK8yix0PwEn_HrkZJ>>FmXCK=Xk|!b zV@F$MtdnJ~iC`x|N9D$Q?$4w;bBCaZX#Cu!mWO4|($gDcig<;JdU#=jjgbs~j_v>e z00DGTPE!Ct=GbNc008buL_t(|UM0?Bj|D*xhS6${aer;wzH8gIZU6sdr+3e{r;x2+Rm88|n zTCxogKwQ@i!_ZyvNb*=rFCvUN=NLbtVhF@$7UMhJb07TW%s%w6H$a-fQER5ii3@j zgpihrmzs~8p_rkhps1*-tE8~9sJ*Jby#_LQiF9=bZlH^X-{-%L3v?9SY1O_ zT2Dq(LoQ2HE>T@KQ(rJzXD(G}GhlKyXLLblc|v7zP-t>fZg*X9cwl^hYj}BkjC+ZV zb%%s@jEsSZn16>FmXCK=Xk|!b zV@F$MFlc`oX_F^GN9D$Q?$4y+*sAy1wfx+s*4Ww3($nAI;O6G#_V)Mr`uZF~WPktw z00DGTPE!Ct=GbNc008_+L_t(|+G7BL!e~o7ix9N}27d@BR&}(uG1AgAv{oqw^P{bd zb+dDFvNX+IWI-b8HivUa#m&Lt zlMv^S$H1V%$il|P&d$!pYH92c#lXbiAMh6f{^_3+lmL0ANRg3|iHV7kkx{M)>fbzt gWVcjb`Fs!?0R6@>BPF%v0RR9107*qoM6N<$g2G-rb^rhX diff --git a/recipes/icons/desiring_god.png b/recipes/icons/desiring_god.png index 22ec3e845f2d64b1f2cd7ab978db3ce128a25e86..2bb77bcc0983c6a6087d7d20fe64c8488032da4a 100644 GIT binary patch delta 183 zcmV;o07(CZ0)qmORDYUDL_t(|UZv8p4Z<)Gh2cL@Qe2nTo!g9%E+b?NHb7zk8k)0H zF#!@2FdjN3*s_d-fGfY^S?|s~?!{an7Lh#`hFr58};DL`-lXW;tjzhn#o>Mg?uN1K5~PcmFVC*Yx^oDoZ`-w1HI n{-VNyOn0`z+jlt}d;cB2>iX@yZ5EK`00000NkvXXu0mjf84Oei diff --git a/recipes/icons/deutsche_welle_bs.png b/recipes/icons/deutsche_welle_bs.png index c0c5b8c0eb722ea657c3df60b9c714022273da36..ad9400bdb524ea92fce6d2b003e48476a37be2cd 100644 GIT binary patch delta 432 zcmV;h0Z;y(2iFCVBYy$cP)t-s0BE`Y|Nj7^`~al>0Hyx`dd&ca+W>LH0C>v)qy63S z`~Za60E^)Oe$oJe)&Py;0FUJWf71Yh)&P*^0Fvndi{Jp2?ErSj`TYI>h1%Wj_y>&L zmCWt{h~5C3^ZbkpCUU5r0}i@#@PVQgd+|M+)$+k2sJ+>9TD-iW>vVU*`# zLZ9MmdHY#(D;^JcwxNFUDz%ML(x8W->DTpTU^U{)>^3nUR6vKLaC3{eKY2$ijdM aFa!YH<}cVHE!PhK00007Wh~N8DcMyt~Z@_F>HVe znxh~a;_xi0MvCLsG$Do{y2bT-IpvCA?FEBJVRaw z6kRBMfubIqHskCosQOXz8W)C8HjIjQxcDAbpHMxDyiVkIfo8@m5UBcao*5cM&s|aJh9e zkzA9@HZ{kou^D=&^pPx`+O&Wh(_8QU>9jj+zg^~WXY|9_b$8|-jW}kU?gh7JaMGFB zK8q$d8XRbJv74NJ%}yValiNIxX3sp|!DIes9o@S}nBZ^SIn6X2S}YN9#yEj&en7ZY z6VF6iu}~ll<{jx4EGv%>t{nYo=q`NYkgsZ8p{5cyO~Q6d;xBrc6CiB05HY$sts|~V z=Xb_b`>UbB(tWtSXL-J8x6y0MK>yG*>1|}016_p&2lPJe_w1UJ;p~*6RGv4Px^|Z{ ztcC34PuTCT4pAwS8#`)cadULg^R`;!<7;Wf%Cv2kVsZNV^qay^MSbrYo}zALo0_>U zj20cDvomQKm32zS6p#c-Mi4O(Bq=5MRP=T#IyRgjD1uPgd0zhkxgtCFSn(1dqNKzQ yiXbVHzX%lESS0Hyx`dd&ca+W>LH0C>v)qy63S z`~Za60E^)Oe$oJe)&Py;0FUJWf71Yh)&P*^0Fvndi{Jp2?ErSj`TYI>h1%Wj_y>&L zmCWt{h~5C3^ZbkpCUU5r0}i@#@PVQgd+|M+)$+k2sJ+>9TD-iW>vVU*`# zLZ9MmdHY#(D;^JcwxNFUDz%ML(x8W->DTpTU^U{)>^3nUR6vKLaC3{eKY2$ijdM aFa!YH<}cVHE!PhK00007Wh~N8DcMyt~Z@_F>HVe znxh~a;_xi0MvCLsG$Do{y2bT-IpvCA?FEBJVRaw z6kRBMfubIqHskCosQOXz8W)C8HjIjQxcDAbpHMxDyiVkIfo8@m5UBcao*5cM&s|aJh9e zkzA9@HZ{kou^D=&^pPx`+O&Wh(_8QU>9jj+zg^~WXY|9_b$8|-jW}kU?gh7JaMGFB zK8q$d8XRbJv74NJ%}yValiNIxX3sp|!DIes9o@S}nBZ^SIn6X2S}YN9#yEj&en7ZY z6VF6iu}~ll<{jx4EGv%>t{nYo=q`NYkgsZ8p{5cyO~Q6d;xBrc6CiB05HY$sts|~V z=Xb_b`>UbB(tWtSXL-J8x6y0MK>yG*>1|}016_p&2lPJe_w1UJ;p~*6RGv4Px^|Z{ ztcC34PuTCT4pAwS8#`)cadULg^R`;!<7;Wf%Cv2kVsZNV^qay^MSbrYo}zALo0_>U zj20cDvomQKm32zS6p#c-Mi4O(Bq=5MRP=T#IyRgjD1uPgd0zhkxgtCFSn(1dqNKzQ yiXbVHzX%lESS0Hyx`dd&ca+W>LH0C>v)qy63S z`~Za60E^)Oe$oJe)&Py;0FUJWf71Yh)&P*^0Fvndi{Jp2?ErSj`TYI>h1%Wj_y>&L zmCWt{h~5C3^ZbkpCUU5r0}i@#@PVQgd+|M+)$+k2sJ+>9TD-iW>vVU*`# zLZ9MmdHY#(D;^JcwxNFUDz%ML(x8W->DTpTU^U{)>^3nUR6vKLaC3{eKY2$ijdM aFa!YH<}cVHE!PhK00007Wh~N8DcMyt~Z@_F>HVe znxh~a;_xi0MvCLsG$Do{y2bT-IpvCA?FEBJVRaw z6kRBMfubIqHskCosQOXz8W)C8HjIjQxcDAbpHMxDyiVkIfo8@m5UBcao*5cM&s|aJh9e zkzA9@HZ{kou^D=&^pPx`+O&Wh(_8QU>9jj+zg^~WXY|9_b$8|-jW}kU?gh7JaMGFB zK8q$d8XRbJv74NJ%}yValiNIxX3sp|!DIes9o@S}nBZ^SIn6X2S}YN9#yEj&en7ZY z6VF6iu}~ll<{jx4EGv%>t{nYo=q`NYkgsZ8p{5cyO~Q6d;xBrc6CiB05HY$sts|~V z=Xb_b`>UbB(tWtSXL-J8x6y0MK>yG*>1|}016_p&2lPJe_w1UJ;p~*6RGv4Px^|Z{ ztcC34PuTCT4pAwS8#`)cadULg^R`;!<7;Wf%Cv2kVsZNV^qay^MSbrYo}zALo0_>U zj20cDvomQKm32zS6p#c-Mi4O(Bq=5MRP=T#IyRgjD1uPgd0zhkxgtCFSn(1dqNKzQ yiXbVHzX%lESS0Hyx`dd&ca+W>LH0C>v)qy63S z`~Za60E^)Oe$oJe)&Py;0FUJWf71Yh)&P*^0Fvndi{Jp2?ErSj`TYI>h1%Wj_y>&L zmCWt{h~5C3^ZbkpCUU5r0}i@#@PVQgd+|M+)$+k2sJ+>9TD-iW>vVU*`# zLZ9MmdHY#(D;^JcwxNFUDz%ML(x8W->DTpTU^U{)>^3nUR6vKLaC3{eKY2$ijdM aFa!YH<}cVHE!PhK00007Wh~N8DcMyt~Z@_F>HVe znxh~a;_xi0MvCLsG$Do{y2bT-IpvCA?FEBJVRaw z6kRBMfubIqHskCosQOXz8W)C8HjIjQxcDAbpHMxDyiVkIfo8@m5UBcao*5cM&s|aJh9e zkzA9@HZ{kou^D=&^pPx`+O&Wh(_8QU>9jj+zg^~WXY|9_b$8|-jW}kU?gh7JaMGFB zK8q$d8XRbJv74NJ%}yValiNIxX3sp|!DIes9o@S}nBZ^SIn6X2S}YN9#yEj&en7ZY z6VF6iu}~ll<{jx4EGv%>t{nYo=q`NYkgsZ8p{5cyO~Q6d;xBrc6CiB05HY$sts|~V z=Xb_b`>UbB(tWtSXL-J8x6y0MK>yG*>1|}016_p&2lPJe_w1UJ;p~*6RGv4Px^|Z{ ztcC34PuTCT4pAwS8#`)cadULg^R`;!<7;Wf%Cv2kVsZNV^qay^MSbrYo}zALo0_>U zj20cDvomQKm32zS6p#c-Mi4O(Bq=5MRP=T#IyRgjD1uPgd0zhkxgtCFSn(1dqNKzQ yiXbVHzX%lESS0Hyx`dd&ca+W>LH0C>v)qy63S z`~Za60E^)Oe$oJe)&Py;0FUJWf71Yh)&P*^0Fvndi{Jp2?ErSj`TYI>h1%Wj_y>&L zmCWt{h~5C3^ZbkpCUU5r0}i@#@PVQgd+|M+)$+k2sJ+>9TD-iW>vVU*`# zLZ9MmdHY#(D;^JcwxNFUDz%ML(x8W->DTpTU^U{)>^3nUR6vKLaC3{eKY2$ijdM aFa!YH<}cVHE!PhK00007Wh~N8DcMyt~Z@_F>HVe znxh~a;_xi0MvCLsG$Do{y2bT-IpvCA?FEBJVRaw z6kRBMfubIqHskCosQOXz8W)C8HjIjQxcDAbpHMxDyiVkIfo8@m5UBcao*5cM&s|aJh9e zkzA9@HZ{kou^D=&^pPx`+O&Wh(_8QU>9jj+zg^~WXY|9_b$8|-jW}kU?gh7JaMGFB zK8q$d8XRbJv74NJ%}yValiNIxX3sp|!DIes9o@S}nBZ^SIn6X2S}YN9#yEj&en7ZY z6VF6iu}~ll<{jx4EGv%>t{nYo=q`NYkgsZ8p{5cyO~Q6d;xBrc6CiB05HY$sts|~V z=Xb_b`>UbB(tWtSXL-J8x6y0MK>yG*>1|}016_p&2lPJe_w1UJ;p~*6RGv4Px^|Z{ ztcC34PuTCT4pAwS8#`)cadULg^R`;!<7;Wf%Cv2kVsZNV^qay^MSbrYo}zALo0_>U zj20cDvomQKm32zS6p#c-Mi4O(Bq=5MRP=T#IyRgjD1uPgd0zhkxgtCFSn(1dqNKzQ yiXbVHzX%lESS0Hyx`dd&ca+W>LH0C>v)qy63S z`~Za60E^)Oe$oJe)&Py;0FUJWf71Yh)&P*^0Fvndi{Jp2?ErSj`TYI>h1%Wj_y>&L zmCWt{h~5C3^ZbkpCUU5r0}i@#@PVQgd+|M+)$+k2sJ+>9TD-iW>vVU*`# zLZ9MmdHY#(D;^JcwxNFUDz%ML(x8W->DTpTU^U{)>^3nUR6vKLaC3{eKY2$ijdM aFa!YH<}cVHE!PhK00007Wh~N8DcMyt~Z@_F>HVe znxh~a;_xi0MvCLsG$Do{y2bT-IpvCA?FEBJVRaw z6kRBMfubIqHskCosQOXz8W)C8HjIjQxcDAbpHMxDyiVkIfo8@m5UBcao*5cM&s|aJh9e zkzA9@HZ{kou^D=&^pPx`+O&Wh(_8QU>9jj+zg^~WXY|9_b$8|-jW}kU?gh7JaMGFB zK8q$d8XRbJv74NJ%}yValiNIxX3sp|!DIes9o@S}nBZ^SIn6X2S}YN9#yEj&en7ZY z6VF6iu}~ll<{jx4EGv%>t{nYo=q`NYkgsZ8p{5cyO~Q6d;xBrc6CiB05HY$sts|~V z=Xb_b`>UbB(tWtSXL-J8x6y0MK>yG*>1|}016_p&2lPJe_w1UJ;p~*6RGv4Px^|Z{ ztcC34PuTCT4pAwS8#`)cadULg^R`;!<7;Wf%Cv2kVsZNV^qay^MSbrYo}zALo0_>U zj20cDvomQKm32zS6p#c-Mi4O(Bq=5MRP=T#IyRgjD1uPgd0zhkxgtCFSn(1dqNKzQ yiXbVHzX%lESS0Hyx`dd&ca+W>LH0C>v)qy63S z`~Za60E^)Oe$oJe)&Py;0FUJWf71Yh)&P*^0Fvndi{Jp2?ErSj`TYI>h1%Wj_y>&L zmCWt{h~5C3^ZbkpCUU5r0}i@#@PVQgd+|M+)$+k2sJ+>9TD-iW>vVU*`# zLZ9MmdHY#(D;^JcwxNFUDz%ML(x8W->DTpTU^U{)>^3nUR6vKLaC3{eKY2$ijdM aFa!YH<}cVHE!PhK00007Wh~N8DcMyt~Z@_F>HVe znxh~a;_xi0MvCLsG$Do{y2bT-IpvCA?FEBJVRaw z6kRBMfubIqHskCosQOXz8W)C8HjIjQxcDAbpHMxDyiVkIfo8@m5UBcao*5cM&s|aJh9e zkzA9@HZ{kou^D=&^pPx`+O&Wh(_8QU>9jj+zg^~WXY|9_b$8|-jW}kU?gh7JaMGFB zK8q$d8XRbJv74NJ%}yValiNIxX3sp|!DIes9o@S}nBZ^SIn6X2S}YN9#yEj&en7ZY z6VF6iu}~ll<{jx4EGv%>t{nYo=q`NYkgsZ8p{5cyO~Q6d;xBrc6CiB05HY$sts|~V z=Xb_b`>UbB(tWtSXL-J8x6y0MK>yG*>1|}016_p&2lPJe_w1UJ;p~*6RGv4Px^|Z{ ztcC34PuTCT4pAwS8#`)cadULg^R`;!<7;Wf%Cv2kVsZNV^qay^MSbrYo}zALo0_>U zj20cDvomQKm32zS6p#c-Mi4O(Bq=5MRP=T#IyRgjD1uPgd0zhkxgtCFSn(1dqNKzQ yiXbVHzX%lESS0Hyx`dd&ca+W>LH0C>v)qy63S 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 Ef=v*;_y7O^ diff --git a/recipes/icons/diario_extra.png b/recipes/icons/diario_extra.png index 6bf53ba09d1dae09cbd4368250e241182314cc65..270bfa3433be4121ae9f538a44926f07d261b420 100644 GIT binary patch delta 123 zcmeyt_?U5m%0x@Q!iu=FCmaP2DDb#kjEdln=wQ`3!X&n#0Z2Ikk#0g`UV0VJ_jjq! z_rK2Po*x`K;gFf`k7hw=3>fe6;HSD)mwtdd^2X^UoT?lQK;Y@>=d#Wzp$Pz4dNuk0 diff --git a/recipes/icons/diario_la_republica.png b/recipes/icons/diario_la_republica.png index 6f1eb152695de640618d7a9024b3ade4ab61371d..9475661e9e5a97e1388e7faf2e15142669ac598f 100644 GIT binary patch delta 1095 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 F{{YJaAJzZ> diff --git a/recipes/icons/diariovasco.png b/recipes/icons/diariovasco.png index 2748d14a944d89fad5a325177ef0f589e64408a8..0d5a3b736dba0555863d0ea3c64dc6a35c00e75b 100644 GIT binary patch delta 282 zcmV+#0p1%uOpsMFxZ}Fk3>v)FcP-FA0x8p)r>1%rPt+wGbO6`V{`PbSq8zp4`0004W zQchC*}^}M3XEnq zZlnfaFrS!{fuA(A0V($5LX%-oZO#kjt7d7=o}a z8$Dof6wnHV)L#FC5}cM8s`$RbNpJ{1EPwz1|NG?U`PbU{*4y{V&-J^)^R2h@t+w;7w(+5=@t~^jlb-E{ zlIwVf>v)FgYkTQydgonl=Ur~)P-Ep#V&g(r<3d*9HA>+$O5Z0w{qgf5oI)l50004W zQchC7G zesb1izdgp%<$$(wWZTC>(hkk8bYEssg1PLV`*Qoz9g0gW^c;~h`his$gC0H)+ zeNYpE7T};enB`m00vvRE!mi2i$=c}l&bd?}73PHE#b&KZbBdIGNtTp9Ygl$QP3S0NzauZ%Y&5oB#j-07*qoM6N<$f?B+pFaQ7m diff --git a/recipes/icons/digit_magazine.png b/recipes/icons/digit_magazine.png index 0c3cfd1d914b65152ac756c6ad9216e2ae621579..83b2f91b54f0c20b7d21cc48cc61ab598ad3cf54 100644 GIT binary patch delta 686 zcmV;f0#W_H1-k{18Gi!+002a!ipBr{0ccQ6R7LI_CIA2b>;M4n9w-0){_Y?s?jtPj zA}Z|w4DA2_|NQ*^`ugq~BkceQ?i?rn@$vut{p|(`?F<<03>Nsbw(b@n?EnVuEHmv7 z6!)mA_ob))`}_B%sr879?kX_wM@;uj~vA?>Rp4O;Px; zv-!Qf{Mgz0$jbMstL`8w^o5A@e}ew+@azBy?FkR^XKMMmyYz&I^@fS=9w_b-82i`Q z?jtJrxx4NxE%l3y_^`6=1`_dBTkkM8^oEJ_gNFLg&+ZHu?ie8Sh>P-MX!e_(`MtjF z01x`Y#P_77_ij4M|n)$!M^LKmx{QUmy?e8!)>i_`tjga@Mt?m*Y?>9dF z_4WJF()OI4{_yet`ug{+ulJ#(?ie2Tudx32_x||!__eq6frb9=?)~ZM{p{@brKa#k zOY&!H@k>zt?(gm}H}|Hf?GG64A}H<_BJLO>k6%Qa0Dk}h4oO5oRCr#^(nGTaF%X8~ z`H~EA%xl}WZQHi-_iyH|J7;~mvg@bbMXI%Y9bM14HN99oqy{`@@zT0kuB>O~o66i} z+f)IARRZYF1$rXoB4Hae;DaSKt#C2QXkb>b4pt>WHC@nCwY~KXjd0LR7Fztab_C-f zOBKw{u76pUEkpL?*8HN={ZUOjeGvf>zkGQNT&^tz9$Cr%Zh*VAq%g5t?)4?+`Tv^3ZnDJ6>HLA(^bSZ{hP?tjc?)7|WkBop6sYu0)Y z%xu9!mth(9@cqnt@4N2}A|nU(bfJB~-U5^iTk!)xRMp9_hNU!?QehmW0a-)Rs8@7`mCSU~Xrw5)-UbMJod*W<6Q#SgG= z-s*L`)c1RodVec52GFwH0LZeY6Fp~0l07*EXNUa2+pE4XVgNt}hGY;jkO=RsOIP{; z_xTcc1W;Hcm`BE(!n9Xi_UrY|##fg9PKN<1&-fxxbc{s%>UC;1dVf42pj_dIK-ICB zXil8}U6B%#T2=%ol{q4iGbEXAf7xKV?KUgv>Ke%&bbn@MqZ$;S3<`kM3rXwr8So-o zplDfUF3)UQn2rMwTK-0%fBay5oj{WiM0;u`0+gO|0a_2|`&xeeHAV@jxf3J6T_{HX z)N&0A60|wz_MLbD;J?;`IViwR1aIDgIyAdo5tJ)~0>Gn4*t&R`X@v&G*vuXN0nKjq zZUm?-4u1-O=984o{1|l{Xl)@PGG)*6UcR!gO;SR-o10z3aEm1{E=MndM8WJuV&mv|bK-(Eev%E# zltOZOl0C#0NDpg#GZ`3A@C2K~Y{aV>_5~Ozl`EvxzpMYtzzJ^>{Ywfk!nJ?1`~Lwy Y0dg6lFWgics{jB107*qoM6N<$f}Nx~GXMYp diff --git a/recipes/icons/digital_arts.png b/recipes/icons/digital_arts.png index 5167b65d4325c86bdf0855d4711eb57bbceed762..59b4005649ac641ee00cc3fda53bd3c5d94217a3 100644 GIT binary patch delta 3581 zcmV9RC}TB!9+9L_t(|Ud>o}a8yTn?|00lnUOR)&;ddMWOHi~j=^{_$EFO4 zVTp}{SyPz9B*nsFu${Q<1fK#^1$GtK6>wpU&DyDKQuczeT@Y}*ti=YI!xBac5XlB1 zS;t7Dk>)(!)%nfqd2ePUoYd|=@i*Q5dir(0{`GhD*Ug2);eX@Dk2joaAROVjBoWRa zLP+XAs~9EW^dBQuGl&}5Ec7R8`VUVOMn$9S4JydBwYAG1ewaUY?AYnkrvrhZU?30( z23by}AEJ<1389|-%E+wp1Ef!aLQi@H#g|`x`LDnE4PXCdeIg#$KvdIIy+qC;l@;pn z24oc}tE0-Y*?)ySh7%nz2RNiC%2!`~#jC2Ke;H4F2w6{hMh`usZV(#SBa;-?mL6z! zVPDZuroYYl+@1hkG%CPIp&>$&ctH>ap68GVMm3>#1bo0Asn8~AM4NicJ?pyyEvuPeOh}Ykiu{xg14IBw71Sh$ zWI0h)Rkd-`W~a+ZewY9~9#8lOe3zRppZm{002~^@tl@x?tR@agxU<)#5^4f6`&11$ z9Z^1Pm4BJ_rNDqNfy#?0K6FCOaDR>}JhiB)@7Wb$oX>f=u#c)-rek8Z z#Uq1YnptQF>Fe#i+}L#S!o{Y>#`YUGqTw(`oALoNP7v=~uwdmcej!>cFg|=jVTKRB zOLVd~mj@92Q&Ef3QtZ!Ew~4+yYn)d#{fA_lCB`ybD6>sPJNDb(zVzY?yLRr}S-0cG z7k^&Z@$$<@Kl(7>_w!s98|N)p@Y8$mm1Rj&O|w?qQDJnz$+!h^_UFXUmG>qDl{2I^ zHs~%1+T~P#MjGRb!69w9>P2-*z-h*!exL8kl`HSP`|j&|_H^F5#b@xzVzvI|k|iEb zfh?!KENg1zfU_`e!^Di)ovzUJ+`%DRf`1t$T3xbOHRMtlE3%!E$srSwIa%8MA~7qw zc=6()g9n2{0aJDuRxx($<)#l52og~; z;4r1;Sxp;mo&-&4$r?i-BVdBf^CV3ci)H4_nedhhNLB1{Zd|}k4PAt!I_$w1k$m2Vlg$k%{Ax~B7cz0_qL$V9yNWtO%rAejv^|}l3Ciy=m-x+snN~s zoT`LEp=cz+Wmtd+v56wyiZTaTn6rWttxms06)4t#5WVj71ET3aCx;4ManY24#;gok z4?2cBMuuQG=_)Qsk}OHtUX;TGvZj;UqH}^YcB> zVw%CBp~0b0IOIv#riF@4|A9w=uCIpWTB=Oe(*&o3`4$5ypw&D#BMy|wo7$E|j|iGN1q$fXPCj~zV95vtlgQDSt&mwUIvLV93p#wMcIJ(zuoxDzh1Ed+Jk@} zX616bd4UIP6OF);Z`;1zvwL?>Pme>e-V><+SCo_o;`)The~DfK6h`pC*rXAy+0ql? z6OOdh$aJa9qkq`@xiHfqK)a0&6+=QLqcY=HlZotb)N-YHN8Kx5U%Es@zDUZMD5VV^ zfW8+3UM`qaL||Ycs)6j9;QdbU*Jx!-{QDVsM+ApQ(@)XlMxQ6a@j;ldL6FAi_!8wew;J>g+rO zjcydb=O7aNXzIt%u*zBS##7FZEkvL+^&-y6x&tEIxT;G3fS&_!m>kaw0usRRK*@(Y>kS|bHoUoc+rtsKMCKED6nK-J1vD~zsZkt&fOs8;ip_HF z+__7ZEM2sC@tqSV0s@p~-n@CMR;@zpWd>+SIZo&L_3LYDY9uKxOR|=A{Q^>czHHgE z&wo9K%Yp}DlU|N2ek3*nykNeUbwequimb_OcT6RU!T=;I%AehR_pjf7f8QH#y!QI* zAAb1Z?$=%`Eh}5NaN%Q*J?3(|5RM^~Mj4OC;Vjp#TQ~cj*%PYo*tB_bVPT;p0lb6* z6BG_HES9lm-P#J*sK1HLUYD4?IWf0X!GDPd2o&%iWkOSaqLiP1sY9s^l!fTW@kso zueNM?=%I&tdwc8a>&J{v(tzK8xv5Ff1wTf~DKBF`st@PZ`m?`{`|bWylvaIEnT|wAD@5zS5G`~`0(Ma z+qN;yzFQls68e$Q6efw8YK2o3Uw=wGa4_-cp~R}cll~knMbx!Lng4TT+F@z+bMm5J zDs%1B0wl)XiKK@aFrjcbf=i$rhajqjy?XqF2~R!w&}v}h47*C+<)jtDxQlan)N&YU%C)HilU@r;((6Q(8cswX`Biq*uv_&q(tFbbs1Jtpv6vGGSqH zm>zm6>$Gdvt|RXqd8MunLV;$ub8|Ou-rVQ)N;;ttC)dWtM%?rL{WH&;J$n{IcGT73 zbT&9R7#RxrzPQ#C??298xlB5JT=+pcqeiRH2nmn`z{o_sL~s2CnfkPLKRVn;s_jHP zi!hkBP0fW17xwSp zkNpIC_Rxb5LPo8vt%u)v2V>C!7RF<-eO?+T;a2K}OT0w}HjwvWSrkkqT2PCFz5hZk z017xUKq4ndGhVfkelH2Sh>Mh#mXiOKYG=$ifBrnKV*vEjsdiJ4CfhT3GYZJAn8c^j zA3<1;_nsq{-20V{qXYThynix?i?#>SUCb*00000NkvXXu0mjf DfrR$i delta 3613 zcmV+&4&w3u8=f4HB!A^eL_t(o!_8TFa8=bAKj+^2_9gGVBxE5Q0m>3U0wq8hF<68O zQFoG$S^<#rX_?{X=2zFK*aVinDNRjP)zyB#-|cqW?SFQw%?8k5yevtXOeP+W2L}ea zdwPPwU^Ef|_yGGJgw`8Dr0KS&=QWh3}FP0zfh^GYN@euA!mf`QNN@ zxn1a+8$i!ybK&98`E%z_{_Ar92c4XW*9Dux$dTuNFyNL!x+4L>O~bIuk>-+CvA{z_ zG@Z#DNPmc_3?AeKK`z>6QNx_w=@{*-9bHp1XYSm?hYlV&d^nv>g9>$1P?w~jFJIg? zDrTd1O$YcY%E8 zFfh>9FG`Xi@IIffeAFnX(*@9|X8Q>9!u_qSq9|_PzCEAInS2uEBs?kT+}*%}%b_xaOSNnVi$tnOImRV@hJU`|mfR)h zAW^88BjQ<4zli06%g(uK(;gV58jfLQnybDChls{}GJ;A>xe#Z}UKINCxx6ULL=WJ6F<;0Yt1@eyae2sZp85MK^PuAwV zl^NI9_7KDwHM(+|^P_h;M#ifVMrIIv=FFKTKA+hPIy*Ze@mNE~SAUsznN?xr?vbrCl2s7RK+izd+zZkp1`bA7oIdb)2R z{KmmWt*OQW@g>jHbj2CH_gE-rs-U8ya?nu4C{5ma z@@nt~h;iw{#W&hsKYQj361cLwb3(pg@Bruz9}D6*BO+6aRM1P>9nevf0Dxg}%2q>zLmz+i(SLi}w%@#UQxF6QsN-{`WwO(3 zsOPM`37X-gku{03Oa!GaTKbyBYN9Xcpq6R5>QDF%5RRZ`VJfLPy;?7-w%|9&zaqoK zy}i9Bjvt5A_R5tjk|;ts$y^yib2D{TDoENEFnHcGLXSjcfpW(7bY3HP?5xnU0^i3o zda+&((SKQDty{K&rf}n!F~gxyO-;?B#fz(}t4qtuyd_>BGOy(G;A6t!a9?k4S65eO zXJ=nuUo;wpeG1?X6z>f)@`#_6uL;O5CLyH@7=7B>9p|#xsHAD(7=rmjVsFXUSs-)N zY6b*kKPlXl9XuY>P#+!+!$`=KAW$fEG_O?4+J9_n*n@#eZLQSTMir(>5-R~gL!%y8 z!0z$L;|jCZ;7c+4!Z8Ny9?ah1aHymvEPr6p6fyw}ZWBOZ#S=2y=2_vP23R(i5<@U6 zM|h<}VHeeduW{OgS$VJeM2=$fDUJH!DMse=2#OUTgfH=WQ^{mWNy!gq&6+xO+L*>h z$bYtC(I_bEf!5Y}^X7GQbO3>ne!k>#yPtaUNx1C2(*v7PPZBiOsn8MJ)uYRoU%qmA zL8RfS{LBj3bA=1aR6SczPiBau6k%Ect#+|kMZrYC%TcHX0nL`JTj4P{R{)iKa`fn* z-+F7of(3Kt%sG7cP%@FAN*NC_gBs(R)qkrkHY*%IEMB~L_pV+2{R1E(!YX=p<~4u* z{D+q=Iq;7Uf0>!xjOuQ4;m^4n@Qrjn0kj!%?R{kQPQE>b@>GfAY%B-~Q9<3Ag?VKA zh7B8z9X+~o<;vf!U7Lu<_wV0-{`^(;IFdc8}QE`Nn0zrL<+$BrE;vuBYysH1oB=Rgu5k0bc%YxyMs z>MTKy8#pwTRC|!^1?hp^^xQgWfr#XRAsK%;hNdjabunimDym9?H z*n!LCve|7VDFNrake0qFJ%8e+>`-PRsem=Qarp1t=_tyiAn8R?4~^_aU$x+dweo_g zc=Vs}he<^AL_#^wtFNzrVE+8Jwl)B?ci-MrDh2j+?b#XY2YohZ%PBy z)l6&CDE}tf|1r9_gie`2Dr9i(sgfDk#GXiU^K{%T&pT8`v(RBfxv_b6M!EuHKY?z zn?jHR9YHeR-Q5i)2Eu4*X<5I1{Y&fDLwOJJ1bEie)C4*LM+KsVvCsn|%w{s4Ak9XS z9XOanGNi5JX$6ZCAXG#;Q4nGo$ZRsI0!a*`R1NaMPn~Eeh=1ZR9933UqHk@@)2E+4 zeHxBq0O-Mk2SJ-^%-~E_N703zglO;Y-T)8=K)~N`c?kHiaN)wSjg5W%{l|_SgMe== zPtiA?!v1&dJ4xt&*#Hp7-Shty-(TZ?x@;Nn@&5q|1Xl1Y!fUU-3YNTU*DjVceqYj3 jeB0thi~hW6(-waKK}-jSJbv1B00000NkvXXu0mjfGIHf9 diff --git a/recipes/icons/digizone.png b/recipes/icons/digizone.png index c00ee4b75c5bb67208d10106b75e39fad9d3b7dd..2abf087a3adcd7442e4d58f33a526d8dc1acb83e 100644 GIT binary patch delta 216 zcmV;}04M+c0sH}wEPvqS=j@c2=d`)v*xvGHYx*cE{{RQ5xX#Yl;Rio_`7}7<&d?rH ziAipo{1zMVa(7`aVATKs00DGTPE!Ct=GbNc004JML_t(|US*HT4#Y4FLJQuqxBvgO zlO~!&6`$-Uf#JHI&&(S@Gmmg<0G<~xOjN~!8}Js^h)TKnf>9P?PF1=|v&JYNp72{xX#Yl;o#)w;?B_G*xu)~x$Kmf@N#$ZW^4I0 zIQl3m{1zMk00-%q#s&ZY00DGTPE!Ct=GbNc004POL_t&-S7nb&62mYEL;b}dW5WGU zTii5@#;i1NMnXzy{#{Cwh=!WG=?FPDfU7WzI&OeVu-q5q!czvrMGed_6(0dqVcdHs zHmC+LS)iJ8=~!X`oSi5)DwRXtfLrcVAMmvlYPtO^JCq`#J)y_jqu5#fHSRtC11Z%B UvxkKD6#xJL07*qoM6N<$g2``V)x(i?vJgS4&GVl+SZ@_%5E%t-?#5*0GDP9i7czrm92PA@*I_Lm!UH%QJ-_K4X Z@&PexnWi7R`+5KX002ovPDHLkV1nsLN?iZ| delta 172 zcmV;d08{^p0*eBWXMc}LL_t(2kz-&GL;(y8jHm(vXbONz$P^Ikx_kFz5RyRcT_Cuf z2p15)3kHYb0=W=@8*qVMh`?P%n84b*cU@e70*NqzyLT@N3d*7gKvWz{LtFLZLMQYfwRrLQM>U z62m_l^ub%icqdVL(ZGY3_+lanykPQ&h_plq0Rtsc62z*c6|{k%LZPkHwm>N@z3ipC zot@h`$8XLoo9%|BV!|(zGv9nO^ZRb+JLh1=&~1gp$7d00Kz~T&vVf2eA%)8(V>+cw z5HQA|X8=kmLRfHBh!Bt&moez!EynQZaT0`%BXrPv0QR*A$y|0gJLWP(M+E@^R|Xit zBY0>*;hx$+D=?j!u0hu z5x;8fI&kvH&*up-RRv>gUmck^+Sb+nTW>OzvTUQ{bpH=+-Q&?XA=a7mgNJ`S*V{kn z@i1gCW@QXu4CR%*%P<8L4!-#EL!W-~)Y1p%?P%`*|9|eD!C@V_kS`7C8@G3E-P09K z*oJ}j9Qyn7ZGR38rwJ90wheCBe7fV0(F_%nDaG8tbbzirAeEE_XH^6j)KxsWq{`Cv!ZVM{=arNcTMSLWqIJyyF!(vDxX@oJgnY1$1lqQ$e|(yEzkl! zDNBX%jSRq~Rz#dgnu$pR{4FchYN|q~&PLB%h&m3{{BmWPza${@MnFZH94S#n(Pi@c zq)3ITs$xE2-ZNv2Fs3S^s$_gg>6X7tEiG2TO@FX4m3AOkyyuYR5Zxq{=fbgR)F6(- zZrI8B0QUsJB>_g$B-O=ANs$anN*P?aS%xbMi+V!g2=e{}mnBIMM4YyWWmB#Jpj(1~ zZVHc(=6EW)!C|kd;iV{WZ+KWi5d&s6P&g(C0ZFnTiEge4Wgz%u0oWZ%!52*xz~0O? zD1THH@SPGiJZ=~k31~`DP)($)?h(VMN!tfwKT)^0rDx~<&c_>S*1fj) z_jA`aeRZ;;O#5uzl1Cn>;mfnPbKrsz5W;xbuq=zvqN9_xbag6`GNA$I*OoKF-v6RQ z5YfA97SF8?H|^{Ey0Ldf1R zxv6K@Q=v@T+wo2MfKX}?7B2RM!y3dfd@b2`F}8eR>8d4>l46xHMhMk)dv>|6ros<% zW-@66{8FSY0D8zLrEG>ipoIBETr;~k9P-5y`o^sthkhD(%`pXT)p+G1I z0?28{H74?^=IyOgo(f8eQqHcOXX8Ut^A|N$+D9eGjs8N1dRz`WQXME+F11n!`?&*t8>CQ(R+iRD9v#+I_aS+06 z$GvmfjDaRr0Kn@wFlPSrFmE=&4!0%`x27gy%(B^uQ_i<7v7WwZK5;Yxxz`|@vqwk9 zY%XL_V0*#Yvn9WTtUUebxDBBXyc8ejqsT{xQk=6|3HdfMU#eU%5uzY-jm1f!RJn&= c9>HzJKdnN%n>hd_Gynhq07*qoM6N<$f|#}4DgXcg delta 1609 zcmV-P2DbT!4A~5jBYyw}bW%=J00000003^L2ax~(010qNS#tmY3ljhU3ljkVnw%H_ z00sF;L_t(Y$F){jj8kP4{{OqSw|hICw#))E#mqQlSQI1-1cH!p!D!S6xA@}9i#{2R z4;p>&fka=$1SKXAVvM5Vi^dQFK_wa#X3We0&H%I1ndwZo+kf8m_x%5DODP>7Cfqc) zr>FOR=bY~>|G|WyyLt)i{uDx65#pK5A>=|xVzNevYTJb4Fvg%KaM`wv5ay@~Sr*8I z$r$wTFUIiG!L$%MjnH?_4?y3GkjP|{p<^aPbT-E!j4S?N1V8CF)6~jvXM$0|G1#3M zAS8&e;($y{i+|^b@(Yvm**w~?sb$+{=V_!HNE8?Zz8*eqAd3|R2;nkCww>iT3t>vp z#nDS2A3QgFd^D$d-u>|Yb!#5*c^xOn4cN;pH9u4{Pk*U$bP9m z?w0jK-~Q3j7UO_5YO5Jy0aQ~5^ zbjB!^(2-;Re)QS7shOf>^FN=Qc;}O|XZ}u=p@?YP*r}NtF3>f3gkZ$m9QAf5qy1Z2 zTrU2XQ-5P;2d6aMf_gbIymWeSLDy{4CL?2+Q)d>`q6rl=F_RnmX)cvkuaiYk0|Pvd zOBT5@zO=BY1_SQ5UfcY{?&N`QE*w5OR8X~8lV{s%e=IDs(!xz4c}2Th6lt0IeG=+c zJT9Rw?LwUaC~AqvnVgo%mLee~5>{5W`2QS8dw<=$Y1)cgj78i_Ih{2E@+o3GD1%S% zqT40Jqp~dX^?>z7K!`<%Eb+2bww+gXcSH^cWN;H~6pAKr#d;3urlo3@&A2cPJ6*C& zlQisPbWEz7l&Mz4xK!h=a@GvEHB@{K6i(Xu$UkOB#4~AQ?7t}5L_Y$=uoHN ziz0J8hwdl`4YLBi+ZLIbFO_u5qewoloXP9Mvn7`-cohjIYwEIEC>sCAfKAx2u`mrHnTYs=hX>EhUYYQf!; z4Ea1_YA!GEoL3Q_e7&=yEmX~xlMi8;+|rLzrf~Nwd`Y*V^*Y+auRgzi_~P7GKaPO0 zhqreG{PM})$B!Hv*}Ojf!JAu}qY4|(h~p`%%O=^f2um9lMIKr-l{GF;7czMb8h@~> zv#C4T^!=&nuaA!>SA{mNZ5bQSe17nf&nvC%iN~XUh`?qut#Jgj@-Ws7OVdq9qbL#> zfca}xx)g#Ez(HD}6)GfL+j007Nw#cTw@4`93WXGiBQ>8Ny|TEgH{71^1OqZVlZmPt zZA~sg;Gt%+xst~%#FHMVBH)wJf`4ySMNo(?96S2EHX z_dD(iBAsX!Z8QXf6lx57qJPn;it5%B;pP)>K5NX)W~QbxqZ5n24a}{KD?9sE#hZOq z4xzv3Oay;1ovI)gO=^s1S#4mTRs|_VdGpYh14~ndt_|_M&-8S(`eRXVU4NeFtfi|A zMTA0#xOoS?@hqyHA1!g;Lp$PLWF7rs0!VoF@ijZQb^?@IGN2F2G}<;C2}=dtrh5@8 zmdwc|9Y*)Eix)(RaJj|1lfk!N>0R8@l2=jBx`g6(I5atIRw#sHKpUD}0>4a4vx%P0 zQclgaTbYkl6^HDS5GB@j$_LmUpWn9(a0Mw_QR9WutyZpqYw72>*-zF z6Re_FTG(4PkVZ.+?bteqKET(06nQyA~r?XK*Zq;Dg~Tr?Sh212wyF0YP@RRgD; zv&4DF*=H)#ifeR)!IQGH9kUHiW*VJ=niWiE`9=6{ukZf>(Vn&NuZJTN00000NkvXX Hu0mjfoLK&| diff --git a/recipes/icons/djurslandsposten_dk.png b/recipes/icons/djurslandsposten_dk.png index 4f28358ac16b24cc4664928318c98886dec60558..6500fcc1323dd11150a9da2e2918d49e20ca27f0 100644 GIT binary patch delta 506 zcmV`};~o zMU|72=H}*AQBmF8+|JC*W@2LP?Ce!gP}bGeJvTQ)K0ZlAMAFgG>+9>y%*4brSsi&vO$jC%LKVe;6@bK{0)zw&2Q(akEe0X@wKFZ2QLP9?}I%Q#D zcXM-0M@OfnrHqJ(gMffLH8qEXgw)g1mz0!+fq|BjlkDs3zrMasNl9K=S@iVuTUJ(+ zN&!EAi-?FpJUnbGcGv&_010$bPE(il^pNuY^YMxVNwX6G008t!L_t(|Ue(gYQUpN| zgyCTgvZ{J?T~T*;cXyZfe+*(EHkgTR`UpXu{=;`n%W4zD5=ko|#-%JVFa4W z2LppZ&JSxC$QOtd#{hSB0TYwH@~4Wx%!lvI6;x#X;^) z4C~IxyacIC_6YK2V5m}KU}$JzVE6?TYIwoGP-?)y@G60U!DQ zA+C0omUxh@g@uE)wU?`_x0{=@ot=xly^XnfR9M*AGiPqyyjfFGVQpsC(%5+N#EH;= zfW+9?4eR^4&{r>C zTA7-zT)zC-vuB}!fu1fdmo8qse)VcZaBys7C$s&&NMePq$MS_x3pZje0lnmDXoo-Z{NK6{rmU+ef#`8J>w!HzkK;}4QNhC$e!K1 zfBpJZSz20{n|tT>?TnO^b!*nVdGjVFJbc#l=}(_NJ$>@zv?)`;A1mmu5z~= z!(4Um)X;Dz2QjgeOHejDg&!(|3rp!($UA}F%d(nOC6~Eivg;=U$6kpE& zp(4H9LixP@9fgLtxR1+^ZGF_XXKf~j!~1uum;O5Z`0{3_H`A}13nwyM{r<`QVeo7Q zk@&9r3=@{I-Ph$x%Hlqu+U%!1*ZJG|2`_)KsUNq!#+2&t3K%G=C9V-ADTyViR>?)F zK#IZ0z{o(?z*yJFG{n%*%Glh>)JWUFz{!EpKi-TG*1 zz;;?$Wu#`NXOu7)S{m^)6tx1?h$EQ;RTG|>Qj!5ua(Rs+2T+M5k`mv{+|-iFf>Z{u ztMm)<((N~e`JtKP8v<0tU}$P#YG`a`WNx&Pc@Z#cGw>ss6P#I<%3$E+G-Z+98=zDe zl2mArCqr6hPAbs*`nh=}Ir-`OX^CZ-$@zK3`iO|pH%U%4Nis4`v`kJ-u{1U?H?g!x nOfJxJdaKYjL00008E)W_jNU5d+NtapM&3im^I^u kYMNc+TGceP)v(t36Vdf5c*3|WhX4Qo07*qoM6N<$g2shuOaK4? diff --git a/recipes/icons/dnevnik_cro.png b/recipes/icons/dnevnik_cro.png index a43cac2d16de00fb60d26958dbce9838d748bdae..166937695b12ed7e81e450475305b3f1f473d2c5 100644 GIT binary patch delta 596 zcmV-a0;~Oo1@#1wEPvtQ;q~?P?d|Qk4;8u)7XJSJsty$C;Na=$>ADga`T6B-9R(bDnp@yfu# z&d$#8?e5H6V6J9q!K9_nzQ3|bP0?Rtv=0@vA}7_TsO>imy#kT z*sHCiZ*Z0m6xyAjyBi_h-QC~6z~Q#IzbGxLU0~wb+2-cv#Jao2ZgJ?|-p71@$xTto zjgRco)2tB}%C4`idVH4=7S2>ym=+tF9U*#$N;Cif00DGTPE!Ct=GbNc00BiwL_t(| zUWJp@mcuX%1%E5acDT&E%*@R6|39=+)Aq#fZgXEfXQW6`p-|N7-{_*eFAEboCk3?} zP&S~XpYTuM__EvU_xruh)mgb1hKoQG2#qm_L=d#gbHoMY9f+uR?E>-K0D|D_xofy! zAXSI~@lZKHLKvCS&1xz317HBRZD2nEHf*r7@xqD$ntunUhlEG~PS}(h(T)vN*bt?F zu7Mc3TMGbAfz5yo#`kQX4#dC&fA*yWdddQ-86XH~5Me88P{Rzc0%Gvrv~5TTXHBvJ zv1#|PE&!&V*#$0vYsxjiw8Q~S46DjEjAT+zUn{katc%9uA8j?sC4dYE2kF9h8_pz0 zGO5`*?@cKF){bFRo&OQM&dQO}jv)g;{p3?=2YA09#`W7rzg=1T*gNI=F5tDM@f!9a i delta 613 zcmV-r0-F8x1cn8WEPs+BCYBErmk}1278{x!A)7-+p$!wHZ*Zy(6slcdtPvNjdVH>C zX|WF!vPn&}4;8f{C$@@=xepb&hKITk7P=A`yBi_DC@sOHrNp|s#%^)Pe1FMJQOS*u z%12DfuCL0#!OUD>&Qw^=&d$%iztLY~(#Xlwd3@EVs@228)qmC1*2u`$h>O>im)L-V z*sHDC(b3u2+1j0;-O0+`-QC~6z~Q#I;o;%p*4EEPh$ z>FMm!)9vl;@a^vL(bDnp@%8of`T6<&{{Dm-DBu7900DGTPE!Ct=GbNc00CA>L_t(I z%Y~EcTEZ|4K!4{Ir!v&iDc&Xs4najGYEh8Q0NLLEg-pwsv{ROUThDirCXHozoc$Zz zv*>(ncor`Ri>*IrI>2N-?w`Q(+jugY%_gJwm$v7+d0-#_lO#a|5Ye!mAvu7~1prLW zOu)|!LL&HmY8qY@pcG_4d}0O=0LDpmzg+dhhzy9wI)CsG2r^XYk8@3mIVKlI4FP}x zIClp!M|`gX9WulVfcJznjE`~vrodf9h9tCgpfB%PQD0Y~Oo2AjfbI$qMKF)Cv}$1W z6`&!cAw1AibOTt{&?5ulK(C=C0bE>jEGPxmm}wAcibtUgWgTcWWv4E08@-Vn7fh!+ zTB8`H5MH3BHD3(;#4$SARGmu4`3e3uHw?{g_9AGUoejoK!!IEEdt1x5oB=oIb-(}d z6%ISK!`)3M|7h(}8n&Y^Lh<78f+$|zqV@j)6~%&SVnW3j00000NkvXXu0mjf&kr*U diff --git a/recipes/icons/dobanevinosti.png b/recipes/icons/dobanevinosti.png index c71208762ba52061c51a6d1d7181070f52b16178..929d7056acbe0b0dcbecd74b9ca6f87f051fa8f9 100644 GIT binary patch delta 171 zcmV;c095~r0*V5VXMc`KL_t(|UY*gs2?9Y7K;f|iC(+2jUbX@|*a8eJzy=QN!^A2C z!SEsvm)DCjFw4ytc`y7JnEesakjN^+S~Rs3HUm5lGdKgZ+F^@z?{LJLI$W^WJKUfY zpgO>)x(i?vJgS4&GVl+SZ@_%5E%t-?#5*0GDP9i7czrm92PA@*I_Lm!UH%QJ-_K4X Z@&PexnWi7R`+5KX002ovPDHLkV1nsLN?iZ| delta 172 zcmV;d08{^p0*eBWXMc}LL_t(2kz-&GL;(y8jHm(vXbONz$P^Ikx_kFz5RyRcT_Cuf z2p15)3kHYb0=W=@8*qVMh`?P%n84b*cU@e70*NqzyLT@N3d*7gKvWz{L$=tI>dH#> z&sDEdNi`Y?De~F-vYE^y`QhAy6S(`K_dfpUg_9>`Eb7E#QL>fhW}wKV!*>HT09y#P zdbwP_SuXpPN{(P`Xz1}`sjzo)Xl8nQ^@&uhO?Xw+;JC=)(n&yeGc z^XJbWC>5TcIxu{HX)v3$tVoY7G#1ICTq3?vt;S0w*wMN>00~tfVj7MeKi=ufpfz** zy=)0~{LtE9hSO=+b!mK@1(1M?$=KkBQ9?Wey3 zATkhHGJj$L8p{?U^r1v@U}NPL5Mt;gGiy8Q#0FAI%9gzlVasqnmm{V7`c!BJ%eGt* zth;U-2z@B(IPSWunZp6V+Oq9}v&xN~+NZ#(%!HXkZ~BcMM1+W61;%0rvqALVxTk78lpn+}eR<&$5wTLe5JVLC{*~Xaj~2(IP>dK3U^Cq8F&sK_^xbpsdkvo~ zi+{De7oZFnuKsZ4&S)Vuv`Y)c)CFL79nc^MGR4BamyW!1_N_B>b3_p-B@z*K=22@M zvF+`6&wcp8!84}=DQo}*UDWejq%|iK@$bL-BC&A!wKq?v2QzossI9NRed>+U?CiwM zwA}RBVg|tePZdPC>H7ztolPEo?&TA&et&-9!u7?))!WOJRpV|N9A(m1TqD=%8_|2y0M-j9M1qqNtGU%p`D{iUt=^^O+ ig@mRarvDGWq52EYlIxlgTBDW#0000LUNbO*9)!MYMl-ktI@Fv0r?_e8ie=dE_wGQ z=197^SZXzC4yXO8eY5lCp58vMhKS^!fc2O7Z%vK(Vf zR5ehS$g>E>{(sEo`251zdr4x-)?N2Po?^2W;(81wYi5r?LXPo)8p}c>6^IpT`cx+b}n@vk%4`A(?zrffCGohlwW({zHzhT{8)3Zv%AlEVSm&s^1)OxxxTteF)kDe<-d%Q zec+^j8%5+i_>cqZas5jMKunU3_c||MyzuPN1GK$m7_ZCkjZ&$!TOGzO{B(~DeKVhq z=GZ%`YXEqOhPQXVIN|(LZ>hYyTQW>-p!XdTJcHRc{1_&4nOqp0%IN@-ZpYd*~z8O`(<*~7mWHO>E zf`26dMRf!E9zA z8fhLHK7MMj*>$SLbGAbAO6*UHnd^$d= zDRFQMm3TzL6#zDyCHf(am+*F800000NkvXX Hu0mjfAg2c> diff --git a/recipes/icons/donga.png b/recipes/icons/donga.png index fa732048d27cedd104fd13856622c3f153af3344..3fd5552d73756a0790ff30b008a3f568b169f961 100644 GIT binary patch delta 809 zcmV+^1J?ZD2FM1GEPwg=`3ib{-0JEJeSl4$p~~Rl-0A8HeSqZd?(p>V3x0tLd3+Cj zfKZ{MQ=+7QxVn?V#HG#7r_Rm|dws~@;KAG65`KUae}diW>J)!~IFgk0_xJYq`1bht zO`f2@+S?U_hQi$4!`8B7h>OeN;mhIT%;DnA;^J$qu5GTcbh5MK?d^86wC?ls@AUL6jE?d2^^d~Cl*7a> zjgPU?(}20UjKIRP)6|f{!(6ASCW(w}uCFJFjB~NF#^2v7i;Z=%vkQNM3xI3R@9$5bqVMzcB#4U>et=Y> zq*TBWC3rl`Kz+J`@W<3Gfs-I z%uI)wnJzOk^L_s}I7zy@&5>5>{HUtTr@FDn?1R(`sek86ftG$%XUb!V9~epq2~2Gv zTrDA{hy>MJC7ATJEiz@G^Oy5(2iVN0uV{je{eA&B{Zk{(=~MOu zC~RnA0*Eg=um`wJ3ZNVYHg>;?7|?!y2P@Ho&sb>Id2<0rkD^8FZfJt(0tKur5k1|5 z*4`P>(tjcfx`+aV1v?^I3ri2rf%Ah>xJDG&$OsJtfm=ppOZwG(8&d6jRT5hj*lrLg zKrG_~-(H5*9_q)Q_sl`18eVzwa9PB_9b|J@FXeH!!5oMEx`oTqI&xXOR)DN9?7XES z_FL~8OaZL%uCIqPj38D2;Ni>R z;mhIT%;DnA;(y}V=;+(&>D=k+-0JGx>gwX{?c?q3;*9T~NWQfD6W@X3*3cf%E_WL1!kBM1LN)`yo%5^OBpKdoJsf)hnw< zm8nbLsxzgrKsQ4b5KQZy%(dsL3Sh}UjeiA{DAB6Q9ty~$iBkB7CFTL_2VA6x z(itBRs^r@_wrw|?^5V!Jb#?e&z-!?KEtWc3y#A8GXKp?JnZ8kuH0nSbwPNE?8+HF^ zlxdv!R&O?x)r~D>^=KJqp4EC-=iaauUBK-eRxs}IXU821%&iQJ5L2b6foVtX{H4eR zp*nTF&?e?OLhHwVHWd3>DE8sSSwH?J7w2T-$>O}ddT%f9>+Am?BuJEK4-k#q00000 LNkvXXu0mjfawoYD diff --git a/recipes/icons/dosisdiarias.png b/recipes/icons/dosisdiarias.png index c71208762ba52061c51a6d1d7181070f52b16178..929d7056acbe0b0dcbecd74b9ca6f87f051fa8f9 100644 GIT binary patch delta 171 zcmV;c095~r0*V5VXMc`KL_t(|UY*gs2?9Y7K;f|iC(+2jUbX@|*a8eJzy=QN!^A2C z!SEsvm)DCjFw4ytc`y7JnEesakjN^+S~Rs3HUm5lGdKgZ+F^@z?{LJLI$W^WJKUfY zpgO>)x(i?vJgS4&GVl+SZ@_%5E%t-?#5*0GDP9i7czrm92PA@*I_Lm!UH%QJ-_K4X Z@&PexnWi7R`+5KX002ovPDHLkV1nsLN?iZ| delta 172 zcmV;d08{^p0*eBWXMc}LL_t(2kz-&GL;(y8jHm(vXbONz$P^Ikx_kFz5RyRcT_Cuf z2p15)3kHYb0=W=@8*qVMh`?P%n84b*cU@e70*NqzyLT@N3d*7gKvWz{L6h3qB-Mh=O>>{ycEm}xLkQhi~f54{N znrI&g#Aq=Z+tgNKVu^pWFBY4WJ{VKc5?j+~EU}^C%}`?#`_Qz;XeDVYL8$aksbUp~ zl)}OSdw1^~&oK9A_hyX`l{YtMCuhF-=6v7Gow=7m7s6uxlz)Gx{4an=v6up;3CMw) zizK7MQ+ut_?otpK`|kwm_t>TFK(M<)<_^VFsM{mzNb9zelgptykzw%hBu%Cexgrxv zm4FHlgq6E<1U`|3t5YzKiap8#3Y3STKo`C71YDnnd7fQB18!0|%Of%=)>x)~P@_k5 zmgJZ)A>?BT6Mx$WdFzjY+*+kHSAbu2*ED6o?q28^%DRG&{t}6z0E(6Ye7-67nH9dS z>&f;@#=vbbklmlhc2)YCDztA#h0hJq?6;@;cWC$)&@1raC|P#mwaUPuM{~EHiIadt zoZZ`2s(rd%*Dd4O)aUV+~_~DPJgjf z(YV=p%{Vxa1c3MYlZOXWl$`j@e79dDr~l-wedcf+53Izf&H*v~qAgT6DN>aMN{+@3 z_QrwZ`l!04Os`(5J-fnp?D1gtlL4Z&_00po`LWcS=jKLcdDFQ>%SGe+F}}Yq_5P*Q z5kQgA&B(ePXt-_95XKxzyC3;B^CuUQ_(|mJ@ z$~k}ayZO;L&E;+VM$bsHcA2OS0A+b-7fg|i0?4T&AT*J4aZm|CE^i#+0maN+n3+ro zTxD2S?DM19J(O7DseeJCQ08<-rc*(m^3YuZWQbwEGPScX z_DW%O)OY;y+#j)Ip^il%HBV=o?$e0>Qn|0WQWqw`tX-~2fy=ke1@G3*YxK9yC}#ts zv#|tksSZDXa{ScL+!*ZugLo2pMvSk1O#rZ#)gf7B->V@cQCBVX?_L{ut|Ij1&$C3SFAZiD zIQrAv%U{nphNNQ#d;369WXE|CJ^f(bk$>7~G^7!Xh(w#5H{a=*Y`+i#2D{eex70-P z1MYpMcmjOF>bvbMD}*CM5TrkDua6?dO=WVOeSzRmNJ1k?r5Wyhqe5GO29+{ zZ_aQN)3?EO%;2pHA_Hr{qhvdhh>|(v^6gD`9N2sYDy(aCKLC^fD({Hl7F1R zXKvR{9E1dFb`UOj=$T2ai_2ExS@`JkGwq@fkdU{W*KAgt%hbD8*B=Q~Z+AM`zM3+) zckNz2C9Z4N2Z9P~x5*`EdbAPmrin=B+erGuO9L4RtlsL~BEZbWa*tmxUj~zihtE}5 zwIzL}Zn#UfT&3mN`5r5hslvlANF^v^CQm=3=$Vs+?+$UNmH6MH)!f3oD2lrP0000< KMNUMnLSTX*M_}my delta 1242 zcmV<01SR{w3GE4xBYyw^b5ch_0Itp)=>Px(r%6OXR7i>KR%>h&RTTct-PxJlcDuWM zNx{a@(xMe94}(I2V)_FWT7m@;6%ectZBhsj0UwDNqejFuASnvf)|73cQQANRwV{NN zS`|vLKm}TxEwp8UzP8=AyE{8G{@Cu!>;p{H{&RAF%{}*g-+wvhJLg`4L&11{^gqDg z100e8fN0zRz7oL*0EmqT457jY3PwPn-~x#KcL3$XA-|!8aF~A=7U94USjcJ69g*gm z=_31w0G|zL#F+}iOa^3IS&Eqe>JxFr4X?sf5+;WGtw`blpvQ;SA$a9G0hIM>d_${f zs_~P#yp1POR)2gFEJpH4N@2E4FHTm0T?6T z+lBFknMOyeSbEWap&x#QrXBiz5?vzUguplkA1P))v^3#ZDE5aU^o_oo3)85D8 zSDhGkw2EP6XS61ZJ2;=`4RXq2!KAEBkY*T?`js!9F;uC)pSUg-O3xeTwS-^a7%ePo23fKtW{h-O%#OP z*F%j)^Y0Z-Kbt)5P;X1W>{ytva;nhgmXicZF|(si|B@d^k_6Vf*1EiIw8k0ed@+7@ zt=Hv6!+&Dawx+=OKIQgG-ptX4M(JQ1RFxBUIn_KYn`*9nGSOD%l0lBA?D?63?eiW+ zRJYWYm(FKY^*e`@j@9-CSKx*E(ZUSl;SLF)rIWcM*Q8|`+&6gwfZZ1Zub-7w(u9xN z0RjLBRgDlWqUaIG*YYlYbvgGp1Mcc8w^VFC|)emilGC50q+R zk5A&hb4nokI?uFXiV?uBW^sFyq{@=;LF-s6F`75Y^z*8TGb+2Adji#qlb_FtAN0xb zMlvv}`eSs>PwokE2G{yj0873fJlY{{pJgtXY@A=~F>!Qzt<7R20P9Y9D_a9&bp;XL zyMGK8lpR@k>MF@N-afMOXwT|tme~_{5QarG-tZrD`V`QfC#)N1SpYQM5*xb3|Qbk4h+1qo83^e%J}Hg^FyK?J4F)8T%Fe*(K7T7` zHyJ?a^C_JkSyJ?A&=?bToC)0lx=Ss_-9V8u5juNq;FH<`!4eX-{StN<;V@Czi{ueZ z1?ik>_5?Znxc6BzkFR+jWz0AbUqFmTO9IBUkD z6cX`NhzUc4lKK$AD5qnEiaM#cS5sdkLCQ?G3j$+=;<_*jpxD9QF-7|OJk%Lu@dZ`% z>JKqas-&Vqj*S$t#q>qqa7-um@XV@;jucXq{hh=83ohYupZ~f(cmMzZ07*qoM6N<$ Ef(7VWj{pDw diff --git a/recipes/icons/dziennik_polski.png b/recipes/icons/dziennik_polski.png index b5acecb211a78cbf45b7762972b1d8acbfc1bc9c..ae3cb1b1e90ab4660f9b2abb4e89750775619d2f 100644 GIT binary patch delta 559 zcmV+~0?_@x1-1o{EPwz1|LnoVyhKO3K}ESkMAAZC)<|I6P-WboqOArI_2A*P1{vEd zJIFLN$1yR?dwbwHLEtw(PDk|Kep{4~2^vBKj)!D-XC->`Qqis3oFteGtwkJ z*eW=j1O%rB3!@1MrV9(fC@8%rC%Ppiw<036ARw_G9g3NkA9}6Q^wl7C_Nlq_4!kLYh}oud?H^bT;%uTu`8nzIU^^Gd~rO=&nR!w zO3}Nf1-}K5EPtE?1fc{5q6G(}0tll42c`uIrv?k72??eP3#knat_KpW1`)Le z8MX%*yayq?2qL`-A;SkK!UQJ611G}^C&mjY$O|jP79_?LFUJ!#%@r@t6*1BuGtwkJ z*eW>NEIY_FG{-S9!zwDlC@8%rC%Ppiw<036ARw_G9;*pW$l$7a|nCh07?UtSDkelS0ndY3F+@GS{p`qQOqu`;X=%B9cou}}& zzUjci?7_wK$IbT2&i2gE@X*rt)!F#h+V$Y!_Tb|A;^q2(c6WCz?Zn2y*4eLt zBkvFA3(q(Q*y~Mm^z8$_qx<;QcGB9658ZdGC;qb}|7j0$Hh;yXw&oXxy@B~=ifgxR|?yll4uiWn}ZO4eJ zQq7v?0WFJGW*bH|javEzk4e4^X0&2VQK%g9OX2H)%az@N@ufG~w5R@POOlk|jEO}N z5|`()5JHyaf+jRB6cwfvH{fQrvcyqV@dVt?O*-ZmpOFBEI7OV2ZYnJR0000Fnuzj{NZQ-$GmRw!r%0=J2Pp*x%yZGft?4ljd%Lv}k=K~#9!W0> zUyq*sM+Hy~Uyq)-E@p}qeOSJkfCfwil*Omv)3yUZ?(42uaFP2?(Ref<%zKvC2p1$u z1K1LTAYl!_P=X8o`S6K=h7DWxq6Pnr`;X8xAiQ|T*l$J4WL;#{oAw_gDp0001hfPi;TPqehO$jHdt+}!Z+@c%(8gB=fA)Ii;Mr8n{=^!dH?_b0d!JMQvg8b z*k%9#0Srk*K~#8Ng_GHK!ypVqBZ+nKmaXmo|7ZuWot}^$-GA4)h%tuCUu5Sp(o^2~ zq}sE8$1U?*bveEhRh)+K^}Q|@G2su2~=54gX-pvpD9HyFq)8(BaCmX zwO-`(ovuvtDSx0`Z(#`ys_;xdJW1|?TZt%`PHxq%DLr0WP%qgbEJMjJl*F)JGKA1J zpz|2|XF_ntL9`7bPY`T7L~9XCIZyKLy~nEPfsuo4*@ZWz{a@JSzuy^*MGqS8!-%L? oPP?@Fwbvhx{`B<6t3Ll+Apj|KR1K@0j<*s`Z7InJ< zphbJk%UzidUmM}X|3~clZ^VKBBo6(R*Z_&>5xgwG#vj|X`j{H=2@nP|05+r-K$1y` zBmuxK5kMb=wSbePNOCY($kH?bHVGX>cfVgqP9z<$P;`4}1`vb}a(DNcBuO^_lfG&# z3IK!-s&(>Ko`0OpfTH?Suk!v0B(il8-C!uHU-efbtO3*P?zvT<0XFGc>*Mu=uT7Gq z!w`zTADRu;n#?)NN&0gP?VHxV*+Eh!$)b^DGyoQ(!4RI4yr-11R8mTbYjiQ(c6Xt1 z*+sk^tur$M!H@H>$=q4w&O>`1`}4y&KfUwgJv{$EMiX%{P9ZRk0{{R307*qoM6N<$ Ef(y>ZnE(I) diff --git a/recipes/icons/economia.png b/recipes/icons/economia.png index cfc055ffc4ee62298ecefd59460e040c2905b139..c9a8a1ee7485aef63191cc52d4df66a7a3e52c64 100644 GIT binary patch delta 802 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 i=mxg|`2TO(eZVgz=l3FU%3l2d0000p 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 RIhFtb002ovPDHLkV1n$8?&AOe delta 636 zcmV-?0)zdS1)T+uEPnw30RjR70|NsD1Ox>I1qKEN2L}fV3JMDg3k(bl4Gj$q4h;?t z4iFF!5fKp*5)u;=6BHB_6%`d078Vv478e&67#SED85tQG85$ZI8XFoL8yg%P9334U z9v&VaA0HqfAR!?kA|fIqBO@dvBqSvyB_$;$CMG5)CMPE+Cx0j>C@3f?DJd!{Dl021 zEG#T7EiEoCE)$c0s{jB10d!JMQvg8b*k%9#0kTO%K~xwSrNP@)n?L{s&;ucYiApYn zQpyaO3(PnW{J6tk?O2GZ33XxYpYYtWY*Z$2~B$kOl_xy zOE8m9U2k7TH@I{;h0^{Kd(1V;b>0fcux>qp%uajK4I?u?5*4m zuP)HaF?d(TKZ_VQRxjS|wxadj;2Yhsa8?G~+IjCYLmwQqDme~1H24r@De%y#R8`*f z!A~4Q%{yF0wq7k@jw&4Yxzi(`IjE|{ho>AD)@wE#S!p-0#x0ABr5Sk8(i_?n1#a!f z2w3|-eRVRm_oS{dqnP6{knG4^P1g9LpveQz{^B{TCucCT=}AMrhC0E@O!tbXOm;h5 z?dffAY7>Gk4oCfxrh2624_rAB<01X&iBoVG+{FmSU W_c`UO8}1nZ0000dMZ|N=!_-y}Shl1rQMs1Ox;U6B82?6BHE{7#SED85trY zBD}x9S6Ek(SsMle0|N*M2$6bD1`7)d4h{~H&KQ3V4-U-F&(YP?(bm<{)z#SC+uY#a z-{a!r=jIL%53AOz0ssI2Ye_^wRCr#s$1?&00Te*N_t%VV+qQN86K$P7nQr)v?02^% z!%(XGb!M^qL?<$2IZzLC;yv`X)<{@BjMTaSx#TSj=v-

86zLGmrfBCD>+a1LI>v srf4P(F`nJkDO6F;6yBX;|Iy+f0)06OUFLrWv;Y7A07*qoM6N<$g4kGRL;wH) delta 246 zcmVYwrf&j0`bZ%IT!R2Uhx!Pf$T02qMLuM#3;%a%Q}vdQ+p{}Z`(uXFeh zg&+I6Vfd}%wzNJ(({q`kPwdl*>07*qoM6N<$g8m6>lmGw# diff --git a/recipes/icons/el_colombiano.png b/recipes/icons/el_colombiano.png index 871f76f14e3f5ae04b7602beceb161231bc4b415..9e3947961fbd50d6fb5ecc455aad634ab394de20 100644 GIT binary patch delta 651 zcmV;60(AYM1(*ep8Gi!+002a!ipBr{0Vz;SR7L;){|^rj5fKp(5D@TX^736>T`DRn2?+^`ii-dL`hWHH_4M@g5)cvV>hJ&l z{Qvy_7#J92WMu#U%E;6h#M1!A(h&at{r~&_xw*NOm6hJz z%F_t{|HUC8A>-rYtE;P4R#yN2&dbvR$khVM)B(!X0L{_@!pTMU_V$K`hRe&#JUl$$ z;NboI=*iLm$$!-a%hmwP(g@7e0OjhC=H}*~pP&B!_RrN>!qWiD)dS4f0K(E4{r&Uz z_xI7!(f|M9@AJ0*|I^;$aqy_j2LJ#8DoI2^RCr#^&c$xUFc5~(e~iJ)P-JFiW@cu1 z|A(l8O4aCg(@H(cCi3SQ<6jZLWWB7bYG`<09!Zwg1%J*=?l9uEYyfg_lgB_C_YrT0 z=q9JLkuxN576eiQqJ$>Nl7RqmAERaj3SR<9;vo@T=|~1GJVb;@D~kgNN(sPBM9MM> z7(*K7@EFqY7-Kbpznws}CkQEjn2@KGAZNf(Fp2MoB&=$GKbY;f>{A|xJ}I8w103Yvqw5p3>3LTDm? zg41X|fn@*=# z0c){X%x1F`%264R>LCF_-ch)wK{1OOd2OM*tDf%;exSVi{-7Y+6} zaDpvPBCvP;4>Ayrz;gyR$Mn<;a0z?{&X@sOxlIO+1bVxsKB9lMt+3I46QEI}#oO<* zm3OpQm?J)#2L{nf_s{nTpfs6Gph2_cFG>^;#Gm&MMt|TcS1Ek`$M(lwI2<-D6m?mf z$(qSzbOk;5cC#05$m~N`xc!#r>&NXHG{^v|p-||Mrc^3XbKalEv;wQ!uLI5(A7y{L z=em|Jnm4R1UvmEb5djUUXf$fNAMi353@Bu(JLGD$iYAsyrHq1nl4Ms?akYCFZG~NT zUG>&$W`DYAv)PO$JF?jg`W*^|f+R_aL;|HluKj+0Yp4Ebby-V!DcvxsdwHd>@Aq%* zo-M=SkT{-PL&$KA6H%#DC=a7h+J?_QD{tyDlav=%Us%2SO4G(iJ-lE)pU*KfI-QP_ uD*ks5<9D3PxE|+w*{!$N?SFWi{sL-QxPnqu5e@$U0000P8*MDz;*KdN&N@2ekHsFt* zz#2EvS#Q!?aObJC>94uzu)6EH!tA@l<)N+Sq_50IUdAy?%R^h^oT=oUs_nkU?ZC+H z!O8B!%kRg|(_nPfXM5Ibe%pSJ+kcPTfRNsZm*0$<_~`5T>Fnc~rsJBYf`bA=reP&5Dl0K-W{K~#8N?asxD1VIpl;fU&S zcX!uScXxMr{|DKoAfaH3fxXXn%7p(Am}M>yfNAYe9KiL`Y&Yt@hi3>l6h#2tG_}v1 zJ-AZxs52wzjI?F!evv%7n4D6rR*m3T?k$p!exTz0HgGpE`l^+2N7g*hY$N+wl6lMs zGKwtNg3^liju;tSfa}8#*hW5)T#o~(?TsZfh}>wG7zRnUZoMJWnf~370LDt5w>@is t^CY$V9f-w!F@URgeG>L<{&?^YbHD$o8WtUH2!H?p002ovPDHLkV1n0XCPn}N delta 517 zcmV+g0{Z=#1g8X$EPuHHBDn%0x&tJ-1SPr#Cb|bFy9g+|2`Rh{Exiscy$~S#Q!?aMNIP)n|LwYkt;jf7fq; z*KdN@aD&)$h1qq7+Ix)KevjLKkKBNe-iVjqjGEw%o#2n2;eU~!;gX=@mZRgDrsJBY z$$@0yTk3i#_hn!?!n3K!^`i-&hW|5@y*oo z(%AIZ-1OPr_1fR{+u-%w;Pu?$_TS_8;^z0{==bI5_~`5T>FoLH?fUNU{Pgww_4fSr z_x$$v{rLI)`G5KS`uhF)`~Lg<{{H^||NjZfiT3~i0MSWAK~y-)V_+BscmN9v3fIgE z28=*lAzhc?AU8c>gaW@P7;psgl*1tGa3$;tR3l-WNGT)*uCibt!5|U=l(LqQw~LB2 z&@zMpZ4j4V&DO)sQjvQ!QIi4y?`B2g4k!6z00000NkvXX Hu0mjf+r<@z diff --git a/recipes/icons/el_diario.png b/recipes/icons/el_diario.png index d9b0b016d4e6a976b0d3da752f0d2aa8a7239436..f9a944fd71c2b9f83fafb6745fdd27873c06ec45 100644 GIT binary patch delta 771 zcmV+e1N{893)l#dBavU^fB*mg15APZ{rv$;e-l@U!PDUW{{C{CxCu~%j<3crY?mWr zk^1}m`15LJf`TKv5_y(bb&`N1QJLQQh5R6z#vvblplp-6o*lYF@;gA+HPQKhT|A+e{$rdY>2jD`3h4fxG;>-1OX-)$NCOt zl~9G@9gW~)l6I^=P=olyN7}@M^gsF4FFL`jt@&9tbYL|_3!YH6`G6yrgnv1S#dEPu z<)CKh{KB*GIW`~jb7Vm|sA4xIi#&xTlB;XvWi785{na4Jwc2&Bh4lXV7ZP1{x*z82 zWw)fX!{2`h?Ia+cdM5bU(f(`0{{3f65l(r5XlP`Tznz^kYSGjH3I)w<6IyaWrS){W zf95Rc@SJlSZR>e27;#kSAMb^WkPbb6n=R$}{|6%>^CZ^Q@RI-l002ovPDHLkV1o4K Bkv#wa delta 1069 zcmcb?v5k9zvK0eMx}&cn1H;CC?mvmF3=9kk$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo z7+xhXFj&oCU=S~uvn$YMqT;i9?x0r3iUk6p?dy(R%b2p6*{9JUXZrvD|Ns2`d-%%3 zpq3Sk-u1%aotsYFxNz^eUD-UhnnedMKTuAd_~_M#S=-M>6>loJjs}z2QS~N zq)aTFwPWf2OUn*i%A2w6=kGtJ1+)JB`}ghVucFyIc!FCw16n?Q{Sn-{a`uk14(0Pr zCi^mSyY{U=Rxoq>k6*vp{hP(2y42Gqn-$E`&z^qe!Atj=#hU3;Za#VAS+_(mtV2Ai zd*z`kZ7UCb`TnzG_2Kl%n|&IV{`&oA(cTM_3m8=<|6-J5S4x`TCbsbXWO=6WdIrWM zZ+Dl#DVa4|z);{U@Q5sCU|_rm!i+o6BD=b?vR6vOcA)mFKkOp@`p^k zPM_FzpjY#E)c3<-yC(Fwc2B%i^&o*IS0>k9qdVsMM~|grSp2Zcg#pwY|FOskauXn-FiMf+Q`?nY|?xmB$@PG<>h+s7#O zUBM}Y-SEgF^|mdEV&4^CWr`kQ7VG4= z2)+8te(vK&<}6u3);|IBtt?MJ*AFOt?HO|5$FJNuxhLP-C*&wx-65!H@9th_`1!rP zv&YRVE^eRGmi(PNXO6?IX=$6L<{mZKHZ|bbH`x=HH%;DT@-EAPFS}g$DOa0rN<{Uy zAUjYr)PMVVXWpVD{nS6q>OX$|zONYJ0gQ3g64!{5l*E!$tK_0oAjM#0U}T_cV6JOm z9Aac_Wol_ z>t*I;7bhncr0V4trO$q6BL!3yUKJ8i5|mi3P*9YgmYI{PP*Pcts*qVwlFYzRG3W6o z9*)8=4UJR&r_Xpk4Pszc=GIH*7FHJao-D#Ftl-jMayW%qd2@)u=^Iy09657D<_P=g g29E_^dJM0`1xr3TnNDS3;9_9#boFyt=akR{06KWokN^Mx diff --git a/recipes/icons/el_diplo.png b/recipes/icons/el_diplo.png index 0092c83a28d7191f2da36dc19dd30f38fd083cc1..bb2c5a5f17e3373d7534f9122bafb845b122b99a 100644 GIT binary patch delta 377 zcmV-<0fzpz1BnBW8Gi!+001a04^sdD0H{z*R7C&)0P|)>^=3%*Xh-#DO7&?=^J7Bw za8~thRQ7pY_J?rzqLKE7ar9wB_K zdG%~h^=3-C-;D7`LCe!S2grrK>E6__?n3DB>?g_0Dti?0Phw6@E-v1Apr6| z67VJf@;V9eGXU{40P;T*?-&5_F#zuq0Phk2@izeP9suqM0Ph?C@FD=ORI6S9001L& zQchC<-P_RK+uh~kzrCEv<*}LBf$*i}jnk{M+upnN!k_N=k*ic~0sa600BuP`K~xwS z1;K>@f&c&juyTRj-7R5(9shsk2B1*u0|0~M2LLGF0RW{O08mT-Xk04QIDP@Br*O>y ze45Zo0NT$EggHR>(SslZ7)B!qGJx@W0$~O)O=mFA0UVBSIs;gIZ_Cws6Kw(5?E(A& XRa6I_LXe*<00000NkvXXu0mjfD&nfy delta 423 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 diff --git a/recipes/icons/el_mostrador.png b/recipes/icons/el_mostrador.png index 858ee530f80dd0270d904002197646116a40e206..a09e56130c3034a32a08365347345c590ea62f10 100644 GIT binary patch delta 589 zcmV-T0k-M&$YF+X+RV!D=P~N3&Fv`XJ=FJG)jq2*^t*);u9Spv6Bx-7ERWuL@2??BE9<#KyG9eGIudfji5ji3YtE;Pv zi;LUa+p4On1qB5*HZ~Ov1cHKsE-o&Sk&)JdDcFN5Vq#+0*w{5SHAqNE78VvMDJkaW z=5up%0RaKf(35Hb9~BJ^4N_84@$vDux3^ncTjS&7jEszvo&hHbPft&Fc6M%VZj;Ud zCx3f;dpbKi3kL&6D-LQw6<#?KAruCxj2Zxqgt5jC=txp~Ny}S{qYSk}7RiJ@`I_CFa!F77Zf--!+kkPH3u+F0L zAVT%liZ^Gl>E3`HawizjO9JREF=jbNLaTkMXK z(y}f>x~)+3^!CZ}04RsSpVe? bALV@m@Q^1uqR%eI00000NkvXXu0mjffK~?x literal 1277 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyacIC_6YK2V5m}KU}$JzVE6?TYIwoGP-?)y@G60U!D)xFfy8%nRRw{1_cF4N=mL?y}GTft+ln4mzOs*G_CdK-`_tyJ-xTLH!CYkL_~yziP2JtJJUfV)I>m(m!+wxNl#C2;>3wF zX3St_W^Qh7=H%r3`0?ZR?c1ZHqaQzhymjl=jEs!J!opXtUR76DCs+v|uT;C~$Jl9Q7sOqj5K{dz$`L2YgAmoHy-cX!{ueS6iaRjjP6+}zv; z4<1~*ZoQ5y&%R=nYfYLrnzfRWl5X6%VQOkBCnqN+CZ?&W`Rv)V;^JaqVc`oGE=-<0 zdE2&an>KItGZMX2uhA7E&CJB;=H_(yz`)klHa|b#%G#Qno!L#BFUwIh z&QeHGgzf+T|EE|hb3xIQ|rRctjR6FmMZlFeAgPITAp@bDl1aArg{r z4;p$MW?*o*_nodU5gAK3EddRgzRoQ3tN?cM~@KN%D*MjVqTfbl2`0ZY3_nk|!E&}`4 zuA2G%X7Vb2?Xa#ZL1n%y($|*VpWGOyn-+G=N_lT1$1kmU-xE9~UuN2bsT;^v2sth~ zzNY!ezJT3#uaq47bV%?}!P$7%cSon4@AR*|;_dcMc|-P7gS&Pwo}B!!=0@O}w}#*A z>lg&KsQ?-|&ymb3bVSZ@l_=W&gF<xlb?yXsfGFM zi`x$XmGC2(6P#I<%3$E+G-Z+98=zDel2mArCqr6hPAbs*`nh=}Ir-`OX^CZ-$@zK3 z`iO|pPfRg0w6L%+F)>OsNHs}HF-%D_Gfho1w6w4^ury6H(gW(y1G=wXj-vo*1B0il KpUXO@geCwzCcat# diff --git a/recipes/icons/el_nacional.png b/recipes/icons/el_nacional.png index 5e684ca4b17744e116d22a2b2bc4ca68d529223f..31b6920694bd16aae250c62a97ae40ab3e3b9bae 100644 GIT binary patch delta 808 zcmV+@1K0e^2DAo{8Gi!+002a!ipBr{0f10UR7C&)0RGZH{Lw%E+CmHo0RPuP2?+rG z(m(#wKmXlA|Jy3_36j>B2fa zGY@@dAo|ZfXMa^1-@Q5h)j{UII;o5*|Kde|Y9M-L9`40F|Jgzm4+i|uKmOc8XjU3p zN*3R`H~Y{(X zC6Nr&`v3p{0d!JMQvg8b*k%9#0b)r+K~#8N)zU+^9Z?X5;cuhOW82rZZQt0o&A*;b zXVB}MoSk{zRf8HlH7kH4Ul>%oG+4-k1CyvE`xHWaK)eFcm#m-#xOyXPJGL)u$8iF% zr0Xt_Du3y-)|6GNdt$9-S(XxrE3BMD_2%MMab|%8i%Tcz8Z1k2YzY?Y|}8~kv5?I25-R8yWtsfQR=+uU-=YOH|#<%Y<1VE1I*MGqwhamv`#?YPo6o9+t zz5ZD51N|W$#m)Y|!Q&?Yo`&f0XbxT{oq$NE6X%(C5~1w_4x&dR mFd^YhPj;*u7|Zr}=f42Kzd@(oF^&fS0000q$gGR7l6|mR(2`Q547T)4;&Mz_KjMvdDl4B8p35%PhM)%!n^N z1Sv(96_Mdr)Y7A58qq`0YeYdl21U?Q8HO5x^kAFoeweP>f`3U8LVX>+<&i62iKt={(c{%b9#{x*WiKQwCqlU$V4=nG*-t|wNWM`H)}T{ zT5dtVaY<>em;8rhZ=+;*Z7EQb2DCC!jV>62JuIqvB%eq6^2SF$=sRF|bo71$S$plv zG6}uE!$nGt_Evj3u4RzUEK8@W-B}2&O;!Nl_A4ADIDeUd&>6QN5Sv6;D>I?H*J|jp zk)8=C=S~Uf>f8G|4}v z1Gy!m=Y&N7yoc&45tCf_iU+$Dy3*Qx+$sPe)oyWO#N%`bkh5_{_({{i#VTt6B7a5S zru>g?*MH%{E(;h_TZ{etF{e2`3jnZ{S1Oj?sUwc!wDT!3Z}$s;L~6`Sms>tfsESKu zNUm)qfQwi7UP@gi~uY8^I!vnurh+@y05rTiH zR~hb8zQ6TvskJ);Oa@BA@}|YN)J4FGp|WVDfEX*w$k(*8hq5K1zoV4`4#@rk{sjQm z{lTfTpFXxNndkwi7q%_=jaSmx?F!m%<2Adv@$$%+nSlRK|EE%~jZ#A|)1 z(>R?PS2LERAJZTe;(r(ON0L8z>!lYD8rH+X{ivfmL?Bg2{SWTV-=8IIQ>6d^002ov JPDHLkV1mIti5&m{ diff --git a/recipes/icons/el_publico.png b/recipes/icons/el_publico.png index 5b82306ee3013829ebe2e14ff9672393c03234c2..c47cb56ca06b3afab58da7c4568ff6d540bde62b 100644 GIT binary patch delta 982 zcmV;{11bEN4WkHFn~y&&wM-|Nj2S5;VvZHOLS# z(Kb{5`uo>VY2=Na^UKoo)7jHNThA^`<&dDwCPm2;HU9kl?|-<$)ktE}I#$p%QTXBI z*j8@oqOaFcYR)M~++}7%*;;YkY=GsCp4CcZ_0`+*$7K0IXMN8wPU3=;=8~cK;^x#vVCb5t`R3}_S8votVBKzm z>ZY^WV0Y-Atl3<1&MQgpy~gOBtM}dE`|t7IaE0D+g;C~{qs}Tw>Y%Oo<>=|4uG(OB zpniJHzRM#~;P@W09X@AB!Qu=n2M^v~AKB}3IrW#o*U`t0x7 zS#al!-B+_xagbao~E5>#4Qww!z9AJ;)R`>>D550005PNkl#Y9Rdg?oWtW zJu!FQ(PPI)I3PY*VoFX2)g3!?Wnm$oF~<=H{m66L^b!dGvU0ZbG>pv2vj&tnpRz?M ze4FpwrB-%}L#4$A5AcxjZ`8j3kP5Ko5P@L2F6^ed$g2Qfx1PWe0Os;lRLf6rOPnNd zO4ZX;i_cgaqKd$ONHsZXH1#oD0XA0mym4x>`X+&yvjhMbTXf&LO--8H3DmZ$jbv2! z`85i#1sx>z1pvtX3H^fu`3i7<4uSDSg0sa-tg^J%=!1&rConKLR2{itebBeUfCj8& zC5a2AWfv6;UA} z5ePjpr3qvM_ShbHB7y&bQIR6>^ci;ncpfG2;w5tf?3KOo+7*a5K>}~zaTkF19|(j# zGC+uIz&?E@@kQ{j4GkI;Q1Tm_2m~WPe+7U4Ngw|H15%Y!6HnhGY5)KL07*qoM6N<$ Ef-!g~oB#j- literal 1688 zcmZvadpwhS9LImB(?!&gTM4IhO4q}ljXk#MW{s#Ov`A7LwpkgAp+t=$N+m^3Nm9C< z)k!CvE()dE+=ZCS!e%aE8o6vU=g~QTIK9pv-{zC;2vU#qyi8cUW zuCo)#9Z~bi()tm3l5U=QhsboMJ=q?B+;ci=+KkDjpOZTofXL+l@J;}rL_YDl00@Hs z=%E5YNC#j6C*!%>S^%b*xo)F42o13UeQUuyjL-Upr5sV*nO~w1l@!^p%Sx>@L=+poFt^DP?35`ZQ__f}ZHBeO2 zxHn94GF_l&{gKQV>+Y4Fx!N9kQJR#Y?3E9eekixtB!Bh3)Wq)d?Z@LIV;?gAX>E+8EhAKY}H?~$nWLe7f(RNv_1GVe&9gTXtnSP|i7}xjcr6ln; z(e8s?nS9Zn(26zA0)3m2wvM_j{$J~w6vFBccAi2*SeBBl(P&!s^8|BIt&wL(#FA?K zPPJl4p8r;(Qb`jpSFCg_U9^6nK&X-{y0ahFIr&OYT^VltRJV;TFvP0*2Zo#5x-e(>G8nWPmj zPK`ufW^`~_TwbYQuvXX zM^tSWdQ5uP*e*zYBJsjX3dW1Ng>IC4x8|M$`OM|@$BEVwC)YbqTna6;nQmF(@XAs% z)A)4n?J+ZpW##)e@(*5*y`4Vo^ut~EEQ-^t3?QjuS*{^?mYvX0qBSRPKQbJ|bUgOr=nb8pcypqL$t5YT>bNg|lQu6`W=2FJ+>p}`Dp1TBOPoY^!colIl0{0^AIR<>3+OT0M-LfcqE zSW7g<+zkzz2XMI@0tyuo5n;ueq>(~stKbl3b9^5X2#hBaDFI<@Ujl&?>=(wS2XP4m zR|E|O=-U%7P~S1%W)cVibQUv!3($CsE(nQ3m<3aE{$)H_{@j2`q=vsl3qcl6LC7pN zoqL!=2j6yu;sh}_Jo0e)E{9A(D1f%Xq0tyLY>l63YkM9+bf@I(U`5gaL?m9!Uf+wf zJf^gEx;p~~E0B)-K8hX04GdX>^PYjJIXbFt8QkutN67H%0?4IMgSL^1Bw6u delta 24 fcmcc0^pt6W3NK5#qpu?a!^VE@KZ&dx6-^icVh#sx diff --git a/recipes/icons/elcomercio.png b/recipes/icons/elcomercio.png index 4e0a601b7b71402c20a3e5504847210c865db8e7..f1cecc92c02269ac35cabc02a9b6b5752f9325da 100644 GIT binary patch delta 277 zcmV+w0qXv}0=NQ@EPuAZ!ji(*ovW_G%gpWX@Sw`v;N9P;wz#9Pv(DAl{{#eFq{C00 zzBG=sCWf#7>gveQ()s%O~;eWW$YOKid_xZ`;>p_*dyV>TiyS?P__0-zi z|F5sGk!D-~005IoL_t(|US&;J4#Q9gW4yhycYFU^7PU%$Vt-)-#sECAnnWwWF)!vP z8{->bk(~-h&8hepNAYbm ziRm&lz&JK|Ks)x@3)!=C900jr@dSsAfnO(rG=M0`tkSq|LW@G@b&HQ@Z;+2;N9QJ;p^1e+s@V2$k5Wl%gq0; zue;git*lAue-gewz#9Pvz@E1|7vRg1O$J$&}yv6T%^NKoxVYpxipTn zCWfyz?=}Mf005XtL_t(|+GS1A5`-WKq{{5fDj`0g{h^E3^b1EUm zP3}fo&; z9jh2xR)7$Y4HN;Q_N5HAyb0$OmdWQf1L9(k>F*E~pfmH%6YF+}f7v=zv2@rR#xV^q)DbU&0B|(0{3>81m zb1z)-CV>CMw3(ASm3V%=ySTS`9+$~SzNH59@5XL*{_FZYX>E`cs{rE`X2mC~(&P{2 zZg+q1z29~gx6D_dF-@K>jv*3~z6Y<0H3cxZ9%SZfN+>TcKmPCkf1!7u9-cd)Vfol+ z?Y)8t7qvoGeCukK(Fi@~JZ0*XuK}mpm-R5H-n#Je6vuC`BLb(*S|;Y``J~J~uJv9D}9U%az%sP}*W^wYVTeV6#7R`GXy SduH<-6d9hbelF{r5}E*)0k~cO delta 467 zcmV;^0WAK)1L6aa8Gi-<001BJ|6u?C00eVFNmK|32nc)#WQYI&010qNS#tmY4#WTe z4#WYKD-Ig~00D$aL_t(Ijh&OdOB_)Y#ee6mvqB7PKoFFKfd4@tkV*s*6s(n1al1fF zWwQ`mVF_8#U>`pk!EdH;!Nw+_U=Y%!OTac-1jC}RAsXFl;eYHnlgtM6cK6(Ke%$vS zPbAg!S|>F(gwciQz~m4om>nRhplJ$O0W`&e#q9@p)fukz3gH12j2*D=pf+F})EbT4 z0*d}ayk8%YUI1Xs(R`67pR0zR+2s~4ybqvUU2@?teKn5=!nVXz_H`@_-1I6(r5iwL z{W)kf(9ZaCIe(FOHvlQOi@9gGR588^|y>40aUg102>inDI0J08Gru`2h!tWNR6sxN^FFOU}B z{8APzbwV)z-CfSg7PER6#JVsNec`=#B%%Rn5DMrZ9H_y1CE(AE@;VvB#&?C7bZ}y~ kjS8|WMa;p}lw#w@54hh6x7&EzOaK4?07*qoM6N<$g22u?i2wiq delta 343 zcmey(c!7C>iX}_Bqpu?a!^VE@KZ&di3=9g%9znhg3{|QO3=Pc;3_t$^=@$$PK^zQ> zq7e)XR?`_6#PhF5$xoQ5m{iZ0f9VN8F+U9e3+&eiXm4;a6Si?)3fN-|(xh>yMM^fre^@w)Mx!ps~>3Nl^FI*8R`Vs=3wsudj-r#dVIm zfS15veX#!T@BH!c;Bq5y0001hNkl|9@WT?f!=(3`kjFDGNu> z@Q2sp7r%kA6e+PYKcb*aVDyO6Lw3$J)h9v3F*7sOM;CQ4rw0+c8h9N9xH9p*`i9CW zGtoClK?4sK%mpPrcMWdU$3qD5(td&#D(Q2p20IvM4OTE(QZg8+8JgiIZ*BMlwAe;T RY5)KL07*qoLyMM^frhlg+N!zLps~=3pv8cfz~*y!;a6SXNl@H7Lh}$1+ATHLA1BHkC3TLw zYK697eXvz@t59vAojS=b0001nNklcFB6Ggf9VN8F+U9e3+&eiXm4;a6Si?)3fN-|(xh>yMM^fre^@w)Mx!ps~>3Nl^FI*8R`Vs=3wsudj-r#dVIm zfS15veX#!T@BH!c;Bq5y0001hNkl|9@WT?f!=(3`kjFDGNu> z@Q2sp7r%kA6e+PYKcb*aVDyO6Lw3$J)h9v3F*7sOM;CQ4rw0+c8h9N9xH9p*`i9CW zGtoClK?4sK%mpPrcMWdU$3qD5(td&#D(Q2p20IvM4OTE(QZg8+8JgiIZ*BMlwAe;T RY5)KL07*qoLyMM^frhlg+N!zLps~=3pv8cfz~*y!;a6SXNl@H7Lh}$1+ATHLA1BHkC3TLw zYK697eXvz@t59vAojS=b0001nNklcFB6GgB02RrQkj#sW%882q01^KH5YLK_{{RX900+~Jk^cY${{RF3008d{814-j z#F(1Em6a05g!%vg00DGTPE!Ct=GbNc004_gL_t(|URBJ~8pJRVhT)*L*0ycixc|%9 z_F$8L^gDc?e+C4lB2m9-68zh{0?!Mh8v#*~RrPW@6L6qZFBD4#1w2jwHW72*FhJOD zG;4EuvfQG_v%6tnHt6*`Yv#uyZdx&qE{?+$U~@3{2?LZ`o;Z{!NO22zD+W z!Or`CmtI2bg24!4B+Vx7j0EQIfp-6ag5T#hdCmIPQ7}f$Er(4<0prv&1{7qw9RJc*C>U+|S^*98%^g62+wSop`8lD$w$G7Q mD46z%(K`g5(lFKo{sByNG@pNEQbGU#002n`MNUMnLSTZDc$j$r diff --git a/recipes/icons/empire_magazine.png b/recipes/icons/empire_magazine.png index 34ef063072f8627a1c47a5a13486ce299b73aac0..f3fd62904c6474dd1134602ae0e96d8322a6c4ee 100644 GIT binary patch delta 190 zcmV;v073tu0-XYoEF|-9bpQYV?m0m2IzjJ6OYcWb@m*u_V`%bZY4T@m@@sGF5*m?E zKY!~RBmVsSL9Wu^0001NNkl}riEXk$D9rjUSo473=?G2m-~>BvegsF~_#ft~O&Jy7jmU?d0_-U5cp3+_N6|0DPV sE0r}6z&s3880auCe}aXAqC`A7X}9I;^Cdu zgzn9uFSM|M5_SfK2(=ifF_2=w1%YnyauAeM>U+S9SjiJOQzg*fb^vs50nKK4PavaG wKY)M{^C}QraxhR}puxa+2b1{y!FqrVNE2sSb-K)Ma{vGU07*qoM6N<$f}F2e!2kdN diff --git a/recipes/icons/epoch_times.png b/recipes/icons/epoch_times.png index 13c5c5d7dae52c50d0521a883388b038da63180a..f69bc6e38bf29569f7804bf222289042c504691a 100644 GIT binary patch delta 572 zcmV-C0>k~@1%d^TBV7SUP)t-s4kt+d{{B*Ad{}9KbAgnEkfEcjxy8%VPGET2-Q#e7 zl5&8QpsKeJDN3xhz{JYZ!N<_s-s9Zg>3^>g`!-fo*({Zhes|KwU6JVKhl) zHcMua`yMJBGg1>QOroo}bb^(rvAx~l$k91XX_FiQ z7=J%gZ9!FUMObo3TXdSJ?=WORw<=7tMoJzJrr?ansg=k_YbAKtubmC0aDtQ-N|HM%T9IYG3_uT25+?rN8c7n&i|0e|aw zSJaP`K{a5}sP@nqfoBu286|wpEWqOOA22t2>a~M|%|>8c?lC){qXqa3F*{%lV5b>) z0(vA#I)(!Xa2%(9IRIKQ(F!c2odM7(@(!(|MC9r;bPp&T$5jO+9V%~E8&X(0000< KMNUMnLSTaL6)wU6 delta 664 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 T!k1}b00000NkvXXu0mjf%WtPB literal 6217 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 diff --git a/recipes/icons/estadao.png b/recipes/icons/estadao.png index dc99c5c77ed75eb3fa7b8dbeda83da3245e404d7..3d202ebdc1e15adff277c24964a6dceeedbf6672 100644 GIT binary patch delta 529 zcmV+s0`C2`1hNE>EPwp{{ac*O^!)y6tKDU(+g+j4Vx!btp3%b9zL!ir!+JEf&qRZ-oyy@%o{GG(( zqrujWt-{md?W4onhPd72^ZnlF?rfsVtI+Mst+3a1Z z-C~rul)>V3uz%jb*6YLA>YBLFp26Q&qT5e=ep>!cOiw)z$7_{;s3#_sIQ8nre~>8> zZOY^OuqTyJ4CnyBY+}jNO8pecZOERuS8&%$*JX~Yhktxw05CJ603aykxWF<&I5!Uq zz+Peyk|>lwKn#mU#6{0@NC7Obu&h=^d3~d{Rfu+W^Rx$GjMzWO;GuJbx&h78GX^}5 z^u=ZCx^Z*c?zmIvJ|bq7d5T)L`of^C#eSgm+xv&2a#m-SPax*y_{j`)jGy>-zun{Qkq;^2yrdz1i&k{Pl{u<6E4}!qwya{r#Q9won8+U)w(==HMJ@mZC^ zqrujWt-{md?W4onVx!dL^ZnlF?rfsVW2M>4U$o`2E6*6VYk!xgE`b7o0iddX z02r+3B2X8<09a5Q7u2D%Op0X%#|3>Q1++5D`ni?lCVzu)dKBx4`c33}Y0RcctfigT za9(t{JAi1pqiL`~Fe)Zt5)y^Fo>^l){o12s9I~&Cy!x@N{n6<&MQnK=*1EM$_V}@` zBP=&2y}Fv@)Y~eSo8A%j8kK&O>m9^cIir2q`Jt&#)$7)aSI|Ds-&`#6-M7QlhwMMo Xg+#&`unwp>00000NkvXXu0mjf#0Eix diff --git a/recipes/icons/eu_commission.png b/recipes/icons/eu_commission.png index 1d949a247329d7487afe7157c56d851c3b17a9c9..47d2c00acd816ec38ae4545c62eea34ae352191c 100644 GIT binary patch delta 117 zcmV-*0E+*&0ki>-Ms8V2L_t(|UX6~i4Zt7_1ij=_umS7?MS3E^lzGRIjg*F~PC8*; zPQZssY9=|4@k+h!Y0|q23&19*4M=kr7B*}Li%Ov^mF2Q0!=L{z`=>D@hQ-tvZ^!`; Xks3?2dM*oI00000NkvXXu0mjfZJsat delta 120 zcmV-;0Ehpy0k;8=MsZw8L_t&-SB=gw4ue1pL{VSzQTPUMA1Km0(sIfa5NC@NO!Xz( z;~()L?gW=W7HGll>?*IS)+C)UtA|mIAi0he|?y9Qz-QCOp0M8K-^~}uHGc(|3 zX5Cd)@w>a^cXyMm$Ql3u00eYWPE*q7>Fl**H(LMz0Jcd)K~#8N-P6kwf-npP(4tBD zE)W0zUk$hEh%6X)j^0&E4k?hqx%+e(cjN20>3=}*rIe5Xbbq-}(EYj%khP2?15yhT zIv~`M2JlJ(Ks)B7Knzr^tO{m;84Cs^fPU7DmTn@Ij~%qV0#JF}kQC%{NC5BvQ9=9! z5U!5^6gnJ%hm;eDI}ph6n#5ToG=MG^g>wE-o(xzv0B+SlwGMviAifW44$KBP`opkJ h0kC`%{Mvu<1QfR$8aodfx!V8$002ovPDHLkV1k7xfWrU) delta 292 zcmV+<0o(q^0?-1GHGj+i0M8K-(;_0)Gc(#mMBPFl**H(LMz0KZ8@K~y+T-I2={gdhlpZAQ@= zwUqaN*Wsd0wdl0$%)de46A+DgPnR+;yq1~%2Z&&;jYT4KTz_gLe_lIKIL*op2u}T| zK<#;JHwA{W08$PylL-xQl$ptT0lXYgz^Z`s@>0@FmF$NDn`Q`b*e_%P%C0AmMF0qp z$WM2Gs_`MvQGP9Wm~a9TAA*qmnH5V2aqNMJ#mdFwqM#lZwd$(3wpr_11z6PQPP@cCK`SJ1n`uhF*`~Uy{@Y&h$+uQQq-tyny^yTIC=H{FT2)IvA=*P$G&(E6&2leOY zq8AsP3=E$S5AD;_?$y4#Y4F1a(Da=NvAl|NrrHAV5|J zSF$zL6w@rQA{A^&WG{A0LD3VBn!VUF(D25h+w$Jt^55U|<>mF}=Jn_2 z_w4NX@$voo`u+R+|NsAo*&R9n004AJL_t(Ijm?tB4uBvK1$wb#1AFiO|38XZv*^Wx zQNx@jo4LFIA&L+ri0McVFo;*g3#G?@29ym2MT(fu&UUL@%6@HW-A-XNyCyKBEOZPe>G`gc=00000NkvXX Hu0mjfutW+t28ujr!~4&F6!600001bW%=J06^y0W&i*Hk4Z#9 zRCr!Jk5vxDFc8It!%*7$A9Vw2{TIul=)t6)GB`VIdZoCax_eZAFh-;>?K4fqkV8tV z!8!sCNreOMs}7CXZXbDzc!Fy2x(;$4QXz40;&R#EPuon9LFpo$~-F0OEl70KhS&A;-_!1mkE_T16-+|v5%w`;5LUxR@JzYgbcry%d?nP4ZDwJjz-Cs&lgo}2} zY8(HgPK3GfFO>kULMZ{DJdz2hK91TvcSN=%*@Q)-QDBk;}#YcprEAj@$uK! z*Fr){0002QNklV}%iZ-5OoFM*x{3!J({<^e9?6$cc>1u?)$U=_ej z4KS;45D!ojm~m?Zcb%AoN5=Msvq`#xmw=K$p9^q12bNU6+HwFXXdX zGRqSLfE6I_Con#qAZbD$Ywfow*xq3HjG5lK8>FwfbFVs8z#o=sE%JxCbY1`e002ov JPDHLkV1hXKjYI$d delta 326 zcmV-M0lEI#0_g&fEPoaj791QLA|fIwDJe5EGdVdqLPA1KO-)r*RbO9UW@cu0cXxn* zfQE*KmX?;8nVF!Vq_VQIz`(%F%*@x<*WKOSgw_F@&Et-|FgaN0002fNklyv3aFu}n1b=Co7DF*{{?Z57lzaom z(-17=0YYZ{$FVYNDAXBf1mf7*&_AFqEBILQ2+|7v=ryoi4|xIHhLPj}c0kx1u%KPa z6W}#4RKNlSuvj@k3GB%O+P7uELyJow98y2T=pnR&OK%KIVWkaco!?#8@oy zUa}8ZWi` Y0Xn%yOrDVay8r+H07*qoM6N<$f+RGRZ~y=R diff --git a/recipes/icons/express_de.png b/recipes/icons/express_de.png index 57a979ee076a145295b92a0508477aa7fe070d3c..5ef67007abf7c832778e458ff2ceb79bdb910170 100644 GIT binary patch delta 764 zcmVj=)9_3}?JFPu{{HYwLixC~_&6GAH<@p!JG_ z{_*hr<>T%&DD{Ye?kyqpiGuQFT4ldpZdSN_K}PCnUnR5hWDVF_^hb* zkBIFl9rv1)`LV3+EFk{v?Cvil^LB3VMm+k-#`Jr1{p;!OFC*_dF!{2s|NZ^?z`gW% zaQeZ$^Jrl2LQ**Y{QUdU&-IIi??XB6F(&X&Me=7}{pIBKfqMPu=kX8!N){MOX`-`(+6O!Rke^L1_SJ~Q=&fAxfYlivXwfAV5l{paTQ zp_}nnPVh}b^n7*hE+YKb)$c?(_^6}(>+1HCjr+>S@JBuW`uhFp=owq8(EtDePf0{U zRCr$P&Et+NQ5c2c{q3^aw#~6^+qR8E>>GTCm=1tbWCt2vNWD&(;zAqv+ zh+-rn&yq^%zKSVS6!Qu&e*?6;yS{h^0fq^H-z32F2Eg7CK)nhwjZMw1dI~xv!q96Z zp>D`T!zYFYs|r?-&Hlqd!KFkvT{0L)CRPTl2Gnsa-U`s>`^>ODe;EXVp+W`7AjI?_ zR0d(b-&zw4L?KoK#7EF*bc_N?B6v;)39E_ojx*m9-&OG8BTDk!e-tDqh)I5mgrC2- zAP6&hm@QxEJTOPiBSbhC7E2ds2?VsP0e}AfdsaX^T@|dZaq9}`psRvSV|+^ig-{8& zw?Ww1)qphKrxkE;SOQrVgkx3%933u30VilWw$@r9x#4yTeE2;>B=Zidh#^$p<3yzW_$1{pLiB|{KNje+>5=q=YH=!_jA7I+eItRc;UXvn;41iPJ zC;J)ENXwwp|5d587kh~apd=3}y5rL^=av?&R;$rpMKQ0UW1fR+|i!5Gs|t+t7AbrcbLHD{DHLQ{V$1ANWbuR0*e z($@e?`dE7v-t< zgc>Tu(CoA(F2da1smnTK{zY!;Y&YMyu1jaA!*>}Q>qHKP5k8(%X579gkJ7Fr7F@XV zs0Rz-eCTyJjkCTGd&0_15ECEmHiJUEu{N+cjY5T?!!c12jIc!Is5#WRz~{b}dx&PK zO1;JJj^*UV{+u3j_RL!1P@UD|*4xBFz3`9a1bXX&C+^SCN8D@mZnC}KBkoO zVRJ?Kv1&v+a*cF!&pbL~Q1W3qnTJ7*x+sw6>Y0h|?`Ugi-4iLm9*$!;3y?`HXk+2 z6`oi&)<=?;zNwm=9Qv#!k19LAt@HLxFqOp07?52dGLu;Av+<#OkY6oP{O-Ttp}En(~ze~vJZXWxsNA*n`kP`(G@(>dq-!& zniHNWx9qO5+NOH^Es4vU20x%J<&uwz7vTMG7euEDvX}xkiN$Bb354K6a9BJZiz6`b zp(H$xL?GcY+24OCad;$V zmIhofs2~=~3*!}6yWYhM9VH>$%W}bVG0?-$#s&vQ27CtGh9%UN*dVaDah*U%*;NNy OfSBlnsH>EWvVQ@~2Qe-H diff --git a/recipes/icons/fan_graphs.png b/recipes/icons/fan_graphs.png index ea6a43fc85ab771cf44e12a0d16889b90d3f6794..223c0e2265384fcd0a4bed52f4e3f57a71599e78 100644 GIT binary patch delta 313 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 Q{r~^~07*qoM6N<$f&gVA^Z)<= diff --git a/recipes/icons/fc_knudde.png b/recipes/icons/fc_knudde.png index e358cefac32bd2c05804d855ca1bd2bea457b525..8253e6eb27cd0a059b1a1ac8e8918e7b6f2e0e2e 100644 GIT binary patch literal 5506 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 UyP}G$AQY&OFVsD*`IL=iE?nA)L46+fq}`f zDkP#LD6w3jpeR2rGbdG{q_QAYp(3|{fx)7;HPn04BLkj0zeS2!+~c2HaQ2l4f1URJ z$K_3m4jyblqCG0BqjhgCxxe53rGL$yfJf5i`tx#)wq3o_x@qFdooBZds@C7SwQF53 z$G4j*IA8_qJn2z)^5RYUn^sEfn>ayocE^s7ZZq@aDn2IN$S^&Svq)zD z+qBumKY|n*dLy{y_V65+yEoJ3@_}u~_->X5TvG}yUZik+&+LfId9T zba4#PIG>y#Vbc)EF)^>G-tppA0ic9xiEBhjN@7W>RdP`(kYX@0Ff!6Lu+%lQ2r)9W zGBCC>wa_*&ure@M@~fd6MMG|WN@iLm77b<)4Xrbrvw#{TK{f>Erb%7 diff --git a/recipes/icons/fifty_two.png b/recipes/icons/fifty_two.png index 8b9b2726154895d5fd6be0496d86f09a51ff9077..c563d3da2e75fc98ba09a03364010dc63cec6591 100644 GIT binary patch delta 154 zcmV;L0A>I01F8X#7=Hu<0001iRA7+ecj#^1ACP7SQ2u~Eg1-LnyPyCt(Ih(n5)*|BWE#f9!vFvP07*qo IM6N<$g0_4;wg3PC literal 495 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJfYb=jG+$o^Eg+kNfrXKQfd$9{ zQb2+c!EVolv+Wp#;B1gGpu$N&ogf;_0}>m3BhLXTTAIyR9OUlAue)1^%=XryMhD-%r`O*U`nB8O#fWguApX6m92tR%l?wFX& zd}P&Apt^n`raMflc!0Rz-$P|}!y^|MeezCjDrLEP=!(2mkMWL-B{+&t zm_GSnK#OlrRKtoK4(=)^&Ym>B1*#II2QCyIIPC7vc8xKrs?Vg5!OgOPS7Cb9m2+J# zmJMQJlix4|ovND=l~Z-$sD%4(*2R24Egj_y47X|*hp3q+-voM=!PC{xWt~$(69BPl BiCF*u diff --git a/recipes/icons/film_web.png b/recipes/icons/film_web.png index 1791a862ba01ba74f4d91c68753ea1982eaf9173..8f2c50097d20b67693af253d10d9ca502d713404 100644 GIT binary patch delta 71 zcmX@c+{!dTg;9NCqP99yYh*YB0|ReKkY6x!$lM)Te;-9z99#Cig(c@m9#Epe)5S4_ aBYNVsI)Qbwl%4=Z7(8A5T-G@yGywogSr{e& delta 264 zcmZo=I>tOf#g--A(btiIVPik{pF~y$1_p&>k04(LhAK4%hK3dfhF?ITh8GMBr3MTP zuM!v-tY$DUh!@P+6=(yL(3+^K?e+iv|AggXtw1hsNswPKbI9BsT7Mr!SsYvTy@e&` zNFGpvG0EHAh4)SMxmH+JA|LfZR>e~P9-8Gbz{OQ>j7Z-

Om0Rkj{@7v8D9v=1O z)_$|DOP-y4vaV#PrA?llp2E7Bj)m;v)cEJv_2$?2=hxoNzf?|2=h(@OyS47x%g?^D zhqtj|r=(?GSvfW|je>fyq?xM5y_mkY6B87KdUHZQJlDp#?AXb`u%+J2zg}2V#?i(? znwr~=it(gy0lWD zpH`xwsl~mX!MQp&HR;&NtjE6H+|FsLr@76;tDTcFFfLe9PUF#k!~5pf?c~+1$G$*4 zI=juo+{?ZF=-BMp%Z{O};s5{vX-PyuRCr$HkVCU9K@dhet39@LZQHhO+qP{y+qSL0 z_GThxHr=bczGsyY---G%3p-~3CB!MP4+J>i<#=Imx}dBY#~{_i>;Cd@lADKLxEXsV zMf3-UB?+Q$JY_h4?{_@7)j8?byh$~3{F8Hv% ze*OW0K|j1h$WX3;ht18)WD${35BK&B#v}Pfnk!uTrHGF1ygfcKsYIsu1XCinA}M*5 z^;uir*x6O8-jg2;{3Xq4Y)nQbm4)c+9KdoB|B5^|CcglGW+Z4f4kL;rXBMEi#4ePY z<@O3Slvf~BSp}eBz?Ik3*0HkshQ_Am7F8>>wJYh%SJYRpU7vCq!fA!pV$tfYUEK)v z^!D{5gy?}skDqK(&VSglZtHY9(KGD%i4%qqOKZX%fL!I6K2><{907*qoM6N<$g0JN8x&QzG literal 1572 zcmZ{kdolxw8OZA3M0Lv}aUkXE%F(d=(JdrnU0>>tnjocDb`&-Z!W%X!Z`*xRgGt-cxn zu*S}o?udGv+!U6fBRw~+5p~PGEtnPnlwMYx=d3{IOM)G3XrNfEF)k;$wvJ2y!gTSRnrNNG;;y8&@uzc{<0GNZmCt?prR?SrcJhOZNSFB(LL_B-Nmn>TIJFg7IW z>uq;)W=UIX@*8Umg^5N9iR6St;?;!ElEV4%-go`&`&mpC6%|cQ&Fk^!nk$Md&2}j( zs|-jxikfPD^7D?D7MA8-$6~Rt33b{Nj`uE%^;|j=j=^Fr zOm|LnKWnckVc1xj!Y0+l*;8*`_4kX%hPs5+qJh?0X4anrRx4!QOT<(BJmZR4khw6vjqkFjyP9 z`OIL4u)3U;nKAsj>r8oxFy^$4rTNT&IOahqCogw)SaSYhnQUh0YHZ}p(DRX@u7!!d zhjeL6H?y05jPwrs-&%mxgG$8q~OEcx|bF(duPsj_0v+c%l%H_{(hhVs4E zDXrJg(5Nf9IoS5-u#3|X7xuM_QKLg$@s;--Gc%mCv+g{tNw2SPzm<#Jxy8%Rt7va1 zxFIA#co#?ezd9SfjC9YB^-TWx^7ClV$C2)TM!N^wA5HeQ%49NF;b52i9eOZq=yl4R z*PqeJsz6)UVAR`wGwWtwQxss@(Jk1Ny#rllwltPX)BD+1KP%{v@At5y7)ENHw=0%2 z*U(m|8NG2-;;?ZG#D**jZFO}&T@9lY^{6X;>@u=KP;4A72GZ>c+c+3CL!}oy8V`II zb8>CWh57Y;iePNddCG*gcFSs`jo?U#8*t#SJdk&+%{Qk>S2G1fj6@7O%|5Q0$R{uB zue;Es^dO}4OZwBL(Z5|@JG;K6cBkTVYw(AO6+@A)aV6CvvH*;IVlXba>Q>AxIqF~L zba^;Q3#i z#!~23+rB1-wzg%?2eI2-=E&a`AD(4JWgcB%#eU)^9k8_1^RYV_x!G)0pjH6GA_hI- zZ2~J7fhR|Rz}z4Nb$|qsAOpzQ0HU&qB$z~n$rL>Zf+5IXb6Ebld{5v9c=EhX{C~n` z%R?eG!CGD+IKWE~#tA}z9iQWkFge~lt}o6QZ-S>7P;n%PXk-A94TvNhi)f7V5eNcd z0wF9c49}C(sG&4IAjn%9J%tJaRk0nDG5#2!cyKrz3K^krJq___1|IFt SUmHQt3c$|Fo?c9IKl3kUSf$?p diff --git a/recipes/icons/flickr.png b/recipes/icons/flickr.png index a0e6b14bdd9c69043d1822724ebaa759ea689661..6c9ca68c31d99636ee1d4d437a38c481aed70674 100644 GIT binary patch delta 236 zcmV;M1&26R$RQvf}BvgZ9hXUppV005IoL_t(|Uft8j4#OZ2MbW{)gY@2e@BjaML9(sb zlEYgkXMH7#W<*43gGxp#2@;F1Y$Bp8BCHaPC@lt2MnL>jFz&vZy-6C}kK4`RRu1|# zAIwJxt}7SRPsL#9Q;R9^SVgqZjXY=r`#e}aD}qib;79OF@@Mjo m`j2^_V^0@{t{z=py8ds$aUJx#A#7Iw0000<{926R$RQvf}BvgZ9hXUppV005auL_t(I%hi)d4#O}E1Sv~mdhbkedf)$lF9+F( z11RFzb0WY2C=pyDGS(rAvxbN$^Z1L;h)kjnctaLREV;Dc2nw|l=EF{RvV!Z07*qoM6N<$g1(?^q5uE@ diff --git a/recipes/icons/flickr_es.png b/recipes/icons/flickr_es.png index a0e6b14bdd9c69043d1822724ebaa759ea689661..6c9ca68c31d99636ee1d4d437a38c481aed70674 100644 GIT binary patch delta 236 zcmV;M1&26R$RQvf}BvgZ9hXUppV005IoL_t(|Uft8j4#OZ2MbW{)gY@2e@BjaML9(sb zlEYgkXMH7#W<*43gGxp#2@;F1Y$Bp8BCHaPC@lt2MnL>jFz&vZy-6C}kK4`RRu1|# zAIwJxt}7SRPsL#9Q;R9^SVgqZjXY=r`#e}aD}qib;79OF@@Mjo m`j2^_V^0@{t{z=py8ds$aUJx#A#7Iw0000<{926R$RQvf}BvgZ9hXUppV005auL_t(I%hi)d4#O}E1Sv~mdhbkedf)$lF9+F( z11RFzb0WY2C=pyDGS(rAvxbN$^Z1L;h)kjnctaLREV;Dc2nw|l=EF{RvV!Z07*qoM6N<$g1(?^q5uE@ diff --git a/recipes/icons/focus_de.png b/recipes/icons/focus_de.png index 5e9e8793462c585934a36bfa90ce59956f4c7221..b8cd4375f15999591716408ba38e14a0c33e9a6b 100644 GIT binary patch delta 2689 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 diff --git a/recipes/icons/folkebladet_dk.png b/recipes/icons/folkebladet_dk.png index a2fcf5f6d9c8b42f4cd506639df20bd2ad4a1b35..1bae42593987424ec0420019e3b7ad08f093570e 100644 GIT binary patch delta 10 RcmbO&_(foX@)ivu&HYx^l0017j41)jw diff --git a/recipes/icons/folketidende_dk.png b/recipes/icons/folketidende_dk.png index 51aa1b0d8bdafbeddaf889e8cb5da5423df77595..e062cc22d8b3e0c8d14a75568b4fb039049d17ea 100644 GIT binary patch literal 1056 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0V7wdP6XFWw{{R2q$gK>B*vjTZ z(E8JN;B1Ig-opJr5g;4RfNOzhgpd$5Xd-YyxHzU_H0vN-pfU);l!cf8bSDtO*+2%w zprVF2W$_hINRxPHKZ@pRrx=hC7 z$N(nQKNF_S)MeRlppnJVBUIF+v?&D-@k z*xMT(zxeUwi_eyVy28p*-nydV>f=qn+uc8~FI)axy!qkVzmGq6^M-x+x{vW0>&?2z TD4BX-N?`DG^>bP0l+XkKgKp&V literal 2370 zcmb_e3v3is6y1fYrM9(2K&T+osc5mx&VP1iWMP;6&;?7IWg|rqn9tjGV0UMoozkTN zEFi6lViQe3{NSG^%1;asKA{8zL{KCsY9eU?gNl|CiywmEo85N7So|bzGCObHJLlea z?>+C%JXtkmVqQ*Bj>Tfh3zU0<@Q#>Q_Ra7=(a_KhZv*1x(-RiUt+$(()w24fp%%-{ z2h~uuQC;zXAZt;ZsAv+fC8KeOwphlFPsT-gE-(-Y)Tl8hdVa@l6j2o?I*qL$D&ihc ztClzDU~0pZklZj=<`s1OIAm;6fCi$#5Rqgw5=#h4Cz`S=z_B@vqeu#3%ypt}(;-q_ zQH6Lk9U!cY!(@_S5j$@qIhx`5F$hJF4xDh{B#n`rzz_mOAw547defD#5cK+ce8DFt zT5A|_0mtj>>uvS4P19>|lIMAxpm2)9AOcIwj~QYTizP~WEqFmf*44P7YB9vLC`wwL z;Y8t1(;-CT6%`r7u|!XzV9M~M7{^H)fk&feTq$(I2!d8IPG5Z1>+n;okF@)}4wCe-?g=#M_51w1kMXc}2ieOF#1e)WlR>X<75WA= z%Sv2W#2DxVVq63^LrQeqXm$fWp!M+c&z=^;&v^eVNgHxr1*V=9wHN-IQJ zjhX2geH{yUMFTidh~P1Thr$h!v_R7WOO+9nKoDtYg{G+C`Mpqzzz7oG@MsZDhmPUy=v;+UVg@H{{}(&6KMNSfBYlU0^DBv}Y7ip?!Fa1W`6mGz>_Ddp4b# zlYMR2Vdoh(ERs+PG=njA*lZ*_AI5kfg-L=YDVccP{;;ZqcmnU5hV1UU2g2nZf;X ztQXfFdvy54;cJqYVt01R)}L0no;$}ca+PJhaw=CU+`FS?+0kvwR!weuH8*>K>$acg z^rM4F>lr9S-<9www@TXzyGc+*3C_g!BNP$f|f5= zH0q<;w|LrGqHP|J>y3d&I|||-bM0>z&vp$c-BYvt0{CG|-nUHF&b;4pD-Ug*VZXBX zy9v^n#^Sbm^zPShnp}K(d3od;ZUM-y?%r^&+c8^Ug{cgC4MYC;}Dq~HlAFv@Z^Id_y2O5 zXkH;?Eht*lz5V%)N!tA*fBUjd&V80FbMA2jTbsW=GW}Qaz0>`#SYIYe?9Dm9RGMEo N0pAqw`U$g_`~ekcF984m diff --git a/recipes/icons/forbes.png b/recipes/icons/forbes.png index 8d06cdb090dec6d14f605a54deb9ee6989988c4d..e8566f8ab97ab1e7239b738e11d11f47145b1ea5 100644 GIT binary patch delta 155 zcmV;M0A&Bv0n!1GKYxBnL_t(|UWL)o3Bxc9MbYPy0sPVopcD@f9BokTKT}1))KUt$ zpXWme31_UMze0k}5dMOV2CNP~?+0vV0yHZFHV5*Np|%8EGOQ7vdyTN|WB`=_zE=P* zfj|>*xrK@Zya2B-ALt06X74hAns-i6;b8)Pp$xeH98!|t0~Bf;Ggt`ufQJA8002ov JPDHLkV1l(TK4$;` delta 157 zcmV;O0Al~r0n`DIKYxHpL_t(2Q-#dY4Z|=9gyBn-0Zf_!NaX=4;|+TI&y)wt3ec$i z-}s~h7Hep)kfKQlx4_wijii}fAeB_0a+$D`D)PusY64j^Y!RMEiLf6c0lEe-V+4-B zZh|yXS^q*o0{H^@0hN3Q=;UW{&`Hu96aavq0072jfa?yB(iU`|Y8*3IH;Gel00000 LNkvXXu0mjfU+Y2# diff --git a/recipes/icons/foreign_policy.png b/recipes/icons/foreign_policy.png index bde57efe318915133d9638507c8b77ca8afc0919..d3650b507c25d15d27647630f7a32caef0b008b0 100644 GIT binary patch delta 403 zcmV;E0c`%g1GNK?EPwz1|Lr#=^LA`}^)dFZ|lq{D0fn^^%D7l#2G3j_*)F z^^k`3jf45XyY-HQ_Meycp_ux~#QMy~`_9Vy)zbXg)%@Ak__VD#h9b5A0004WQchC< zK<3zH00022Nkl1Og!&UK=4{i@@rl4Z)5Us zWAk=t^LlUefpzqPcl3jJ^of7#618200DGTPE!Ct=GbNc006W}L_t(I%hl4^ z4#8j$hGC3|xHl^4*}_0iD=sp5Dw%(ymbcTktEKfJ8^F{0!LB7Q)E18tyje^B;7-0L1L n10yL3V+ycq=A%En$>4JX6A>#y=arvw00000NkvXXu0mjfn~vO> diff --git a/recipes/icons/fr_online.png b/recipes/icons/fr_online.png index 0a3a94ae93b72637fc800e45573e4551d46fff7a..ea682c21c5587b98a831d5fc80f6330fde8c17b8 100644 GIT binary patch literal 682 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0V3Z2*32_B-{kFIU@AOiXlLI0p zd3g<0RUipr1Ifrefe5T`jHXy}^3g+CZt8y}>{-e1|_!fxL_i zFAoopp{=RO!NCC(mzR|VB3Wr^h;}(?X`mVi8_0l|EGHuqxhDWfM(pqhia-P*%7C^* z7!VSu5rQDDf|vktlDMcS&{aTjpaLKXX8>h&G&Lc{0u>k=8Y;-isjH|!k*cbZfdNoU zzz+8r|MMmQLno#r$S;^-)~yNUvoFt^w5%&~bJ&!$I978JN&R#s4ci2J1?cwYx(&j$A%g+D-kNW%nQeW5J$$E7w zE_LGO4Rzl4liZYbgALyV-eZ{9=FZI)!BrVDEp1wMxmHe1|D_8b;xF_E$6t}0TB0j) zU<)Uk-@#e?HynH+(GxCQ>cKl}5$~>)Bb(xuFz@;*ulA4eEF)*aCDvt+KF*9c;4V(r zV@Rn#kR-I|5%;w4FvXqE*j{hEv5d)N+cwu4{+*k5v;SuQ_aLAzs6fZ@x=rIfhYOcW z3g$JQ){ebyK4E8QLHYZ-ckHp54eqQqk`H!XeiwRu_PX+)8}`q?q*cp!)yOAkPSn&t QK<_emy85}Sb4q9e0Q4l-T>t<8 delta 673 zcmV;S0$%;91+E2<8Gi-<0047(dh`GQ0(D75K~z|U?N-lAR8bf`XWsZ2gP=5Am~R>> zkr`4baub1sg(4(G%Up!*TD3@`jmt2I{(wSSMHB>q(5@gzl5mqG?#egs8O@Je(xRzzk6SPoWK0{}%MHSdSv!k29Jv4}i0_+uUI?MH3BtTRvx zeh|z97;8}e(F{l)*o%SAl;>Rl01>na!xaM_fUur8 z+11rb^7?ixA~U%{;g?Mt^;gi}olH)~9p?jp{tCCKwXS)wHTni#X|3-9fQS@oiAW9t z7&NH7Qfkx>!}Yxy$gKpyTm!nF0Db^y2jBwea2)4t%Ja^x1i?33LZn+DA~Tbw1^Jm#cLt z&$|j>Xonz2Ql2-ls~|acGM&EEumO@M2$(;mDUxZ6t`9)>P7L&rJhe*)^pfng^|GD= zEw20Bb=^-UXFGsg*Ugcv)_twi2ng(~wYYAvIvT%;CIH|!%Jk<_X>Ka300000NkvXX Hu0mjf?oB%# diff --git a/recipes/icons/frankfurter_rundschau.png b/recipes/icons/frankfurter_rundschau.png index 0a3a94ae93b72637fc800e45573e4551d46fff7a..ea682c21c5587b98a831d5fc80f6330fde8c17b8 100644 GIT binary patch literal 682 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0V3Z2*32_B-{kFIU@AOiXlLI0p zd3g<0RUipr1Ifrefe5T`jHXy}^3g+CZt8y}>{-e1|_!fxL_i zFAoopp{=RO!NCC(mzR|VB3Wr^h;}(?X`mVi8_0l|EGHuqxhDWfM(pqhia-P*%7C^* z7!VSu5rQDDf|vktlDMcS&{aTjpaLKXX8>h&G&Lc{0u>k=8Y;-isjH|!k*cbZfdNoU zzz+8r|MMmQLno#r$S;^-)~yNUvoFt^w5%&~bJ&!$I978JN&R#s4ci2J1?cwYx(&j$A%g+D-kNW%nQeW5J$$E7w zE_LGO4Rzl4liZYbgALyV-eZ{9=FZI)!BrVDEp1wMxmHe1|D_8b;xF_E$6t}0TB0j) zU<)Uk-@#e?HynH+(GxCQ>cKl}5$~>)Bb(xuFz@;*ulA4eEF)*aCDvt+KF*9c;4V(r zV@Rn#kR-I|5%;w4FvXqE*j{hEv5d)N+cwu4{+*k5v;SuQ_aLAzs6fZ@x=rIfhYOcW z3g$JQ){ebyK4E8QLHYZ-ckHp54eqQqk`H!XeiwRu_PX+)8}`q?q*cp!)yOAkPSn&t QK<_emy85}Sb4q9e0Q4l-T>t<8 delta 673 zcmV;S0$%;91+E2<8Gi-<0047(dh`GQ0(D75K~z|U?N-lAR8bf`XWsZ2gP=5Am~R>> zkr`4baub1sg(4(G%Up!*TD3@`jmt2I{(wSSMHB>q(5@gzl5mqG?#egs8O@Je(xRzzk6SPoWK0{}%MHSdSv!k29Jv4}i0_+uUI?MH3BtTRvx zeh|z97;8}e(F{l)*o%SAl;>Rl01>na!xaM_fUur8 z+11rb^7?ixA~U%{;g?Mt^;gi}olH)~9p?jp{tCCKwXS)wHTni#X|3-9fQS@oiAW9t z7&NH7Qfkx>!}Yxy$gKpyTm!nF0Db^y2jBwea2)4t%Ja^x1i?33LZn+DA~Tbw1^Jm#cLt z&$|j>Xonz2Ql2-ls~|acGM&EEumO@M2$(;mDUxZ6t`9)>P7L&rJhe*)^pfng^|GD= zEw20Bb=^-UXFGsg*Ugcv)_twi2ng(~wYYAvIvT%;CIH|!%Jk<_X>Ka300000NkvXX Hu0mjf?oB%# diff --git a/recipes/icons/freakonomics.png b/recipes/icons/freakonomics.png index 1d5855bfd849c9c04180a07a3fb02551b4e2e2a7..16f623a72583f697bc306c209ffba7669550a22c 100644 GIT binary patch delta 3683 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 BARzz% literal 3733 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 diff --git a/recipes/icons/frederiksbergbladet_dk.png b/recipes/icons/frederiksbergbladet_dk.png index 05f107580a440e778ae50dddd11075d6fd1379df..c531c3367664939381070bac54b85e49bfb4ebe8 100644 GIT binary patch delta 234 zcmVM�r>*QkDu>>!={w1_*OkNph?qdt(3-6zJ7FpnYOUzu*Cz zPigi+AqgH*$p^;-s;Uq4@jM+H@T3*qfove{Ks8WrFomQUKv&Rl1IxGoE8s1#O}sz} kmKLQ7fVReOKFlAy0lf$XIWmX@yZ`_I07*qoM6N<$f`)Wy-2eap delta 273 zcmV+s0q*{W0;~d%B!3xnMObuGZ)S9NVRB^vL1b@YWgtmyVP|DhWnpA_ami&o0000p zP)t-s08MrfSbrd6hcIrCKzf)^g`r@Osc@UJfTp{TvBRId%&*7Pzti2%-sRuv?(g*W z|Ns9egHrzh0056kL_t(IPwmb@8p0qDgVDcsuuuxk{ZH#+K!06K^#Gb(^1VgSm+&*7 z07;URfU*N7pR;sf5&<1F405h{bfEJh+o&eyOAiXZcz?VEhwUM69tV-%$C-|H{@H_xB XhA`wWb^`BPXwFX z3(>q4qRr+%lgV$6S3vQv5Xl!IstkS$+yhGghw}ap6=d*Rni!Z?92hU+-!I|c%jiGj zM~F0o|12f{_U9qW-2PKG1=~FfQRDHS^fE;Gf2b(XG6;fL4Ce~^PXLK?2VnF`05LF)k$xlPnfR5nzpJX1KdSdb^rc3pV%RF5iLn`9-UU(WV5-4%} zV{O{YcYeF0qXJ*PJLcyQn^kH!-Sxsz)sQOArOvzF>|uTp|8#Tt9pm=R<@Y~Tf35v! zd48t#q_q+fXO8^55$L!5vATuYjeW_p<(nqk9+Z*(!Orqa?%56jyDJw>ePG{T_M}QXJv+sV92OZ>8kxFU zmaYD&4O_nSocZu(&YeAfnD!M+W9;J0`@84L(?iFOdarAHV07{Ac~SH4tFN!Cu8?Yy z=E%FJmHGA4w~6oWY>nnWrS$I3)Qei~0gd{tr&dk-J+YcaA^gH_Q~nL|N%bcfzjypm z?Qd*SciF#u!J4B1S8Z3fv-La|*%OeQw)cv_r7JvAj;mIGWD?YyvhPq8FGK#VTMHNW za=aFC3^!MFY`DCQ`3n12r5CT|Cv-m0J!buJW-~{dfr~+pp;A*Ivq77oMpGe+@a%@H zNfTCn=UA5BHG|>VhG*g_-QFoPdADfWyX+5DYv7mv-|X+zaQ;jCrC2m5ONev4ha pAYb$6$6&?lNA-K|E8J}Q%lv7>!|M~mKDRRffv2mV%Q~loCIHAxP)7g& literal 3886 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 Y0skTiU&ISwr2qf`07*qoM6N<$f<>}pkpKVy diff --git a/recipes/icons/fudzilla.png b/recipes/icons/fudzilla.png index b61d5e7320956a01bfe305fc2a3b89bb3cb2b0cf..72b2ea302a67fc43e4f9fcada3eec8e054aeea2d 100644 GIT binary patch delta 227 zcmV<90384N0rUZo8Gi!+001a04^sdD08~&+R7FNmX5Z4(Z*zlgQ&gg!sEv)8T48nF zTO&|WVuDmlj8#X!SunP*x68rBoo-`;gp%H}rG0aK)yd7kxV~j-d{9|!p;tbvr?1CL zG~Y}r;7%!8QC&?1FvkD@09#2!K~#8Njn6j{z#t4oK_zg^Ib7WTo(5qRbl&d$MkDy8 z#YAkFJE!Ptj|XH3g@UQCqcAv3#v%%X;6RawvMDM(0?Hb2$$Le~8aDD@@623+Ij=ii dF%PRBpT2V90{7>Gh!X$+002ovPDHLkV1kKcVln^# delta 233 zcmVuy$+M>q9_{U&iH7ovo3Ut!N-PB$wr2TyVx0su24Xvq0Y|mPwR7{5dqUlj5mW=v zYOEf(w`u9!Xk}ys)c`abDBavL32Zh30i6nz4G2uyA87zo-C`8t&43(4m@Y+<&a`}!YnwfX$}Gkv=dX0X@h z@Xz7xN{7Rz#^9^U;eV{u$=vG`WwNl#;YNqTONhO$%HY@K?thlL*TUB2qr~35)a6Qq zxB^t22w0!&^Yz!{?M#Nbp}^UHt!%;itpdjj+z?@bd#ulo(%~ z)8Xpv^YwS3#JbYvrNP&+&Ewwa?>cs?xX#~DgR!~K;0RBVFKePAVx9T?`sD2JL2sgV zrOX#kjgYIzPk(x-Ent|)*W*KfwXDb8`~Ca<|NL?U@p1qF0F+5YK~#8NoyY~M13?f) z(dzEexVyW%ySskb|5ow_vVf2Sb$Jwh#cQ*29%^#Voh~v&%@9u-wG+5Kj<683m=Ft} zKY&M2ZO~gp&7%b^L6=mTAfsi`+|Mr+OXUh?wN^*Baz%^U9l49yll$mkI2uo;`dm?# zD}7C{vA}L`^l&6Nof*5{?u@a=lK@Q8A5N0~=@T!`Mh`raDGZbV00009@c2mJU8|488R`_LgVAz!eGJi!K`sBat1Vt->e2dSOV!~XmlE#S|e zBB4^oAgOKG?32KyKGa8?{zklne4#CkcX z8&^bNq@BoUR)W895;p&YR7aE98SNFxM4&#kA-78fxt=Cv>+AuF`_+)?%#6c9DBA;> z6|Pbw6M^Q;hJOOS67##4q%7)DA+}ruR>c$??iZ%O>IA!Y1>()~h+xHNMtqqNEmL2g zL)%^8O=?KDe*E>S5R&b4(htcE5y73yjX3T&QmRBaq3_`g+}bAABBpc00000 LNkvXXu0mjfZ>{1Y diff --git a/recipes/icons/gazeta_krakowska.png b/recipes/icons/gazeta_krakowska.png index fc88052b420e766105a4b579ffd402bdbf6fc1a4..387dd9784f5a54e6211c298a1e4e3c08c87f1a93 100644 GIT binary patch delta 175 zcmV;g08szc0n-7HEPrTyy3Et)^7Q%t|NoAi%bKXsLR_hVlg3VGud}_}bcn#&-tOk< z^QN=cvANJ&a$~ zad_7^n64cpAdAR)KXyZ=L4_Gon-os-O<|%~gZL5!*nNQN&nRRGm>?zz2-qRNXRCq= dC7|mU3|&dv1|)*wkJB#^CV>7-&Sk@nd^}T?JGz=>4p@wT^=ZSUF_KW_t zsNwAe4d76m8XDlA@D}~<9p@Lu1CH(@OG+2>3z9nutKySBfMff_#^$Z=n$G#y>#y2p z;IyT@t7&DRH=}j%JbdT@xW6sCNk4nNs@QBBQLdi=pB_SAe#nH6C`cmpk|_W)g37TY mB&-M>f=c0N8vrEhFY+7E;YUq`T@|W3_00_~9&;e`%|NsA! zKLR9w)hGS900001bW%=J06^y0W&i*H`AI}URCwBBzy!Jq@~U!hpu@nRXfQF7g_D+)+R1+3d!u#JIXMR`_j>GbA? z=!%wAUagxM88-df>9*RR|LZ=(yL>GCybKI`tPLIX-6iaeTun@E%`6xgG#Oc$*cmw_ n7}*$ES(uoW85nfrkbw>Wrkh4Y&lCf|00000NkvXXu0mjfC!DDU diff --git a/recipes/icons/gazetaua_ru.png b/recipes/icons/gazetaua_ru.png index bc8402a93bb6935498097aabb8b1f44dca6d927d..05de7e0c20e5fd8d13f10d53d80b1711bc7f7fef 100644 GIT binary patch delta 10 Rcmcb`ca(2}@W@fk$L90|U1Z2s2)~TlZ(9q6#|ze*Oq> diff --git a/recipes/icons/gazetaua_ua.png b/recipes/icons/gazetaua_ua.png index bc8402a93bb6935498097aabb8b1f44dca6d927d..05de7e0c20e5fd8d13f10d53d80b1711bc7f7fef 100644 GIT binary patch delta 10 Rcmcb`ca(2}@W@fk$L90|U1Z2s2)~TlZ(9q6#|ze*Oq> diff --git a/recipes/icons/geek_poke.png b/recipes/icons/geek_poke.png index 1be49bc8a9285e58c1027dcc0bb0dca129f39a69..03e8dbcbe2e948eacfaf5cec870a5e9506edbc2a 100644 GIT binary patch delta 333 zcmX@je2{s9kxFG{WoT%qt*vcsZEbjXxQ&fXb#*n68x|H;RaF%d5@Ky_Juyr%8mJD) z0xETOc8-jUtf;84va$klfhvG(Afvpz94HbS8(Uml3^XV@I@-<6Ej~UzB_$;_H8mq6 zBPS;(H#fJiu+Y!Xue7ujXqvsf{mk1%cP3sktY7Kr;uum9_qI1&sL4TuMSg)s*CGuz zo)v(^Oox>_6g~U9-n_QcFup@s3YFWo(5xIgf@dol-SjqaZ+Y#b@@Z zURVDxZ1`jU;9>oW(&bOvJ(v5rL~Y)$W2IBY+bc5l+5~yFw_3H$B3F2pOqtburQ*mH Y{SL{Jjf)RmdKI;Vst00xkYz5oCK delta 343 zcmX@ee42TJk&3OYZD?p{Wo4y}jZJuXcx`QMb#*mRFf1&rs;bJ`+Bzg8WMY_NG>`>R z=j`kp85wD1Wd-C`R8#;NKq-5Bdp9>XKR-X9YM?mKpy=r6*x1LY&R6uSpJ%Op8QR@aD&DovE^NV8nUGx zS|7c{)#&N_aZA*Kgah$ER2Le@ylOqV!1w;vi_-a_htjT?{b3k&}M0J^%m|G+p@R8$HI3dhICRX8~3=jZ8wY5DxJtHF{ zIyyQP6&2mx-Ltc^J3Bj4Qc@Nc7LbsTCnqQJ^74m=hqSb`pP!#CEiL)^`641B>FMcE zP*9_zqXq^B9UUF$=;*AhtZ;B}b8~YK4-blpiv9il`uh68!NHlCne6QBu&}UnbaXg4 zI1>{S-rn8?1%CyUl#~q(4Q*|0hK7cnot+5@2@DJjBqStOR#u^*p*cA@jEsywKR+fW zCh_s{0|Nu}^z`B3;dyy^SXfxhx3{aStJv7s1Ox>9{QRx0tqu+jH8nM1VPT}Cr2p@+ zWo2d4)6;%_eg_8!+S=O6%F2$8jw>rG&(F`mz`#O6LVtvWgjZKr(b3UaSy>w!8~?Gf z*Vor+X=yYxH0I{!yu7^0$;pk4jevlFH#awEXlTa9#)XB20RaJje}9*km*eB(Yiny; zTU)-qzJh{+|KPWlmX=ytT1iPsU0q!%DJgPtayB+L#Kgpih=@;5PY@6gn3$NNqM~DC zV;2_}(0|a-O-)U>xVRh~9P{(@k&%&ARaJa^e1U<1zrVkOgM&v$M|XF3B_$=AnwpZ5 zl3re3czAd`JUl-0&C>t?0lY~>K~#8N&COGjBw-YU;XGBnZcE&9yMbwqK%8xPIcKcXWHR=QlS zzhJN*Prr_R<^Y5`l^WwnO(BULu~;l&uw%GscDSmi4PvtESu&iP9$>-S4as+8PmmOX zSbx$_GamQKRelEiXnN-8b0`sM`xnEZ42`swxu|$?i8FD>%z1!n{g%c@LQ(uX_F^v0 zUVksK;MeF7pzjM7bq@+9@jL^LK0LoBzqd_g-48UjJdkGG&B?v;+QVEdhIMh!3*pK@lLJov`}F}00Eu5p?`FFd5_x10val>>yF#oFI3_=R!EE=lBXmz z50K>edrwTJydEzr?bPa6Md5DRY_xohtm4Rm%(vU|NFU4-{vi VX`i#CGM4}V002ovPDHLkV1kM>`BDG? delta 1071 zcmV+~1kn4q2+RnOEPu<(%NG|H`T6;Qfq_LuMF|NBn3$McTwHT=b2m3P#>U2qii#2v z65ZY1p`oGwz&K@PWhp5sRX8~G^z^v6xO{wkKtMnQ1O(5|&sSGh8yg$u=H~tV{lUS( zg@uJlNl6V24cOS&oSd8_BqZ(JmzP^xTOS`EuCA_fa&k5{HpRuoiHV6#O-&II5%cr&x3{-H zKR*Kl1Co-GR#sLT8XBposcmg-{QUgDz`%rrgh)t83=9nav9Z_J*PEN0BO@a^Iy%Y8 z$&HPT6%`evq<^GnXlN`fEW5kAe}8{NLqi7#2bPwWT3T8j9v8-7;H8nNF#Kee* zh!7AEpP!#&V`C>LC-U<0dU|>S0s@hdkyTYy85tRDY;3>3zk`E=M@L5s3k#Z>n*RU* zUS3`zA|kP|u{k+8jEsy>P*9_zqbn;by1Kf4ettqiLVpGZ2F$m&(b3UaSy>$&9q8!j ztgNhXaBwsQ%mDE4 z@U*nFczAd`JUqo4?W+I)0nkZAK~y-6W9Y;K${B!QuqlXetE`s6rC`2=p=#*jh0>Y^ zlbd9*D2Q;?U%|W}lZ!!F%{zb`tIS6n_JQNoS|cA`1)!wE>nua#IvT3y_N0 zV1%w0{cV`1@RPyjd>si8%v8AmaJ4ZE=N+}%(Hy< z>VMfr3{VhV7}XqTy`mM#ig>0-^GtKSA{_<><1K>S$-G^5*~nIiOq|j#v>|=B%2o!3 z@YT6pOE>eT+C?BK@Jr&Bn7neXajh2vL-%y66@l|Kw5N)0Lb5{Fysl!KxS7XxpbOk2 z8;ToJbLCOI8L`;LHJCR(lOc;C%XJ;6{(pqBWfnV;6?ASbYg_8?smNQ!kjAh^HBOAf zQ5D66&Q8^ioK1`&%IyrRs-|?x&0Y@?oh2Y309CNKU(F%hU1tH9F@PG0g1TwWq!ln}T(WoIAZ2>C{9d$SCc? pssIFnGV=Lak@y%2(7|9)005}V#!5HvUc>+Z002ovPDHLkV1kFL^Q-^> diff --git a/recipes/icons/gezgin_dergi.png b/recipes/icons/gezgin_dergi.png index 36b77142fcb048e06bb596d6aa6066f080c65a87..8696685d52372b3b80c49915e275b5c647e84a0b 100644 GIT binary patch delta 429 zcmV;e0aE_z1kD7HRwVq(*R_mdv~|Nr=*I{Bwa_?j&Fu2%i))&BA0{`m3ztd6im}xn00n$(f2^ZGHYTLB`YR-X)WFo9G2~p- zB#eyNE$D?Aju&H=Xi<}LZ3jRJMa7lt5j1wVH0FYS#2?nMM*0S=J%ir@XtXE^T0;Wf zklvb(dd>78sEG@zVo?PZ9YkF;qTafw2c4I-`MsTM| z-A`^kL(21c0Xh;L#QZS`L??YJbgj1^rXv!^kN{-jI4PjLKg*AUp7@X0OSnxIiWB(; XsVYQ`mt31nLBkRwVkte*Dp&{_x-Uq(T1f-1)R*`_G#F-nstl*!(-Ha zAvgDyBl^8|{MD!b{QCEnB>(*R_mdv@nk@LCI{Bwa`L0&_wPpR}$NlTo{_*4f`0@Y$ z|Fxcesgcq;e*g(^NLh0L01FZT01FZU(%pXi0003qNkl-|J5JU$7#ejsE zxWueuT$3vrm-_!t%LAh#YR>V#R?V62>N4gQyda9Auy#!jVhYlRcjy9U9Tlc@8Fm9e>%&tq}dSQX(y>qQZ}UYB)tMD z@V$|gQySD~Dd`I$FR;LHCv~|y8X`K047CH;YrkaDeA4HKCO$# z+PGs4VBoY8o@MlNJonpqA^em@cV5W7)w%8}x9VisGzS}+3)By5wl0;{7}IpRy`me> z9Wyl72mnsIRv`v#?H_|@p<(``UP<_z#5Xtc2W?77h$=v|YXATM07*qoM6N<$f>KBC AF#rGn diff --git a/recipes/icons/gizmodo.png b/recipes/icons/gizmodo.png index a0678cf317ec93cea42663903fda383978db4ac6..02ba202d4790f2e10ce3dbaadcbf00cbb25fd743 100644 GIT binary patch delta 296 zcmV+@0oVS~0?PuBEPr~Is&hkvP^7hW! z;-A|G&`Xv(3Cm&&Rs8|yP0G;~}6z)Wu599L}q?Bfq1Jk$5*a#Cc4H#Woen@vn uCktSxE+U}Cw;_k4SW|x14t6jjn)w6s#s%t>PDgA20000e}M!*5c;Y-saNY=F-~Y&f4P2*51O> z+P==#y2{qJ#?rFJ(ze3RuENf)zRIe;%BH%;qPE7Kvci_GzLKuGj;gwjrnZWvwu+*% zhN7~Dp0a|Tu6~-XdYY))T1Lfly37*g10RR9107*qoM6N<$f>f8K A#sB~S diff --git a/recipes/icons/glamour.png b/recipes/icons/glamour.png index d64144f56ba5bef3525f64fcba9686f959a43b39..648092db4969be576e6151787e51329ce1d641b7 100644 GIT binary patch delta 904 zcmV;319$wt2c`#*EPpIFf%enYDK&sAHi7um)+#lC_|?|=)z;T-zM71$C^Ub9d8CDVrfO%KjDe}lvcqg>oHRXzv7We?i>^OL ziKdmaa&4Z8e}AZpfvIn5ol;bh&$Y!_T9ozD)t`{D-@(hmtH0{X(C^ODCNh8X(A31O zz@wA0hJB{jyU0LBiTc*oW@MS?$j{BQ#70YuhJ2pp}TN_0!d5W0_A zqA2xfeSflKOVvPs$v=WfYa1K=M<6E^Mz(FgcPVGXz^>cTDp+N!&%fIcu+2AE0Dvn# zNC$u%Udn(|Di1eM(BnXK@QMO3r@eexK&@&T2XsrTfJ=Qfjin4!YMS){9aBK2-;mz7 z%!z?r+5`6Vb%r~;L=rWw=X~dK;{iSEodCai7=IDvUNUAp^MQFW8Q@!q-LwDb)Gd9n z4=P!}-PDPY#u!UJSRnv>G8EZ*$Orm~1G3Xi)0dCuYFXWEeNa*W`a9AS4{H7UBX%5fk0FZ-I9Nhdq zNq_dh%n*pW?=vvrb_bn2^Iil1$+-a3&xKce%6Wu#5HPS e5Bx{Fe*pkAKS&9lTaJcRJh($l!c z)40buL5B9z)tQX1*}cg>Mv0=5vf{(d#jU|BHi3(Qsg8oGF@HOQacrLM&C%}7(o|NG z^wQK?Ta@?H)tZd2+P%p@Mv1+rz2L&kMoWx_e5NTifN5r$Oi+$YP>+m(sm!v&ubj89 zoVb#PtMARyl7_4}K!&xUx_EJ*_0rUsi>~$3)jmatK1GRscB6!Qr7AUmFgk;+o44)E z(Nk5C(6+{QaDSll(bQR5l=;@ypOCTN!OOy{zbG_+g?pyu#?Gjgv@tq^k%g<#w#GL< zg|wi#^wHGUyU0C6i2BynW@MSetH7p}v!<1_iGQdsIf8F$ol;bh&$Y!hK81B}pYqVt zm5HrbS(EtH*51I&fqA4vNsGa%zb7+)q?EH~WSK2Efq&}C(2#_xkc6u^pYzbv zmWi#oq`djn*4)3!f_bE6W0|Ftvq(*ih<>LlH-RoVf;2sZv7Wf`&(qYo$9i+2_te$* z)Ye^Hm9+e&i~s-uuSrBfR5;6>)A?JBQ4|O8vCP*Ja*gHMJJxHG_p&G zWbEBajDHkP#31h5pX&#|z3=sSI6r@$vpvr@Lw~8vzk$d(AB+1VP&=}V9&UNHHt)m0 zH$AE|Y%4?W&-)PY4KvvQfNpOt1%SG6fB|i76}l(uQXrcD%mUb1GxpTLer!tt*IQ?x zi$7$ts|;Jb?o%UXSfD&1AEa{iO$mYY0S*STU8Rg(16KEY2H->z>Xw=TmoQ6+?}7_y30Xuqzv#G zT7P(V^UI4i1eSIGew31TjT`_fw?spK9#`bw>UOTbZ z`*L5-nSs5sR_kEHKAI2b&N(yBdCtte_aFddbJEVdv53s95wJDUwqHywWs+t_U|BvEVMfqMX+Zm#Xu<#DYpxU_ zL6J1Ov`;BSbbm=zTtgeT;g-H2BhLG3gUjCbolGfAO7mTJ)+$ya)L=vZ^$?74T7Pv&ADIk1yc*CyVZJbCKF^4t<>d}RNFg#x>Hp52Xl zbp|l`{|Y>NYb^l8wHv#)5A55!C5omK!K-uT)nARDKRfN60?h+M!D9zLweyo3bU4p0 zR5oDnuzvcY`i|9Je=Wp4){7#13wndhIsFn<2REQ1DY zOWjEg*t2(jaG?0H^@CAV%VxUHP0dW6yV_msaYDCFWKB+_S2PkILcWPc1LE=2OpvC3bVDudW&YEQ6sHxf$A zC}(q7opq5L85~j?NdY3x%*=WZh|2kdP~tdtk;~a)^+r5%b?QwOj%6g$#3~ z6n_G6XI*40sivn=srb+tQO4N*{t^k0fMC<2#EA&1q^p?823kbKCxD$1IPZxR5lvsY z5>>-2S)cg3?%vVY)14&D$T7r-M1WS~j_q3tK~6Q>+5tdE1nE(NF(!_9^6bBuGnHy} z{VfBZe|V?Cc`X$MHN-GVZfw4N_`!XN0$E z>7U3SX(bt@7q7dnys6i?ZyofMvVS6;Fj!nz0HDql0HAyP3wwX~y(gY|`jMxIxVc5CjC<6c~tmsj++0Z4EZKX=MJ& zr8i!`*r-F5A(vGZ=Z8mOY~QXVF)q$JZ{I)p55x=tLK`r}DqX|~z;t}##DA`B8*@Dr z*DFsxI(F;8(D}3FC;~^x$3L-d%bn|o`U~Z`^7_$@Z@hV8wk%?4jT)dZ@=Xdn+Dql+ zg`5lkR7HgnXdtXEE%o+~{{HO~FCLjlJSxxxz#Z0rL3`{v z_Wp&R{Os3%c=ycp1*z2~bbsD~1jX*q;q{7}xfZ^BdHzWAM|zi1o=EGoGs5!&y;=ap%H5|o&pB#0lf&MXk}w&?CR@k zzdE)E6w`7=NCd%q&kW2?BvD$C$|=AAm{2@IBEW*}B3c9r5w+FW25&&rz#5zek3i@N z*%}P2&TDIwVlrfcLe^#x!E+Kr%qYzuBm`_Bz5Iq0)uXT$GPAI>j9GvaFx5Qt)@ zTm(J_W^taQMqI6j^(czHXYtIbtwgQfs5ct_29%am`%kJ|YybcN07*qoM6N<$g4}s5 Ak^lez delta 1655 zcmV--28j8Y4Vn#*Hh)h^L_t(Y$BkBNZ=BT;erC>jZ@cTY*K0dYQYTJA3!x;WaTK6M zA*s|0(5fKuG2*xNLseC%Dj-!9fhrPO*`lEm6(l0Eb8F%_zO3!+{e9naX8K_rdwo+q zI-kyEW}fHFjR3@jIEo_zkt%kIiVDC`1#xc&022WKc=H}@dwCxv8V zd&eaRZ72f30tUbkKy<^EeGKTve!J7MQ5*s2J@DIZ5MYQr09J+)!2nS5fchSeKE(a{ zdrS~f93_@OTL)YI5_VuwIKIcJuKDhYE{*#4=P;a*c~qr+$8U>iE$S zqa9;{j<<`o^=n^To1S_6{`;3kM@OCWPZpbh{%{5e_t3Mac2ddOPm580_3Gu#v>6 zvX#u50P(%KCo8pvDFzdhq*kVn7|A*_dcb5bfw6E$t3;+->-udC-@((7)(zF#v z1NZMO-v0jaV0p;-lx;?wGlh+H%=}{(Qh-KK3+*u>yVo`YX`1FP2T&QVluCsx?=st) z-`(~O00#l*u1o9z4ovax+%hzTT6o^==oSLft$$Sr3$cw|>JE<`GM3nYh!z)@ya$nS zK4;P_%UtRTVY#`MEk0feO$czn;lAO%0|7A^YkOHu#Cev{R(>+6#z82ao%3NxVGua) znOId1^asQwUJP@q)m0&YK~gT4`bvT&GQ=v{DwI$xMIxy&1jxanClyQ!8{is z$&e8-gF;K^UOrt)3Wg}p-9f{&M`aKMSt_@0{Z}NaH=7g3Mt}Xwseo!$>jo{NZkn$> z_x$)9S1xqBZKi;TNO$y-Eip-)^s01Ut$&7PUR$l5pE_36>8O%%5g9} zQhxPkXWshlD@l^4&9yiPf+$#7?aq9C*CW~XqeQB@M=erSf%*Q<>R&(q*PCyj0h{{M zM?Zb#nrd1ZV&4DHv`Uz{2*SBl0EGw&NkCW(qfy@til`V0vX+i>>o{eM=w zb>hT{(XrufYmKQD70JY?H`19e?|kynwev5Hk5ohNeQiCRzH$Gc2mmO-GjhSF*XQoe z&z*f?^4O7Tr5v`?)wQO-d2`{r|I`u#h4;QoR3rrZCYrJ+LsilD2Y_?jau!1)n0tf= z5ATyautp1gEM{mtcL#|PCkpRRX4_|vqy z0iuviG(6-&>zeJQN;MigG$v^?%gn-)HG$dNfRKQo(Ecb2kPUO^;;A2XB%o7=Yx563 z`}AI?ji#Uwo4WJkM{w!Ng**>j7CRq)_3v+qqW<~eS+Su#5C{RnGt)ONynl4EFvMBA z{@z=cj*lLiy|tXCaGam`;nCASo;Wm8T3%V6ICAo{&u=U(tA=}1MCEFkm{hkRuqu(} zXHOq__np^|j2AVn$0k763Y)s=YGT3EY&3?Wp|Ae=?H@mw?q+0?0fCjiSSKP8F;OAf zooIkzO6Km&-+%akh|9$?gMY8dvKy)t28u~_a40Dj#n94nZDDcQXC9r*Qs6leSP$~giuR^95ut(V z0Q0tj-ZNk}nw=1<61?{!5OJ!xv4%~-fD#CkdP1%crOlCa6M=|ORB(vmV!SIm#EP&& zU{zQVFk3QKIP=m_qc$20VJT02-pP)&dsR!dW;AwXhbjfQ4E( z3s@Kpmm?t|frgu>_IqfWOiDLX?zF<#hpf>3?UI@sum{%z*%d3w>6dGP`Dm5H0`p@g3xFy+0ImX;#{l+`RE-^+n>)Av1w$Kt5w*S&jfMaK002ov JPDHLkV1m~vK{o&Z delta 177 zcmV;i08an<0g?icBoYa5NLh0L01FcU01FcV0GgZ_ks(Qcen~_@R47x8(Mu7-FbqXe zQ@9jDArwNP6iPu9LctVFK@>_M6fTA?GMTm$JE?X$3w`F5vhY<)DJS^pVL1Z;&s4Vz zpbzN^b2@}-0I-cm%l``nam-qsQ!K&_q&{BgyjJi4V&v9D?OZz8FW8 z$>&sXA~TU!bWYp2DvmsT)q6^Ef7{-k5oy9fBrkbwf*~L)EV>A)^UW2G~* z1?}bgI)2Z>{(0`mfpe}CUeL_t&-8J)pfVuTF|I>zD6*bYHRN;wOz#9P| zM#Y6XOsN23q~fA6;b47PjHKcskQp1tUB!hbnUY{g+;uoikspS^ICV^gUkW&$DyGV> zF33D3Ov;T)0BDQYM0Nhvb0eufR}7KwDhA;z z6Zn!F-xLA7@twoqI$y2$2z=J^f=%drkZ_WyT6otJ?jnov%5Z0fg*&T7Bkq(Qi2RE9 zD>7s7okPMe;#ZpBL$s5uxGQ8|aPUzB?lRkgfBOMM$RM6}UR&M(0000_)%o@A>@&sp0ec{#pPVan9}{v*laI>}kyHlG^fA#p?j2;C#~W)qn8%8?oe&+47m(^C7e4 zMZoFA>-dS*@r&2-p5628`2Bd%@2BAO8nNUMt>e(|`TPF=7q8=Q&F;$W`T70+Dz@eo zuH&cS^+UetSHGi80_b&r?{d!W8nEQq^7_~D`Wdj~CbZ?n>-dq`@;<%j<@Wqi!|Lew z{8Pm1JiO?p;PiIS?`F#Ftm5_9@%kRJ4DSmV#(|{yXXw7;(5{UdeQGSx#wrg?SGZq^Pb-GLB8p2&F(q7=TXDz zHM-|!%k7xl^F_buS;p%wx8{4%?-H%!u;ljY`2ExH`jy)9SjOwx^7|#U<=^!C;`RK@ z?fLio{_FVt_5A+p7P6cG00C%8L_t(|Ud_%`Z|gu1hT;8Y?JzT^%*@Qp%y^iYnVIqL z9$T%pX@6ozJ$HRCMjGkSXxNc5Y-wf?pt$0@M!9hTPIG9)WdUAsN)JTF{4AibCWNe@ zT&he5Pyp0nGK1)~^yV-?=a&RoUeq^Y{&uq0giJ7HEkNi8%PIP5V?A22hg?6X>xBrcu1rJu=go+f#^5@q=82qPJiif3NSsfJq4Z#AalI*B+Vp>d0p>- z1pyX~SXy3L6(_Lf`!!A|6QI^YH%?j;T!2%o8st7Bz}a(v!*>1x_5gqWtu737{uDrR zaEdfA?+G)MB5yxO;}*huc98c2A@^hS(@fdy+{C2cU(=-Q%_*QI2ZYwP_6}8Fyma{r zC|GutrM9yRBHX+ckmWmf?}6aH?`Mbd1%fK!N-FKD_OP_<-916<*Ob>MvCR4h2J^y0 p!y|#wvENK5$^NJAua*5*{{USQUVI%bT>1b2002ovPDHLkV1h8K6S@EZ literal 1501 zcmZ{ic~Fx_7{(WbDxktpY!Ht@MnL7rmvG0UgeI8OLb(+1DoMyyfdmL5*cs5$iegJG zTCY~ERk>3@tssh#QwSi3D4@3Ff`kwPfj|g3`o-x?sWbg!_uY5*_dM^rvwJ!seD^AA zCu;z}Dk1?F1xIJoSg{PAaktL2!oh-$4Z{LZU1*~tS;DzlderU^!0lcyGm#WRR2Tr+ zZUE%w0icDqa-{%dKmd#<0D!p!0D}GV!^j=5?pefVF?gfVIHlD~=5$~6DL37fxZabv z+?C|?PtiIQ;LZpr7BKk=8eaiQ1fY0?*{O(Wkr%w0cDpaR{ANaHFyeVKvl4+tso>I^ z8T=F3wm(KM46F7w%gsv!kxk>rdX-MqV%tj5iT)|BNFCcEC-7u?gYm~<)nBuVsoj&{ z_DEc-T&p(>Olj9ui_Z`TrnPIU z#BTQ_BDJnlv0%>?{V=4u_I74jso>7Y?9u1S(=VqhgzCX*?Zz5$gLHmlwK%ALY*@88 zq0#SrBo(T4Hs!+Nk=Y%Or17ot{wb}&XguDlw67FZiRZ>O`f{QA z_w>ceQ|5=$ZT1}YII`|QQVQ@GIazz&rr6xV(ZagtF|)0m>ParV72Jt!J6FKB;^tm0 z;t%2D*Z;~J=xY9@fWM5(D=OskU$-GrEG}Np33f)t+8xAJd9ny4S61%mt7~*@tUFfe zGGguWTW#%G&P~dVP>JOArGa@3dmG4-Cv*bkx}X7a@n zGXK-(-Bul4eB>5S2TyZV!8wQX40B4^y^2TX&7~D(T!Ap765o!=PzNv8IXPXet-E?) z|B157Ve3eri#gHT30CfQxzg|1h}jAH;LyNoJ{tdar%PNKgY`vDW@FyzqAPBnUq0i3 zC~yofxXx`2wg;d_6t3EweZv=i6;}>Ej+02@P%)G=DjWbBLiu|^0bXdo7&L_O^TVJ6 zJRk@IK^w5~-~C6B%t~a^vi@I?v|fOM1)-)2=`0#2laxjU#AFhk8b+csDMy_By#u@h zy@H(KlD=M$pBD=49EtLGW^g!c3=)}{nd!|m(eQw2Z&n)p&9@5J5IC9~V;C99WDEw! zqGTjfQ#cq*1dOHwRI#xh`5yDGCI-WxGU*HsKn0;~LE1MkgIMD84-?Bw+v?DXf%q zDFj7LFl$#PGJhO3zV9WS7-*y*Db=8i9)aa}V4b(BHo61a=DA1~32(J?#W2=I-PN-? zO?8^+P^pKTqG=VAIVLwvQf2{`5>lZV0@*!l76t z7?*FVv2N@g2_GuT(!=DlAi!Bdz_U?_FA(wl;{nNyAR+A61k7ciN^wbnKky$~n+p5& Sqg3($0000-4>UBwIp4azKQb z2urqL2ZZd7)Ei(;P8_lVm_L7HOaFBj!SiGt9rdYG`^_)E0RoWR$AuvRp>SZrgViy@ zHUf;2up>!eG$1LGW|j;SYBwgi!~>)VQgT!nLxq+?b;-=$)_*v|s`IhP9F+yBIMb<= zlBzbWt)_D5gWQ>rlrwR;h9aZO9B0kyozP)T>!m6Hb0o>23a-qMj8l;<>(YJu=d)B8 zlw?RD6aaG`Zy$dj-Jv@H-v9CE)Abgsq-!Fk9De@k-G}$T{{88%yYnFX6*^Dpk`*R>@_CZfDtpMQA#DB09jN$$BUT2tSp%9>cHNgx@h5!Hn07*qoM6N<$f|2{r{r~^~ diff --git a/recipes/icons/gofin_pl.png b/recipes/icons/gofin_pl.png index 7231bbd7204926bf7118fb40f9921d5c2ee7ad06..6efbb2a7afed4efa29439194c71a473b1cb4e6ca 100644 GIT binary patch delta 502 zcmVgv}30NDTl|Ni~=*w*^i z+SmaA`|Ioe@bLWb^7-1>|NZ~|{{H;`|B>}1fB*dc{{H{pFE8LLGT$dJ`q0t*{QUOJ z&H2;P{`&j*(b3!>A^Yg+=UiXqU0>!~V(M68+W`Ri!Nm5!!1cYr{q^(w`}zI*{`b4R z_PV>gwSrF8u85{O<1HCoSP5E8Zk2-6JUf`}qI-`~Uv@{r~*k9wpuzBi|Px z-Vhwy3>4Z868r!D{QvzA-r)fN0004WQchC_RUrxxftr5=)bl8j)Qz@A52)OPqRXF)018z`-QA#%T$MZ0^Qchi`5H|v02_smASn>d-mkjrd#~<;pA{@Yc>eGy seDG-Q+Wb~v0X>TlOr{@&&Wx^p0Ut{k#Lm8biU0rr07*qoM6N<$f)s)-0RR91 delta 506 zcmVi+us{QUg=`~Ls@{{Q~{|NZ~|{{H^{|Ns8~{{R2~|Nr^g z*!S4h`qtX{)6)6T(fZKQ_RP)q$;!~V(M68O+6@wsYb1Zy007tl0NDTl+5iFD0RY?} zA^Yg+{Oaoa>+AgN?ELQT{_ybp@bdlj^Zfhy|NHp={QLj@{Qdv@{{H;^|N8#_{r~^{ z|Nr~^|Nr~{{`~*_PzBMP00001bW%=J06^y0W&i*H#z{m$RCwBD&c${DQ4mGZCPI=| z6?b>T-Q9oP-J$8%!2f>$6fj|N1~u604oXmLCI|{drUd2U!k8fe;~d&2m*;grzJ|t6 zAmO(9ZErXskXq(q^tHWnUv-JA#b-MAfei4*zQ+g-Or`i0NFxw0c4dN|qZB$uln#;0 z%f-NwO5PpaYP352e~keD^2J}?y>@0J4}F6Dvv5Ks_JU~41RFpA@aQQQ4DY^z_mmNc w96QfnD)skMQ=YCqMHq4pC^MK5Y$j|q2Vc)1Ja%xGh5!Hn07*qoM6N<$g0n>?F8}}l diff --git a/recipes/icons/golem_de.png b/recipes/icons/golem_de.png index a40c31353bb2ee4f08d915b41bd5726b730b81c8..9daa60a26fd1bcc3761752008c6baa05ad1c81a1 100644 GIT binary patch delta 857 zcmV-f1E&0!2Z0BWHGlvA{}2%oFflPuQBh!FVQ_JAu&}TI0Rd)bXYcRtKtVwuAt8W( zfWW}O(9qD3kdW~3@UO40H;J6j&(DvKkD#ESi;Iij-{0Wi;BRnnN=r*BH#MG~o??nR zhlq#?4hL6QSiir&HdkL)id9~hUWkZ@Jw86Judfao6FFEi2Y(F;D`m8TFI5I|%* z88a?Sy3raY6H#(6QI>jBmUbpICMP^0C_5b}Og>r0&su3~TU=ZyWO6Ar9x66B8YUDP zClX|rQDv4)AVozWOHm_JSV31sW|vGCCK@qFB*)Uz5*iC*WMn{bRVqw7Ebb#;7weSD0Let&;6Q7<#Ku!OxSU5+Cc1V?u4;m2Qz_t@fF8}}md`Uz>RCr$9 z(^YR9K@4NeoxN4emZwF5Q*%nnw^myqI$L* zFsKPhxPJ|%i8lubliKXYA_V;l2)1pj zT3Cn{@wT%uPdN$z2Ch=k*SFd^90x|lmzDbGDSyQQha-|rH^vVxAmin3kr94c&q~>p z3`TfI!2T^sG^W{01h;ymq^&%sZeY4Yn)+tUqw zu~JmnM=*<5-UTDJ%VDYE&iyy{z5;h`16Kl#;~w({yN2ct6(&8?iz8#R2g580(xue+ j?~76r&Z`QrJwDEF{6bEPg8$qb00000NkvXXu0mjfuUK$j delta 879 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 O0000h4II31Ws8BwR%08@HKCE0piQ__r z@j`|4MzCT=uwzNFWlXYXRFL&lwr*UxbzqtHW3i}YynAK6eP_OYY`}tdr}ultkA|=_ zjn1Es$AXp8rJK^RpTYT|*RP}4v8BcOsK@%L$oi_t`l{NutAE?Lu-&|~wIR3Q!nxtZ zzt#J}*Zacd%){9H*Y4fe?%vw(;NI%}O(c^L^kS0H|j<>>q#{0 zN{CYURY>euOzijX?OagqWm)cKTJLOO?{aAHbZYSV^6_^d@qc#lfq3$Y zf%1-q^OTJ9_mjE-JwyAv1^c}R`@$Uj%|`srNBq)Q{L@+dkrl!^!VrqsieFBywl ze85Mg)a$>D#Vakqpi=4!QC7kpz>p9ypp+UR%704O1Oz1n3@D{Wh_Vv8fuJ-4gG#B- zL|F-20YiMiC#BSp_ar%NX?p>$c!2Lp?VPcLB%3YmDBzYd;Lp=FraA9Cad$f{%UXg~ zBDt-=I#XMGjhJ_S~}OVtj=dZjffP4fn8?AqP5$$cP*P@ zPJEkVS^I%dzGziw8_g7xXLh4ih$Cwf+V0tAl8*Ig^%)Z3GSH5#o@$O-aR99`PhyG< z96cLWE|}gie|gtI9E}qs&gCafeV!!AtPGPPRFNU6T%G`Nftq|tms9HY8o{99b(dsI Z+HB*K~#8N#mGSwfG`jP(OMV_wXhcOuL;O&=poxPIc5G> z!{-2SK@ZM%(1X*Ipxo2}n}AAqauLkggTC?vn(|L5_yDSbQ6iiHCY%8#XTlA*A4F%G Sya(6-0000Pzc@G$HI0uG(;97Hl z>u&urHV;=vzx7{LfBAQ{c<>pSG3>aIalkA7V#8clVXbC~E&2vb|K5ezI!t)Zt`qcE rT!;Vs<^+}_O&?SmRYHU~s?wXKcgVY>d$ms4#Q+4Ju6{1-oD!M<6#6t6 diff --git a/recipes/icons/granma.png b/recipes/icons/granma.png index be09e8024b9daf896ea85e5e0e246362f166ddad..31a52f618a6174dbd5c4aafe071b3617483fac96 100644 GIT binary patch delta 182 zcmV;n07?J72Eqi8Bn|vfOjJbx006%L0M!|hZ88BIk)S>j92?gh8r2#a{{R60007n- z8rYHWMFAU=HUTRHM`zu@lV|~Alm7uCJ$5=eySj@?51_X%Ns{yrfWc}qG(6HiHlB$| z3?`YH#>{MLbIez>PzQ@yT4rT6Sp(J|H?fse8h{eElhKo20xB-Fk*8w`PcyN2FaQ7m07*qoM6N<$f@2m(Z~y=R delta 431 zcmX@avX^awiYH6Dqpu?a!^VE@KZ&di3=9g%9znhg3{`3j3=J&|48MRv4KElNN(~qo zUL`OvSj}Ky5HFasE6@fg@jt*P#1%;IXJEJ?Gttn9QF3C6oshKjbs4FvQd0kc3K$r! zNlRUwxW$!GX5uex_W%F?$Fs|NPS#{hGGa{fc6SMR!rZYGXaZ+}M`SSr1K$x4W}K?c zCk+&2FY)wsWq-yl#x7XrUf5X8nBNH1VE3 zo;+$ZXB|6}lT~~sd~*PMcVtvrs%a$iHFL9#Q$wU0gtkqdJNXr(R{fV5%hL~>&i?dm zUJb*J-#(wd@;1zms{7CIhxuEhj6aLDQwq>-)e_f;l9a@fRIB8oR3OD*WME{ZYhbQx zWE5gxZe?g>WniIgU|?lnu%Yu>Jc@?g{FKbJN}L+Z3=Uq&dkoYe39=$MKdq!Zu_%?n jF(p4KRlzeiF+DXXH8G{K@MJ0|h#5Rx{an^LB{Ts5CB%&@ diff --git a/recipes/icons/granta.png b/recipes/icons/granta.png index 64261e6f976290bf71cc50df7ced1b75e70a3a35..65328de7f28f975d95fa628ad10992fb2f545884 100644 GIT binary patch delta 20 bcmdnWHkoCD^2UbejGPRfu6{1-oD!MFdh=fK6P0(PFD2BR5cpDaqU2h2ejD|C#+86<;#e ztCqM%l%yn~>+BjXT5Gb;l_D^oLV10yQ~1M7`Fnm_{(8glbf zGSez?Yw&twG8L#HyecH3Bq*_5p`a)~Ei)%op`@}PRUxyWB$X{Stjw*K%q^@e>^)h8Sy;iP!Q^lXv-0K;h0`~#oH%mkh|Cf8(+wUAy!04e Wi3^r|axw+k!QkoY=d#Wzp$PyzYh3dH diff --git a/recipes/icons/gs24_pl.png b/recipes/icons/gs24_pl.png index 67f40977384a6f0db1f2ba12e17b241948a8626d..0be176e730ff55c2aae02fe801052492176ac8e9 100644 GIT binary patch delta 213 zcmV;`04o3d0r>%tEPnt&hQ80>5KWHm^7hW!=dQ!pp109{pTa+Iq%UNgA6S*%=I;+o zjla*}KyaiWS(SgF!Z2l>ufy4Ile%Dtvrl}hkF3hb$jA#A4#EHc0BK1?K~#8NrO&q( z!Y~vAQ0W!dl7#;MUy4m8R?v8lGzk38>nG5sh!5o^((V&>Yfhb%td^M5gn%2#(lh+R zG6Bfv+5yoM_G65LJsIRPNu(fe%4(QU%yhvclD--&v5gjL4StP%!T(?0cwYp&(>{Nt P00000NkvXXu0mjf)zxZt delta 216 zcmV;}04M+X0sH}wEPw9u_TJ|2&f4e5$jHCX;J(k`ufy4{!`Pm;(T}Xkf1tvCpTcjG zx?qU2PkgIDaHKzQq%dWjFJzn`S(P7Hl@Lvi4@`{!L58@Eo#Frh0BlJ_K~#9!rO&Yz z0wD}U(FsTd7j=;(%>RF>!L4jH4%I$JV2;#YSL@HdBGDxXXx{wPARiJXnP)NqX_~ z_NTPS;^ysBYm*;8a>2^jw!zV{z0M>vZhTYw)FjsxdQ+iMuJoXxbO4%I$JV2;#YSL@HdBG|^Y*N`%cr!+h?%uvc$_>~ zg(^vU`uqIq?egO0?VGB>Q)`njPJJXpbss-+!OGaS!O^k3&YY^jlcKwhp16dTvTA^# zTXUFBXprMeU73;FBY(?DL_t&-83n=1a)U4o1<)(W#^gc51TtN)>HmLMr>i6_&|t8Q z)N=%&o2wKg2Nneg)K0#Y+e_t{xR{W$%V+n-)}?bOFsbx%o}a@**Kjmi98)#yxt~;b zKO0U#n&Vw#&IPtLe&r(N7$*!%0W3y?IpqS38R9cQWp)4nfMMG`zrots+J%9I|NsAFUu0)uXJ=z)b8mBXadmoj zdVP6)e|vuz5g3MphMSn1oSB>&6B?+csHmo>BON0qA11=U!p6kL$;Zjc$ja2x)ZE+L z-Q3+$O;X|D;pgV(_V)H!R9TXbl9iH`d3AX}IzW4OdqO-yet&v?NJ2<~eSu0vN=-;j zjEIaoH9K^0beEKunU~mB2!LNp`M|lpQ59nqobjtq@kpxqNSyyrB+Z@S5jB5 ztgf%EuduGLw6e6dv$eOiw^&nHzP!G`zQ7w48(UUe!okAD!^OqK#a&okURhpYTw%+~ z%gxKp&&|)!&wtR<($ivHV${>r)zsD2)z;Y8*kfK}B_Aam6&&8(-X|d^X=G{Q;o|1y z=4oYV_VxB_W^4WZ{WLB#NkmCXL`wJe_xbtxsivu_sHztEj81sjIB2tgWi8t*fmv zEHX1KGqA0&AQ>RBuCcMNv9z(YH7_+~U}d(nwq{{wxVE^twz;~vy1Ti%W@2W&yS+9r zHom>Szkj{IH!(M8V`#y^!8$ZLAsQjX!o)l_JZxudZE0=D$H;JNaB*#M%E`)dZgR}a z%sx0i&CJboZ*D_pceanVkVg_?K7(z+1EN{A76@MnTNh!fwO=-J1JoOC~0XWy`5cr8jm8+^ zSBVSKD>HSf4Wp+(}_ZWk*_83 ziTBv8Z!`lG_-J6@jwF29{@@^EPR4ctQ@{EQ3*~T52!v?r6L0q%+47^kJ&*q_f6&Ai zBa6L;cB<*!j4p!Qhb?{UhWP$-RkDZOwP&p@Z-$k2O+j@PFK`zC$Q9LFN}T=i)Uwiu zb7@U1WI1f`U)V?7f7o4nML&CvV-S^su-?XLWnIV^fPuyR#^LJ`7Lro$82HBJ|70No z@L%!0X)j1w$ona6T0Ghe4hCDYje9UPu)A!bxtrQ>qtd*}G-3icm>X7L`Dn2R5|`@| zvTBHq=Vv`ng?G~)|G09g!B79nXtSDLKOzjNN}dYnOxx_^Fd@8F)VXmn|K%v*zFEM4 k{O|%Uhr=zpcnf-!-+W1e#{Y6#NdN!<07*qoM6N<$f=V}R=Kufz delta 1232 zcmV;>1TXve34#icEPodf7Z?#3850>A6B-*78ygiH92FcL79AcJ9v>JVAQ>Pb8X+MY zAtDKsrD{J3&G`LPI@6Mt?v?MnOhLLPtnKNJv9S zNkd6VL`h0SN=ijaOGZmfM@&sfO;AfvQA|-%O;S@%Q&dk>R!~+~Qdd}0SXoqATUJ|K zS6p3KU0zvUU|V2eTw!8eVq;!oWM5=uU}a`uW@ch$XJTh(V`pe%XlZ0=X=Q0^W@~I` zY;9?6aBFaJZGUlcZgO*Pb98TXbZ~Taadmcbc6fAnd3AYuc6xhvdwh6&eR+L;dVYU< ze}H^|fqj92eu091f`Wg7gMWjBfP{sCg@%KMhl7WRg@}xZjE#wnj*5B?R$H>XY$;!ye%E`*h%FE2l z%+1Tq&CJcu&Ck!y&(O}$(9h7((9zP-($mq?)6&z_($v(`)Ya3~)zsD2)z;Y8*xK3J z+uGaQ+uYsU-QL~a-`?Ng-{9fk;o{-qDYfA;K_?qwbG_opIh$=kPSSwpG~gp;kcwXtpC;yHrIma4X% zId{?Uwn!haCuHUh7W6||hx7yZo_|L(GlzeLYgB?0p zAR(uE z%G!LhGppw>jl(9-du3Uqk56b^V&jZ7bxdhK|J;B8hbV*f1@-AB&auUT!l?3=jnWLP zDgKX77HNX{g0?00$ntix_lPpc*st4o)Ggdt0Vu%IcXW{}LY}{Q*A9z_S?6AypUNPw zCuPLME_dS4woVDSe*&xN=U?1z-(2_Xd=&!&hx3z~HFM9uI6vJTu0XSW{qq;+;~4a& zJw2z$Q2XNi-KJ}s*R)t7>?pjr;>@aL!VDhI9!0V)xVGj@zoqQiGG>GV$;yov?_abx zK6UV9m)-GG53lYn5380#N%$fClaC~AyK&~x^|LoGoH{b2IzL${|JiaM`>$LCy0000U=rMTSZ?EnA& z*5m1Vq_=&hxqqm;gQ~se?()vw<+RM#>G1Q|0H^Zx$-e5JUN&Od+9-{yy{zIvp%oV&_6%*-%6`(Kog+H&GVUk>elSp_#M z?TJ($=zhgJanzg!rEv3UtGavuq$xKv0nZP}=kkp3Z(K5b#3rE4oCvN|nMu zt!~O1YO#lD@ M07*qoM6N<$f+Z8s5C8xG delta 569 zcmX@g+{-#arJg0-(btiIVPik{pF~y$1_p&>k04(LhAK4%hK3dfhF?ITh8GMBr3MTP zuM!v-tY$DUh!@P+6=(yLI2qs*;#$6FTiK#*waa!ktk_+@Vt3V&9Vc!*diL(q)3+ZF zU%OwqczgBI9e19;`Tzg_wZ|{3mTWIuxUFpA)|#a|npf>@U#q>Zbm7*{bqBVeyLs!` z>z5xszx({{#*F59cpr&&>9;dFpOLy0++$#?i54?Uge~%nn!~0)K=h@htnHU#;O!;;1oDRbb{b{Ses@=>l zf0c6C~aVh!W ztM^*InyGoY!gamkfB8V~sFt`!l%yn~>+L(32&11l2~D+6gTeK*(sq30BFYqHvj+t diff --git a/recipes/icons/habr_ru.png b/recipes/icons/habr_ru.png index 147223c629f447de448feae7b16eceec62440bad..869425d86ff95791e37c79829b17c55349a8a980 100644 GIT binary patch delta 366 zcmV-!0g?WV1;qo9BxT7^OjJd8qPBLTwt%R+U=rMTSZ?EnA& z*5m1Vq_=&hxqqm;gQ~se?()vw<+RM#>G1Q|0H^Zx$-e5JUN&Od+9-{yy{zIvp%oV&_6%*-%6`(Kog+H&GVUk>elSp_#M z?TJ($=zhgJanzg!rEv3UtGavuq$xKv0nZP}=kkp3Z(K5b#3rE4oCvN|nMu zt!~O1YO#lD@ M07*qoM6N<$f+Z8s5C8xG delta 569 zcmX@g+{-#arJg0-(btiIVPik{pF~y$1_p&>k04(LhAK4%hK3dfhF?ITh8GMBr3MTP zuM!v-tY$DUh!@P+6=(yLI2qs*;#$6FTiK#*waa!ktk_+@Vt3V&9Vc!*diL(q)3+ZF zU%OwqczgBI9e19;`Tzg_wZ|{3mTWIuxUFpA)|#a|npf>@U#q>Zbm7*{bqBVeyLs!` z>z5xszx({{#*d}Tl7hW^*B$Kc;FDswBmd=d@j->knq?Ck=d&)8Z%LGJssEC`#--$g zuik6cptHiBA&Us2SPy>UftDnm{W~YQE022lUi~s-t diff --git a/recipes/icons/haksoz.png b/recipes/icons/haksoz.png index 8fed62e5d402ad8aabbf05f8a4bab548c80043b8..6bd5a3fad6b483d8e8089d03bc8857ce04358151 100644 GIT binary patch delta 2336 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 AcmMzZ diff --git a/recipes/icons/heise_ix.png b/recipes/icons/heise_ix.png index f59db714ee1481611fe9488b304e61236f276151..b79ab2473ee6b1405011a4e4bb57b63f48fa609d 100644 GIT binary patch delta 1311 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 AcmMzZ diff --git a/recipes/icons/heise_open.png b/recipes/icons/heise_open.png index f59db714ee1481611fe9488b304e61236f276151..b79ab2473ee6b1405011a4e4bb57b63f48fa609d 100644 GIT binary patch delta 1311 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 AcmMzZ diff --git a/recipes/icons/helsingin_sanomat.png b/recipes/icons/helsingin_sanomat.png index 25042f9cb5141164b513eafd24862ca2ba683e11..52118e97202c28de36f5bffed2bbeeb295cab339 100644 GIT binary patch delta 576 zcmV-G0>AyG1(5}iBYy!>{s(c93}-Xb?+ zBRFK&+TtWRW!>QCB|2r3Qvo4=COc**J!ts&`YAnV{r&y^{{AyZZ|%~lcK`qY{7FPX zR2UiU!PQm*0RTnOb7u$<5CkOc?!xZw?(WY2e{)dQVn%<#z4_-DKA2eYgHDV$+ zV0U>|(_W1Yr`S|$y`uh9*{r&#_ z{{R2~TcWYw00031Nkl#%G zs(;U{hQr~=m*#H730JAyMsby}JMPNeh7j7%v%Hf@H{2s9Z+ hE6s*6&{0B>%{SdQK@1lHrc3|;002ovPDHLkV1jK#50(G` diff --git a/recipes/icons/high_country_news.png b/recipes/icons/high_country_news.png index b53f46f5abbe247156ac035f3440ef3e8bcf6942..e87bb8fa55f11fdd5642acc51061573dc26f0556 100644 GIT binary patch delta 423 zcmV;Y0a*UT1i%E4EmJ;W|Ns90JYWGtW&%WJ5KV1Kc#*@>+Wh_f06ky;L1O?oTmeO9 z06AR)Hd_ctX#g`=5l?O!RdgLwav)iEC0%+bT6id3dn{vrEn|N_af<*mS&^za0{}f? zlK}xme*iUF{r&#^{{H^{{{H^|{{Q~~Kw|(wWC22D4M=GLLuCLuUI05^uENZ*#m}_I z(YnmlyUNqN%hbKi*S*fxzRuSGJYV_x`}+I)`uqI*{Qdm={X%q&LvxHnbdE-AhDL9R zNOzA&c#%iE|A_zq0J=#;K~#8NMb6X0#4r#Af8Zo_*S2lj_Wrhw_kV!xraCt>`7X`` zAUT~EJU}4u&oInqIBWm*FhT&Wfe`YRh&Qq=M-hZPqI2KnAbVwFEFV#4uV&uurpdgm$wwC&8(s zLE1v-{VJ-IUMBMO&1>%KekFn+H5QeA3qO7NEDTK=R>MI32X$h8CW4jj5e{hd8LPqX R^q>F$002ovPDHLkV1lk>%C-Oi delta 399 zcmX@Wa)f1quDfvrgK;E-X%vG+41;AXgH;@Zbv%O&5G63!0#PD^T_S@+GJ_)!r7$|B zGCHR+x}-6>rZKssF`Gqmcx3W=WD5A^PK=k6a4P9_F79zI>2u3&bIb4aDDU^EnCMwC zabkm|+`2<&HXc2{<@m*2XRhu(b$QR}D|^pg-+S)bzH`?PU%EAMk8AzUU%!9-2BJT| z|NQ*}M1O(c-`{@_@c-X`5coa!_$~$phJBtcjv*4^$tRi`7gQJ=U9fuf%B4$}<|L@D zUd57&MXnOe}#f$Wn-P@ zlpJ7cdNff}vi{^h0cS_1m|Kx~?*dLAVCwl6`7)D(F_F#Nz}Sjc>p-#`pIApnGuMZx z9mmsBQ;)GMb6{n8vWI!orb%yl9<*(mee1_E^@!CL%bi$TZq(k{Y;z^|s#D^%PcPhm qdUm%d-cdZHfAZkU=b}793=Fo(_C{ef>ij^jFnGH9xvX=la`Z}n3tNIFfA{Un=^kjGc@Ms=x8DmNbzk$2$3o)R`oQz|LPSROkSzWE(oI zEdav>GaD30_WZ%s23F_d59k5D1sWY8#9t|}@C8tQ4H8fuKtuXw-3jpj1gkm_)z;); l5CN(io~AJ%GY_BE+Z|U@4{13(Q>Op`002ovPDHLkV1oU)o7(^Y delta 358 zcmV-s0h#`$1E~X$EPo^+A|@guC?zH-CMPQ>DJ&@}FfA`MGc+|cH9b5&Mn*_dQdLw` zR#jD3UtnTlV`gP$Xl7_?Yiw_AZ*gyMb8~cebar@kcX@hze|vp@et?31frEvIiH3-a zij0nsl9iE@mXnp3mztcOp`)Lnq@tvzrl_l@sjR82uB)xEt&yc8f7aL8;o{@w=jiY7 z@bdEW_xJet`1$(#`~3X;|NsA4)?HQr005y$L_t(I%k9#)asp8RLs5~#vth~EOIor` zHrd?&|0)VtXc)r)C8|`MQ6+IBdV?1Ng>$vINmNrI&(ircraWfdwv0*lP9QJ`OBv`# z;7h(OKv(*87mAz!TxqB)0>DUGy5I+}lm%MX!5?6$2<)x{&Vai>u;DmPGylZG!=Ug# zh$(R_w)}g5fwZ`QNk#J7Q&@GPQfbEK`$I|&-={t20Ex@vaVU3wI{*Lx07*qoM6N<$ Ef+aSsApigX diff --git a/recipes/icons/hindu_business_line.png b/recipes/icons/hindu_business_line.png index 7c75da753bfdb161cef270210da071434e6a997c..94791ebf0db8be490ed749d82ec5ed65738a8105 100644 GIT binary patch delta 1542 zcmV+h2Ko7p4XF%}HGc)(Nklm)9*^*oatP?mxI~+ZNjmG>s`BT3!dD;EbU0Gft=p6-8Sd zbr^>Z9PHS!ot{10*VeYi4diqns%AP3dYbXm7G=Ds%#RpksbG4~=^hHa&jp~xa1?)Y z(QrpuJL5PJx_{CaO{jp9v7A$1TSWuTLvTv|KPgy+I_s2;$7j)F7bUcobcOfp2AYPR^Msd7%O!rixxVYZr>1Y2%nQ zow2+elqw4wdMw$?^Ew}vIccw9Px)HL)si^HkDKjlv#cxg|6|8RDWizI+($~lqi@nnV`r+;ZVQu?uVaJl-S1>mI=EIGmclhy@C@!WPeqc znAWYRka(JXr-psouT9NV041K(Sse^Y_0UepJQ(MPvxm;7Etj`0t$yp37OW|kgTR8M zUdJfN8;nt`O7u*n!3J&>K0k8x+2$IJVln9|WJQnXOa4H{wlWTJ_|S;CYIy?}{LGcf zx4*tf0=a!%bLYlKmN!*RT7KT~3x6g5k4c9VJmT{S)3uzy_5~pJ{+W5>z<_nbs;e|3X4z?I0LXez|@(E3gqpGqK24#w8DmzpG(eMMVGdl0JB?fYQ0qD`k6>|~A zRo$YRkeO*zT#KN9l)ZRGPwIY9^a4OeTM#$e(?T`$Utxp`CpF9H&MF{N^}fDyaCP(o z8K!WgS_tfyMIVm`?VC|tjDHbme2I$;L3Al1;{>n*w~`f!-oC!*^1F5G7TXCx0~gW_ z1fT-~Q-%YBO%aJ9hH_o(QW+mxA}RZ(U49ofHcn+S$GdyhtZmQciyWV!Z07*qoM6N<$f)haEzyJUM delta 1644 zcmV-y29x=z42}(uHGcpJa7bBm0000_0000_0b`2&*8l(pE=fc|R7l5#R$XiyR~7!g zb7yD0UOP@~$8nk_qOMV#mOLaR#3gN2K}k`C6cr>8eM6-P+6NF)h*Ttm1QI+T)JITa z=mRPbRHEep2}J>+gv3ORaDP-eO=W0`<#l4m-XHJG&fI$s4}UYeUSm5wTFuPfd*}Ro z=gfBwOQlj2Mc+Sj_UsRT6c&r#djYYHXy*cW01B^ARaGd|tE$3#Uo4fbT)zCwsmBKg zhY8?U=cb-}?i*3d8Y9`u+{T7N0jNTC{l7w?@X%~s70&x&DSYMp)c0O^o>#9F<}Qs;Tn;JL;GRpqm0z-&?6*!m zaCB^NVY&J0^-7t1>Y>3?$M#K(_N!{@^hTxe{(S8xf1F)Q6{&mii(`)-+C8$P=iq3W zWHa`v8h-``3L|7*TmXP$FI~J|X(DnLUYdOJ@F;)}78_6h;_A)0>cD9EwQnDN^6(z7 zDr7Tpht6KORZr3K*M9ui@dpPp4c6;&BMC%A0H7F%WO?(d&D04H1`GqliF2xP>c;o< z{ov{GfgNFWEjjat_o|W2Kxo7^AVb42VAQf+)qgq#P>(H15eR97Ks&oa3JD-lBnlx6 zX@shJXm5X2G0xzk)-Ymw}{oE@fQD~M8;SS=V5xeX!jSw0E?k@bB6lf>O$4$D1- z?an%nM#~3=1dTvIBjk1lvbqioN&aPKp>ZdU694nlpB(NBo!2cLsmB&jBNPEi5fYHR zOn*0tb#PwmvHjEa`PbgQ^W86vedVzOk524XRU!XRxgiy3Szj!O5ZRAy#@u8;6@k3< z-x>?%;!Ne`f6PC7WY6i72PO{=b2B|DKqO9MtH$Jrx6?@y+&TiUCm!7Og~OvyOzb{= z{JwAg_N`a{cI%OeT|fQW!=HP2#H(%~E`PG#vRP!gS`s7`*!oRfnCoY*;67A+jAE_=&G+= z1nanG>Iw#xYTRg9`52ZXK&awV`*%dCqG)C-&DGd;`cLPSFq3@22;cbE!g4(!K7WQr zH%JH=L5KJ{jzj%T(z0bEJcl_7_pn3uYY=M z$a#19#`255dly!pe|-O$XC}r*%FB(k8mF~p`p$BUT7uNwska)jjZ$xjEAKCT{rpVh z!^S{=n6)6Ps`WZ^t2$Sy&o9&zP(@fU`$qd)iJiY2-8aCMH) zt;MyfNSItP2BEG!hh~ zXxQDTvuthP8?Q!4zdo03DDUjFDn#HtR-@E;UofVVRkm4yl08eitwI$fjUW)xI+xML zmYhl;PW@_>=Hv=WMgWB5)PI2WZ##EtJ*q$u7?ITZ?m3R_3Bq}8wA@-^4H-zTJ>T!* zJD=T{HtMjnV7m9`7oSXXUmrrwWc=+zp(x3l&>BMJGu2k2Yf|NjDB+E>G6=|cm qk*qso8K7CS^UfE0+vEH2@c#f!&2FuiBI0fU0000 diff --git a/recipes/icons/hindu_business_line_print_edition.png b/recipes/icons/hindu_business_line_print_edition.png index 7c75da753bfdb161cef270210da071434e6a997c..94791ebf0db8be490ed749d82ec5ed65738a8105 100644 GIT binary patch delta 1542 zcmV+h2Ko7p4XF%}HGc)(Nklm)9*^*oatP?mxI~+ZNjmG>s`BT3!dD;EbU0Gft=p6-8Sd zbr^>Z9PHS!ot{10*VeYi4diqns%AP3dYbXm7G=Ds%#RpksbG4~=^hHa&jp~xa1?)Y z(QrpuJL5PJx_{CaO{jp9v7A$1TSWuTLvTv|KPgy+I_s2;$7j)F7bUcobcOfp2AYPR^Msd7%O!rixxVYZr>1Y2%nQ zow2+elqw4wdMw$?^Ew}vIccw9Px)HL)si^HkDKjlv#cxg|6|8RDWizI+($~lqi@nnV`r+;ZVQu?uVaJl-S1>mI=EIGmclhy@C@!WPeqc znAWYRka(JXr-psouT9NV041K(Sse^Y_0UepJQ(MPvxm;7Etj`0t$yp37OW|kgTR8M zUdJfN8;nt`O7u*n!3J&>K0k8x+2$IJVln9|WJQnXOa4H{wlWTJ_|S;CYIy?}{LGcf zx4*tf0=a!%bLYlKmN!*RT7KT~3x6g5k4c9VJmT{S)3uzy_5~pJ{+W5>z<_nbs;e|3X4z?I0LXez|@(E3gqpGqK24#w8DmzpG(eMMVGdl0JB?fYQ0qD`k6>|~A zRo$YRkeO*zT#KN9l)ZRGPwIY9^a4OeTM#$e(?T`$Utxp`CpF9H&MF{N^}fDyaCP(o z8K!WgS_tfyMIVm`?VC|tjDHbme2I$;L3Al1;{>n*w~`f!-oC!*^1F5G7TXCx0~gW_ z1fT-~Q-%YBO%aJ9hH_o(QW+mxA}RZ(U49ofHcn+S$GdyhtZmQciyWV!Z07*qoM6N<$f)haEzyJUM delta 1644 zcmV-y29x=z42}(uHGcpJa7bBm0000_0000_0b`2&*8l(pE=fc|R7l5#R$XiyR~7!g zb7yD0UOP@~$8nk_qOMV#mOLaR#3gN2K}k`C6cr>8eM6-P+6NF)h*Ttm1QI+T)JITa z=mRPbRHEep2}J>+gv3ORaDP-eO=W0`<#l4m-XHJG&fI$s4}UYeUSm5wTFuPfd*}Ro z=gfBwOQlj2Mc+Sj_UsRT6c&r#djYYHXy*cW01B^ARaGd|tE$3#Uo4fbT)zCwsmBKg zhY8?U=cb-}?i*3d8Y9`u+{T7N0jNTC{l7w?@X%~s70&x&DSYMp)c0O^o>#9F<}Qs;Tn;JL;GRpqm0z-&?6*!m zaCB^NVY&J0^-7t1>Y>3?$M#K(_N!{@^hTxe{(S8xf1F)Q6{&mii(`)-+C8$P=iq3W zWHa`v8h-``3L|7*TmXP$FI~J|X(DnLUYdOJ@F;)}78_6h;_A)0>cD9EwQnDN^6(z7 zDr7Tpht6KORZr3K*M9ui@dpPp4c6;&BMC%A0H7F%WO?(d&D04H1`GqliF2xP>c;o< z{ov{GfgNFWEjjat_o|W2Kxo7^AVb42VAQf+)qgq#P>(H15eR97Ks&oa3JD-lBnlx6 zX@shJXm5X2G0xzk)-Ymw}{oE@fQD~M8;SS=V5xeX!jSw0E?k@bB6lf>O$4$D1- z?an%nM#~3=1dTvIBjk1lvbqioN&aPKp>ZdU694nlpB(NBo!2cLsmB&jBNPEi5fYHR zOn*0tb#PwmvHjEa`PbgQ^W86vedVzOk524XRU!XRxgiy3Szj!O5ZRAy#@u8;6@k3< z-x>?%;!Ne`f6PC7WY6i72PO{=b2B|DKqO9MtH$Jrx6?@y+&TiUCm!7Og~OvyOzb{= z{JwAg_N`a{cI%OeT|fQW!=HP2#H(%~E`PG#vRP!gS`s7`*!oRfnCoY*;67A+jAE_=&G+= z1nanG>Iw#xYTRg9`52ZXK&awV`*%dCqG)C-&DGd;`cLPSFq3@22;cbE!g4(!K7WQr zH%JH=L5KJ{jzj%T(z0bEJcl_7_pn3uYY=M z$a#19#`255dly!pe|-O$XC}r*%FB(k8mF~p`p$BUT7uNwska)jjZ$xjEAKCT{rpVh z!^S{=n6)6Ps`WZ^t2$Sy&o9&zP(@fU`$qd)iJiY2-8aCMH) zt;MyfNSItP2BEG!hh~ zXxQDTvuthP8?Q!4zdo03DDUjFDn#HtR-@E;UofVVRkm4yl08eitwI$fjUW)xI+xML zmYhl;PW@_>=Hv=WMgWB5)PI2WZ##EtJ*q$u7?ITZ?m3R_3Bq}8wA@-^4H-zTJ>T!* zJD=T{HtMjnV7m9`7oSXXUmrrwWc=+zp(x3l&>BMJGu2k2Yf|NjDB+E>G6=|cm qk*qso8K7CS^UfE0+vEH2@c#f!&2FuiBI0fU0000 diff --git a/recipes/icons/history_today.png b/recipes/icons/history_today.png index 1de9424c2a13e29bd65ca2cb4c89dc248ca67857..cb9ff5bf45554a319310b3c0fd291c7fc9394ade 100644 GIT binary patch delta 577 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 Ef-rPACjbBd delta 621 zcmV-z0+Rjs1p5S#8Gix*005C)ALal60zyedK~#9!V_+~$VK7Kx&`$=E1BlR_0mOp} z4OV9vj)vA~XpK5+;L)hFMx8b4EGDB=%eZ#$+({7RoYsq}LByfJD`z5Hje=J>vMifv z23*P`dxC^>F;-h8U5h=lp@slWlW{M_=PY)!j00zG{)d4_FMr-)nh133?>~RxYH~W3 zA&!4|>wR7p&2Uoz#t^wS-@l=?|)g2h9WCw0SzL_SwJ*p#Xeva ziCkU)4WWNdL^uoH?l^Pp0a^mU>nyzl27^?ZItzkaGx}b?`%JvE62bL0&7H+!l3q7$ zBeLEBjzl;Tvd`ah;u;CgVl+$zBAPi1f%L-~-+cHoNJ0yNm<&@B>*o(1XCV-?aT>4% z1}`rNJ7$a3B)x0lj_pS;L(rlv$1x2x2yfhS_##}5Z~kOtS$>;bpdcJ&cPz!)#Q?Tf z*X%h1Q3Ev1CB1hjb;w4YHR`NUXN@{*)LEm>8g&*E!=MBJP2&_=x{gc|00000NkvXX Hu0mjft?wjV diff --git a/recipes/icons/hotnews.png b/recipes/icons/hotnews.png index 8eebaba2943fcf6fba4ac6b1595e30982d5729dd..46f9bf5f63b05d4f11d391a96c5d6bb87456e1f5 100644 GIT binary patch delta 346 zcmV-g0j2(e1AqgNFMt2=)c@kK|Hf+S=NnprnR| zh5#{^wzjrNbN|lH&Z?@aZfC3C{U!jh7bpZBHV0002Z{I_>$tfk{<=mMn6wSQboxo^ezSSMvpJ3th` zl%{3v51Yq+5QB&{t0PzBK0x~lnU}p7zfIuElBT+S<;}&cedN|KhR#)Q+~cwyLVCprE9Zl9Gmo zhX2NE|F}s1swMxb07!HH0AiK^F_vy_ZU7~7Zk0-h0002>Nkl1?mK3WH&a29#A4tjvjy%fRm{XfQmHY$j`V&2*`h& u@Tj1M2=h|9v|zfG(KSH7Q0k4wdiM<_i$Z4YPE&0F0000uGIqi*HM@i(X4L9C2Sr z|ILk}rKfvfK%|FVRZ2WtO+A*DmjBh0+S}VkKsjq#L&BnNq<@{2p_PgM(TRFoIsf09 z+1J)IE+~+HYR1URj+2=I zrj=55^LDl@!+-x|nj(ZTmS8=g8`}uvxTH@dLD&w;p;CtNw|#-^fvZ9_?1d!l^EYc; zU(mp(=FpTUfVngFvnuy8owu~p02f2Fs6{lAmFtlofL&2+e-lQNrEt3W^_1~E3c@Jj z4p~BP#NxqZLZ`vvd>w!uFJ5FOXUDb%OPcuk{x#lscP3yZD^3Sb>yIzEsiy4l{nLg& XN6ilB!)iLu00000NkvXXu0mjfPvF!$ delta 440 zcmV;p0Z0DQ1Kk6VFMkyk6&Dy8G%hGRHZC|gIYvM^R6{RSN<3RlJzY;fYg$8bUr2jk zL3?08dR#ekS2m4oM2l}rhjUwudSsA)YMz8+q=#LeeOI7)O0kPxw3chQnryD+9?G z_V)St`OEcmKmY&$0d!JMQvg8b*k%9#0KZ8@K~#9!ZI4+Jf-n$7CmWPkbdeNDgW>Z*bNa6v#w6h&U^os8w*)J(xI+0VyVO&43iDORq3li8cKm5PDtznj7;pah-oLD>JokfTx9wD@hI`O zOo^}gC^bUOvNg?6@b)K8)a{PPq-m{cc>(J!tw-yZHv+Lgj<7^FMx iybe&2HfisgPica;5PV|2~^^R2akXQAQSN4)w_LN-qm0t9LKaqDK1N4DFk((`l^n*hD zpE=GpXvL;dUF{p{oY?c@FL=>GET^m;b+g+>1M@&5Vt{`>g;{r&#_ z{q=@O^@&TcDFu80004_gL_t(|UWLy!5`<6`1<`lE;N$M@?(V+-4GqPRP!rJM$(sC& zB%~MSu>OF*8D9|R^ojDI4Ks0PS1?RrJAjU*4VsRQI91Sp?z;}8t7vU-Knd-b16CPI z6S#$hR>$fGwHz>}DGaP#xELsg^VtGa7ijdlCLfbCdemU1WRm2LGAeKkcsJrE`S%4R WvJMMlaSf&b0000^n*h5gG2O$MD>M5^@d3GiA(i~ zO!bOP^@>gPica;5PW6sc^^R2akXQAQSN4)w_LN-qm0tFiUy*kq1Npw3k((`l{NK;~ zpE=Gpz{+x_d|{p{oY?c@FL=>GET{`Btt_3{4p@&5Vt{`>g;{r&#_ z{r>*`|Ns9LDcSY_005FnL_t&-SB1{S7J^V1Md4qrh0Qgwu&`UPu)wk04(LhAK4%hK3dfhF?ITh8GMBr3MTP zuM!v-tY$DUh!@P+6=(yL5DoANaRt&22mUksZ}`8D;XlKIeRltWfMb7aFHnLp$=lt9 zEir9JEs(=r;_2(k{+vx%L{IEptMx3Pkgg}Ii(?4K^@*O^MJ2_G-asMM64!{5l*E!$ ztK_0oAjM#0U}UUoV5Vzm8DeB$WnyAwV6JUoU}a#CFQ@YyMMG|WN@iLmZVhtIQ=)+y O7(8A5T-G@yGywoLBS|a( diff --git a/recipes/icons/ideal_almeria.png b/recipes/icons/ideal_almeria.png index 90b5e85b4a21b4eaca899e7d436161dae2a2efeb..022305f78b24e9bb699b32baee9cff11fe39c682 100644 GIT binary patch delta 213 zcmV;`04o2M0`UQmB!4qdOjJd=03pjjS=@k-&roK)5HHhUbl{Jk*l>d7pRC3(OaK4> z_TS_3&(-d~$my@T!XQ2S@A2*-u1Np@0C`D7K~y-)?b10CgFp-g(J#$P&#>J8v<^%G zk;=~L_>}@l(%a7!KQq5d`2kkWoV%mmWXT1n+NBCeS~@TvLrx5EN%Aj{3NRy}36uyp z&?At5H39|LB2a-AfdNMjKnK#bC~H7B+ie5PtPMC)xa#3KI#ycV7yh>oPp$^+;rRl< P00000NkvXXu0mjfcx_`Y delta 250 zcmV8kjp?>(_nPH5HHVAX5f#W#xP6epRCw$g7)9z^3T=ozsURV@#(L+ z!XP~e<=3PD004POL_t(I%k9!R5`#bt1<~KqtS}7R|FjNF0dtYc&guA-0!h-_&m}u^ zyDIqsHqMN@S#Q#E0;&$F0+Lz>#$$*BCQ0@MQUXQd7pRC3(OaK4> z_TS_3&(-d~$my@T!XQ2S@A2*-u1Np@0C`D7K~y-)?b10CgFp-g(J#$P&#>J8v<^%G zk;=~L_>}@l(%a7!KQq5d`2kkWoV%mmWXT1n+NBCeS~@TvLrx5EN%Aj{3NRy}36uyp z&?At5H39|LB2a-AfdNMjKnK#bC~H7B+ie5PtPMC)xa#3KI#ycV7yh>oPp$^+;rRl< P00000NkvXXu0mjfcx_`Y delta 250 zcmV8kjp?>(_nPH5HHVAX5f#W#xP6epRCw$g7)9z^3T=ozsURV@#(L+ z!XP~e<=3PD004POL_t(I%k9!R5`#bt1<~KqtS}7R|FjNF0dtYc&guA-0!h-_&m}u^ zyDIqsHqMN@S#Q#E0;&$F0+Lz>#$$*BCQ0@MQUXQd7pRC3(OaK4> z_TS_3&(-d~$my@T!XQ2S@A2*-u1Np@0C`D7K~y-)?b10CgFp-g(J#$P&#>J8v<^%G zk;=~L_>}@l(%a7!KQq5d`2kkWoV%mmWXT1n+NBCeS~@TvLrx5EN%Aj{3NRy}36uyp z&?At5H39|LB2a-AfdNMjKnK#bC~H7B+ie5PtPMC)xa#3KI#ycV7yh>oPp$^+;rRl< P00000NkvXXu0mjfcx_`Y delta 250 zcmV8kjp?>(_nPH5HHVAX5f#W#xP6epRCw$g7)9z^3T=ozsURV@#(L+ z!XP~e<=3PD004POL_t(I%k9!R5`#bt1<~KqtS}7R|FjNF0dtYc&guA-0!h-_&m}u^ zyDIqsHqMN@S#Q#E0;&$F0+Lz>#$$*BCQ0@MQUXQe!YIquT>f)%^fWDUQkyKxlM(Af@SK3s^Nz$9W5!L!AM?1M&kTj!@bFMW+D*?<5dM z>(y=m2RtDeiEj_S0Lq4;v1tgubBGWYKx_dnJ~Q|o6svEtkbmP;021CwMloIZ0^LTu zts-s#q;@z#nX`3G0%#_sVfX@l=(`;W=2v3s4l~*_1^c4V6ssN$tPokt67ggB| z{xp3tov1ep)vnX_(Pq-Jl|g{+t_IE7dxCs2hNuCiAfaTv*c=@cC0;x<(CZ9D>{* P00000NkvXXu0mjfqdSKZ delta 332 zcmV-S0ki(M1I7c8gMThbL_t(IjfIojlEfegL_1!B2}&{~rA1u*&8EP(Tzoh*Uq;m))!fn!uxGOvJo1E&_cEP&>-0imu+ zu#7Z&TmXr@MG98lJ>*8Z2BjAOh^Fr;3Qdi)*vLzdO2rFM&wl{2({MOR>wZn3Sl1_9 z6d>gYAf^WpkE0W|dG?GcFf`a5bkX*$rq+xe>2z3FXSE2KGgc`)zr_FR~ z>4R^05$yUZRYkfLQ^{bo5K;z!Uaj9hUIe3EI{@tT2)XY+-0Us^I37{|0k<2WDpo++ ev5F19zW*P(Mh$9i8`GKq0000`_DQ-vYwfNfx%@-*D(eLMzsK+5LX~KH#gVV*!chd|3LPJ4I6IWya{BBh=^!t zXt=q#Nl8f^IB-BvP!K2!1pNH`Kv{?ad3pKp@Nl3?At51KTifpLZlL)7{rd|G3l$X= zfo!1qnKNes$?EFrlP6E|@bJ{z+1ZtsmxqRiK6&y4XqJhIiMO|RadEM@xHvaAH&BnH zq$JQ{c6N4NUf$Z;+Qo|(1NCcbYbz@&OGrq>#l;B=3olxFEJYn>cae^y$;}_4OkoBZ0;OMRxAo$y?9M3)C=q^5lq!2%x7q zI5;j{y0mWHy58R2yLaybo%!(LL!f;I1_lBG0s#R53JMAZ1qBWc4jP;)u|SFFk|4ie zhJyD30_U>~&ih1cY`Ujl&=F9uFM86$)xW^r%n!Qn)KywC3`HV3A?z0)yi?k|P! zGrql^doSVVhpfl-A0vMJJo$6u=l3g8{(PPC`ud%OCnbMU3O_IS9zElF<@b({an1t3 zxY6}=aSW-5+j{A0(P0CDwu`3q$ zHdjxoZs0g)nBDJx!jRRb_=ES4z)+>ez|hdb!0-zw z)bN6Vq11qZ;Z*_ygVhWM2JwP9y8>;15==?n?k)`f+xyS#2lCiUJbhi+pRtRvOKYkI z9F_;lb9=fthIkymI@LZyI8f&J{@L~3@vj}X)O)Yv5EA&}SYlihJS}a9Yt^?egfCdSL6 ztPf6dMa+A>%;(ZQt(#N!6`b^PV_cv&bK9OMm1%N!nKUxq?X3viteIJ`FY;{bl?}6Y z$Fn49Ki^mGpOmq53u}N)^b*09i5hNZ2cPCLv^{G|xW~ruzFzX6+x3GX>homv)5O@Gxk|n>kf8SiIU1&k#8I2J4cw`&pL6eP?L$U;c}c^Quma^%sVX>*XAt zpRTM;J(kd+1+C0k>+ z)~BiT!tC1lY~4>@y%d}ADnNJD`^}$bEXjKJq0M~if(NsgUT~W7{LB4U4-fe*Y5eu! z1`mf=NlaJAEQgmnmicq0M}0lSx!%a@T;5cf>xGuy#-Hx}zf;GMnC}Q!>*k WacekuCGRm%1B0ilpUXO@geCw@cR1ew diff --git a/recipes/icons/iktibas.png b/recipes/icons/iktibas.png index 9380a447759c30e9c95db9eba4a95ece6afc145e..d7454cbd929db1ecaf7a06d10c3fc9fc7e17339e 100644 GIT binary patch delta 10 RcmbQq+rm3Rd80)l8vqkG13Lf! delta 30 jcmZqSoyj{vS(t&dz$3Dlfr0NZ2s0kfUy-s=F_{ejd>II< diff --git a/recipes/icons/il_cambiamento.png b/recipes/icons/il_cambiamento.png index 85c8f1997076c2adb6a6dc9740d48198a8963733..fd3843412d24c5e50910012624649bb336152667 100644 GIT binary patch delta 1213 zcmV;u1Va1&3E2sdEPws`|Ns2|{{Q^+^7ejzec9LB3k?eU{rVFW5=u)+KtVrNS5;(X zV{vV3kCBd^7G0tnV4@vnq$+ButEjE7tkTHNC@Cl3y3Xa|=I7DjGBYvz`29ILI3pw? z8yp({`uP9+`cqU=CnzTM>+-z5yJ%@=Iy*TS85gxnfRmMxzJF+wziE`gahk_)o5{<_ z&5*Lrk+agExY@wUprW7I+t=K&$KK%G;=R!=FD&ld6suCqdWl$Mj97hXq6M&I1ws4s5n%--zo>#TB;@#pOT0sykKv8AV^sH&%hhlJ_N z-PxzV%z~(*i+_~o>E^3Cbjf+4)1AD;d!aWuHnX*|mY9{8K5e_by4>E|+^NAPC?!fv zO5L)^PEbwXw8{hr0)K&i;I78tz|7&};Hs>t&-RbM- zhb~_4@$SRL!t(U-^X=>x7#8&R^JQmbu|RqD`1JSr_J8>9^s%$A`S0}%4-0E;YBmog za&&QbCsVajgtd~Txx2X2)zZ|k!;K_e4-pO@As#zDIyN{p(9+MLqo9o#RLOs(1qcMv zjXZ*1_^;m**^FXz~t0007qNkl2lDwr$&I zY}>ZYYuosZv-`}wy}M_9$cNNZ>rGV>U@HTQ2ey6vX59C=%2tYi&>dkSinV5rGkys2 zJLRopG9*O(?S~DB(>G-sna+g7vW=0kq=EcSU4I(BlqZaB^51tF&X$fqhmx#NvcB_& z8`x_5El>uPYn`i~H8v03elkX{4$9GxSQ2=xwjOLNak#A3g zy1)9^$ap?{(t(-?RI;bh?YPr0K5l-gkQKkA%1>lr`ziq zT4p+~WvS2rij#SZw}wv5Jh(988e3(i?LmW!z!IVO<{Zur{r;$>u`Xu?useie9 z9&>x9o^|Q|{~Jghh{E^R7(?D}J~+jkicB_2@p7RSMxY;Hl(~H~alV$s$}e$88Cus( z{pC>EN-H^JM&vZ@>e4?FC9gWHgI3*=W>YYV!EC%&P%%L4(Bab`hUK>Pjxwo20X|U?GLfhRfM|3Vm;4J(J2orie3I7R@EPnt300ajD1qcKO2?YoX2MY}f3=azr5e^d+5)~E{6&Dm27#0{A z7aJTJA0ZwiBqAm#B_}8*C@Ci`FDx=MF*Xk+HaImmIW{>uI66ByJ3Tr*KRiG|KSxPM zN=r#fOiE5rO;c1-R##P7TUcCPTV!QpWoKk)X=iI~YHe?9aer-Va&&QbCsTYfT7G|h ze}R61NMeNzPKAerhb~@;i-?UFRE;EEj$n6>CSQ+{j+2#zNWChX_Uch zmcenF!^OhHd!faAqQ`KX$hW`Ad7;UFrOC_4%XFX2f~d`qvd)pS&d|)z($CV4veKZq z(#X!!oxIc4($uiS)tI@}%hK7WzuCab+1K0I+t=Kw!GGMb$K2lA-LlBu;N9P}%HO)q z-`wHguEyZN%;DqU;=R%1x6S3^=I7Dj=jrC@%iZbg=<3Yg>g?(4)7|Xu>+am-@A2;N z*5UEz?eg^T^X=^O_44%c_VoAj_3QHX-zM4tl*_ho;XeW`F^o3PlCHhBFvf z0N9-YGs9rG002r}(E*>oB44#ZS%`J-EnvuIv)Pt=97?vLD5{#JC;+h40zS=Kl3$rO zV}Ahv*w+Qpd731;R=Cw%nzdE5C$ipD<-Uy6-P_<>gz z(2VS(!<~1#x*L9+t!}L~T7V1r zzhhP5)}B=F_WfmLW#zX-3ZbR}T7)8@%c1fZ-`{ztEF5Zh&2m!BNxy?Y2{QMDTjY23 zirsagl1^r+cV?7;QYd6xdrKc1c5UnJYE3Xpr9-1M;6y(2^vBnn>Tle8-Iy3>rhlV$ zs~1v$)!&)uQoEQ}+dAT@CnE-F?m+quW)w?nnfXGvUv9Mg9HqqLv3t2%YVL ztBL2#1y2V(`t1M!01GxnjX++U=iFh)caA7IsYjcmdNMghhCecE_yeRm+j}5dFFF7K N002ovPDHLkV1h!0kNf}t diff --git a/recipes/icons/il_messaggero.png b/recipes/icons/il_messaggero.png index 392b9019ae37a8f01a44b9322b23299650d20769..6d8190446c18931857400d178ff42a72218501a6 100644 GIT binary patch delta 759 zcmVt7yw-^)^iin8~2@1=~%4K3>Asrsc$jH9Ey|=cuJvux4`}@Pf z!Z9r_n3k6l4-kxsi$Xs@Lq9;w%gYrI5p{BNMnXdf0|W^L27lt>;u8)Judc53_4PS5 zHoLmIqM)Irq@-zQXCfXSaBgqW(9v61Sz=&e*VosRl9GLTd(O?ypPrr#2nkhEQws+O zp`V~HEG6`S5#F|PEQXD3F#rU>PBNagoju#O zZQHhO+kg7*RiQZ|G9@ls>&npK$czWWkXg--HkyFV$0~T;Koz002ovPDHLkV1f-1U%vnV delta 765 zcmVAcE@Y1Y>K}&F#oLe}D|8#Ty6$DWb z_q6xa%%lgtm+!os?>W!+oO8H^g$3NKxB+28BCeH|;zMsQ8h@oyJZ*e}ndxcdUChOt zbq)+)hlf#AP>99FMT%`U8#rl$1U&bx@bTn<=jIM~mo3;9z(@W42N0)-;I`QrB}K*f zu2do>dLO*(x#ic(U^bhHgu7E+gTpZg(bLt9NA(Y>a!pk=TAG^C*4l~_M~=eZ%NwuT zJ0R=q#Qwd}RDbM?Lh+kmYHA8^dU_~eP<~TlXJ{Cnw>*PEZ@`&UF)8{aAA-qbLc-~I z1o;O*H>RU}jvcG3t5{xMri+|&=i%Y%hK{y&Xf+z7iBjoG~aI3NsQi%jf0s(#0E$gDL96At#upPlDEh&LorGjvy0(LMJD=RB>akaP@ zzC16ydVlc}KMe+?GY{-0u6Yo@!2^eZ4ub5=EO=~jg?#WMRU{Ls3xhBkjdlV7d8E~> zQ?MQtW#wpYYQp^dJdJKcy=4M00000NkvXXu0mjf@&9Z9 diff --git a/recipes/icons/il_post.png b/recipes/icons/il_post.png index 7e8dcb8ed7e912c4059fe0b7c53cfffcf991bbac..f6454d37e19a5c24cb20753da4a55690aabc3bea 100644 GIT binary patch delta 555 zcmV+`0@VHH1i1u|8Gi!+001a04^sdD0XR@hR7D7%!~g&P2A;wMox%y9!v~(i1)jpL z-|GdP!X~Q9Fs{rWsK_a+%4o#eBB{yI?elEK+x`CjD67geu+3M!*3<3u44}kXzSbJ1 z$1knSkJIB8q{dRa)K0n6IkL{L-|Lgrd%7o^5;$J{Zl%zr|)&@QdavEb`0t;_!Y z|MmO)OSsd#*`{+!t6HL%U;_W4A%(Ho}6^!ohn`1^^_;%>&<%>%TT)1o7d*T=kN=l!yTu{($KqJ0002INkl`r`+@ zpxLOeNob&rtdU3-qp@Mtnvz&W%48v5u$RhAf)&~3wnM#v>{i_^ho#%=52yi%Yc!si zrZUXtk^!EVuv%~S0QME*$>GtlnVy`U!8u`)3;FU&zrMM>V^+m=4-eMICnw)!d3k*U tc>mzDL?p+j`t|+e+!;ARB)@;`*a8`^7Dr?z|I7dY002ovPDHLkV1kChN&5f* delta 600 zcmV-e0;m1C1m*;g8Gix*005AYXf^-<00DDSM?wIu&K&6g000DMK}|sb0I`n?{9y$E z000SaNLh0L02UVwX|9}7f zfBpUE@4tV0o`1d*U2+WW4HPH2Z@;we{%fGCF2DT(R0`B~=i@h<&FA6n0V1ddppe?C zQ$Tf3zWmTweM)KN$%KP9x-ULD{QN`U?khl_plbm7FMRK{-#}yk{hM>+888M+H=G69 z2DIemx1TQCE~5JZsKI0Vr7yq!09CyE@w@ZFqr79c&wsrB1mpriFnTMNvMms8{#i(tG~@ z=OWmcu^5~2CTF1y?+heJQhj#4>8!1~%Fb61tNCT45AYncADA3IFv^AlS!L-5@va|~{I2{Ua;IQQww=(llS2Ag~lprc#qI zxTtV82c!AyoPS{paI|1~7OTj%C0 zQT@x0o8lJ4nnd9{+>9*wQ5ql@i1&h`TYMOSQUaBH*E=eK*r;gR$PyiP9eu{(P?0do99+p9YXz56#LiysCvil@P_Y4=n+41byz{NN~Jz?p_OIy1nB!# zA9uh8--Q|2->%&COY0$GXK>E;G4PYoOWb_I&WE?G#%xvk2As!I+{u0cY`P}7G_4hg Q00000Ne4wvM6N<$g3)6MaR2}S diff --git a/recipes/icons/independent_australia.png b/recipes/icons/independent_australia.png index 5091d43d99df417eec119efb1916404d7ad304f8..a7765401b3ffbccd63629d6c1c4e3149a1a32fb0 100644 GIT binary patch delta 1019 zcmV881mmT5o)amy4F8C_Gg!LRu3kL>n+l zvADta`1w*|b|N-VjhLk@Kv;f?nIJV!7%oUHL0OidtemE?pMR*cBso(0`uqI-{r&y^ zD?e8kEl6>Hk8^;KBsfxcgp_%Pm3oJkdxw{Mh?hK0WIRu0fQ*_xP-Z_-XG2tKhLWF$ zlAwr_p@@{BiIky3Rcefvq(oI~L{@7>R%}LBZAVybk(;QJoTx`xZb(^fl%A>(CqfY? zLM1v=PG57Os(-bjstMWGg>dNn39=Nnl`aft{wYhLN8+Okz)8bSFGi8Zb(orm_+!LlP)N95G8B zGE7)!ds=CHT55e;YJH@wx23JOrLDN8uDGVJxu>tWr?9$PYkm$TK`udA4<$h_Lt70b zKVWZyVSjLfV{wBMDn%bOO=)+DYIlj>;^yMz=;i0?=du@~ z*;AW&7vJhx#5n;gYI|(e>HGa^C&;&{)~(%hcYmK_>%iP|<8lyo{m2#tr5?xr3kn_> z-aR`jX$B_E1AM1%Iw(;7SHY?K(lp3J(vJm|{6AG;_Bw~#S4av;}QmgvHRAUW+X$W_`7w1T&CAZId? z0exD_axgJk21_{~*h4|mu$|)_2{$;fpsN7BRR`98V8HOYTYC#a`?JG+YU+HSDc)QB pWVagR(xe~u`QtAAIuijaXc4J^U!-f1xcdMA002ovPDHLkV1ij3(WC$X delta 1038 zcmWl{3ox4p003Zua;6(;wb*o)c!YSikx?!h5=p#Y@qX32NrjdnQW7a4o)IBJG>H#h|@dN-Yeq@WgUIP9S`KZ2l7slqEj@| zB_836M|uY*gi>YS*-4S?k$CvAL_XCIQ~*^EM^pnN>VZ-9;Mr;E*tB$fW(YX(wG61# zC?;n|r=E{qDs3syRiKIkLxeDcDHu+c3$o8y*~K!@XgzI zM@R2J93TI4@-N^&!0GAfj)B`#Jw5$N0tOxD^DH~l&S+xTw=Vctz~15@$J2fhSY8pP z`WBLS=F-Q9zavBAVJTOsN&av-B6bU6kb^4Ub_(VX8EGu0 z?vcU1(s#4i`7+Nz%QRM~`~Ma<+u-be6AD~R_>D`eMwPr=38>r2n{SPOckn`P*m3mo z39T2xDf2Ka=~%FevMeavJRdzbQ)~OJ2*#E(XgZ1v#JUc(n@6?j6VZ!@IzNdiXq!6* z0NVXKjKVs9ZK7u zYXB*VB2B~%i;5E7R!~`uVhPbvEamB{Uoagw ztAtDw$gAR4ATdYBav+3fB%Oa|9AlZ?cM+Y0RMIX{QUg(_V)jM3jf~0|8)TW@8Jdp z2LE>e|A-w$MMeMZ-2wsv|J=T|wzd%w5ef*OMRuR(}DINklH*dPI{~Tj;U74++rkg8abD9Lk=zs>_`{W~ff@0#^`lZ5B*e zt~3Kw6HujI4MRWIZogXa!|+WKW|Z^4Jm6O5lhF?0RBhvP0{B+>DmXB}@!m=w8L{)^vVa3bkMvF|Y|xjFuPL<(&e0_X mB(0{t7p0CDENa_1`u+ev`hD}iS4F}A0000WVYQedp^E*yKLCqXkup?r|v{uhf&5|*b{R6wnd*7UUe$Vr~ z=bqikNn%gd92Nk;QxYF5Lp{h@GpC_A+gs)#&EEcI$DiVprVzJ3&f_?R{Q3#tvu)njtz1_vd1^o&pliA$d3{UEy4!}GB z^8xH@?C9ulb#-lNX@RC|_#=3> z7#iH*wPyHY0$S>zfdvOm&{7M3s)c$0@0&V1JK1bDi^cNv^fVfc@KPh(?hcE6;E>s7 zv-S4&n$6~(o*t{!3MYo)!(RC3xOYf&CK@rGmL;VEFpabA*tMsQS6AA$ELz?$`1*eR z(s_HVBNYM5etdI2QZn$1FuMQt1=IV?k`+&)s3xnG;scBK-<(jJ(WH z4X`*YHuq@CGnwz9i#yNs?x^IGkA&;)9#~u^^9(6x*D%-H2Y54LESoAlx{lSgrm=k# zRh7mLpW!ika1p~=?PfSnR&>V!X72iNtvqdwoA=}P2tjtgZ;?k=?WZHtf1P`1?t-MS zwgcXK`{uvdHa2#Bc%(G>#Idc?mzNWX6E)Q#g$;(m2Bz?O9bfWQL{wE__dR**!iDxl zM_=r9&%ENe;^#VAo}Kz!@9q0=@bo8w<0~I5vlv9AGDL7W#lQ6D`olF6>8aF%a;ZZ+ znqsWuNhyhR&j%wPKBI|q!NI@Bjb2(&XW-9y>~Z$mMDw%TuQ2L?tBY~_yy~7im!4nn zFB||{KeN)Oq~?yYk%@avS+{n@AJ13U&(i$n9%Q+F#{79&!q{}TqOU8W9N(QCBN%Qo zziqynP`S3req#aXKeXy(xR^A(QJwZR7kIQTsop;@a5=8Uz>az3HGWg&QnYQX1YJTf zEm}(}NSY$jb1BpS55onaI2MWt2~wC>9=y^MX=vevpc$wFH-|*XucI zCyffxoSagNj4)ZpXAXJoEsiFZc z;CX@EU5J@I#piEkjaosgoXD;3=g&ll{}d!qt)b}6S_-_KimT05E^l9*^o~!W98mxd zkHGjij_RZRS|vifrud|)w@?5R3TqBVKSf)sr)+&JlYk6$7KM(_)nwCIIZAE@nXgXI z$<}fIxft#eo+1MyaZC`FuHbJ}kQ6Q;1w1~F!ufdE5**_o1qaP{M}LQn5`aXM6x$}; GQ1lmneD&x6 diff --git a/recipes/icons/indy_star.png b/recipes/icons/indy_star.png index 43c79bd08c174f6fb483552bdd913478ab787594..59769c64c6430c5706570520847901c05a854fd7 100644 GIT binary patch delta 1611 zcmV-R2DJH>4ciQmBYy@WNkl2%Xx@ON+E_mf;J0=#AD^YplxBJKYKl5;@NC9M! z^C1dNSAYypj}1Ec^*e1iaDJe=<;soQhVC7C+eQUDFpOdB##2~_i$4sY6Hy>(y7qyQ zm9H7w>(c%Mr+=7djk;wRz+4Ps6>i1me z#aM@(IQxSF&cSwV7Yt;NxUlOb!ll&Tas6=!kUCl?Vk^G#zXDd`S@dc1wZiih$`luI z=xu8UYPvp#nwj=H+&ELf0G0^%9g3m}w8-{FwkAM+BOj zo5hi13;VWXYU~2CW;6`q#SzJIdczLFwmxA z=265C03Zf1iNrbqpn-0#owtpkhd4_nB`8vzB%Jis!CCGZipT;2K!N}Pynh$E1NUGO zVt{i`@_*?$u_fK@30=vgd=NSg3yqXfzmmgMWddp{{GJ+5QbfEo7|j0JOG#_jLJ$ z{92SDV-Qe%#D>l=)J{0k&yk_>5s^1IY4Bz0O7w8p1U0)L{2 z?h*ID5ctWKK8h$HF@0?71h7e+<$bBX^KW6{YE$D!qe}HKjWW$TNtN~lt@0Eq;iR}k z5iwFn>#gY(ykIDNNE&ZJZqn9p+(C~`h6#0 z9DM(ZrTvV)!dp*}n~juQmTWh{1b^jS)W&Fxk~T=|Gy`4Px2-IcW&-#X#yS9BT*2jM z+HgXDc86Ct(U_nR5Vi>lWJbv!V)^Ge<1WH(4!=rLpMpeVF6-TSwDH$)8*~6}TXxXR zgA-odyYJHUz-|gT;*e}g^s3?7LRXeH_~DUznYV(oZ>Q%3#@<6z(e3j7H-E_@FzV`8)a}TqZ zG4=+HE=vmkwx}>hOK04IC!rI7C~XSUroQ%w(Y{kRSB|~zsy&U9tsQ>I4ga}QEsZFk zpTF(xvGk!X>vzuPjGwajEPvj8ooK&HdUtgKxC1|jX#q))d2dOBSYPWMt>Ti6H`X?u z^2o?%pO-;|6flQMD|FMNT)H`LXz`CZ^*knCc6o6-F;Y;%CAe?q{{n#&X{#yyORZ%xhQ@_Z=rTT;w}tgGt2KSI+{n))4ogoQrh{tH@ed8C%5X9)lR002ov JPDHLkV1oB)5kLR{ delta 1670 zcmV;126_4043-U$BYyw^b5ch_0Itp)=>Px*P)S5VR2Uh>!F#OLbpZhI&+m6$_a4r@ zmn)Bpp&@b+6vdg&RMez&(|n91ahX&uv8_MKd~KD^)p~46rK_2fX*F_dsg%tM(`n90 zzL`KNFQt0{jVo}u_n!ATzi(bUB=OYx!_OxQAs7`c@vek1QGd;nDG&L~q$degiUkT& zI?Y~C|Jq+W%eOyO;4uHQu*f@N`x8@?b;X9uob&RvUKyT3u8-2IKi@Lc{jvM{3gnH- zAm{%Og%FI2me^N7g*eAA4!ShzE}BmUj$YgZn-PN!KC-H5RZ{dftFVYx#Hi@YOt zFcI=3bywa!Fn?#Y!Hq8L-g<(G=Bt{C9!x+l=Hhz%2^(;|_aJs~M+lyfw)EDH<+F^8 z(c9f`?oY=6Uoo{F}^QSn~=5{v9dY+pTi(toIcG*@@6CO(qLwJlq_v!RoR5xAn?9yFtSuSQ9>q?|t<{z4C+Z;| zlcY#bq`rf`$9**$D^ET`HJvdMXH*)oa!4zsvMNbdDP&bbR<)MsNNuw#J=Lbl_>{<% zxXatpTK+uixy##o>VzB`WHYRLf^FzXAx1)~lYh{pf2E1_0YCb1iDp7Nq7f5SX>TG$ zt;XQ1^b~AryTbf6U$UuzjCr`r+tOMZE53fR_JsCnKhzU9BWm17SMHCa~sHKpQji?S$8=~_lW?iMxd9Kes#c!TM2fC2ht*#~QKjI)B zAeusRghrW0i7b+cIM9qpGNjXFvP5a>b$^;wGTj{c33}&RU7lqAeXQSv35b!{uUO0Z z#h>mdg|VbxBYPN8LTMZ2?Sv9>ouo*8n9x8(#@lM7I+-<(-Z_?SY%_aFV*M6OLWE#% z87;AvM{1|`9Xs@}r;DdaD(h)Ownn{3vqE!(#t5NAqfAn#StpeyQ&(!%edC!f^M99( zMa(!P4y33hwp+^ceYNq>K9f`)D|nJ29vw*7E7uTzsmf zM|Wpmv1%E$Ve%1ilPFJSC+*vr^+`^-g?J1*R*_T*HBy5v>Rd~3FcsWj4}YH*UT#Y+X-2M>-}ONUhRenc~vErM|uE2TYvJ={M4GG=r;1${6F~?$^j7aUhqV zY!9)P9UC51TmO3ln_qhU#Yqb<2!&3Pb+q-lXXuNL+47*@4$X1vf%t>)f}+1>%`@yBNkq^p?16l{r9^1pEs+7 z6nS*>*v1Ys?rpPV?Kn>PA=6Idt-q7n;)2d~H0f~=J|Y33 zQI?9zYh;{;Yw$O_CCFm}e_YA*d-zG^W+q?cy!8D#^Oda6OA-VHh<;Uo>|zx_-D1Q*$ZZdwMPlQgStbALI^%cu@Bh40Q#(jt0=pM Q4FCWD07*qoM6N<$f`RKip#T5? diff --git a/recipes/icons/insan_okur.png b/recipes/icons/insan_okur.png index afb4b5f1ae580aad9b0c2267722252b68226688f..46d8bed655d94d0318666022167063f835237a60 100644 GIT binary patch delta 1505 zcmV<71s?jR41^4jHGc)UNklD@iB@qNGF=XpNQ_w)RCU(VXv+Q*L{KW&lq^>y0cDt|jtW&>coBiCfYf?)$N zej&`k?CdOIlh6?>4#vC1V(~m8Lk|ECv01G>Jv{<}fXn4pRaG%A{$XJ@n-3p89335f z=gys(nHe@ktE;QfbGckko;=CQ$~to72**QU45f*QiQ(a4!XTLO-^QOme~#c@zkcoS z@6YICose+3y?>+AZnu*%$L)4w0ln1J)RQMqLTUg0{Ra*lz`vK5S42caOG^v6c=6)J z<}GcuW5+eHC;^X571_p_%O#rLaT31&mkx0_h(|vt? z2?qZcFJ6?(9lR zTbG%Yos*N3pPw(4N&`L%;sO$ncRVdULs}ry)M&0;xdQ5l5WrxOot+K+{CqLU)M{(f z($XR$BMk-vDBXi#_k6R*GHW?>$ZzuZNgp2{Tz?h^0LI$a$9HaS?#-JwsK0&t_I(!+ z;BqaY>hIsb4+24PaBwhg30oAT_ujo=Z)9ZDpUZ{qnKNg&-*P!fn#p7WA*f}9uTUyQ zIXUQn;^JaZEGv+OgoGeQN~1`Wt5lXyJ}I}?u3du~q6BI9mq?^?c@b(1ZG>#Sv%K4E zG=HQT{q@6%6TIWc!6TG_QBqO@uD}wxGeGqEn>TMheE5*rMA{Gvl#7ar6iUVU^FIaz z1nQcbolfUAvuI_--PqU|5Evv( hMxttJY61fTDdd%v74$0Nh34tgr|DWKVrWwM zLBYY|>*9HJyV4i3W5lMglkBeXL;J&g)M zXH`^I5Jh2OVdxmxqlak+rP5OX_So1Moqqxb z^T22{qJdCj?d_e6$=3of2EDz#F)=Ze9?xk?A}^Gq?-=Es0YJzviW0Fy4dVH>0bRxx zquaeoDcubK%xKU)efpFhJ@-dOMqa*r$;7c604CT+j~<~3STC~f?rwAwt#=PV(LEb_ zHZ(MZ1zvxo|Mu)R1{5ysEG;e7*4BbL9Q`){RGO+16^1BA7KRV8 zPe(fjptzU?^N1Oa!td(p!T|q#O3G|u19)yBlVEuhhK;(lQQV)tLO7m03=Iz8bpdkm zN}+Erhr@w*X+pezKTEOW69nU3_jAMa%#7V`!z*ZMadFwTjIWKw#YOVQ27e%;I@($d zrd#=Wd6`*R5858Y#>JZZ`tJR5j~~nL?C21r2=Mkd-M+0-s~Z{{l#0Tn<8 zS#Qv%qzER)C*b{y0JlxX@`?&Q#Guo+=np!$dqfSSzzDSYN;jr243wHa$!UAZ)w~f>3Ky^+{O)WUE zZMWI1?o~H>*!f{Yp#SIG`>ClvUcGwdfbHk6Irw4${J-)SH;fOH=5HR100000NkvXX Hu0mjfaVXgz delta 1540 zcmV+f2K)Jh45ti`HGcpJa7bBm000ie000ie0hKEb8vprx|HHtx2Q2OiCHx<8kZImm8uyN(@5=DmuRcv z0t$-lo8mGm(y7{NN0XQgZqeHL^vX|+sqF}Jz~7tupL@P@zJIg)mv(S)aCmt5?Tmc= z`qkmSLpev8W(R=ve=<#uoC9+NK-q;*FV@!92sRPhSCl?weRXx!_LZ&ECjgWf4TiqH zzV!5T4-b#Z%1T=Yzqjb`@4s^8N_2Ge%a<=#R#xmI*x%oW-p@bEB$!MJ+$D$Pc4 z7)tZ=^Y7okCw~@+(YZh8CMG7}+=mYz+}+(NeXJ7_5<5EeCX9h# z(W6JBqho}t699w3P*YRGMS=Hf1u|!omW{(-8oiYHw?gkB=`cEzQZv;c~ftetyu#NjQke zghCOWo`W40i&a`u3J<_Ilv7hv*=#o4Cqd-q=HhrsNl8*tQdwCUNi_|B8yg!+rBWyq z#>U3-d4Ih8{CuphUAu;3-rnB40$zA{xKt{;bt_b%RF000f-#)GbLS3R2?z)vz@iin7aiHTJzYjPQROeT}V;qdu<&trjKKne&5%*xK;3xtX) zMM_Evs3U{`28+DBJm_;cT#%_$R%c~pg@uJ_G=CaUdIa$Cua6+EsHiA8B}FI_A$In$ zfV2mZ46Qz$NkkxE%iCOQCkpjapn`uh6997!XKl`oYRlYElgrlqAFsR@N34No4A zFMk#nqQ=lh$kyl8&nJzBRHMIsy?HYrFc3UK2^hu2#o!7o5pxJxqgFqE{=BQJi`qom z;0lxr3*ooq?%jKyo?fcPMvKMbU>5D|S?lZTJ-xi?**VXiJwv=zRaIVIUc}|Sy*>0Q z{Do#nNC*iFMGQ^izPFDLw}880Fxd9_sef5SUQAEVAVy>o)=^!=7Mzur%E5S0P*81c zE$Jz+3I$>vJ8#~+Jv;SI%_7o!SYV`JW&i`}K~cjxX=Zd#TU#5cdUPVnx22^8l;Y>u z*cc9;9sc&ijws=|yu9q=+M6fXJ%%|Tfm^KwOTD22sPHxq5q!s?*dQ<1_lOVVq&QDP}7u%SR_W@ zon!qx0Fo;_QIzog;8<+GHi$#pq_tZ2&;J6R2SEJApdA?*vHQ+IrlzK5XJ;utE&@O~ z+uhxbCb0XE_4f9nn|}E9{s92-?tk6*yYcaHEb#dw`ky|1((CoMVOr4n&e^l!a=z4UB)2A!^` zQPitcXUE1ViY#Wc(P-K|IPKSu)nzt_Jk qEqz>ETm+fMC%m%6P7Z2FGA{`~Xao3ANLFZ#_m z)w1K(x(mdM<1Sf_~HAnzi+?(lBwQZzTsNw`fHb- zezNU5Dpj>Re$n|0Pd+l{Y<~LTyZwY?$xALy+<&ie+s$vk{;=k4@ttwXxbslbk_+#@ z{p#9%$7Ry-yRW}$w;VWd>&=@lKTq6$zxU>A*_u7m4n0U+c9E}m`^!&1YPZ~A%i9_> z`?OW>5wS-s7lGlZToU9L%%Jk~SNMs}S6?p9p7nyq=rsrL98Tqk881(s|1tAx$AvDg zZ>noWK3@ffcb2D%V@O3@@8qjRpB;EwuNxlqlg(Mdk$yNLEiLVs{Qdueua0aqTX%V8 zUATLCnabKNr&*>=3cAe9oN2l=;!J-Eo28(M>N8a#QIv)}s4txDZJspZnWgK2$E$_Jw|Qm%+4elBonyOtNQ><3G(+Q|vgu0DP` z441_Z>@IlJbM#2fsqGGhk~jWntBECZssCgAVb7cpR>8&HnIQKg_I5mz*CB4hsb9SK zA|BVs-uU6V?3B_Hdxn_8f9wZ%X9}uK`@*syP-`zkg{ZeM`@K~PtD;LBrrc;_unuuD z*~$In+qzsn2AQDAC1=$d+BQ#2X7K$!%VY&pL$LciMvHQG*Ts6_T2p5!>*q$6JqhU9 zSfTEG(fMGk*jnj$f!ArbzO%c{RZrhi^7o$e-lvkuUoxr}dD~Zh literal 5081 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 diff --git a/recipes/icons/interfax.png b/recipes/icons/interfax.png index c4d39186fd4c6c1a4594e9ed32225c53891de521..fe314ef3dcb56bc11dcc08f4a0f00a0e066ced98 100644 GIT binary patch delta 2368 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 O8Niz#HG62{7WaQSbTX{~ diff --git a/recipes/icons/interfax_uk.png b/recipes/icons/interfax_uk.png index c4d39186fd4c6c1a4594e9ed32225c53891de521..fe314ef3dcb56bc11dcc08f4a0f00a0e066ced98 100644 GIT binary patch delta 2368 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 O8Niz#HG62{7WaQSbTX{~ diff --git a/recipes/icons/irish_times.png b/recipes/icons/irish_times.png index 3f9153bf74e23048145f711b2a93ac2997771aa0..469be9f406b0840a39c1d9ebc61076942dd4eedc 100644 GIT binary patch delta 244 zcmVo`{~R0~0RaK$=jS0IAr1}>2nYxuARr7343SV;e+2~v`1tr6 z8yhX8_0j+U00VSVPE!Mj?bXrt0RR91nMp)JRCr$HlgAFjAP_`>UBGsF@4dJG|GkVN zOD0(HUM=Na7!6N*fmKQy`iarFr%JRk)q+tZP#a;g_TVtJL;poF!QGg-Lp0(=;$Fx* zdi1Yc2y{l7Lf9gI1e~pBP9!uT3<7~RWucLULboJ$AX@?bfx)t?j)S5I7CasA)%&lOaU)CxHO0000JNR5;7!)5#9PFc1UKBrZ@&*(v+JgYp0W z9tSEBTG3pQ@Nx8%9A<2cb@&=*jj^bVHLe;sgFt=;Ox7fMm?k5BG<62dk(f;IE_~KkrnA1;})nlN?bEmsbm)6V8 zeO{f*jk2lg+h$#z%k|-2pt_u>zLr*+&`_7v_u*YrnbOM5ee~d8i?OO}qrv{=Sev+? z{p4BA&U@F=bWN7mdaAdeyPNjmUe?lej7GTxt+GfjAo(4 z(9n6h#))B{$zq?#`r}*Q*KSRg)>N6%fUUC5&wFN}#buzzk+i1d*=ud2zge8lyT*vj z&U{{;%6F)_?cHVm zqk^rmV4lg|)(~&K$A{+HY5C$@ZKS|=skoCy0X73om)4Vi0Vf05)N_-m0Y!g!skq0? ze^Qy#e5$sGu&wjPi4*_;0a{5!K~#8N#nIJnGcgc=QID6y%$zbaGcz+YGc)|m46oZw zcU48HH&w6tN!HvPX)GH(H=E-LkfF7tSmS{)4iW?Dafm9buoFi9s5g~>hSKSC3cyf7 zDS)b)He*lUFqq7LmF_^F>zRMlG}fMpi6Gl)0xmZ%-^se%MMP0tQcAOdvhpOT^dNH8 zmPBuDU427iQ*$h6X}uqm&)(kQNa*VR7Yp3KrOEgDUHt=tFl37X$zg=>sDRM;1ZbH_ z)-V+XglQhBXAquEz#^Ta=few&{-r3eyt2xRV{P3U;ku3BmSa1(69s>KtUV$q`R2QO zJmSUk(0&p?L@0K^fh4SlN6ErtfR6zrA)K5V32*ER^f+L?_}VAA0h($!37`1EuTJrLFDk)Z{L4#KYvk3DUE7G=&3&l8mU^kTR+`Xvskr#!T>s@)_TgU5&U=cn zt7D+Yw#AI!*KYdbTKVE!$IO3or@Kv;)=rn!QJB!J=Yoozg zoX-B`Sdr`-Ih(kj{p47)!;YZ4oBZTi^Wb27sVZ^BtU(AdNU1t4H0t0qnFmcl4DV$TMi8J5=VsQ)()3q1|1^@uo5JJJJ=C#ED0000< LMNS1ou0mjfIYCxD diff --git a/recipes/icons/istories.png b/recipes/icons/istories.png index 8d9e65e6c8d1d09675612b8917240244d3d51d5b..0ad0567e4d1339b932dabe549dfa936670d43de1 100644 GIT binary patch delta 9251 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 FV1f!`I&%O3 delta 9620 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 KMNUMnLSTZPqXcLG diff --git a/recipes/icons/ivanamilakovic.png b/recipes/icons/ivanamilakovic.png index 4a3a02de9f1b24d6f5df47c376e7d9c920a169cd..293ea03423407a59b8f0d1005bb7cb3e0fca46ac 100644 GIT binary patch delta 2221 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 delta 2265 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 diff --git a/recipes/icons/jacobinmag.png b/recipes/icons/jacobinmag.png index 1ec92ecb4bdf3ce805891f426126e35e0cff9617..0a301bbde2d0ca216015f136b3373023356f2c91 100644 GIT binary patch delta 197 zcmaFPc$IO2L_G^L0|P^2NcwRgr5oTA;tHhyGcY(XFzjbwXkcJ?&%mI-z)--zaG!x; zJp+RQ1A_nq!+ZvY00xEx28QoRf|D2+7#K@}{DK)Ap4~_Ta>6`a978lFCMPH`S#T*e z2&6JCTETSC@5~9NiJ?3^9!$2O3S5d4*b4ca8WK;~ajP`39C28qqAC!O&%(*EfPEq3 vE(gY04KLVC7$ez27&%!(8c$s3Il{o8mLs|GvkT8QpcM?Bu6{1-oD!M diff --git a/recipes/icons/jagran_josh.png b/recipes/icons/jagran_josh.png index 3e6ba0e54259d0fbe3588a44ba2f87732c01ae29..bf07ef70c38765ea902e837a4fc0bff6f51778c4 100644 GIT binary patch delta 1005 zcmVJ$3tb*L2Tx)7|MzCYi~HH+ke1B6{_B!`U zyZzx{aDFiN#q74PXBR%3-F9v;_d$Q~zjk}hIe?9KW9~Z92!Ll>t(RIePtfRRNdnYS z3j&IGC@2#yyYQ8c&#Vr=%rYQ_Y@>qp=?9&Av)ezSX*vLp{zFbLVvk->dNvWzDl@zK^unfa;yDzr?+V}8ShXz6z#l93P1z}z*HY7x{Ci% z%r~Z)%g>GCjcZrWXQTBU=8oCl>ULjm^faeD66%ksao|Bgi3$i6)CdS9;LsKF&!zEG zl27T$<*Of$hPOJ*`t;Y@-P6rpjwa8?+72aySmk1-RDY(`q$+|cF&AC2crc(~p-y%| z><=bP@#@kaXR}dt9|ova&!w$1z0H&)FNWGy;uwTjinL2R?~|F*Sj7MkLC7z*bVZDX z0Cn^-{*y#Kb<0rW3s-)>=rae_0Wg5my?$*~%43DSl^&?=%I((NAW?~h5Ucc?>$tWq zeTYAokbff$GNmucyPe+AD~l;e3o~ z4cDw*7b4V&<0>5*pjcrQJz~=nt?m%)2(~$Q%+BOa>xUE%39*oyvv2m%3ju1F?KwS9 z!y5w;yEA2Wy8L!3u8IG@iA$InP!QsX8Z7q0%769XzyQa)-S+e-8LjUh z5rh)jY91Mx)Du9dkU$MxYK=|~inEE?tz^V8fb1nb7?(QbfsZbYma)Lowt>a&RsvK5 z&U<4d2AEbku#5qJ`>QU&Bb!Fe_ri@|=Jb0^o8(dK{wQ0tG|XFj{PFIoPnYdJ=XR;{ zTYr!a1kmb5mN9_A>=_|Yg5fp>5*U%pXU%u6ubt}Eo+_jV5j;eq;qXoeB*)sVqp|6B zA$uYvQISr#dHNG$`h{5-Q-I;_1STX4S(GVCp@b3yK!}O(-EW0`*w)jDkxsOh7(lU2 z6$?h#6tED%upxz%`u4K8M?Do1EAm#=TWFb!o3OP=naQ(hw+4eoN@e(<9>Ml bs~-3h(B7z~qmsOG00000NkvXXu0mjfT0ZSE delta 1066 zcmV+_1l9YL2+#*|G*0^Z4mrFg7+e#cq0@9 zK~PJDUMg6$tg%hoG@G5Bcjmn3JWo5eqUjF<6|c(Y?_nAscM7v*bTG4TKU7UdZzEM<_vU@A@fec zyL2#T?W?~^;2^Dn}`nZf~lr<*># zJv(+77WbK6@OaX+WzE4@6A%zE?2!CNmEXx^sec|1-+S%VpZD_)=DaQNPaPb>Q#(69 zoE&RV{Lb3cAP@^Mjh?p3sAhMPD?zQ!&Z@K2x|_?^7+ngs6?pHU@wt>7A(_5 z4$QxH?SJpj=RQG5ps)j1PJC$hF#6+VbOsincx7~UmCh|f>%6H7MJR%cFGJZ+qEMm1 z4#+M3-V6_a~Ol6$`b!L>B#w2?&BQpO@HZ9+*&4UE*KdCKIrJ|G9J&Kl?o%| z^paU-oJGKiXIf5dmS;EoNb@hnUqQCOK=C;v*q_xKuh#^c;p<(u*N5^6*C|xIpwklf zmY$UYTP7E7%9*)LvIuK}&ieG|weh!KtuJ8Ds?4C^b9|1TXZ%f^Ra=H^i$U*Rth)>Z!^fqc8;H0=HGVyCnOO*M!M*u&sd%Mv5k;> zdFF*aK9JH`OZgsj8!8yGVY*qof*|+LQ79nXLLk7LaI&BL;Ps=+v(Xm@oqd k|8tUfm&6B=x#R!U8+6pk)#Spkj{pDw07*qoM6N<$f)YCxf&c&j diff --git a/recipes/icons/japaa.png b/recipes/icons/japaa.png index bc618a2c4c619a027de6fcc154b32705ad2058d5..cb5228093579c41951f4aa59be4718e982ae5788 100644 GIT binary patch delta 1411 zcmV-}1$_F*3#SW^HGc(MNklEd^S_0u`!IQkH}e zHwB{=T{fcSk8arIj~lfVUC5FcL>f1ki0ES6B}FV61fu~7$}VL=cOfJctC&I}urZj@ zhJvt*VoT{`-}KJA_c(n`It-occ8UCQ-pwR;&iT%qdEd=NPJg77(RiY7W7>?#&#Yd& zVQbA>dtTYH`{kG4T=m3?$0kf3m-&7)4oD0T={N`&HEz<9mCtV9x8WaOzSr3D(XqC_ zesO+pI?^8+nsr3Z8CF8?;^B{9*Fl6I6QO|P=3P;VWdT#Rc*&~a@PYX_^ z6nx(YKtuo-V}D?f7B?dF>d`T6=fAsh>@UqbU+v9@=OF_`|LQXy`gQf%%@e22AmZ>l z28I5mHEx0kMr+UWKW%lC9nZgOTK<#@?^3Jx?{jdX@Ks*|&s2~0RG+`}`6p}slo(q0 zAGcE*%{XTP=nrFz_dMh)-|Na7T{`Lab;HZ&z;n|Y8GnIH@2>mkvYNS1tuopumE=M& zFbo97nGk-`wVXi{e*4n!^2#0!>0|_?P)ae&E?M}8 zc*#^K4JiTebaiMwviQzNtJe?=8N(RIt--lTb<5b0lHb%o0uX@YQaVbh`Ab(P3Pz+< zk_iqFGJjC85ON0?z(5?tg5ZK{uWN8|S@G;;P@arBmfP80GP%6$=d--)9$*^-fCUdT z#E1#9En@^g$c7jZ6B;6AEHI+Jed3fd1?3A`{=Q48b_SsOg+D&{t0%5^UJcsM7(_$l z@s82*q=7&`I7k`3sd@DGDcfd5$Y6|-k?6^H)_*nc-)ZF+Br5KYk105wjCJ4mIx-?1 zwqvK;My}amVqq9UHq1bWS;gw{U4w1QCt^1WP$U*AoTZxg!a>6(`10e^T5qcvV2H!^{ zFMl%e?ooXFgfW~k{MZl~OY)I-4CRTTgK>M{jO8W5>6PKO!8qfc9oKMsAj0*IBtLtm zjfn7L!x&4Y^u^Zit)|9Plctn}ov<+kc5@3r?>PXloMk`=-cSN%X9}AqySl$RcELJW z-%z<=X7RY9WKv}r%@7kLK;pXLHbLj|0e>L4ZwV%FlanbN=9e#AJ%8p~2H=;g*56Sv zuiNcpT-?~4f-FSaRP5FW0o30EimbQ4sp#+{oMrpakQEtpslM*qf!!8>LkA9CEqRP| zG<%3h`m*yUk)jFF6iJ>-Aepj{pBmQQ6@k#mSed{rcwJ~{0$@oej(;9boo!4^nt$$l zF6RQEK!7Z!9cJuq!;L{M0ppuIE9FXG2%Q>j z!Ek(qLmT0?qX)%<)q@jlt$*|4;eVZ$lNV^1TdFR4;_zmiIRfcGDF7CF@m%RdzuLM6 zK+$~*Osbo-!oV>GRv1R(&?bETK4cWiz4>0GJQt3W)@a%Fn&0i&;TS+f=D!S#48dqH zj!+EOE@SUneD-dpdbU7-=Ru}t^~RU4S6w-MgmGIAiph{*yx$dxK?oL#*nHf{gP+35 zeb`oo)1L&BasT)27XgW~nfI)GZe9JN+Qz51G%bJY#EQPCYg%4=bZO0wB?Xl$P*^H6 zi33DBJ^l!Y=SLq{H05`Hp1p40wi%`rxblSaY%_=`~XUUdM(4; RR3QKW002ovPDHLkV1k0fv5Eiy delta 1445 zcmV;W1zP&23&;zQHGcpJa7bBm0000_0000_0b`2&*8l(oXh}ptR7l6QR%>WnMHK$d z%@(`J9lP|KW?;}WKk>j@PBdP-aGSsXJ*bha|rcJ zYpsJY8XHMp*Q{H4@ue4>y<|bLxxmiVIzRB@n(L^y|W>Qfd_8>5E z3}E4sC% zXR@!m_O=I}e(C(x>zD=XXhZh6bJJJJ4DVbV+g<&E$rM}8Thp?tJ3iJQ4<1Ai!kRh) zK^Vp-qnq!4eA7cu=NpQZIPxrV+B6r2r};6tc7kWI=YMeXM3rHyK}&nfsw=AHp{T#7 zRzscgJ&JC5@QEAmdBjvI)+a(rc7%UpBuJ92L@H=lcK0&?5AJ#oI)I4*i1bwBIO@9L z&g<`b*i>R>CxpOh8VSjXA(iR8Oynp?SD@|r~9vk)`2nR=KCKjwa!aSg`@zu_Bbccv_3!pfd!br1Qt@bc)Y1&MaRYm zVPYKyA{ra%T)B3|r5oaj7|3`}o`q-P^huI4%Fh7*2?v>YWxPsKIAuALB))a+CWLd1 zi4}mZjn_3bsfn>NNl}|1O3AD{nLR%0>qU_OlYbYDs+i<68RFVid+%p043}Kq)%W2R z)zVt(zM{CO_3KKZnkMR`0JFlH?=_K~n^T09(_xxG;BdNv&CTq0VK! z2Vx*W}fUHHnF@OFg3&ql9iv$t=Z)5gKi}rJ7i_+{SoEZ~DjisER zGvI!05fNjfC(aN%GbTnW;=uj^X8!+uCrT1`wEuUpcXyAe8X{r_V=B@z@mC#~0T39( zOkm<#cnB#f!qO@r<0su^mAnf1v^3fezRL}@8*#9SX2_doL zCG7tkI!K$a$*ux%q=T^c_00g<)^GAjm7O^acJHBb@DAHw#FuYDhcMBUwgrHRBLqS3 z>zgaT9bItEy}7W-oJ^Sg-)tCrhIb%=YyqQ3@Y##_;p19%Oz)_yg$_r1cYl}rzR8tZ zb<5(}rKF=}pYw8h6cLmpp|iUNE-Xh@{`iS|_bD9u7CL}AId$Mn`?F^%t+;ae`c+qT z9c!Fb&{~q9+DkF1n8Cx9*WM`(eYHa$+h=Mf{*wRz9cZadJO-fH60X^>bm>KH^EzfV zHU*x!JQN-68}9w;$gZvXCLYHJMYwooYYpHJSETF0nUUO!00000NkvXXu0mjfN~Xay diff --git a/recipes/icons/javalobby.png b/recipes/icons/javalobby.png index bb07a0de285705109d3dc57e86e4cc9189ddd4d4..9ef38efe82106a9c0bc5d2eb8ae141313b25a0e3 100644 GIT binary patch delta 11 ScmaDYy;Nv|^5&N;Oq>86jRYJ3 delta 1240 zcmaJ>O^(wr6wXMqnX-co5-dVy0}ID?nlwLZD}`nTMj~QVby0RWi93y$#8K>_omH;D z5+Sy1SRio#uE0GIhX5!2%czA&k(`(J{k->Vf4Tqr(TCGYIpX6N&y{aKAOBJm<)nD| zhAYaw2lacTeER%QQI0>Rlc|`7ue_LLI!RbW^<`GbY(?oWJ-N)NAnG!^&bhZ7XibJE z*L4_bYLg<~4YX-^sg77lRZBN@qzyZ&s#g6nnR(;je2tf118puu;X$}qEc8WJXXO>d zuIoZ%K*Ins1Mu5ikR{0Z>54@XH9P_3ahVn(Ww~1Ol8D`ifu_|;+h=VwE5dM~Rco!B zuPc)^!X+sn))CAycnHeHnC@I2MDodPLE)Hkc2mYwrk@@n66vh^2a)_WYdjpn$(C=M zbWncV6KHIPw*%a!fITs|4=|wPoUDc zb8;Q*v^Tbig(8|b4oGm<2BzJ^z{QpYaDuR783!;VC5uAgD&?VlfCzdt!X`3nNea?t<) diff --git a/recipes/icons/jbpress.png b/recipes/icons/jbpress.png index 56eecc17e708d98fc48941a09e488b8cae8e9ba9..501735029f87ef865513c6d555c7e9a0e72efc9c 100644 GIT binary patch delta 604 zcmV-i0;B!F1*ip(EPwz1{{R307#J8OB_%>aLTYMiYinzil#~Vr1~D-)J3Bi*K0XNv z2}46eQc_Y=Q&VMSWoT$<8yg!X8v=TIdK3=`6+}z!!4d3723=9nB=H}?==;`U{FfcIdunz6* z?G6qOHa0f(_4W7n_xbtx`uh6&`}_X>{tpiiJUl$ex2_8S0004WQchCk(rs9nVI4JAE2JZj+@GIZ+emM`qxT-N17FXBX{)^ zGzSezi=msII!(Tq9r$4xrc+p&HhQceAj|S}go7|0!8Tj7qJx7F9RiKThGK8z61Z9Y z8T3~JKe18ZebPDb5laBq0q0;%)iiDK1xS!%VEI_4nxOA@fc;oGki2)^!jSnwn1g7+ z(0UB)mV-F}5935590WOkP>6+q=Rpos7)nH&0A3{yzKAUWZxbBI9Awv1+-(sDsTEz< zw*h249MC*0X^H3rwy3#6O@o~;v=Px>w&FH(Fjk#8bJjJW?0AS>eO5er{_UcY?QW7n qsLzXT80000V5BqSsy8v-RIB`GN>Dk>@~D=RQCFflPP zHa0dpJ3Bl)JUu-R)BNJvOYNl8jdN=r*i zPEJlzQc_b>Q&dz`Sy@?JTwG#eVr6AzXlQ6^YHDk1YkGQmeSLkA;20Q`l$4g1mYJEE znwpxVq@<^(r?9ZFv$M045CI#1*4EbC+}z!!4d372;Nalq=H}?==;`U{>gww2unz6* z?eDV=^z`)g_4W7n_xbtx`uh6&`}_X>{{OxY|Ns9`&V8x?0004WQchC)O*6=R5ReIyt(F#&MQhYyv6 zaUtv?e)f}~kx)2H({KVLr1ve$I^{91h$gq3t}-gXZOv6D?Nb;T5%0IVc{neWdCFg| zImc`snO)L{6|r@DXPFM=-(HlrM~0WNVpc|xvC^}Q5@Y^B8U98X__KZhjrxJJUTCOl P00000NkvXXu0mjfrECU) diff --git a/recipes/icons/johm.png b/recipes/icons/johm.png index e4f399116b02e4c95ccaac1c964aaf06f1eceb1a..3697d42f8f97d3019d5a14b58fc230e333388fa1 100644 GIT binary patch delta 749 zcmVht-By4#`4<>lq&F)=au`}{SM$H2hAgoK3I z=kwO-^VHPT*Voq-fWDZRn2^HY-Q3*5+3e-<_>Yf|-QC^0ySsmXe+LH#N}S9d9v;-_ z^R2C|4S>F}(-!F0>GbjQ^oWRv7K6YL5D=8Z;pp@ElRyC*3{z86sHmtgFfdJ^(358Y zIX};Yd}{zSxuoc4Ab<4Ix)WKcQHP{w zTu@4pcePR?<8i2jLFZ8^3xol>DgL8G7ra^+mbO;6?Z4%!^qc8bD3aTaCKe=$&nWnaA&G0j)w zISUyjc4hG_aO?xXdv-R50UF)`g>m?=*^%X^4y5QC(0_t`IWUv&Ew_CXmc9y&Dw;-6 zH+1&}FCChGdgugbiaxLawWV;!H5d?S$O%!<;&a_UUl9sm4tLoAsC_1wIQLTKB*58A fo?-OAzi9(+B&$cPO54fs00000NkvXXu0mjfWT>f! delta 750 zcmV;^L;JrttRq`T6;+&E z+clEM)YR0Nn3%@K#@*fBN}SB`^YrNR`9MHGO`y=v&(9kh8>gwN_xt_&{Qdp?{e^{v zNl8f-fxn!boRPra7KFebhr-Ir%8rhXoyX$2xVUL)Y54f~2!99&&*bsb)6?qe>aMP? zHht-|&d#CB<>lq&F)=au`~3X;{J_A#goK3I z=kwO-^VaF~*Voq-fW9LmBap)2-Q3*5+3e-<_@tzy_xJa^ySsmXe+LH#mX?+t9v;-_ z^R2C|4S>F}(-!F0>GZ_J#E6K97K6YL5D=8Z;h&$MlRyC*4CLhGsHmtgFfjZ3`;%t@ zIX|qdtjpEh|Ns9>OH0n#;FH4OCMG8C@bTsF_qDaPFD2~RIl$0DC9OUNb`~ChGf4Tnt{=&k-4h|04 z+1Wsr%AK8^@bK`ow6qI&XUhNp0VzpDK~y-)W2h$p7)BM4ra(GANl6r+g86y=uTSNh3|1fpHbGubRh7ZVe0ptgy8gVPrf3Ubh+C<5*ViX3s|6aaHz$CJBMlUi gvUUqc(*`4?blKJY!03i&}b>WP_V-i=v&d zz@@gurnkn`-Q?Ti=;G+@>+kaM^!E4p`uY0%4LxKWNp1c9{vb?nEmnBx?eQ*GdF}D@ z?(y_ZZ;VlMk40*SMQVr`Mr&JqmR)_9UVfNjftq82n+Z8#WDbLzWrUm=M{FBNZ3#MI zk-04u9!qbZvcaIU!lARmqO`*wOK%D~Vv+kle<@RS+2H38KV{wI=@dd}zi!~b`jh*mHAALfB;}!hUg<;3$y~gRbL36!r?tT=m#U|$XlOa zq^no3B&~=RrCbA>5koB4z$Cz+P91MU zQTiANF3h#6Wds=>K#$4K5S$Po)s;c(1A;jMAUSc2AhnK%Zzm5h`OrVI_6XQK<3VT# z{!@G^0iJf(Mob6Kt4_u>o=!XPFa0)NUp9#6tg`w8Sw$dO(tWI zY#T^z97$~+OK%@bZy-!?DN}VVR(LH}c`jFZG+cc+Uw=7ZfIeh{KxKqMW`#m$hD2$H zMQVpdYKTg1i%oBgQFD)4dX-yzmR)_9UVfNjftq82n`DEVWDbLzWrUn=i=uLlq;ig> zk-04uov^^4vcaIU!lARmqO`-Mw#BBm#*zC!f7RXO+2H5f;^^Jv>Eh_^{r&#^{{H^{{{H^| z{{R2~|NnLT(y0Id0VGL8K~xwS&B2EvLQwz);6ExPBH2Q=$lfd0-g|STY*LE<{he;c ze>v9+^nJ)N&f50SKySShfZOASm4EY*riaDq(iaNo9N{#7B~M8K-2yYB8`J9%nglfd zG^M2i0Qs$gl7MEGQoI-@*C9y(%`v5W8?wr8Nde6zGlp!)8Y2ZX2Q*!dHWfjtiN#K! z8Rw70MYz+0e=6UoNNo9GHJGLyh<|L&kR0hcr{J5rc1(iWE(SfXzCrT?&8I)4<6_C8Uls*Db z88ju&muGgKnXw9uK}L@FC%xo^#vns^ZF_N=Ubh3VDD(O309=jTef?ejLgbb&6ySM+ U7oi>xumAu607*qoM6N<$g5?t_Gynhq diff --git a/recipes/icons/juventudrebelde.png b/recipes/icons/juventudrebelde.png index 0b2de7f97a2c38bdafa3dab68004dbde45deb340..2e08cd9f644c95af1fe3434017f3841b3904530f 100644 GIT binary patch delta 991 zcmV<510ej<3G)b$BYy&CP)t-s0Gi_e|Nk{zmNZG=Hq`u+YPs^&CZmN8nCEU)LT;P(Cg{zYn@p4#;D`u_F%{`dL& zGFy~c!0cec?PrCsXT|Pl#_npy?rVs$38CYl+w{H4-PGOb>^SiC?eX@t#M!vy_X3{c z1)t+GTa^x@(tRC9CEuuIH-W^#GgV4Ws18>G{X%`N-<|$?Ex}-1Vm2^+#-Pl{*OK+k%v*-e!;;%2^_ORjhF~%{{8*_7^meKr{(_s{u!v{8>r=7dzIS&00GBI zL_t(|UWL(Pv*kb#fZ47 zZQO!|xJ3mu?Fkn>a=;y;SbsdP-T6!3bW;X`*sVl7n`j$vQUIh$Y2}nxu)JSv*hn7f z3Ju4-J)HCL0|@qj5r91I@rfysFyi|h016pj!kX!82yi>bU@)A%=D;H1JS=Go07q4x zPN(af=EZ;DXe#VzCP2|ThGDGFz2t<|GYL?lqG{R}g2AWBa6|ftGT`o1&xQ?pw-PY9 zf^FS!;(9w$0`AOyn9nDbnv_8n{+?3)6paCPbgea^F#!Kl`T=F|YB@aog%>hV{0AWW zfCM5tMuX3b0Ytn(=v|m}1mAOV6nJ(&Sp=#jUqpX=sTailMpF%Oo3tH(CkRGlYyl7-f8=;{S++$4!rto$I%Wp3^qvHs(7E~Z)wEP{?N@|rgx5%{ z;6xB|j<(Q{df#Q_@8KR8q1lB2&Nm?%{cY1X5UXw>>VZ{Y7(uO?IBWBd07$FU!jnxk zQY||NS2~?5P8Dv8Lu7aLyEPikS#uxtKKAOFm@Bc%kgOX+!(xN;egjg3sDCyx(iQ*! N002ovPDHLkV1mtMGy4Dl delta 1125 zcmV-r1e*Kv2-69WBYyx1a7bBm000*f000*f0cW4mQUCw~XHZO3MF5)O0Gr|foZiAUY9psmpEUTIkV_Gv*|jt={sSWJGALM zW12o>nm%NkK4qIfw&_7{Y((Sb3>fzEA8}zwB9hsae46V8ZQTfvsZ0?PG$jWP`6|gs*0Vux7>WX2tGjg|KJE z?r6sDYR2wsh_YG{X$`N!({ z$m;pY>iNsnDAup)$jV*;p^G(``PjP+VT6_ z;_Tex?cL<<-{$V%lO+Ndf9&w}?eX^S`Tg&_{{8;{{{H^{|NsC0{|DO2 zApigX0b)x>M9do3lhXhI0pLkQK~y-6V>m+qFhHo&O^HbmkepUjf7`uyC%S@@-V97o zz|73XD`!`+8cl&86AKbxW@1-vK7>aBNR4pyN$gfIF+){I_8~j6J~A2t!hOu7Sivfo zJoh6MAc2!xXT|Y@jgVP@RRIv}4+C4n-G-!K?VS1Z=g(bn0>bO&16xvqP;jEa!p6qN zDq%N-J6!~9M_gZUm_X!(bGM(tqkzY{{m>aa3YcZ5!a@~8 z!5$}Y#0Ty|Q;-SwW~&fL0e>B;f}<&51u^^1oH-B$as;#De{2*5JAyQT3e-}M0ohBG zz$!epAS+mCuMIMx6wGM=#{^dyl7hV@IzV|n{Z24%rytk|`H5f!U0u^=_U4%C0_C;c zRzl31p#WClxB;XfM9Ej?YZw4SE>QdsD0b_E+D z(F0Kc0qPFDe`nyJUmC2!Xw4ahU{!4$9c@iDgMbOA5DHG`vw@>5em_H%my4~fvu}FG zrZdQ3y`7kZgoL<43&X*U%jeBmw(;;8G_Y#&6d;(i2ms4f_~u8=D=z>502y>eSaefw rW^{L9a%BKPWN%_+AW3auXJt}lVPtu6$z?nM0000<2SrXqu0mjf(tBrr diff --git a/recipes/icons/jv_dk.png b/recipes/icons/jv_dk.png index 24c532aab892f4388149ea7e9045df8d56374532..ac85c3e996054483fb8c5133bd63fdd5eb6d2966 100644 GIT binary patch delta 537 zcmV+!0_Odo1%L&RBVz$jP)t-s0GW^f|NjA*kFej|vESXv>F3+<@8a|F{Qdn3os+TO z+y|SI44sq`pq8@W-OcOi(d_Eh?(Em^?Hr_=C8wY3_V#?r#jD-fzU1Ocwy#aMuuizK zM6|55;NBCUmywnse-56Ngw4o_&&!e1&X(2Cq1n}@+t;Yv*a@7HtKHhI-P#44krkns z8KasSqnZSokhJ06wc*~k;@`RA;JM@Ay5r!xgXV)ozCm&&+F;X?CK4kl+x|%)a~plsi7;Xq1f;4+3@Z%6s@G)@$caB@!|9GHLj-T z_4GKer;|znHh(ygnU4gTkOiBO2b+=! zoRSKilMJ1d4W5(^o|O`ymJ^_t6``0Jqna9{njEB?9i*EerJW_GpDL-LE2^O~t)w-s zrZ}&sIIyTau&G0{tVFb|O17^}x3EsQu~50PQMs~GxwBTgw2_h(e|*Ztgw4o_&&!e1 z&X(2Cq1n}@+t;Yv*sI;ytKHhI-P*9<+_B%>vESXY;N7&~-n8M~wc*~k;@`RA;JM@A zy5r!x>j%j)RN>gdhu>CWrv&+F;X?CR0%>eB7&)a~rn z?(Em^?bz?`+3@b$6!7oe@$caB@!|9G;`8$7_4MoZ_LDjRHh=v6{r&#_|NsB&lr($* z008?*L_t(I%VSVszzZZ8MgcO=b~iUQcgwdoH?{CpWMJTOvotkxU_(-%?vtD!li-}2 zpPv>a$-uy2o0*^D!h&LkW4<3FgLp)KASak>oUMUoiA}yQBLjm~exf{x$L$}$hgE@G ze7*&Ureuz;BKQ;y45O=9&<)-Uli4DhmCkTT-Fh4>JivqWN z11L);(u5IHUNAUG0m@_&V#Q*Beoizq18%?*o?$0|M Tap|c-00000NkvXXu0mjfLP<0o diff --git a/recipes/icons/kahokushinpo.png b/recipes/icons/kahokushinpo.png index 15fb5d7577f85abb875f8e2494848f0885823cd6..60049a41dc397f8630842ff16a4e87d25f9b1604 100644 GIT binary patch delta 1699 zcmV;U23+~R4YLi9B!7BIL_t(|UhUUyj8#_^$Kl`Fd!KXeoqOkx!k|Fr=aZ#( zkg=9Hdj$9r#&5&QbHL##1@M5t!;xW=2%qrEsd3MyOxZ2UB@ZY90w|zNP_3YP1S>~y zW(S7VBz_}a*8$5AehB<-(f|rH2CT1hJncR8iSqjSDu3%6LyikmqyTymmETZ|4p6E6 z67_fE*@B}5`U;*zr7uO(7!OAWI^BM|x{~)R>0bI3I327`mdV5ZpTSwKy zjuRzDP!IvH5N9yI5gu3o7SPEWP!)^;s}qHX6Mql^%P<)`@cKEd1Z4UMM)x{~N^=S9 zT??k?c)#1nQvMNLwcTDp6b|*nlDSX}xRzH?z*!IhEXY-(E-(j4w1-;SLBPGJ%yNuY zZ{zH6>u+0GZ#6@Hmt40y%|KgJ3g2pw9A*m2)UWLZ)1oD%9tA zABg(}@$bd|t7{Xocu)7b=CD7m|f_UW$0hBre?STS-GxRtS-Y$k*S~!iCwtsp$ zbB0?)IC)j~GF*lJGF*(|Yyvs1NIm2OxN|mK9ETGnxD-Pme82;NVP0tYlm`?pSmCg# z59nV!i~ZB`Fj`|lC1q);(o)M+2UT*J=Zgpp|2$n~-?0k6KM5~)!@dE9ywG4E z<5~hF9)`zZ#ccR`r?9j|Xs;8jQTort&F>7Q5A_cFAoYOol0xj7a%kpx!@%yq@qjU` z5ySSukz~g}Ii)LC2LPGJ1cn<)J+#$d`D|4WCQ-PYtfH2DcEB12>ddR1^M7*t8$xi# zf)z?}jc57>Ym2K%)@DRDjMeMhZX->9PUqaO5y=p$+zeHyDYFO1{V$08ClRVd!3L7_ z0HYZga90MKLgcpa@y2?J(SIgj4S_QVkAd{?Kk^ENkj|zWFOcXxoR7+%F{1v-1ji25#zb~Y2I t0YkuE?|C!z95T>_P#p2fr2c<9{tuO~<|{q7D+d4o002ovPDHLkV1oE?8H@k` delta 1710 zcmV;f22uI54ZaPKB!7iTL_t&-8O6a{j9q0JfZ_N1{(r5tXZD^`r!Ae*7F)0#X;tJD zXygzG27xvRsZk>3!gxxEBwk2jP>YEgHRy$ykn~0{3dIzSSYoLbLSm#;khExLdYI|Z z!gP9=+0#9HX0N^0`aiLp%0P$On?8@28E2DnmihRP#{XoQaew1H<;yEzR>BD(K&;Tn zDFMqwx#ZtRxnkcq%eU+po7>-itOXVD(m-W|f>$uxM#;Yj(ns1%LBR~1Kw*G@;~kzE z7$m?Ak>d+qSrsYmD;IKbaw^}`fAIJ=oQ@KykcVyp^TAeA*jH)hZ!7C@kQJEqvp~$bKU=3+;C-8YRY{1C_ zzz#luXPN*A+>t2vi*P|O_Du!uFN{rMj0V6E5WoQAO=fk>cH(3wUN@qwA`9yXwidV? z;d{XEnMOH@0UZ%{w|X8@;ey=QGBU+Y!T629D5t{!vwvfl{F+MgSyZ3Jte3*pBlb0f z_fX!ZLjQPJ`+AL^CXTku*qj?{v%qf7l+#_>)A1!lo_KtH%0O8DaNA5LdmA{Fx53@4CeuUYjyhRH$3bS^C-MgZq3yPd{pvpRf zes$m!Vt)mvoDoGp^%Y|G1B49hJOcM3Qb7cbRV4(w7Uk9z9pHo~him+0bONe%I8uk9 zaX2~&PB=XRI3NT@(kA3NIC+vtpT>X?Zo}*<-ryV6dQ3<8uFFcsf>s&ML#s_l3play<+rH|u=Bu^2 z#DDT}kJ%Dh%TNovR1HH288g^5BK)=FAyse%6b2nJU;p$x%85`Ir)0)tTZx08Jg;QaaR zU7qf42!Ruv3c~4$Jj*|^kd?Dx{mU8D4Sy6cX4b%-7|OY7%997dw{=C;TC;!@Fgo}! z3Iw24h!r|pVcpdcn+KqtL!`_|p_4b%Ge`@nzBgeYj~u=o&aJShH6adxCet{DvWM3v zVduDUEE5W#FsQ_E%|h5T1S1)=dx#W*@h$<3R)vL;ae`L{ya+E>Vn&zGW>!xdy?-TT zkqCP^i4z!Wz=0YZOX0N)O1vu%&=SGb^WoSO?5VL}S(VkI zJbq|A+c;dy=_|EzLJ`nN`7n8ao;Gj-V-08o(9Ga?b_>nyhGC}+x9T5y7k`(YYmdRJ z11D6|CO;lJ)V#Qn6+J3)-()lVs}#uqjyD+uKAZq~fC4BC2#|_gNLTz_0x@O>sfVBR z?U|~f;01^r9IEGkfAk;2-)B63kVq~f&woMhg0GOsVazxaX3#X|ADRlkBnclyXpltr zk>%T%C_usUmJkdQzn+hDw0}`a?gv&Ac}2JfWPtxuFeu0Lb~gDHnO(<`w1%bMVQ$;Q zr~?56l#>O6miXtLoh^6c-2lQZWW}EeVGSbfoCY(YFw2ZKP<2^bIza(^SSz>@ZaD@(AZ zGvUU>Q7rCa1N-G?7%ze`o#z9X0A62UEWL}+_#MuDk*>Aqc=pCFS z89FZ3+eMm390s2!4QtE=pZiRf@} z>wkanprG-krS!MA-WC?@f`awFzW(_5{Ojxb+S=`xm+XawxhW_ z?d|&I*4Fd4 zx9fX*?v|F`6cqgI?Cp$<>wtjlii+YjHRMrI;V>}z*?-yfz`*jYt@qH-{qpkenVI*^ z&F5ZT@~W!*@$vlR`?UR$`SXl9-q~u6Q>UenSd3obTMⅆ@S&mONJ#FJllt7; z@vN-(%*^CVOX5C0{O0EQ($egblKkoE^RKVzZ*TX>$?=|^=UZF(*x32k*YTmD=3HF+ z=H~Rhy?^-G+4Hfn_|MPoo}TN0f%@3k{qOJLGc({XFZ$lz@1mmOOH17r7Vn&#{`2$g zjg9iOwB}`H`{m{S`}_99#O{xe{_*kRK|$asDE{{L^}oO9Y;5IPTJfo=`P9_i8XEfJ znU#~PSMFlAz_$CN>_uzg!3(P z#}-Zkg{!+4FG04@4U%@W0l-%|aSFR;?IVE4Q=e`k3?>Nu5u&t7j?~n+=q&G!`R@$8 zsebx?J!Aey7@{2-!}Er!Rkf36nRs7kNq@JjwPA!P0l+_V^Xxen0Opl1*u_|lgcbq- zF-w>AtXK&Zt81LB&4zK9ux@?yhB$jgRZ)}9qDn-F5@P)T*)D_22mYR|PAh995rkA8 z6appNwu_7rM-re9Q~S;imP<57658AirZ#BUS9XBK=zk%g2=0T2+Pi>1a`c!pMSmnX zOmKG{KT&+j4(`)u&RTUOybh*wd95uM9d_Pzsj%77{|ma*uAIwW;Y!*y07r4hb@>LO znpLSZ>*lT7cRY9N0q&*UueIo~atGCJyW_#b3|;P%Mu2B&WzS#q6`LNIm#**TqhDSp+_z{uQmO>r>5?Ml2pq-eiE#hP($0B-~E}ferfueq^_?1{=+MnF%BPl zjAVx8IF1#C$cyt+U^DviFN+!B!LA;?yomH~uZEz(@f+2%d{(ScRVn}g002ovPDHLk FV1iV&%K-oY delta 1258 zcmVUt&j65C&CS@j zr=}))`LY@a=*c987K^EW{zy-rgbp8eS5~@KtqL4C5PyIAH0$%}lB6dRdOEFoy;FDY zggQI9#Q<7c3BY3UR97RfzlE19Cttk^9X;xe!g*bn<)m`f@CJ z3fP{v0|2a9BlPzpHS=vv)1pync$n|#0Pmsh?)ale*+76z8riJ;@uS$^AL!@+$)rSH zr@0!K0Do310bIMLMIwf(Vt@zLYFPb`rd< z4%JZ#V8sdo=(Uf<778UtMkX8%)DPbBS(A%Fd<$KynOCL@3TEL^(8ZQDi%f(cvfJeL6=NgB^X29@KWhoNa$9i*Wy z`F;CP(>Q2&*NYRrxK+#o=<5SB3;<=-8voEU7(b1{9cY~dsB!JCE`~YL=0J?4{ijhoGf$BOv zOeW<|pM(n+xXqi1q6N}l6d*4$9XCbMfLc1h9m209vxap!E20=+GhfUExHO8js1l{E0;|A{#i1d15xQRV{CWAq2YvdKL?YN0)b?Q!SSh@S zwU*Lm7md!Co!dQm?;h9Q?y9Ur+nF33gvXitOC*37>+OY8QELNHkfC57rR^GQAb-vH z^XI`MN8D9aSR#DtRQlb!zxnbtBncQ)QCqiqSw$5{T~@;}(&^NjHxtK?Q%AE}1N-+& z4K2Q#eQ|E~`(1R%%SS_nQXrlNwXh?Qwkk(d9|vHXu;7BJ`gcY&5*S?}g#Z8m07*qoM6N<$f`!^*^8f$< diff --git a/recipes/icons/kellog_insight.png b/recipes/icons/kellog_insight.png index 3b162084c9407ddd04435a20a9321b7630f7209b..df0d0e5d0da396980e226f6f85c56593e0d7c682 100644 GIT binary patch delta 583 zcmV-N0=WIz1;hoAEPwz1|NHvDQw4lPZqQlC()a&K%_x1TvBXaur`&B7*{QLa;{Qdp?{%=Z( z{{H?~D|b;Oa#JUCs+78NPK{3@aCA?ObWn~|D0FsGka<>;dXcLdKUFAnk#we#cBhzq ztDS_fQY3Pti?miLbyq8PQYCU)E_th#yR4SHt(Uy5n7pr=zOb9Wv7NxOoxoZzd0Q}f zlLP@L16(nBlP&=#e_=R(@9XmL?DO#L^Yrre_44;(Ie_@~`S|zxWUErtL9 delta 605 zcmV-j0;2uI1=$6VEPqcTa8M#}P$F?qBXLn9a#195QY3OxC2~?Fb5kaBQzvv(Cv;RO zbX6#HRVj5=DRoyXc2_HRS}u87FL_%qd0a7iVK{$cIe=z9f@VI0XFr5!K!j;Rg=~ka<>;dXcLdKaX;xk#we#cBhzq ztDS_fql&bni?pYZxTul1s*}2^l)9^yyR4SHt(Uy5n7pr=zOb9Wv7NxOoxrr9!nLBq zlLP@L1IoPAlP&=#f9vJ%@9XmL?DO#L^Yrre_44=k_4)Yr`S|zx`S<$y`1<=OS=FR8ert29OKPrJr+h1L!}> zBR$Rsz7k0ae>}Ex0Xz+;I&e|T1M)#qeF^9bWj4=jdj_;I8-Pq^fJCbe;8bMQkYlXv6JT=hMvg+FN6Poboh+r$ec`( rwiF62Z|{13Y-995Rre=I(abl*dQ)_sOw5-60000<2SrXqu0mjf8tyTk diff --git a/recipes/icons/kerrang.png b/recipes/icons/kerrang.png index 2fff619e1efab054b5b98d59b5d3af1c11a41c18..0fa4a4b7815c3ab6a5932b9612a21f33a0719fbd 100644 GIT binary patch delta 10 RcmbQs^N@Rj^2YdmtN<5Y1RMYW delta 69 zcmaFJJ(p*KvK0eMx}&cn1H;CC?mvmF3=9kk$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo Y7+xhXFj&oCU=S~uvn$YMqv8Qp0C-jsoB#j- diff --git a/recipes/icons/kgsenghavebladet_dk.png b/recipes/icons/kgsenghavebladet_dk.png index 05f107580a440e778ae50dddd11075d6fd1379df..c531c3367664939381070bac54b85e49bfb4ebe8 100644 GIT binary patch delta 234 zcmVM�r>*QkDu>>!={w1_*OkNph?qdt(3-6zJ7FpnYOUzu*Cz zPigi+AqgH*$p^;-s;Uq4@jM+H@T3*qfove{Ks8WrFomQUKv&Rl1IxGoE8s1#O}sz} kmKLQ7fVReOKFlAy0lf$XIWmX@yZ`_I07*qoM6N<$f`)Wy-2eap delta 273 zcmV+s0q*{W0;~d%B!3xnMObuGZ)S9NVRB^vL1b@YWgtmyVP|DhWnpA_ami&o0000p zP)t-s08MrfSbrd6hcIrCKzf)^g`r@Osc@UJfTp{TvBRId%&*7Pzti2%-sRuv?(g*W z|Ns9egHrzh0056kL_t(IPwmb@8p0qDgVDcsuuuxk{ZH#+K!06K^#Gb(^1VgSm+&*7 z07;URfU*N7pR;sf5&<1F405h{bfEJh+o&eyOAiXZcz?VEhwUM69tV-%$C-|H{@H_xB XhA4 zF$Q9Q5CR6YZKlnNsgc#N$xVW<^Unn-`g&3dAVSKyq3O3YjiROm5hptmW`>%PQlzdT zk|IKR-WpAl$+-wFh*2j6WK{riD^yh^go-GEB&ukuV1b#l7BKq$e#Q>(pCm0}=nl@J zJ1~=j@4P{h3V(@$7#=m%Ol}%dJ;=b|>=H3}|FG{xs-`hCYasw*6Z>mBibkQT6G>1^ zTjm_SjFsnqg%3m9jPvZeyRbY8reIcvqPgYNv#*-i=R`gXJ74)Lul{2lH32atff6IU zy=NQi{Bmri$x)oZ>(cls=* zV)yGW@Y=|eG`;tzVjyB5imZF!yVmQs^cw}BX<8&yeDca$n2Ple-3->?9;n?10G{*l zUOQ>tUccegbH2>{g(pu&ZOg{rPo!?AuM>%GMBgI@Qk@Jz2x4x;SPNt0qr9_k2ealc z1>n&BJ%66JX`LDoBZ(0(z}$FZiWKp4k3EUG3zoZK4;}b7FRZ@@(LUWO5d=~)s$`@9 zF(@eoLlR(u8MLk9zA*Cv(Dp}taR`**VMh(xoDnf6pD||%%U56R>-PR@Gds5ag-D-N zM^ZGZO7QS|85>}vC0snAp2~Zu@V8F{XHpy{F$d~=^0ls ze}C~R|NPqK2YGk*RytMkRMi!QT*|4HvIB_Eq@Wqa5VJzAKp}?7*~)V>$gOuhmae>p zd5c%M(?_296}`iIsjGxY_6Sr7QpAZtz`&RU0v-WOoB>fq)J2qGI7~4Y7ipUoqhiHH z-)H*F1?2u1BOC6fKf2G!tLUVD`Vfr}N`HIDP3^uC5g!(W#N_FPAb=S5!7A`M0J&*_ z#3>hikAb1%IDB9i+yDMsw11GQu1FDgdWu37Ai9?nF`TgY3+DvTQU$rZB$h$FuCa1C z@Cu-waWu=$`6iu#*&IH&iHImB`wX6|x2dU+ZLOhABQOWsMtB z4T+2=H9=j2;+IDO5LZb7xfz>`z_h_REWc6{N(Le(|Hz?bnzNYGFS(J;>wbrF47& zDMrQ)>~^vE`zfn0yJd=q6FBMYFR^Xoy~G&l^?%DLRjADT5MUG_ z@MmC8QZ>{9CW;AURUhR)08mnakfi|;LItDy-{iT6f5KUp-$GSHEIR2T0>a2M_uw!C zH)J4sgG%5B)GbDM2p|~&bJ7f1K@BWJ2!t4?l#D`79tFh1%At4P;H77O$I6SZrGQ1J ze9G6p_|)BK6=}25#eYZDlyg%bgBRfq-y)w>@tqHROfiR^yuDKAK&^Lj$g8r0)Bndg>*D@8a4?n@CM{h@}bY~>p0qkW85@M*fs0xP<_2~EC=eWg7S$yI$rec4! zl95+Nm^~ycn17j=Kh#op5>@#{+^vi^Mji!&b1u7{0zPuuC0j(~{wOFaqj1D7{*JDhk$MT56D-qJkJ2edkv?rI+q{bbVS>c#7BzZ?f>lM+h4z&IWwkn z)Btq5GCt6eeRF5Xj-gI}*sOVe&IzB`2<+ki;J;j?k!osZ_&5Ln002ovPDHLkV1o3j B_doyu delta 2148 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 delta 1712 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 diff --git a/recipes/icons/knack_be.png b/recipes/icons/knack_be.png index d6ffcd43ddabe52a215ac238b67530a8c284036e..e543acfcb8423f042008f74439543241c2068a7a 100644 GIT binary patch delta 576 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 diff --git a/recipes/icons/knife_media.png b/recipes/icons/knife_media.png index 8efc7d131a3c90282d0654e544606845a945d9d7..c5042da1fe956a719c30fed9394219415f3936c0 100644 GIT binary patch delta 20 bcmZqSU%)#-d1GM-8z+OOtDnm{r-UW|L^TGB delta 248 zcmZ3$+rmFV*@uB8-O<;Pfnj4m_n$;o1_lO&WRD45bDP z46hOx7_4S6Fo+k-*%fF5lweEpc6VX;4}uH!E}y(nv5c)=wZt`|BqgyV)hf9t6-Y4{ z85kMq8kp-EnS~fwSQ#5w8CYl=07(P+Ks7<2AqWk*`6-!cl|T)K7Pkul|5mYPK%S;0eOFmNkl}x6nXVvMTuj4J3o4D-MYYElFFF14vSj z`SuG-LOu|dnlH@gf#yWi}-?cy|CL=C_MA~fP^7b6Ibe!SynSRo;4VSXP~o|@MD beJi{Hf8i88Cp39{00000NkvXXu0mjfZ<1hw delta 452 zcmdnOI*omTvK<3Ux}&cn1H;CC?mvmF3=9kk$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo z7+xhXFj&oCU=S~uvn$Ysfq}7bqpByP!NTDG^ZozN_5MHG{r?Q7|5I)MPqz3!!T5il z{{L>R|D9_8+m-&e%KvYX`Tzfa8tanJlV>wl)-xt~ySr3bTy4z)TEJf7>Fdh=j9rA) zlC5Fil4hV#g{O;Sh(vgDVnRa7nS>c9Pn?)>$YANklk$e z08^C!QXuGk*YEu00d;tBMRrT5C8*^DjPw5?b)^dd-%=u(@|bCB_B&qRwA y(Jdc!AFG>YscM>yv|9@7U8yq12=KvQNAQ%}S8XF)N86YDg9V{(2F)}n486W@Y z05LK%85gD8y^}Q9~v4T933AYA0HqfA0i?i5)~mUDjhE{GcPYR zFD^AKE;bVtAuusC6&D{E8Xy-JAW}>hR81IMRTo`W6=7NyW09+Ie-acR85$q|>j3}m z0R80vMFSW900001bW%=J06^y0W&i*HlSxEDRCr!>&%+V}Q4|EwJdG!|ZQHi_|G$!{ z$_*BiUYX{FiCp^h=YMVn>q^R}P>#FD+7 oBG$SU5x^LbLUJ@L1!cYKPn*;e4D}F4mH+?%07*qoM6N<$f&@czWB>pF delta 343 zcmaFP{DOIcPQ8$bf{=)!kf@@tsG_L2yqLIxn1q5j5J>`&yo98Jgp`6L2+2!I1CfFh z5XmS=1Cg}66c9#4F6v<{C~&r_bEfLq3=Hi1_s8GAirP+ zhi5m^fSf6wE{-7;w~`OA{rGuA!9iqtgMq$u`vwJT>E#9?4xx9$+M{F6yTx;99Y~RT zbRg}?kGl-tYDLsLSObdA;vAKi(lN zXQa}yFG!R)T6Ctq0ssI2c1c7*RCr!x&PNWzFc84N<*u4*dUo&s|70v776O>ZIZ>9G zs0KUrrDKKwdyA>mXK>I>VCNYWyzKs<1shiH3Rh4M>k1uc1D^_x+%}onp}+{VT};!( zqd)`T1c;DiCIPfs+n67_3!oi|NsB;Ra@^kKi(lN z?HnYsFG!R)S~|X)YXATMc}YY;RCoaez`+WFKo9`Xw==6n0eR6Wq5uE12s#OppsgAJ z0E*}9YI(E{te;Blt+q4h1xZ6&Ku{T82SAX-r4>Lp(z>5w1cWyx?tswKcgil?j1bty zP)a$YO62f65~wN-$+j1O;6uWR^M4&;a6MXSy|y; zVc-A&;uk357z8Nb2^^E_0UJ@@2^`)43f~YH)c^qA0u$jCDA)i3;8)q@P2p-}cF5(?8;R_(%LrUTvFybF9-cD2D6D8dN4B{v> zlLi7jf8r@I;wd%x|Nr{`|NH;{;wv-aEI0r8`Qj}<-6I!X9e*yMIZ|7;6C5YEXCdMyVW#Fk`GQn(y zdC>QP>~zV%wSmcyKC~FXd+I=p3Q!%y=R@(G(QNnvFmnIi-6t~GqX73tW>A{Y$(x=^ zz|IQ9KCGGG`P}ub;2A($3I9d{QH|Hpwpj`6RRB)SsXhiEvWKbH5}2YNylSTbKGORF ze=^`cApj7+TaGB;>arOI*s%CmD0eHszxLtnR(5jhp`Rcg_*Vd`&O z+pPt0JW06XRYB64gPYEH689@BPHqMmqFz84rEw~-x&m(OP28mbii|y~r2v4>Qdg)& zA7$lZo@iJ=UxOk5n`P4iYj2+YxdhfBUStWDf&p;;b7i+uUP@rD|9Ia`vk~B;v&o># z2%NtI;>4O2j@aCqrc0}xYVMeJaN1Mg84IJ!5^K2CmkU?4tzUHf<+cC+Z}SJ=Kb)`f S@;BN50000oGy;xs_wHbUYzLgGG1;y+2^KuqFSS{~wBV&ZUj<26C!JWAt7Qsa4m z<9vYRIY;C=MC3k2laT=}8|8k3<$r?ZgNEghkLHSw=8ll(mY3+0nCPadlkovOfA|0Y z`0Vca|Nr{`|NH;{{Qv*{|NsB_`TzU-|NZ^{|Nl#I1FZl60kBC#K~y-)V+bbz80e}X zBsdTS1c$&CgarEg`2_?eqXC2op(#ZrCB>1}Rw%&Q6RIFM#$J<)jg6C6gbNw)N^3=V zfEBnGO4V%Jv}v39HdL^lx4{*xf55d?sv0CPcs%*T)J(WkrUTykOICcF%b=R zkb)M7g5U%>-QI26Hga~ct^z4wW}Cm#n{C^+4Q#E>US%kNtDF}(T)S0Qg0;IrfLSO%kP3Q`O zvn`a+A`4AH2Breu3?xT}M7zkB>Zu}tioTCbbO^E|gB{hSP=LA%TB?rC%0mXZnbE{2 c`vK0I0Mmhk(*@>M{Qv*}07*qoM6N<$f}Itkt^fc4 diff --git a/recipes/icons/la_cuarta.png b/recipes/icons/la_cuarta.png index 751b9447567d4a9a87cc6dd0b47de243d610eccb..e13b93235480575c931dc403ea039770ccbf8c56 100644 GIT binary patch delta 300 zcmV+{0n`4|0?z`FEPwz1|J@=Y-4PN0?(W@HRoyc)?z_9)A`$MHnVB;)?##^Y-QDhp zh`SL0y8r;)Ga}40BFtuH%pxMpRaM=Hh|DuH-FIg0cX!=pX3QcH%n=d05fR;HRqng0 z?wN?HGc(LYM7u;p-9$w0?(XiYs@->YWubV200001bW%=J0DnN{*k%9#0EkIMK~#8N zRm{~6!!Qg$(HMeI?wINSKXKl&O4)s~k1Q+o{7d%nI`wVSw1;UJR&5YSFz=-ogA4+F zfiVIXFbT528e#=R7$LI^1284{yC@I|qA;B|1Bq+EWgcx=+Fx(XsTxD!L)7W^oi_4j` zw0CFDyt{k%|GRgQtCz1}U|?V@3GxeOsCRgFBMr#w@pN$vskjw-CR*r_0*^yrPsfj( zsn^)Hy6B|*_crO8yYRcV<>bzw*{+-Tr*GqbEHUq`$z3Or6C0{Ms@Wu2ela;S={+!t zXk=c()nU+>D#?}8={IMc=>oQH(>aw_PqZGCKbjl;$F$zTO9O0JTyeUT2fdeHtbRZ&|wUou6{1-oD!M23g4*c}=&MYz2O;X4X6vq!0%N!!g93#pdBg-Epk=;}b%pfMrA}Gru zD9a-$lRW_#f5#6N$PgFpxVrASyYRfe@4dh9z{2pu#PZ3?%qA@K($n-+BS#sv$<3K0GF_x<(^b0d!JMQvg8b*k%9#0MkiCK~#8NJ<8LPe?w6K1<}2)V%yHpwr$(CZT$FD zPGzPaS2gnK(j({iPo8$TNXG3DU4QOW540sz(t09bvcAu5xk>I;HE>TnSDlJCo6(bJ zKdb|uTWqUE{jZ0nh{j49?}5|tyH*Dz(ns~37F?Rk3Kx(A6S>S@Y}Rgdt^a|I-N40= zG$9~BUcHr5cv%WTc93sjbBg!2k%O54oASTQrD9a)!%OfewBr41$E6gS= z%_uI-DKE?_Fw844&MYy`Ei=t7GtV$K&oVg9GC0mNILC1Es{<>>$ZGALKl# z--@G+c`d27$#7kB;4^v`>iTP0FMr(I@sX8f4cSx8MIh3qds{n|J5$$fkceD8b|LC0oYz%Yo8ivo*rwU9&Df)Xq_N!p)hx+LxitwpTQAh zni6E2A#I@?YM)DqvJ_;S6=j?+cc&C(n;UALONp~hinCCSwNj3@9crJW!`P(5*rvqU zt;*cY-s#li?A7D#^!WNOcBd9*oEK)C5Mr7PV3;&|scD?QX@8x+Yo5S0d#W~jsyBS9 zJ%Fu1fv$e5$bYNIfUL=dugi$B%!#ti8)=^*ZlNP?qE(Q#T$H+AmAYP*yJDEUW}3cc zo4y%np0Ui`aG}C6c&Aj4wpEX|G`e5%JrhOs1Xq9$>pCvl@Eaiq1* z-@4M`yVK&o)qmuSw9b^d(U!W>o4(bbz}BF_)}g`IqQcieg04V|iv2%#FltolgMd_kVuL z-Y0p=$VI1UTu`E2&Y39Kw$)I!`3&e%BYMiWcs+W#I!X`dTvo*ZhQ9crH)Yo8u#pB`(V9&Df=Y@i@*pdf9bA#I@{ZlNP?q9bpj zByXZ7aib@3qbG5sDRQJMbEPYEr7U%(Ep?_YcBU_OrZ0Ao4(bbz}BF_)}g`IqQcjs!`P(5*rvqUt;*c7%-yxl z-@4M`yVK&o)#Sz5=Em9Q#@gn`+vm*Q>CoWo)Z^^cwoKL8|ug~NbniwI&kRb7&wNz zrQ2obF-Y*3XAAIY=X+XNc*ipasW3>ec_*@P+9t(1tHwlF=}0q3Fh>S6vwAya2MDL8 zxG({2;YzdTHT9LtjgrnZGdJRA5K_-m@bK|ti1xS1VhJ7ZKx^`Tzg`07*qoM6N<$ Eg0w^&r2qf` diff --git a/recipes/icons/la_tercera.png b/recipes/icons/la_tercera.png index fb70af7790e07070c28c5a072f8a918bf031f498..20b722c379e65e30eac28be82641813dfec4a4bc 100644 GIT binary patch delta 187 zcmV;s07U=e0p9_TEPwz1|8<0vC^$*X001U7NGUl=A~QuoP+&DgRcitQEIUmkG)DCF z_E24HB{oPcJx=@k{3tg`Rbz8uZF;QPZan}109#2!K~#8Nh0Dnf#4rd1(Q3fiGyDI4 z>j+u0$SLpEB|z2+6SjPd#bA9)>j3yqa(BHvktAr^86@UYpES7!rK1FtFZW@Kt%L_e pMBlyWnHNKwCOW(5yFx}``U6xP11%gr=6(PG002ovPDHLkV1i8aO>_VN delta 191 zcmV;w06_oW0pkIXEPp6BNhml;DLG0kJ54P;P9`=;B{oPTG)5vbMKwfKLQr5(U2Iij zb75_Ib%d1k^!EGv{Qv*|%m4su0s?Qhwr&6b0AEQ&K~#9!h09S6gHQ-W(J8jCRokNX zx&O5qj3M~t`5?>$gtx+x&%a`JX;{NNR6iwXgFMWjI|(qN+IIqzS#O! tim%jrKQeI4cU+`44Cm$-nUio8&JHos2lTA(mvI09002ovPDHLkV1nkTR4D)e diff --git a/recipes/icons/la_voce.png b/recipes/icons/la_voce.png index 174502abbf26f1f83e91cf53fb0641281429ac51..bbbef124edf9a38e573dfa581a00be2dcc1c766d 100644 GIT binary patch delta 1048 zcmV+z1n2wV2&@Q@BYy+)Nkl^A5ryIZMP?SsrX)y~;ElcUReV?RSygN*t&~=&TsGC~#RDF-wk+if1~zXWFMs&~RdUy3e#V(Y7v2iI;7@ zTKYp~&koe8!WwRhkeak_i^!6%C6=;H=eAVf;2S8fmVZ`IjTp|RA$mwjgr>3W%mnEl zKYx6>^+?3K&GPrZ{FN&V0)&l#b^YPfPruy#@_P5GMYSE$egFR_6;d5(&aSHxaS0{X z#DxS;>Y`Fh0jFo$M#h}g!|1G}+1FC*!gK8FZf;)QkB8$J5`Y0Ip{`yp0LdVpX?hM% zK?w=5Qhx#CrPNK71VfcI2!BWf2*fi9(wR=qLLmfHp8DeqX>%qK z*9L}AEFq|yvJ@go0L=Fov0}WcDG>!t=OK@vDgcBm3Cblb`thK|6Owej7DSXWX3qiA zKR><)n%br`b76Z|%imtytV0-P4&Y0^8(xBhIF8RVA8z}@DF=kX7&7gV6R15WIHDpj z7k|>o@$2F9dQgVUZum5Joe3%8aQyW&zwYZ20$QjH&ygJq0aSsJzKI{GMg<{pRjMIo zIK3&7Vw55lDXvcoftJ#06maE{knuDE!xYAV5LYUKQ|7~*ElL1Rdq4%m6oQFIRcqG< zg6VPF^QM&7OSKJz%oUzFxx$hR$iy4iCx3we2_;E!C(>c4HbD32_+?%Gw3J~Bj57ha z)Z@E*iT3DbUhfS;sN+5X>b7nG!D5Oy&7KAMUvRt`F{^--EyHo5aOA=6-S0q!?3S1u{Hz}A@SekjNfy?kcuV390+Fb{{RGb z-S-C*TX)m0TZ$J9Hu#9MoGxCPc-kBZra&MB3RF@UV}U>*iIY@plBKn!C6Z6_VRSAS zFVUctDO=1|Bc$e8uI^d(z6vy4m|pI)Y)y2P>)PVYGJoH${jsW72Qt-o!HXiKMY>&9 zQLWxdtnI2?S1p09Z=k(hmxeN8xE2l3Ln zK-h>AVI$y&pZ@yG{V(sX-Y$_opv@ZF(L>5n=0qGBBGfn%m&6l^XA(zEEn5SZXW2%^ zoaA6Jgv<-yJSDrQb%90Go#P@Jp1Og;f6wRF|haqhax<|)va{b-fhAl9z;tS)d!I?47 zbc=2#N5&Y0P{)08kXN|@1gmLcKYJOl6rtcmsKvsW5MLD(Crd<=%HG#pBAR5#>qfS6 zQc`GF)s4Wo(Hc(_(Kr*rTo7Pb6iddKwV$;}ON1fK6Wa(7h$Hbz28=l}7!3ag;<9MU Th1?MH00000NkvXXu0mjfT=n`k diff --git a/recipes/icons/laprensa.png b/recipes/icons/laprensa.png index 64d8d907ed78c8220add710d6ef818b88d68bfb1..60079f4bfd51b95ebbd2a3074e6910643b81316d 100644 GIT binary patch delta 267 zcmV+m0rdXn0^|aaL^&{ex-xvbDRsDXrqRsa?lOD3B5}1ee7iM$yh4S*Lx#abhrvdO z!4_$;l)BrQzTFXKum@nQ31O{~eIb7zZ?r0QxG#CSoWI_u#^R~S;h$UH_))ZA1poj52y{|TQvd-51_u}!sDx$*8vpa2j^{u1qNes*Mwo;sU^ZB6mIvddW@8rvZ!YS2qf?{&}zPM{iAc2kqU* R0dfEU002ovPDHLkV1iA9fHwdD delta 269 zcmV+o0rLLj0_FmcL^%gwtO;SQ5oWL!X|W$~v>h$UH_#HmavH$=82y{|TQvd-51_u}!sDx$*8vp;d0^fD=3(VC{7ej8Ww6l zQ%e`Nwp1+Ja;fWQluc)~)|NUqYt30|Z6ExGIBag0gQ2k?DZVg=13{HgV*{uJk?63M6y+^@X!e{LIaz5* zaW>%nkFh99QGZ?>@`c)N{?&S;J22q4(zMgUWM{c^N~R+k!|%BeS6_WG91&uKh-K)7 z^9o+7SONfWEHS2V9N&7qtF!5rizITVW@pWxMq;Fq2uB;fsk_xXG}?p2__)e-kFS4v zae~7}j60p1N?MvDNDjk1~T=z84_C^CM-NmXxq*)uT2mYo^vW z=-Yd^rhn$=CSM>#W6aVNOVf@xYkqFV$|WVG^YZCfoMct?Mg<=)AY=b zvj6ilwHKNc1w2+2?Q}(wWl53YP(dLAg7U@bOMmC;Z>S~!JqJR_@L*zwdv4)mGmG}F zzGKz(vSJWd^wV{fpoIZL;w9M|41ZnQc*h$Y1;C49D9Uq!r~yDUlU?ydTtK^ zaiF6ZVqm~SmIXd41buuY*w)$e>(!3o$f&nH{@U|b_8zK%{K&`){+K5?|@ejC5EO(4dLSHx!<2g6sh2-+{tO|p(@tDeE=bip0VJgV#R`8Z>%w; zP#XlItOK-l^$mH0=_!e$jONUoa$j~zTUS5ugyR$yROO-qVN|F7fUkIna4a$UAHk-( z&(ql4VYWz1O)gth7Lss)2kT*Kpno94?G2v@lQ99n1e`!dPaQwgAc~`jKmf{@KDh9~ zc>xirrdxrL@oAGYS3YuoYyh1EGB!UoT)+Bzx7jGs>3FwtZRNTrax&c%#v?UG;Ype9 z=gZ3u?c7*eoc}K#=l~W9+h`mW!UvAmgt@5M&gHak+fe@9{w<&F-0;q(r+;2q{n)O} ztE+ZytUj>i!#AIsJ|&A7TP%fZoO9Q>7Oc%Ed*P!awNOs#{~`Whq;%EWa3?|#Ohgfa zsCa$c_I)Rhe)|hxjLpd$>z-Z@Q2YtqXI&rpf{;a%=;cg9cJY0|gVB~I!5rGf{l)xm0$tLPCMK_3aI^y7GDM|4< wy6W);2R!~Ef5?29u&^{n=r zoO2P3b1Z!LAJ`esd4A8^?|J?tTk#@c5|Sj5aVU(8gK3IlD1Xe*6bFZ)Ifk*Zw2ft) zEaSGb88#-3WnC;|2R(!oRTVTPB1sWZqzEe^AHZOY(o>xUIi5vccV2b|U|@u%r(=;= zA{ONpNntSNFy*A_RF2JZ*)!cPhQ^|-hGGdEj%b8t4WJQ3y31Z)U9x4}%9<60`Pmr` zJNP}LEJ-piNPm-|Xm9^ePxoMWeA-6SX&h6KpIKPH9MKrQPec=KEmvc4s|Z+zUSC)K zdh=!g0JCNVnx^2moVF#4G8fdCKfDU#_dgK z>&yA62uaQWD5@5hWJy)$0Bjs{;FayoTN|jyu|6vfPb!z{n0-|>Eh0!UQJw?XwxM?K zjz&xClC1ddj@|CNH#!mG6GE=nouA`bUXq{wkVOzoQx5}#1!1mDvolg(+x?8)#+t2< zOoTrFrhm2dQfDX}r7>n{ilu3n!&X|9yK{5>nz~ZUd@7n2e}7$Q>()%388r~2f&s8NH}lDgVzc{w z10%;<+Q6t{m^Q2%M+OQ5hAhZRFcSOeY{yhEGVfbKlA^qj5G6fDkWG&}#SC!edhfll zNxhv(a9>qP5{|5p2$7Vchyo9Z&?wyNkN^3%Z_~OeBQx5>RQSei|D~&4P&`pGx@eI% z!+)F~$jP-XKX{Evq+2JDqT45d=Pnj`Y$zZ=%PTMyWQFML8C?I=ia!5H+qK^I8-2rL zLC7SG6D7%mqWmng`|xky;3#~r!lUV30eSVL*$=(7J0~-ZMueop>BNF3#c2Z{V1CqA z7Qq31m{cEsd1@jUp}Ud`UrWrX|-(3UlU~dpxLu zm!gDbhNpR;f?0UlP$2AfdRRLVwDj zuiJ0>21h27L(@PfvNO|)3%q6vIwg|;!{b3{ScazN4dL45MZcUu6sclf@h5cep)2;i zcLX8bz2pPGnzuE4_|6_v3H3n$WzdV2y$o6^~H#A0N92P)(Ze43ehpC!DZ%sp(v;+VZ z2m&QJb>dt*tnzspkboV}u3f*jE-WFar2AQ*0}E6>ex z&+j&lU0q#?uXX$T2S$UTNM5dfO{yvbN_*g<^xyqT39`y@goM!P3!PO< zFTMPHlX>vLZL6rjTV&OX|0_@f4N-(Bslia<;Ng?UPX8`S@?)8RT;CfGD2g@7_3CNK zVL=js>F~Rs9y@%zH57?0(0{rhNXJ{QG{5~>SO0x$N<)#M1{9+Y1Bgdd#Tz}o>$m*r zDK4NW++`nS8d%!x8~W($Qy+YF(myl-rEIFN01nO;`McAXfx`OtqDXK^V+;%kB@vZc zE_Pn*=v}k2bW3A(<+1|UNN`akjaXxG9(GpGo#9{GZo;}Ao(Lre)LW0|J62bfCyD*ylh diff --git a/recipes/icons/le_journal.png b/recipes/icons/le_journal.png index a8c49ed9400e76ae2d4c4d8997ac3552cd4dfdfc..18021722f7da76793e71e492fc369c93cf2cd5ee 100644 GIT binary patch delta 10 RcmeyvbCqX;@l9101s*j{{R30 diff --git a/recipes/icons/le_monde_diplomatique_fr.png b/recipes/icons/le_monde_diplomatique_fr.png index c63a5b51a3e5a38c89475144a41d627b1386cf78..27447ee2e84ea81144da57b6ee3cebd3dc99a099 100644 GIT binary patch delta 220 zcmV<203-i`0)PULEPt5*0H6^O$ai+nhlkv$sk>EFv_wPk+}rBQ%B?dq_wDS~n3$#_ zBICNc!DeO5%*^WQ>GbsTze)>#0001jNklg~JZb@tb zd9Q#Vhvyz^fW!*mBo!^t2Ecf9;A#QTgG0u^(2YQJlmI@t9^}Mg1VlHi(Rgwt7+}rBQ%FN8n5t#r04dHeE0001lNkl=}B8HK8v;huSM7)dOTUivK<59`6OPqfERKnE6CXf2v;4TMGCOUBao&7mw2co zwt{l60xfssXGJ@6fXix|Mw17HNV%zvhBWGyoT1H;?9kAi@dP=HT}E08ueHcm@Rn>lmlnKNhp|Np}HDWEKhW2nm^%Byu$vxfB?Lh%V$&b>nb8BB>#C;=q9= j89Es!-f&Dh#LBRF5$D99yLUDKjbiY0^>bP0l+XkKG2t)r literal 333 zcmeAS@N?(olHy`uVBq!ia0vp^0wBx+BpCi@`0fExEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8ZEE?e4?NMQuI$g)#n{ETjAouu017#Hx;Tb#Tu)9oz;edG?U~GG zPD?h0V}B;s7u7YjyiuQ@_?sd1#)E_>>dumxX9U{LdM-GT&c?Rw$;@MuE%}WfFfvF! zk=Ypk=o-jrswJ)wB`Jv|saDBFsX&Us$iT=**T7uY$SB0X+{)0%%D_U~z`)ADU_x4SxZQq=c1d;>%YvBE`22tCB&FaWGP!*VYO1gs;$+7ga7 zaI(cVXZUSLbRgm*kZ}aL@uRH4DqOlOXa5&I#RLDfaek2`0G7HBdah#2mD4d9fLX1t; zXf8rF4>>%LOd&|bg=C2M5T`(ripwWaoQ9HgxNL;09jdcYdmeHDZVR#50TgrixMPTap90qJ;MM3n4v)&x^7^k6Mnw2``EXUt-3m^YX%9-6<_rSIw6(hh+w( zZToPhS?jf0`{#RaacDDJM zy;EZquxO&qODWp)(Q&&S1yd%wz0CSal%!FzI!RBa&lPm67`6Fz$&^M;^hzuDkExvx zj8rc>4|4O%Co|YW(F>J*89!@j>}EoXRmHNhMlica*T11h`nhgq;kx78cQM;iC!kNb zEuMQTBH)&{SRz&p+uZ)Y0+YcGZ4OQ>{0BNT B@1g(z delta 672 zcmWlXYcLZ40LTBsJf`GzV{&R)PH1&`PqNrBA+N4s$|~kfUMsrRQ63S^l}C<4UR@*9 z;*d9eh`OWWbcwr~t4>2lXhr()`}X_vyI($Wf_UFl1dSxm;1qz`_aDMS<5m;^N-CS1 z8kiWFD&!|eKqwT_qZ49NF7uNj=!waKO8H46fWY$e4r1@h1On8EP*(*>9hw^0OMJY5WVP^rxQ8>}zd<IXflQopMKBv7?g;e&&jVqe;u?x5E(l5IksOS49)vtx3qxi&ZeBumByysV zAA`HGD2PL0Jj7Q~mWJ|lRArz#6OTn`xP|5{NOI7ci)VT0EJ9Z?dc^20LH`2`Jj6&j zMl0~H7E|?j-+-AW%r@i06MTG%ISD>Vupny1mv+cH@bv}0NwL(4A6;1P#(E!q^P9^w7?=1hdRN@> zDru*!$v5Uhd&$z35XYvT?RdH4MjfM;KQLTsyZOB*AVw+@(M)BFlO?G-HzcZx@jXgw zrRkMP)kj=3i|*|+YYsFC$EyS3V6EllH_E5y3Zi!B2Mf@Kf# zf`Z{c9X5Z^QFi-f)lVN=?xcXIZx+9JfsuQLqr;pDlh>6kce9~;O#qLbtuzgvruHjk#0EtBUE&u=k diff --git a/recipes/icons/le_temps.png b/recipes/icons/le_temps.png index 523130c91aa39e37e166b20986838fefb942957b..819ce67ea58a8fb9185d40c5a061fb8b1d2eb720 100644 GIT binary patch delta 305 zcmV-10nYw_1N8!sFIV2Ty_FX%{{H^||NqE{lEi?Ev|DP+j+eGzZs5DY!+eOteTbba zJ)SK;r$JJuLQ<3$EVN#1wO(zN7%aedgOwO9l^QOV8ZX6yjmU?Q$cK`a8!wj~GLeQM zf0!LI-nqZuy1$qpGxzTB`0(+WAvON~|Cu5+N3#J(0001&Nkla8;X{K!z+Wm0phrZ@QJRD#g-JSG?1?Nc?m}3`Q!0nw;;95mi04o;-PX@pmF!&C@?StO|-Zq$nI%trI8t6M{koD8x z4_bEt(MN#2wGnVSVOAOgI`Qh)Oail-7N!R@vP00000NkvXXu0mjf Du$_<} delta 316 zcmV-C0mJ_F0)PXMFISWoER`25l^86Q7%i0=E|wZEmK!gZ9Wt05GMFGUnIScqA~l^W zJ)SK;r$JJuLQ=F_YP4Q#wO(zuU~a&7gTs7?!+nUvfQ!X~jmU?Q$cK{1h?2{Wmyw1c zf8Mvf-nqZuy1(GN!T0X)`0(-m{{H^{|NsC0bFva$0001@Nkl-2@ zMz>tO=9#_cn!o3q$LY>Zx@AtfW`E9&b;*T1v`|gDWVhqLKYz7TP(rud00007bW%=J z0APE5$>it4a1W0F005jxL_t(|UQNgcu7fZTMbSx2{M=11>AmNd{ckQ{5aLSnG&)4H z$c{xbBDQIkerVZj81egS(w&plQ_z;I|9y7#>pM_TMWOI$PX)-3)pZ6O+b2MZ&*hS$ z!U<`B1WBGJcurS962Reay>@VWzy*+Hn=Ix3wv+*W&RzX>piL|eaV(4x!e~p2>u%C@ nLG#s3`yqW>&50PdW1P_!;&v4${wE^D00000NkvXXu0mjf{X(v? delta 342 zcmV-c0jd6q1B(NYK7Tp0OFFYnI9Nw{B2xnfPa zWKFtdPP%4e!*gZCb#Ss+amavk$%Aysg>}h=cglx&%ZY!^jfc^ci^6b^#dDzAs-)Ym zuHUz=<%_Z9jFx8uLL;g-GTnZ4(lzvrCC>CVsW)bslF|9}7gkuU(=00007bW%=J z0APE5$>it4a1W0F005myL_t&-83nYE z6W*>fp$+!{cD-VF+GGM&)9F2i@e2SCLriA?I8ULnK{Y7B^}PHT=l(Iy3qh$;hUrfc zq$;Uynj|P$%1{1oR0L(aadiHCR0KWOYlrN=`)wyUy})bP?c56O(ppO`tz;9+Tm|UF o68-diDL}hA_QZ&l761SM diff --git a/recipes/icons/les_echos.png b/recipes/icons/les_echos.png index f5ee26289a5e2d701e263b889a5211987ff50d8e..fababc58bb76d566eafd14de358c6c2a984f9533 100644 GIT binary patch delta 11 ScmeAd+`%pNU9Jh^n9S6syX_u^Q z@G5Lb49q+Y122HnCIwwlPxhyC&Ud~ae|zxx;g@F7c&skx>P9TomPpnZMj~Wj!pV|N z8HsiuezHNMaqn3?o2z;722;UJQew=Ur;YLqF7lMnHB%sD%Q*3jU*CTiAdWoaT_-?6 zI%cc*^pFzH_DsG{6C))}C>6H4m=A zSV#uCCTdZHx}fKp5eW$m!*l}^Y4XR`4d@@?#see@^(=X3hDU}u& z!tHizZXHv|CA3`Eg~*1s-O`X&wo4SrTS?ZeC481q8K){12`D8=C^pJ7wAW)1JU!}a z*(9qvrF#Z*l0wTwWl=RS2sE_N9?oGoKfyCKWtHP|=xnx28Jx0AY$VmgTQtjDDIQBk zl#nwaZtF%~uY?lWN~FLpY_koX2ZY8+8G3Qx1Oc8TnIZ{g6W=qmiW$cd9=hHB*y*`r z%kEp&<)}Y$(6Hw?wr{%~w==4_zMvb<5><1fzue(nZgC=)JHb<(+GjHU$f7GLI5^ae zEcLh&+6*oF>rmwm0|6d8zZeTgzf973>^cI!ZYJnBekJA@miQapu7yS1V_37)= HNB92#Kami~ diff --git a/recipes/icons/lescienze.png b/recipes/icons/lescienze.png index fba67f3a49b6f3f08b56c01bf75da4b4b03f04a0..c1d4ff066afd23ead12b918081902d89a8d95943 100644 GIT binary patch delta 20 ccmbO)beea9^2W*Y**F@cg z)(pVz{n0=C7yul}8gpgX**o_g50YJLSO6Or0XA;~{Kl)zeSi)D_^|@O9s`W0PhKxx z#=?oTwwz(E^`rWJbs9G#t*dhtILOztx7!bQC!de>E{NHi!r{VYcH6}S8lk34s7&=H zDY1gaH1iT6j1d3b{xXUT8luDrz4iY$oc_A-4I8StLzZ}=%iAv0BksEv_d|PCGL%wJ znUYNjJ$0EBWvWSw)Q|!pQl_fiZdVC&90PGBY5QO9{z64KlS?*(PSj$olGvTdjRj*1 zh-N^y58kN%Fnv?T0yp#n0o2sSpnV6KanTzH{e^CXjtFw*)62=@$ebPWvckv}>!}O< zvD7s7ue@5C?+XodtR_-Gi+V{fQmUKxKJHZ!85`2M7H+se%H>Bd(&?XATNgxqPMuiZQZaPP7Tf-1v?ZRaCGbQ P1Uf){cD}MxUfDVX0;;-1 diff --git a/recipes/icons/lesoir_be.png b/recipes/icons/lesoir_be.png index f8422850bbff1790446ba85f14bda20548d90060..9a0aaa786ad4be9e5636d93f4282c825a005418c 100644 GIT binary patch delta 1991 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 LNkvXXu0mjf(B@w9 delta 300 zcmV+{0n`5D0=)x}92x;LqqV~4hn_$hsi1F>wCWf^^RW>xc%fA^s`HhqfH9*z% y{quuia`}z`u6T>sUK8zW`{%0$|2#1q^9O;$Lex-)}iZMdOM-&lLwL)TP4TO*Ynz1~v!bs*oN~CaWx?)47k}1w+zRFs5$-315Xc>z z)UkTrxNQ|v3Two#$D3c+^0jxnzq>yV4-lEDo|~1gOkO7{Hr9I2HTx$c0VpZVI-_?6 z`tm_Y2yS-u=jnzd5G5u80fJ5h$i(QguFmC8Pz6AMPKf*n2%9EI;;!(xH6Eb|xNTuW zI%5}#r@IhCV}FixmYAP=+%6Ef1(v7@w|eoJnWIoRBn?iVV3EClG@-jUWIInua$|9M zF>*7KY+gu-eN*=rdV^u+g-yczsUuJ_BHKP^0s`%UKK#&d5tg>j-a(MkR4;}weL_BF zOe|0VX)JW?+;to}aT$hH9Y}UyL8^O@#L9|s7^Z?&;D1at4XCa;!#n|rzJUm)+;=^dxLrJ_T869_7dKNpR}|=X@Qw+TN{v zo6uc|_#tQ@i8zab%Lux+Nog5j&%v`Ch=rnpOnkhy3~9+O<_JOP4;hxlY3afu#E9}e zDCV93<{%bv{FCReYA2k)(#iN=eq@HVxwUvK~hDDnX<%=26}B(sZoZ`a7cg#@6Gj4oQx_$8REH5m5XZ6?OnjgC>Bp zzJD8d^YeOqzhw!M2e~n4(!+RXRVkX=0?Z+b(IbLJaGq{};xlw|_~0dZG-w(-i{!=} z4x--c!_P;XB6~sL`8gBt#H1oiJkFN}G7)RUS8|?-D2Er8OOx2Uc`=6b)?_|U3t&Ph zbGv}b-3@4H^ua=Ek_WRM9T5rAaOi0OA%Di=TOcJsQI2UEs;osQ$W6yU$l#1!E`el^ z4%iixxY>0ZRo@7+$X+srVL;aa-6d>(c1ALbhK(>kA6uAU9YyBcjE-A3j%^U~nqV=jD7`Arf>@zYYJ-uOU*ne4zt8IQ%KD7>->62qBcg#?YMfHc|ZS2OIz%JU9TIBBwSnAn5i?CIVzaw?`ro;BnOw z*8*u(ATJ}e<3>jyA%`$$aLT`8|6%Wo8>@fXuI73O9w0K(k^@z*E?qBrdN+e9wZ~2` zt=`|Tt~1b+rwMVN%T5@QmEO5_+3X!l9)Ik>f92>(>cI@ur2qf`07*qoM6N<$g6h?P A-2eap delta 1377 zcmV-n1)loR3iJw)B!4?eL_t(o!^M_aY?M_L$A9;J-;C3CT4-q}Vr>PhAlMp3L{pcx zhz2zZL?Rju8htYQgwX^OB|f+$#y!yojccMYDk71ff=Wyi2*JqW0ywf2aG=!EmUgBy z_uk{fH=V7uk%z*WoR|5|cF+H8YABT{|885??QgI9?Xlx0lYdi1goz}j6tz{6;}6|B z`;~?B8ds||o%`;2`u(l1_VlM>6Y9ByCRc<{th)cI72e6Z8{gfZ=$~;WodcxOw*26+ zf$4sC@6c2xHtYXMC;Cz`-#B5SasMw;2)+oovy=fR;3L3U$^Zhsko^f{tVP_|vf+?M zDV5h1A!A%|?0*WvzDI!=8RKvzDOL)?ArA>4u!ysSe2*p9UqoYFloFeWzH(^b|v_H^~ox#KYNuWTeXxg3Dy&#vdI z&NzagvPvKrW7)PRLFcX`bX?Iub!7yl6qhyC(sit#UHiK6J;lDGeSG@EZsyF0;c3M! zPkh0jhktwVJ&2b116o87ae%|qAR4UAgCGdL4~Rnw8AmvzIFLBWfka;p=bFk0EAO30 zbu^5%4o?dqU$HZOoP&vethM+aSv!J89HY3X2FCbaz|I9l_RiNkIFRTAps9W`FFxEx zw9Er>C?%xQhE&>&($`8Hqqv|9l}KP@JVw~^>wjV1UAsM}xA5xeEbExba5{r>7BvE4 ziLMs06f(G?E)X$LB2y$U762=jmp|LW`Ymys6QX5do_X+k=FW^^Z3b{b{fH^j*Fu2j z2s_CN7WDfF=3{bBUI54QA9b?t@7`RiuDq|6x~d5Ic~h*faWZZVkX0HBdvPNH1pc*x z9e?q^dE(6tB!@EqESP&CPd#`8#@HYMQs_^z87Qc3N><}YhZM><=ZJ(gS_j60w*}v{ zf(={ae7@=T>|9XXy`Y(!<~C)AQL!z}2##3}a4ihN<*^ova~4lK-g|N}4Ruk3KwjYh zi^3A}VAb0j*|;qZKt&`>`Oi}8Zst>F@{STYgxIx zjpT5KR5Hc6)#b#hqqMd(QW5d7Ht2OA3PDE}b6Z*T0Uj=!RJ=YHOjXzJ~LrR8kQM@z$4{ z`FZydl!9|Ynoldk-XdA$~MJh_oW?dw2>4 zlPSL3w1d?@LQmfy6_GGMZrjWETYvWe5b`zAh!0{wf( j?uVDoe{D&}oX`FRJ3`is?e3|lsp5_ro z^qsQzv&7~MLixqg`qbU~-{$=3?)~lY=pa(;MQ`aTS?yDL^`yA=tiI|rVeVdn>pW-i zc$WO)=<|%G=Ne7@^!Dyqe)-AO^`W-t9#HRQi0nsj`{C#9P=9y$zRdE3pz1MS^N*z+OT2( zL_~XdvGqYwezNikWVZ=f5$3KZRvb`~^ULzh0a!a09gx!mLx)t*7iDKEr#$P6`{z|B_e3dkOg|LvZZq$~+8l2=J{R zM>vHG-2yJXY569>_>3yO?B;1ckNSp?pc2BUV`srhVN#G?`ezh zagp(OmhpU<@`9f7grM_`rt^=e^OCCcowD_zw)LdA_N>15vwy_+yUO^!%=yLA`N`G# z)ZP2v=KJC2{Nm{R>F)jQ@%{An{`UC)|Nl>indATf00DGTPE!Ct=GbNc00CD?L_t&- z8EwIdVwyk{K+$)>NR?2RWRzM6N(n?T@Bcrq!!|?OoFfbE^?cavj%nG*cdjwBU|-3< z$_Otyrd1^)4b?2CG9>)|oD3z)|E*FDcc!1s}Tb2BAm+gf|_eY#zE`pb`T? zt)mNUI`ulpWe2%VDqwwAFXALIp*k_}bXRA^i#(I4(|<-ltg{-7?0HtN-2k;tc5s$6 z;X}Pg3|AdpfH%2;(S^?~SM7C2tXeuC(}jC*SJmbmVy#8wCy?qwas7Ra7#R^Mv5a~r zybrNrtdlwioLA}9^xk#y2$`)q8Dv;SCkE$_OboEo(FG?L$H0sBngpkig*>>awnc)o zuH=ihj4Pk;g0uXmF>5B9ODErIZdv*(D_dFk3#V>NX!;dqo&W#<07*qoM6N<$f?*dG ASO5S3 diff --git a/recipes/icons/liganet_ua.png b/recipes/icons/liganet_ua.png index cd15080f02e1ed74b50f4ded85ab7cf0361ca9e6..5daf0130e66e032d4d0198fddb6e7b9cd160d06b 100644 GIT binary patch delta 530 zcmV+t0`2|F1giv)B!7)iOjJbx0089xIsgCv^OCCgyUOr!k?&-N>`is?e3|lsp5_ro z^qsQzv&7~MLixqg`qbU~-{$=3?)~lY=pa(;MQ`aTS?yDL^`yA=tiI|rVeVdn>pW-i zc$WO)=<|%G=Ne7@^!Dyqe)-AO^`W-t9#HRQi0nsj`{C#9P=9y$zRdE3pz1MS^N*z+OT2( zL_~XdvGqYwezNikWVZ=f5$3KZRvb`~^ULzh0a!a09gx!mLx)t*7iDKEr#$P6`{z|B_e3dkOg|LvZZq$~+8l2=J{R zM>vHG-2yJXY569>_>3yO?B;1ckNSp?pc2BUV`srhVN#G?`ezh zagp(OmhpU<@`9f7grM_`rt^=e^OCCcowD_zw)LdA_N>15vwy_+yUO^!%=yLA`N`G# z)ZP2v=KJC2{Nm{R>F)jQ@%{An{`UC)|Nl>indATf00DGTPE!Ct=GbNc00CD?L_t&- z8EwIdVwyk{K+$)>NR?2RWRzM6N(n?T@Bcrq!!|?OoFfbE^?cavj%nG*cdjwBU|-3< z$_Otyrd1^)4b?2CG9>)|oD3z)|E*FDcc!1s}Tb2BAm+gf|_eY#zE`pb`T? zt)mNUI`ulpWe2%VDqwwAFXALIp*k_}bXRA^i#(I4(|<-ltg{-7?0HtN-2k;tc5s$6 z;X}Pg3|AdpfH%2;(S^?~SM7C2tXeuC(}jC*SJmbmVy#8wCy?qwas7Ra7#R^Mv5a~r zybrNrtdlwioLA}9^xk#y2$`)q8Dv;SCkE$_OboEo(FG?L$H0sBngpkig*>>awnc)o zuH=ihj4Pk;g0uXmF>5B9ODErIZdv*(D_dFk3#V>NX!;dqo&W#<07*qoM6N<$f?*dG ASO5S3 diff --git a/recipes/icons/lightspeed_magazine.png b/recipes/icons/lightspeed_magazine.png index 8d695519b488e50919500a484055e142b6aaec85..9c6b9c9cdc71c6706276dd5886341fe2ae571dca 100644 GIT binary patch delta 2040 zcmV|(-`?w6d+oi?l~q$l0ph3x0D*{rLVp;65g|!cL=b=?0ucfP zib|(S0s;XHU_IlB2mld~0IV6*0+6E?l!V|yULQyZENB41FywzA;J^Yk?{vs2goP*K3%udm*i6=)bJ_-z^L>QmfGrj#tDE5=A>x&Ui4e?FvrapmJL%ZzStQH+ zblQ)_@|jF=pnpG^$<_#JtSJ+*pfSdtDY~OU;)Yvpx&E4~eBTE^)z1$feeUU}(;a7~ z85$8I1|lT0s^^^^FX~Z0HnqO)7fY|7ckzsJ4}o9^=5$|XSJNlk-+1H9$rE#}j5iV! z$ikSlbLPFcY2( zyJ$lDA+|Pa|9QBA#1b;LN@rgFnM-Z@^IuNwcyFO6`xR+rG6R*!hC%J`IoXFaiVziwAd3OT_M8 zdB;P|&8PZ$Y~-r}e9AC^cti-2pcpAQ_BnR@!L2uZcU=2_iv6d>*Qm8pqa~hVUl$8Q zAb^59+}N=6@{1QNY^Lqe`5avQ&2^EYKE)Xc8#yIDa>Gb$f3Fdpmk0g4t z7H-+s_R_0cHm`f|;>wF$`w;+uGXrO;Cr&R|-vZIxA)uH@^$3FS9Y8ohj7WHuK%;lD zpnt2oXaD~FXU}J5rIHdvegnBg%!eP86mX=;VhD-21kvL`1_v@dgZZj-nz_zUp0@HS#Bp^4iX}+yt;Mk0aM6F1d+*Mi6rzOav9NYXvCa00DDEk7B<=MP0+~%f6FLBmj^+*V(h_nfGqH`TMh~%W>{qY7qd~o67FJ zdP)7Z*ItVhNH_;k=_3VspkJv50J6kc)y(KIkFB};>S-sW&>hBQ%NDKO^!5Ym{u6uC?{VV74gfBvr#O z6pk;hzj4{JrPHTO5PU$Nk=wCErlVNM=SObS5`miB-XC5$|E7jZK!21#FTJm|s^FZY zs!%)v5+Jhs>ZeSd_r%ZQ`Mv1+y!4Sks=DP=5BvK@t_o#zU&q1SW@t}0v?@#fJ6#yUw;tPncgB28U?AWo#pL+g@ z4J)ROj$>#iGy^Ci2uwLs*MHuA<>se0r#sKpSYu-%=exR3p0l$itpM}*8kqFygL#+9 z?Af~Y-h6KLs$1&jElzs}#hnoCql}_IxxaAf_T2-2{KMnjAAdK^w`Pn$zL)Tih&O%l zl1nd}Eh1k-psJrX?d|xasS7m~RM4YrCk7yn1R%x`rH(57m4yY%$!bqh#taf2svXKcW1Z)TF;wR}SD{*M zrP7!(gBak6$Z>;Zu{@B2J$L#nEZARYvi zBuE7m2t}QUDnJl0fc1<=5&#KR0M<-M2`H$PL=^;KNFPWM7HR;ZvOh^G;GiXyqLWFd zld1)U00EIPOj&rOPk80i*j(P7aMBA?@O?zV0UMTPi<|EuMS7LQNQD+v@=gYwJK}WE zvq)F@*{q*P6n}HMaC|JC%Qq2C)>M%eYK*bRLU%AoU3vZWm#@Cq_k92=^6CDAPe1;6 zcHmgEQ9~LUkgA#&IpyR~C9r4ll8s zZkQmrB4O=$OP|}gq3->cke>hm&$<=eBLQbNyX~ z!k+g!T7L|UKqJiQD5?B_{xfT8Z$>#RbW_Bj5;0igR8Fm`n3K}Wb7z?p|1>JYHPfi zvrHz*Ao4A_{T=PIs?J`%qB|5*rl8VS1V8}#3V)-O3{;_H2uzY*b#2Rn*?N30tVD~w znT^FYH}|c)(Oi5TGpXn!!m-TIfn^sg8wSb&3@XP*pzuzUAEYS67zd=Y+38bKS{$w{ z_7<+#^l|Ua_dd8k)9lrs74Kl389&xIb4JPfHmW2N2$V*mf;uBWGzTagKtmePR|FEz zC4YjGL&Ljv?K*xcw=k1t5aSILQVAb^V9OaOebs2rD2+xHOoE8b11H9F!xP1ZY*q_{ zQF_w<(Kl9q`<<<=`w#W5T6Kk;eLhq{y6VQB&AUyhSP205O2({-Fo+O{l*JNB!{Yep zz9R$YH8ks3cQh?Sdl%GoJaEtY=U;l`o`0vhl8GSFtdUb6cD(l5ELSQAh-k_<1NCBU zgQ^NhwLpeQPENf3cE=UBtWET9*Ze2|peWt{Uf4Kt`**JR^WWcj@~LO?CkLl{rkZFb z8hm32WehSZEMkm-5+I}WkcG}7w{F?;ZjUnyzRIK@bH*!4|9jI9J^j<`mTtK3*MDD{ zKR-}uHkj*i8fg%VtJ0UUm46Xfh8PA&lQTUg297`c`^P`gC1(DmCh5~x`a_7E8t6R3qn(|E} zt;u9f>?jNedGN2Rr|rG}zPs(B#ebiNIx1>ZRim>8TDEd|Xq<u4d|Q@4oet=EE!vMSl6J3-8(Zue*Np$4BqKZC=eG zjPHwgDE2naJdfEbdj`mWRDS}fl1Bw46$=7z&`AXni5YbXo_rOB{^(>WKiN2|{@LfA z@7(>Zc`Z#W>_e>o6Pc7}gTTg@Iu*Xg0jQqks#O;OEaMCSg<_$n=kP>6WHw!HHHgxo zm2KZ#yLQdIIWvfN)h}NniBxVNEEUg?3Z4!irm*wcivUq9ncN=WzXt8Jvu6DA z#aH)UFt;T!xm`660LXFpZ)vkhC4?77;L*$Gf|jjYUzt)Ym@>$U;EWs1ceKU-G1#K2 z+q`+pC%p}*ZD(N0i+@Y-Ch3>r*TACo(rvrBg0XDyOJFd#tqRjcwzP{o$dZ9e z?6Kar2^Fr(1jC~JigD0IvSf^+!l+6s3k$7QZSy4>0NB|yb>vIgN4C}$7h=2rWs0yWObY(K`9W9U`l7IoGsMZ+Mq$ofgLJgHF0g?8- zjAxRD7Kl0`oAOMObgJTcN~#kAFh&hdxAahw9%)T{6$PpZ)wHM(pivW-za@OaqS8j= YKlI-DIx6LIE&u=k07*qoM6N<$f|DZn;{X5v diff --git a/recipes/icons/livemint.png b/recipes/icons/livemint.png index 3d4457826c0a232736f920ef338a205e28dfd7b0..afeddc9ad7e7749ed7a67a4a9a760d538b8bb8f3 100644 GIT binary patch delta 266 zcmV+l0rmdZ0@VVLK`!>KK>Xyt`N@FwrZDuG5dQe@|NsB>pdkM7-}t*=`p=L3>(BSJ zQ2X7l7L$yGk#81%yGcYrRCr#^lSvMPFbqW@lQS~4z5i8<{3%TYk-O~MdWq#W+}H}5E|^-dbb}S%SB9Yu8p23j@TaaXIPRY+lFx`4hC&B^Vqz?N zg#29gSkk8xI)#FGAp#dch4h$iK#ilv;~N%B-oApVg-8KWUi>qG+nn(vtP6Ls1{SuN z+r&YE(8T142uIx)EG{d1kecwBE6CR^56qy2krRei8vFiWYiad=7 Q$^ZZW07*qoM6N<$f>@P>-~a#s delta 268 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 gN6%P45bDP z46hOx7_4S6Fo+k-*%fHRz`(dIz$e7@p%TkOMdpVJj1S}(9x5hQPv{o6k;oAtiE{B=&m6D#>g z>RhjqeZQ<*{Nu#IFRSK%oZk7qv+_f4-KY7JzU|uh_wDQdK=9}H&#UKOgxNooXMAd} z`g!^6Uw3bQnAGyrPUU%^)t9x4{(bxMVN&yRZ{r_lkNvuF`DL{0+uEGJpFVtAIOUm( z_Rk9^|GaqmBGT!7SM{gGywe`*3VhkP?1_cU+sgEb??n?HY4UvCvHsVst53|O|Gatm zZQs_{c~SrW{`sF5qPXG^hk^Msg2^#i>Lqn`2MCi;ca>9QzwndDU4dozn?sqJdx3({;{#R z%>K?uU>GwddAqx)Y+?I36UgB#@Q5sCVBi)8VMc~ob0mO*>?NMQuI$ekSw$p`{Q4_8 zfqKF{T^vI!PA4ZwFdR71;XbKzX6w}6xy_A}yQRBz_|^60_ctt>y*~2tXDc2pX#M%McuO6#UnLG%4bd0FE*_KzpeB;v+JE3%^&+ps`E{oWhyLT)D&wTmdVs_wk^wHzuS6^$G z8A$$I`NR7Wr^C7ihp7t^Y`A7PhlDtN)Zz#Z52$utrI~(QO{bRohw?-HT6`3R+^fa81J)JQlhnA8j@31@~-k;?Vs;) zF()T0Z)L_&pKU&Ca@*d@9-13_*LG3i%)RwD4mmq3?^qvSe#phsjaO_=;<2-b4tYFZ z!^Pl{Yc{3XKW95I;8japBT7;dOH!?pi&B9UgOP!ek*!u%ix!Oigv0jgp!G&3;-LSqA?r!ON{0+sM1nG>8@mC9h?H2AjWtqwOdBysOP}ff}OtefiOGz^`votVDHb_mhFikW| lHA_h`H8M9&GB(o#>d*r^aOUce%Rn0#JYD@<);T3K0RXC27ghiO diff --git a/recipes/icons/ludwig_mises.png b/recipes/icons/ludwig_mises.png index fc3d77af188b50916e0a344f5bf5db2ef3c0191c..d342fd390a7d60bd88b83ecc4ffd5d3cf53dc514 100644 GIT binary patch delta 2254 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%0Dq2)JeO39_1iE^XL^yk zRhjg49uvx8Fn~Z5h1QN~JC$)=D)XEVsnMHT&gT#N*Zw^}O6HW7u6K^Rf6j+1TC|BO zt2`BQCxL@eDz@9{`ij9FtFecgo|KY+fDobq${>Z7g1bHm8ZI>&+id}ynQ4W!#a1$C z-8t(}qb0}b zc@|pZb5?mA+#+T{3{&e2fPp(!7_RcjO-?zT~#x_{XiKUI3=`hQOXz^HJ!O*tC^ zqo*bB$T9(jJOQB6J|zU8e0<&KHlatEu+ILoNTzn5V zPvPDbSgsGH2=YAJaW$R-q8lXk!A_SI$JOmKlJx4Fh4@-egdbyHz^B`~S zv#OIK-roM?uUXhJ>vQO@ZC!j9fC(5x5s~I!_WM6T*2AN45MtCh^E;$N0M4FoIFpNn yaV?5b?_qf3%-V_H05D6zj$;HM8$Rm>{10Cgq7nW*!Bav20000x7Db93sHlzbPM#l`;n`>X%}<$iwVfr00T zhv5-An2?@>%3)VC=?4O_VzP{T@Ns)0Cf8SkQ{`~yB007`*WbUe}wz6DW00001 zbW%=Jf!)9 delta 268 zcmV+n0rUQ?0zbPEpP%lks`9?R^u@*Z+1dEo+W6hw`QP99;^O`G_Wtp>{QUp_|6la8^Z)<=0d!JMQ-VWKI%)s_0C!15K~xx5Wq;2}7J@Jk0MRrR5Dilh zi89*vzoZ-bJ<(eJT{$PGoRf^NGI0VvJol-}0T3tnho*kiR2T%k0_D~iyb=JzY_RtL zgxz2o0k|&)m6pI|?F{m?4dK3OYe3P=@1<)s`U8%l#xwY+^$DQ9A^&r~`ybS42HunP SrCR_1002ovP6b4+LSTZT60Fc7#@rc;baO3i-{0MWI)d-ymUxi7bN55gtYV# ziK#(oOha!>24RT6y@o1B>N6cujeND%K8cw4rW{UxRBRXx_!{>>v4Wf4?L7@?BariM P00000NkvXXu0mjflTSnC delta 173 zcmV;e08;;$0;B?ve}9lkL_t(I%gvL)4#O}ALrFqX+GgPY|JofUwK-7Gq#ef9PnL~< z=*QD$5A|X|jQ{~~M1V4e1{QrXhHOvy6lO1&8jL;Q#69K#a{=H6uv_7%Q6rQ)cjuZdHY1F2Lwkc5sg@iWcUkHf< zmr78z2yyF$pBD85skrlV;Rv+1Rzl^1xb;#sJp+d{fr3dDdA<>dhK9BAyBTrKX5;rTB&m(TLf&R zKYn5`sU2bWtKO$mCEQl_;+02wK7OWuAYpHrx^3_aLS}6t>I)cqZ#eVP-Z&HDKjC}3 zEJ|NT4=`1W6o01Sel+P^7~OTJv|RN%atQJE;q-|^$&dqlWFP`6L`09$8$+qn`Pz50 z6^dzxK9r7qINBGtqXGySREydiPdG3(+IKJ&V{K}%0X`VXjP8!ND^IT=n&i2G_}Ss! z&wpLTsNs2eZ~UDn)9ukIf29F*$bh~7RC@e=ZSq0G2!Bh*VC-1mzJ$~6Bm`5`aqUk! zV@ET|7#P~Vc_{JvU}76LnWH+;A?Va&iBorbzW=kjRm5P@`RHh-$AJ#F&qwCMk`FWp z8Yr45G_|dkSI7 zHqa1Bs0vyw`nQ*)&lc)evL&t&AD}yRy#LJO>63?p(L^CGXG^~o>K24F&!o z>C&zFnr?mTiQX?>-It67vo|qcyLx8@qYwaF1bkSJ{73r%&wwTmSgx%<~j?b=L%dN6Di(EHn*O=gP4$T$`MbOe9a>x)j8s5cQ zi}_VIjGkX}$yIN76y|ScrU(*2RuEAjQ%1Ix_b4A%h48lSgDwm@Kh^j!ML(5S2_}4$o9ods0 z!^I!x>TZ)+pxr4Ru0SC;8zO@$ z&_n%~4w+b9-;i!FvvG*QT;L;!*6%^SSoxifWS+{x%oMk&4-l1v)~d=_d^A-1Qy)^NYBn1Q!1Q XEW@Gt{CUzG00000NkvXXu0mjfyMO>@ delta 1066 zcmV+_1l9Yo2)+oABYy-1Nkl93}wUV*?uBWX$jxLVpQu`u!Kv27z#I1A<1- z1{jlY_JzX9(VS2c-~g3P`_kR&TthsT4G9Sb7mn?F`(VaFAYXs`*ZOjLr75IN5rgsm z)QOR-Zwvh0u)mmI#mDkqzWHR&#h3aAGe%dGHkb+smAfvYe~{mwj1^vgA_Y?J3g4UL zkI5-TYJY}u@l@8lFtIO}G+c$a3Gl(;{E0)^kX=`S19#pcTzhvkcX~9Z-C)s^ z!Dw&d+(ci>h}zE`0}xxN8Iw&;^c~D4ASO=+W1o)~CibUd(?@P}B8WEf%3$iuST8ps z-DhtMr9XNuACIooxbukj06u*_e|#X-&1rY$lYH{UzJH;N8Ml*pDcW%y$eNQ!3)#eW zC!~DuQ0ASH%&yxx2vl>&ay<2P=G1Tx*yt(?9O*I6A1(BlY=_h5Xhs`op>z&s5b zR;^$Jgnx{JD|8G-u+hdV_o{!bxBL}xX1wrfe=-e{YWG0SrsBIQO410>09DZ&6V zP^>hs6<3AN@NhnH?pXha&*V=Y&ineVyjooSwbBe$Vt@k{Bv~Tj^_kT>^P7I@`_J}% z_0~`};nm*M{O0xFS1<~Jp=&^B4gvuJF59D^*X zPmN3@tA`9Pt37z@{@RVw!?;qUrQ7${Knh)l!4yf?fXhKtks)ri?8|o+%MYzEdU?&F zrDn_4)q=s6FD26mI)aD*!HJCAC@wax{=6i%D-gHWrk7|*yD!3pt}I|D0v4u7y2pf; zsDIkNzgL-hQ1h=(J*bmVY6U0OaEe&BiX=w03LUuAo0n?VH$TmH*`^ozT&!7Lg~${+ za)*N`lC`|Ukhoo1xiPcsayzqpt5nrFL{BZ;%=D7!5)cuA-qtxjOAV5KPSvKx70c{IZ#a`hV%~iu6+5XzMcbd&5E;=vILgfl%%TNM=re z)SsFEE+v=Ox7@}Q@W#Y&<5ncwbT1O>4412$zm(UUTI7t){!_PY=WxiFX1_h#wwPV0 znOv2x^Iu(Bs0+>HSGE&u=k07*qoM6N<$f-D;m{{R30 diff --git a/recipes/icons/macleans.png b/recipes/icons/macleans.png index ff34f867560aa083c1e2623a331a4f7277a8cf80..6724e4a0ece3d38c1fb9885df0e1a64671f0ae7a 100644 GIT binary patch delta 517 zcmV+g0{Z=x1e64j8Gi!+001a04^sdD0W45VR7C&)0RR90ZEbDQ(b4_={l31we0+TL z^z?9WaNFD4^YioH-`^J(7jABDY;0@=1qChw0RaI4(5|U0^T17kB^U)5(6g! z0qP_R!WsulGChcj!fn0001^Nkl3=!M5ZQFJ( z@&50S)XZcmi`4B!FFy2NIjdR#jZ<~MPG9qy%sd2w9eyL~X)Qy;c)|k%fFqmTv39vV zJSYKt{tRS8;Rt#_G!}WyZrErJ5NTz^20WO50n?0m6Y zNq_-G0>{u#u-O8;+e>hO;|b0exW0+-PJeuUegFL8f9ORwBG?oI8O31>00000NkvXX Hu0mjf;|%v{ delta 518 zcmV+h0{Q)v1eFAk8Gi-<001BJ|6u?C0o+MMK~y+TrBY368c`H=CR!tj1X4vw2vKP0 zqR^#FyR1K;R$Yh0g`c2wCH?>@Q5Rjc#hu`y#YGWUF1pd8i%OA53>hX02^I01Xc;+q z@3rX+C{0T*9Ju##&$;J)s7T;Hv9h@mhwD5(?`ZLq=ahYUfPdpSsMTs{G#cdqHhCV$ zIv$_Q8vHm|$D8dnJeEb|LATqDYPE`5t%hVW$$HNm&++xcM|^*@fs1{PK#Pu5i!Zwm zmIcD$Fz%vWue08f*9$Bb7`=N7(Zb=%s)k_E;r+w4WdXn6|BDWYq6ocSk9a)JYU%nK z;>#EKwx`0YO@ID3hhVk9Xf%S&W?3%lKpZnw*Rr}0m(@b6IJ7V5tAycwaXOaK4?07*qo IM6N<$g2Ary2><{9 diff --git a/recipes/icons/maharashtra_times.png b/recipes/icons/maharashtra_times.png index 3218c21cae2838f86b460acb837459e761e24cfe..44e25569414bd54c8d23ef187d47bbb4f63d1b36 100644 GIT binary patch delta 751 zcmVPVT&e(Es{H-_08FF| zU8(?1r2tE#092>E&*T7ArvOW&+U4;ETdDw7s0m!E09L1=yV?L*sQ^)>q`uqPYuS09vWd-0TTms^{+Zd!Wny{{Cc<#DAW<+PKc-08OL-Sg0Om zucN%$GI6xm;_v`brT|i<06(B}o5`ZQ+5khK09vY-wbv3}s-U~t&D-osf4lJY`W9fU z%G&FFq0DBI#SBrU5?`zA^7vYd!pz(3*yQoL%j4AG?hII|4_>MdS*j0StQcXf4^X9j zqRc97vj9}40DnfI$=T}G;_v`Wr2tT+06Lv;n#dYuuSb5mexS_Y>GKR-t9_x(3{Itg zqt1Dn%CE-YEN!#w^!Wf)sPp&xz0c+S{{Hv+{MzL4vB=`tg$O4_&Krn#i2E**tc)23DsMUVp0wO{4%!qk5prW01svrOs%S z#Q;sEM}E7OwAPff)jD>!@2blA0003fNkluyv3gzCx4OC+i-q>`^GMPTo)SwV6cu|sT4@9MMk{dz8LuWF&-Ek5(ac+bPUEPBx!PLS`-jtc5WUL zi{bF{%BmnB&b4*;l_?5>n}KcE*|l1&`w2mS4rr7^2(Y7LN>7wi(GIq=^9z8>h$DKX zioZ$S+V#!tUEFs6APx{E%A-M(4#kr(`MKm1W6!35hb=DvuWzXXhxY#Q@4rD%O4LwQ hJ4JsW7!#CUJ^`QdDT$)R-qipA002ovPDHLkV1n^&iFW`1 delta 753 zcmVKiDn%h`Nt&5FCXpa%Y00E6#7f#cy3xi=6@NmSQffm-NobQ}Dj~Rq|#iLaV@IL}ny&}!|>s37&^!SjDM_L{q|3z9C(({P% z)dr|o5t+TG20~+!?384OB)ev0{?!s3ozh<#k6=3pzPlpwlgZSHYe5{@;Q+NoM4j%7 zBht_MKW&2{2!9TTSSth1>6;VN$y{V1Cna+E&F`ghrSNqJeiF?rtb}9B?=l-;2PHn{ z;k1|0?8*Kh>aGR&T?3=5mjaYQusY)e(En(bGJgu1K7ZYFpoV0-yzwgFL$0`Q z#~rEThaE8jxccx7G6(>!8$fd+-6O&+CK=FNfb{$E%P;5-!8#sS27vHQZgk!`m;MZ1 z7NG_@VXx~u1l0cghCSLi0Cz%{=YlP4bHp=iTi|7Nz0>EHsYkPOMR4!M&Z10rhTv1-xQ+l}oIW-8tJ<5r2%~)85>wy8& z^l5byS^T&#tVs0*;NM}Vf+E8 z95mrj1OA?nBpF;EokjYl0&uBKFox3OiN(>-`}UzJxPb?3X+;nH>Zs!=C?G jK*(hGso2(wR}1h9<<}Kv@q%v600000NkvXXu0mjf@*7+C diff --git a/recipes/icons/malaya_business_insight.png b/recipes/icons/malaya_business_insight.png index 85beb689a2d6e4323bd6f963a32999526a53caa2..a0acdcbf91e3e87be49d7ac1a5f63343f2d2a99c 100644 GIT binary patch delta 1463 zcmV;o1xWhv3&;zQBYy>tNkl=)X71eG_1YV6*LG-Lt2RlS5THs$ zK!g`jqM`=EmqSF&Xq#Mn{@+5idw zk?M=~V@0gsz8Hcb!X|vIiU%w}`1v{HA%+78@h9ju2eh!r?Ku8orB}K!v zf-jE_H%hziW_JK$0A+Xa+R2x;ZoZ(r^FhoflRtj+ngb4p+xc9NB^8+g>Mt1H=ZY448*x zzTPBlihn`E+P#8vt{q%koEKOy07fh~NDw47=H120t!zOt2kC|3fp0yv|8SDo+#6^^ zu-)+0v81sy?@R><1nR%Mfe5q_Sv!Bi021YItrp*ytP^rXePRlEzI*Q@Cl44jmlZ2R zgg{>$k%^C*rJ)KSA_O5s2*Ly)U`>_Lhseh!&VO8O&M&U0M2QGM2tk|DGe`GTTOWCl zbyOQtHC}vg!3-oSI@qA+rXSsB4nZ)O$feoj-Rt#dk5!#St3=-CUpV;0cg80Fkayte z>yH-ZvjtF<5C8}s1oz9whYS~9ySmhAt&&nAAfk}FO0|0O$aXX`aQdZce&u!>@3gRfIu4v+4PX zOkB5@CnEQ8tM`6)U*TA-bt_wJn{5br00Z)w97KX3P0n1;XXX~PQW63{EbyRT{N#H- z*qNXEXS+N&h!7C@i~=M8%33Cuk~b&s5P!T7Bf4XeO1XG$e0-Wm3Q7R_zXTwl00C6; z>ierJD=S*DL{w6`a{20+>&@cm_B?kR`#%YhSn>N|1;(heb-FxtmlgGz86zJ_leBm|__=h6Z6) zM)6vE1Q0d|hF~DJnwJu(ou(bnV`@qL0A zOpAqJJ*-mzVn$mpAhu9PA1R$W_J3GtE>FMDSpdKu0qOr*J^%c-TfVU}eq{!aeZ#x1 z6}C?8+dlC4t{Qw!=p76A+>O>Adg1t?-_(6Qm{?~=Cw~3Y1?!@pIxgw`_;h9THO1R4YgfwpRV|1bUyv|x)Q RBG3Q;002ovPDHLkV1i;#sU83T delta 1503 zcmV<51t9v!3-Ak&BYyw}bW%=J00000003^L2ax~(010qNS#tmY4#WTe4#WYKD-Ig~ z00odqL_t&-8MVR9Z(LUyz~SHfo_o)ovBw_Uv7NN}a)pw#2`N;mD2O7EplU^-Y$|~e zQYCh5V3GfT6)RTVBQ}+y2B~DxrV6MCBC57gk~Tq+6IrBg>VL!&SQirl z0%F`Lb9lwLmk=roq2wMZp$>I8#cQ^Ii~H(GUN{-(tFI*uy?_b^7?25*3%w6!N}o$G zZu25k>~6|8w|@^sz(52uK?eGKr?=MW^yXH&@3&$WW4E)ooJntYE&s)f&ph_{fn=qK z0A}VyUVr2G2UE`1nxTpyZ1PHbwfozj@B7&|c9zz2qTKSA6YstGx5@vw(RRCZ-TrKH z*-ge_$lo~I8W@=R)vJ${-L4Zc%rGtLPma50Z>D3z5P#X^g;3hWcyVZZt?bR4xe7DL za)0gGm0$nvkCVH8N^=B+0jIdU?sc>l8{@<6zMa=wS3y(|b&8~v4n6bK_eYmPud0iJ z0A3`N@w>ZEO}R9m141DtR~u-yEJ|6^*{V6R?*a{ zRiYBB-hc4O)|U@$XX#4Ja;U&UvSz>EVskUAlKFbF#0CSZs_LYwV^uT)3MeA16C+vS z{f7(9%dL5*nv8W~MEKsbdk^NVP)1MYh0sgi+M2f)=PKWXBnY<{BoRcoMKLy*0=bKq zSL*Mbn-NM-KvYs$?wWYwm3^MoC8y4dkmPG)c7Nux)xNhx6OpPccTG&Zw5Rs`=-TDhLdOr0%7lXP zP|E$1QRGK1l+&~GnRAvBh#C?3%`boSlYbrM+yCk`1_nrpFG0D77|_V<++zOsb616E zqJV*#H5>J}j~}1Xv6?dwz69l7h9nGJe)Qv&<>lqfscmw3-|16lk6c)-k8dl>ikpo8 z#SAm$L4x7EJ2oSyE?!qBW`HCs|MAaLVQfE|LE=FSV}r0x1QA8JO%X)d~b19Zo=CFYJjG49K z-A`IZO4Zt5Po4>*`G2(X8buL}2t|JMv!<+T?M zJYI_(%~9vYnvgh)SW}L6D!QvAXtb)rJ+RQejNidFf3(YSi}g402m0j2%9>w!a#(Z3@eGW zBNz`L+$5T@LAXtL5J9+`aD(u0itz{;83HB<|9=L;e*v1B#CN)taO400002ovPDHLk FV1oV#>@WZT diff --git a/recipes/icons/mallorca_zeitung.png b/recipes/icons/mallorca_zeitung.png index ebec7c47f5ac6256f92b85db9424bca322355b0e..1baecf8fb704ea48246b81be7b85b3ea2bcc1e13 100644 GIT binary patch delta 10 RcmZqY|HL=JbfcFoI{+3{1DgN< delta 30 jcmeyw*Umq|RG5LYz$3Dlfr0M`2s2LA=9AuNY|jn=fUF0! diff --git a/recipes/icons/marctv.png b/recipes/icons/marctv.png index 6a5589a900d13a146f91fbf7dc57a88b4e4ef156..77d3584efe7c76fdd3e1f8441512d420281faab1 100644 GIT binary patch delta 706 zcmV;z0zLi139|{1E)xF$0001ge*k~~09C30f4=}fu|Q1$R>KE;!UufA2Ykc_e8UHP z#0Y)F2z|u~gUtzq&kBFX34_cEg3Ahk$O?hV34_iGh0zLz(hG&q2!6&2fXNDf#t41G z3Wd-Lhtmp()(VQ)3WCfUPUrHI;{l6*FiAu~RCr!(lZRK-KoG{iTu3;?f(;RCFW3uW z#fB9didaGPtf;5SU1Dx_Q?4Ii{g2Jw?V5+)_g?1Zz3(?OJF^LtG{EeJkYS*W3~YUo zd7XK9?Z7HPZ-Z#)dHOSoiq#-HJ^+~i6FM-=E1zx04}!;=V6-7LY|*=SnU|5rh`ZeHN5{e;ufJ0E~p$@G2%lE4dA!;;fyk?uQBNoU_79SUS~E_t}i(7VO$Z z;cIvv1;aqcSi{c|Q{u6I6M70Taj;k#L^p8Ms(N|iE#E18zag?A!lPgN(5V~fy;}}= z&J{!0rSeInbos&by;&bgN2GzxV_sO{o?i;5x53Z=Nh~oSq?^Fn^V5a$mnVmtypj$j oqZ2^?j$ZM)@Xt-)w5+A{2XXQS)nF?m)&Kwi07*qoM6N<$f|plMK>z>% delta 722 zcmV;@0xkWs3Bw7HE)oC$0RI4ge*k~~09C30KfeHfu|Q1$R>B8-!v}oB2Ykc_e8UHP z#0Y)E2z|r|eZ>fV#tDPW34_fEgU$(r&kBFW3V+86fXE7f$qIqW3WCcDg3Jnq&cb#eG3s zal;ii6mbPnTv5>`T$9W=p=n5IwN?9%eMmy8cE0aE%{eo3X3hoT&;X+Y0FcX}MdYyc z#pLVBhu03Q0`wLT4V_Pag<-MePmlBh#{UQ%7?8?mQ+dAscoSOG&^2s$R-bc!NvUGF zuLVMD&jUxMJeOpKYRCWWlF0+u-hbw0=rD;bUJtT=i@>t8lVMN~U14G|gF)n*^6kK> zcfq`4S*|cMvt1d7mUho!<3wZ5w27ISg^9_VUJX4Jcr2`%5m!W5i0Nx}S0W?}3^iB@ zMeOLY74*kCbl_giO6V*iBE)Qe1y^D~2KQ@)q_}qV&#vie~WSp2x7K^ zb68{mKyOiz5Jd}EsKBba8$jowmDmzGM4mJwnAr*68d!7reWe@AKg>FR-Y17C-2^cy z|McSPg<^dOz*waa+`90yX&KBU_UOk3(bNh6gTZug6=Ojmy#_OLan`g-`(Xe(=L^9l zEFEtw+>@D^Y}c$^gaK%H9{K}7cDUhL#7T&2)^0-200s^gt9|GIjuuMJ3`H$Oxj$|o zGL?o$zxSa{&!OvX*%!-MCMIQ8%O{c1W5aa6S&uA`2R08oK}8&|8cb|ME-_^mOQN&r z2C(-0gj@dd1J2^Qy{r&yLNCcDJUD_sIzcrw z#!5gqI5?uBqJ4dRrbRuqM?Q2tIlD+dzDYmX+1c>$@T8=q)_>O4JTx?UJ~>Y}HMzOD z<>lqAMm}sjI9Xa+NJvOnIW}@VIlH^NKtMq0>FK>lKdr5;H8nNBNk3g)Uftc@KR-W! zKRU!pKz2PjW;-{JK|9FE$WAvkmO?zIMLndTptZHNh(J0tGc#~JIH^TFnVFe4H#e}b zutY>ehd??)Lw`fAuC75rK}}6fOE)!|nVCX0G@ziMn?pQJPEKWIWjs7QpF})VR8+x9 zKUFw3Mm9BjJ~?PRI7v!M!%0AZfPl`<&Wb@hpqiSEK|936#E6K9$;ruyKs#bOH?y;| zx=24`V`Ia^!@@~Gfq{WTHZ*p1bxk)lPf$?u^71-5I)95nJClx$MMXupxVV#yjGLRA zJvB66UtfPeI;cfGHZwEk=H{lRrlCYVU^+MX`T1&UYBn}D(9qDFLp-ait3Wk0Z*Onl z;NZDPKb@MI#7RJdKRTU5JfK89k&%&KIybLIKC(wX9D*5V0004ENkl z6^>zMtxikvgB}S4Fx345F_i@-wB491)Yi;U+R-dfqh(Qzsg)mO$x9;GOn#%Cjnl1H z6=Y9B0!nP-zGLvoP_O$ouFs<_j4JF>a>=hd5`P%_ACsoPAT*ACt|7ox z8ziuWj|qH-B{a69BLgm&ki7iaLf3ao;1G)mJaBU=%$I?(paRuR|0?c$nb^M8abKKV zHn{XIgXntrK`*)zf_VL@w8b+3=!D4Ghhn_oGnaZ}&#%N5bUM#i-@vT!Xe9kn2Er6r z8E1%mf)=a{vGU delta 892 zcmV-?1B3jH2b%|wEPtk^rfOI=H_-iIsN_pH8V51NI!W#IXE;k zh(J1vK|8avvr0ELJ3Bk6MLlRcIDmkFYdknaL_{_-GrLGXzJEzSuCA_qKRPxxHlUiC zi9kC|O-)NTHK3rNn?pQ5KR=#CJkHL}szp6QLqp}|<*i0OY&HBV4b^78VXLp-2FJ&}=-UphChMn1Af zKGxRO!AUbsqKQ%NtIXRC(JIKh$PB%57L_J_SH$p-}ay>c!|Nq0o!@@~G zKs7YQNO2B(9qDeM?R~ot8_g%Z*Oli zGc%f*nVp)N#7RJdKRQA+G{;In+1c4mPEPRf@H{*`x-H8`0004SNklB>DMh7ID5`XZx3HKyei18eX|4_?znnwr$B*GXB6;^RjWFlagyQnrL$Ya0KgX5em@j>24-RNnaa&uwHp$0 znssw{63SF(oZo((IuEunW9 zVVK*!9ck`S)`8hINUgL(Fuly>j+cJ|GRmK!7u7!Pv`k3XFqw{tdh!45ztt<4Aqv1x S#?He40000A51M&a> delta 48 wcmdlWd|h~gvMK{xNswPK0~q8jW1k3Qa29w(7BevL9RXp+soH$f8x{LG0TfXSBme*a diff --git a/recipes/icons/mateusz_czytania.png b/recipes/icons/mateusz_czytania.png index cec6793f0d5ffc2bdd41e440a33d05bf15f11183..ca9c8e888994a2d756f54247768e1fdab0e1fd57 100644 GIT binary patch delta 306 zcmV-20nPr}0@ebMEq}fQ{QC6#`t1C|1pNN={JI4E=A8WU%>4G${MJ({i=Uj0gIZ~>xx zB6OjB+y2pQ#QE{BpI`Pz>% delta 310 zcmV-60m=T>0@?zQEr0&>{QC6#`t1DrREl{MJnT(oFo)MEuS~{L(!9&OH3iH2lge{K_Q! z#w7g49Q?u@{Kgdg!VLVv1pK}P{JI1s4$$cU005v#L_t(|+J9BdOG3gx6hzVcUTZoH zB!Yx`%)uHd&3xc>X4nl3A+J{L$@})tz|k#`aSFhROPhgJtX; z^!iILC+k&+jLT$hZ%@ndvl>x~J42s;Cew^yb;a*X^szJ}&N}DKLQ6z58f*?Wd+&pj zSyvbAhrD*uJ1?W-OTlyI#nzT)WQh!}GdUX7)><3eW-`8~e|Fy!#e;^f1^@s607*qo IM6N<$f>^Y+$^ZZW diff --git a/recipes/icons/mdj.png b/recipes/icons/mdj.png index f44a0db2f16a2b48699407fe5be1388dde654ba4..b0add5e63c00f090a6cd884de9bea0cd07470bbc 100644 GIT binary patch delta 10 RcmX@Zx0G*!@jD0S#jccK`qY diff --git a/recipes/icons/mediaindonesia.png b/recipes/icons/mediaindonesia.png index 18bc424e32f808eb402543be78e6e52af40cc915..6b4b7479688bc309c20a5b75dbbd65654143cb2c 100644 GIT binary patch delta 982 zcmV;{11bE62;m2i8Gi!+002a!ipBr{0o_ncR7C&)0RH~||NsB|{QL$62L1j0_xJY+ z2?_iA`$I!T3=9nO^YauG6!G!#%gf97`1r7}v51I?-QC^!`T6ql@_T!Ir>Ce=Qd3P$ zPJe%YK|w-gWoBbzWa{ebY;0}q?d@P-VP9Wh&(F_ZUSFl9rhk@}m;C(wOG`{DDlD|L zw8Fx|`uh6z_V(J^+DuGLH#ax0uds-Si0kX?C@3lk3=HY%>Y$*ZN=r)|92_$^4GBUQdw~>*O78VwUhKF);bp8JRx3{-^e0}Na>P}8jR8&@=prGvR?QwB(Qd3iF zYi#W7>?I{9KYu?#;^N}6va-3ky3x_nCnqPIoSkWDYUAVN{rvqoIXS+*zW)CIR8&<* zM@SD34@^x>$jHf9SXtcM-BndqQc+XR&d+dga>d2QP*71kJUnPf z#oXN7OG``J+S@`xL)X{WtgNj)Jw7lnF!%TP!otIunVOoKnldvp^z`&;X=`0wUcJ4( zR#sTMySwS>={`O`Yo4r00DGT zPE!Ct=6~2`00059Nklx%#=mGHu+X-l1TN3L3S&(V)>oAp?tTDe_VL z@e}pH^c1KrrcBRJCX?wo@FJl!fG)iR>rY?3Zg~4no_znIR-`_DYHs-a#eeDgNfh;l_=U2ofpjT z5@x#1Oh*rfm)Xm*N7D?&aA9T+Z6@L{aSw}yAt?6Xa_WovvVS=ryx?><`}sfmZ>f{5 zknkYiKF|N0^E>DJ&gEl{u9%qX4d8S-{|A82=L<$T7_k!ohL&g=E!>-_wDc6LsFet|?H9UdO>c)Tzo&B~JHMBkn z1OS7<(A3myv)jX1N=izC(3v1eva<4#R;zV5w#b!W0mjG2Yib^2qg2+ACOG~<5R${; zh>PQIZ+~yEt*wOyFg7+;QSlH~78ZU$2tnMweTS~j|9X3S-^%3*Y^+vm&;XuefYH&< zWo6~8r$^WT0HP+7iBqQI!u8J1&SDb+0DaTzM@vh~$k~~h?-WE)(LJ(@Ld6Qxs9TC@ zWo0#FfP#WTE>&c?c@)Il+&py~taWv&!NHH@1b?z9Gyoh?PEIaoYkJy9K~z`E>3p`f zwve4vAAbX&*Xz-wxU+V-+&FGZ*~LW*IDLKE*4F2o2O$IKbUIk{@7{=sOf)n+!@s$? zvZW;}_cdeyl&)Buc$)FY4gRWosN!<$jHdU!$bcd`upEQ z0e_7XtJ!SkgxM@wIR*IL>?9J&1puUgDnoBswk~st1VY84@qwR_kx66gxGl4VX|Y(s zS>oC?C?zEoocQ>7r}N+hfIKyszLGoWGYEnp=~H)i5BQXw#G$pPr-xJ$L?R3pa8M=O zz@ZA=+uLI(|KxIINl9sW`Gdm3qN%AV|8*d2Hd|_H8YYbY1#E0=R8>`@FriXhe1CZ4 z6XZ3THibe_Tl++zP`0$FPe$hX_rduYKn`9a6KM7w5B($mya_O20zF&KsL=JWif_DP8<7a$Y4N39tF%7xW8rSEShioz%Jj0000dP*g!ID8l|j0P*SRtE=(xlYck@rvLx}5lKWrRCr#M z%7<=*Fc3x2p=?WL+~|FK`v3o4@T?^uD?quj^b9@_`g@Qw!QKzS-^hhFJ{+VhWbsu0 zlRE$uivbiu6ygEIa38)wth5f|X(AT=iH97xXFS=R-()TSfODljFL|6F)v-+D_@=6+ z6C)trl+8_A@_$?$Fny)sn#o)YXMu>KFAJjR$4(hV)fjLm3yeA2Nsaj!0WyXRJpegC z10W!d0XaYn-iiAs$R_{^kpMXchzv4B2FT5WrfshSsGoo(Xn{u5sHZ?PI9@da?s~c@ zr8TwRmCCeE%bsNXVu3R?4|UQGrk!?RrFD=DAkj&G#x8dTE5UXdY{6Rw9t1B-;5C3S f@OE=x=LO*})&fbciEUKh00000NkvXXu0mjfRTQaD delta 446 zcmV;v0YU!w1AzpPO@FwHSONk91_lNd6%`W`6Bid385tQ03JMJj3=j|ytb$WED<4!r zEHWq^Cm$GLOEXzUFMnY_a#uPlBO9`YRbNRkP(3LY5)P(*P)a!_Lo_3pb4reEMLsem zoOexuV?lyrKx$GqnvNZ+0000CbW%=J>8l|j0P*SRtE=(xlYck@rvLx}8c9S!R5;6( z&53SS%gRynU15;I>{#8|d`%za_(+6nnH_q+x3+G;Y zK%5gNzd!8JtFaeY-vYdL>4s?@ruTT)`4dH?_b07*qoM6N<$f^v?!1poj5 diff --git a/recipes/icons/meduza_ru.png b/recipes/icons/meduza_ru.png index 19c9cbbe8e5e06a23dcfd8599d61571bc48831d9..6acaff40101a33675ec3b92d4213234c5e53958f 100644 GIT binary patch delta 437 zcmV;m0ZRUX1o#7xO@9gs3JnYl6%`d385y{XSOx|L5D*YqMlTZ+69NJPHY*=8C>dP*g!ID8l|j0P*SRtE=(xlYck@rvLx}5lKWrRCr#M z%7<=*Fc3x2p=?WL+~|FK`v3o4@T?^uD?quj^b9@_`g@Qw!QKzS-^hhFJ{+VhWbsu0 zlRE$uivbiu6ygEIa38)wth5f|X(AT=iH97xXFS=R-()TSfODljFL|6F)v-+D_@=6+ z6C)trl+8_A@_$?$Fny)sn#o)YXMu>KFAJjR$4(hV)fjLm3yeA2Nsaj!0WyXRJpegC z10W!d0XaYn-iiAs$R_{^kpMXchzv4B2FT5WrfshSsGoo(Xn{u5sHZ?PI9@da?s~c@ zr8TwRmCCeE%bsNXVu3R?4|UQGrk!?RrFD=DAkj&G#x8dTE5UXdY{6Rw9t1B-;5C3S f@OE=x=LO*})&fbciEUKh00000NkvXXu0mjfRTQaD delta 446 zcmV;v0YU!w1AzpPO@FwHSONk91_lNd6%`W`6Bid385tQ03JMJj3=j|ytb$WED<4!r zEHWq^Cm$GLOEXzUFMnY_a#uPlBO9`YRbNRkP(3LY5)P(*P)a!_Lo_3pb4reEMLsem zoOexuV?lyrKx$GqnvNZ+0000CbW%=J>8l|j0P*SRtE=(xlYck@rvLx}8c9S!R5;6( z&53SS%gRynU15;I>{#8|d`%za_(+6nnH_q+x3+G;Y zK%5gNzd!8JtFaeY-vYdL>4s?@ruTT)`4dH?_b07*qoM6N<$f^v?!1poj5 diff --git a/recipes/icons/merco_press.png b/recipes/icons/merco_press.png index ba320e482fa8de62f492ff992ad0cad0f09410d4..551fd740290ce653f894634f7707602ad6ec35dd 100644 GIT binary patch delta 292 zcmV+<0o(qd1DOMmEG<%boJ??(Pjr|`Y>-ubp#T5>MrVw3kE@EJxty`Zt-Z~zzt6G5 z(zwUfRC1M(Ss?>MV~CM`9s@jBevzIYe?DA+p0C56w8yNv%dEiBRd$!HzRy8lgs#BR zu)xt*fuyp;)LMk6!OYpi&)d)0;?msa+TrNv?D6RC^7HoiT!^Z@#F`oa004hUL_t(| zUR}Ub0>n@Z1<;$P5@Xx8?foxlzMF$E851JG;K$&~;LISu9c!(#mulTSKMSovUV|V< zv<`^O{P-n;?9NHaMxL_6c#6WbTU+#fX|pqi@hrAwYtW~6p4g=kfXI@WNL qs+gPAcv&bjI51c;=rM2*beK|koK$j^Rd$zEeV|u? zq*{chT!^ZXSs?>+kE@Y=9s`P^xsje8f1I(!p0C56w8yNv%dEiBt-Z~yzR#||&#u7H zu)xu=!_uWu2aR zeRo=eW?_x!5E7Z~?W>2Yf&E`S$nv z`1<_&275jRd_esD{4tSUGLc_1l3+BFU^kRwI+bNWnQB9tY*M0lSEPDbrhQ|pglDaY zYp;uKu#9oCk9D<^h`yna!>5+UteVKLo5--W*2D&TKM8$7xPREjy4lFP*~q-w%E#c+ z$>P-teL~RW+0*9R)#u#S=-%4u;oIur+w0-o?Bm|-;}C#F;qK<+@95<4>F4q5=<@99 z^X}{O?(6jL?Dg>N^ziQW@$mNZ@b~la_w)4l_4N4m2z)>lf=T)N`T6_$8ih^!{QLX- z{QLd<9EMK){WSd^hfx3i{+r3LpUScYdp+gw>9p3vB#KpTv5s%BjBl}y8--1CvygMM zk#w_>bhMETenOLH0Vif1hEIvVp^w6*kHV)7enXMNr;@{{lEkT##Hp0Us+Ps8ZLfxQXz>_B8gMD*ToHgL@10`x!A|L*~co4 zSuBoQyV}XT+Q}}CT9XU{8-Fm6UC80n$l=s5kzO&6T+HIu&*a$8<>2k*;O*w%?&d(4 zY2xnY1$#afgGm~MOhlV)=kn}EoNnpz>_?n#OP+ETf=E-McT}Qx?SJ*~?e+0gqj*%K zc~zr%Rit|J`1KZpNm!+PS*3jS`S|$v8t8 zs(FWX)z(k=Jn+*?eh0O|$RBN8SRu%`vrc^bewm0fYYJ*lA%E0J;h+48$~aA6a9s4P zRmd>cgMGokoP}4>m56}+gyWD(AmRT8K>x<>K}z+-^qo`(G@&=M3;*(gFRcg0hI#Ah zKkh*6l+Hny$5({$__2r>pdkp%$H*L2Ar!>;VHMWF*vRmHOz^F5}Z z6O85Y`y~QhaDS3r%}pvd)d3r<76=)kbfcthNkW{;=do{h^wj|u3}FBE;xlOk7I?|> zEj%a#v<5bm($}zHDM+KL*wK+6$kGJi8U}=okjrGU5Ugb>O;k$RF`qxqawU>=AOP_k z$0Z?_CHGhz#qubh&N2omBB0*5tMW^I1>?+8LGYzBFIBMiRYf>XAXH%`aN|AAzm@C&;f-M zy_oOGKN_~H2q=wm`wzx{z{{@)_fMlHJ!RGuS}1K8g1+%vG^ zO$@S>=YIup2W_`VW$7))i3kd;2c)DL*m~hQ?CX}igzhGb0i}_?7iJmoDewx3jTQm* zPs^1|g4(KSFU%8==x~D{tYzRGI=wgur4?-o$V?v~RrTu1k`tR#KgI*wHvLhYJ90Em zm|b8C)l-=E@d!^&;ryIW=d@N{s$rvTR^B$|7Zk&hjZ{RjC|dvk002ovPDHLkV1n;! B;Mo8G delta 1358 zcmWl~2|Uvc9LMp=V-zaS6M0;@>xsNVib_TsW`xnBQi!BOl)KFS?naKy7^$3d3}s>w zB``G;yuDI9LJ4ew{ec7rh46Q3Y(0lS&dw zH5sLr?5vjJqMqWSk$OQh)m7`U+o8v9+Bmd!8d?X3)=5L_0%;!l=@*Y=cpc5WY@Fq7 zl6B=c{@QVZk7@Q*Q-Y5f!Ph*;??jH@$=rZbxdE0$|I>MaXYvBil7e6bw_#83oG%Q9 zKf7oDJjCI72&yQ|r8wNRBobW~<53pv2`Z2AEPr?rM1JH&esrk$qO3esu$a?TPJG>De-bjc5 zH4!3dM52$>VJlpFY1)U0o zTq$*+cj_~PHN;>I_pwL%*&_pN=HSfe;LO*5XTA>2jgN39MmUor^OMZ^Z)1zBaS)d^ z!DUZyrzd&S-y~+fgO)(EQxbDi{5ckXj?JH&UY?&3EX)cPI4fMvDsO(3x3J1vT;p>= zKbCny!O{=G5&%Bo3SYP)__-!nUt3vUTiX!+koYOw1g-zv+F0M(*x1_K*xuZf*xD3r zZHczGB}CghBGHa$2b_3EEEfL){rdHuV%H3T>?%RRZ9OgfQ-;=kGu<_01!1v#>lZg7 zGkN;vZ91g`0}PV>jT^u+UQ;o4II^5!*jjzd?ThpID<_6$(INdq9N+sz<1H0zk-%y& z=EFbSSRW2XI1i-yOnY@cAR}O{Z5zK?XDF;h((oswal*?McN|g%2qHzYgONLhWvxZF z0ywCS;!~jt#^vsh7;lZ^iALTJS8CrJ)E#-HL&aoYGD55n+v1XVFfBi#&!(j8o4HY|^syJJ*kpGgQE9 zx9qo5=}0g*jQSkpaR_@5rQY>KbxO-tC%-@7esP7dhe5!QvRJWyoWV?VBpc;@G$vC$ zu|xU|m~Kv^vwarCQSK%VURI&nkM<>1_g038d`JMj^Z-QIZT~kxk2IgIA$y9{sAQrN zU+ZRUam=Pnx>o1;EH>tD^KEwuAMsZe&Y}eRA_P0{4`XysFmu%m=(h6uX2CbHf7nES zZwxCr+864ya{JAkhQ2COgQVv>S{yYhCBj(}@X>-EFKwz(UVF!C>vVg(w>;6Ey!dIi z1%Mkjtf^^_DT3!dm`pKsl`e8FfUXzn9`;ZAWC07TXbX#yk+U#QN17@@(irA>MTtEW zRt0SkR@obY%`QmjzGS{^;CX@kAMKFpo|ziJrBxi|$zP6`T3{GvntLQ{DRFL2ICGJ~ v&CuQ;&Jp_iUiCM_?~*kMj-q;g$Z4@MexD|EhKWrgHU_WygG0wSHBtds(VyLy$Wn zO*bq_82}R)8wwH@1PcuU1qlA_&;6u)|JkSLt9t&Qa_H8#^^9}om1pRVW9^b(+J;`u zfls-FS@e5X|I3x|;KSULw^?kw1iAcQhixBZagJu zED!pwkw_>4{i}mH7YYBQj1>d}=&FeBpMn4Bv;Y79@8Z(wk5=c6T;_;Z;D=G&mu~;d zsoREC%8FX&+rYMvaHEZVu7G3J)#uWwhwFD!%6n(Qu$jeSP{f{wxp70Yv%s9G#hZV9 znrvX2m7M>=oPUR9L#$*v)X>MVSSYPpI;>bL|HX{K#nqfmB#KEegH0>*pl|=Rlz}f4 zaBgo~US{v9X8-xUdrLWgO(SDQ75bNa|B`g`lXCx?Z~BvLGdVpyGc7YJH83e4D>OPS zC@J!hY#bmV|GtC$wu$}c$mWl8=j8eEiCl(=l+t=lpMRF!wEzGB0d!JMQvg8b*k%9# z0QpHoK~#8NC5qFUBS9EI|Ejhb+qN^dZQHhYUw!(^Tpj=bLddLF*PIN~r(FIqDi;I3 zZu*Y$mDfAnr340h4zf-rd?9y+=>+HXc>KT7SaDQ~UY2EU{lrGWKJMp;oI@NbE_a*AN-I1;l>{Q-ry@adcJj-_)EC?T;zR z=x`=fNV}Vy+Ye@N<b%707*qoM6N<$f?@w&kpKVy delta 694 zcmV;n0!jVL1=j_TEr0+2=j8eS?$G}2&;94f@ZiJm;?n2az}3~~)X>Ml#nu1$z5nU6 z|Ju0!*{A=@sp!_X|I3yC!kqoWl>fzy+{dT?wUqz7jQ_ra{kDn!w1oPuk^QTK|D=rQ zs)+8Nfzqjm!LXUco`$otz?`YYnw6Y}h?M`RdjG0*{iJ;Vrhjttpl|4_dhe-b{-1LC zmwf+{bn}yP|C?|6lWg*lZ1t68^^9}om1pLUbLfs^@rhjRl3(eMR_Bde=7?9|hf&>^ zZrX-k+lE!jidxNqPqvV7qm6y8fMdCYS@e5X>vvPiduP&mO~-sz#bHpjepR_~L#=yR zs%Jx+e|?&4V1Jo$PLyUvhh;;oWIDA{GqG4Gty(&)SSy%VE0akuoJ}N(Nic&=E0aSO zkUJuQFBEWYZ(CkwOiNOISvPx2Ie$$fV@4HjJSAu>4^1~LNGJkH82~dmJv}omGb%MO zDIhB}IxQ$EITr~WAR!YN8x;ft3KA9s3k?DV2mk;81Ajhq=Kufz0d!JMQvg8b*k%9# z0R>4!K~#9!1wacs!eA6Wd1pYSB+(Vnngmo5h)|dmMJO~BY6Ya)zQ67vA^=>wc^weN zoyI#60YJSLk8TR9-e|p_0C1*8d~a*s9uUoM3IV`mPKtyhf>|6Zln5NaXEQ;kH=L2u zqCf!8cYjw3=aQ6qy|j#M6JSbtA1hf!4z7DFwhaRI^?FA&_aHmgk`~(vAvK;pF>v=c zPm9jjvMWH=U0Wk>?JXs%#gIn|#fVD`FBlcp3})mrpYN`RkRGxRDvVW6^T$iKhdRQQ z+hx=kW9^z;ywD9067JChrfM4dnE9R7&C-Wn&r+3E7=jb@{np&1)vv0tgKuCA#O9F39J)i*RYH8r=iC=@MY_(4m7ne*q-O|$1i)CfyGMUpoEvu-M zRaUyEz3v&WXU1DoTWc^eX2w=fSXfjfp)Gdxlc#a7lT!T8(3maE0pi0O8310DnVKw8 z6fe%q*2N|RE9-$QEKb>@QmOVITs+iW@odlYcH3A=Vl*$->#1K|y)ev?Y6=_}o#((} z2sUY!vJ@;aX!pv2;U-cixN%g1Bgg=u4jkQlTY&jVAIv+4AWV&mVVAn^E?0^Ps1o3i zCvUKZ9%W%Mh>oYQzwhwDax>DpG!RCyK1B}VVk)3z`(S-?0hX6UP)l)rJThQeQK4cY z)qR*x9O>DnQK60ql;U?KQo<-cQQ=WobSW=;lT6oT$W^KMn%L% z005%0#OYE1ASodT@4x&Dlq8d%w*=4A==LG3X%ri6Q5}kaKXjf8G<9d&1?$;}h6D;c;WaJK^zS z?PO(XFODWPsD!D)B|HYX`vvQ)+W|P zqre_L`;giQ<)-INAWB%b6a_N%tR_wziH~zm!`Se(3-L7%&vF?ZXO~ffFg9#0-M*zR z-ZF&Fuzu<%4C#|;{KYC_8_D;LH7>YGr&ds2t^vq4~Cz)*h{)Vfo`0?ANa;9ix;pnepJLXsa c|2hqSIoF=L+DcyfXXYEr%E(Ud-mNnK0~OX}(f|Me diff --git a/recipes/icons/mmc_rtv.png b/recipes/icons/mmc_rtv.png index e7ec62ab6c6f10e62eb3bbaeb9c70c014fe1ec56..7c6d7395f3fe8497171c88ff9397315af23c0896 100644 GIT binary patch delta 1009 zcmVgg5{ z6^^3FI5|QlB`FiNiZ2tcKU0Yf`Jwm*{!@$PTR$z6+ z#KXkQ*u>A|$7Ek)_I4Ra948XFfzyfs36=P+(0=PL`y+mZ{9v+vC^Y z?AYJyO;u+g9)Be+FgY|gL)_x--Q(}xhARH@b&KR?|<*`?@?N9R99e+qsn}ZuV`p& zlBUaRYG`Y5eFzE+)Y#zE+v?TU*oT|Jh@8TZqQ$Ye#5#;Fb<>u(-=H}+->JSeQ6cHGUpT=x!X{ou_s=C*#yxDGS zYw`2;@_+O5^Yila^!D`j_-}rkaDJPOk(u}T`}z6#b%mvNdxHD?{C0$({Qds@{rz`` zs2?065!#Xh0004*Nklu~i@6tDboPP%%ft?gn2+oeS+TBAusS0SjzVL1d zV37icsXzgw0wkT`K_F%51pR+T#86S&&)`akOOu&MYL6ozCl1X7?B; zCVzr!SpY?4!5qi;!zdi&TmTD&^!?4y5Brf_1YE3#J-%3s0RsSdXEtDb10diO*gng^ zJP4^$(*D3rQxha6$-s4RHU01ks4OzD&js{FU$uwfrLPoB{UYGQ(1_1m*u*W!KLm+) zr2wY`?S0AF$HFu{G7xeESjT_w)`R2i`f6n7b zz?{v5Kf7EG@O}n-0u_MyBL&7@1%Nw&7O4ax7zkZe0D}x{ob(LZ3fVi6#tle*nDM{D f3?0DAlYEG6DAsHhbeEPH00000NkvXXu0mjf#k@DU delta 1037 zcmV+o1oHcj2&xE>EPneDszlWQ_h@8TSii(Mx!-<{4i=W1epvR1$$c&-Ljf{$ok(rI5$d01Oj-ttqqsfn> z%8;VPkfh3ynSZ8{rOJ_|%aW$clai2=rp%M4%a)asmZZFvsmzzE&X}vtnV6QFtl&*aI|<$uf7=FZmU&)DeD+3C^O;nLdb z(%b6O+UL~O)zsMF)Z6OS*4WnDu(-=H}+->gVh9=;`d~>gnq4^z88U?(gsK@bB>O z@A32Y@_+O5^Yila^!D`j`1SYt_V)Gn_xJbt`}z6#`uh6&`}_O+{QUg<{Qds@{r&#_ z{{R2~v0fr>0005BNklS126zs!32o>3N|?$3V&LmCe*ltcsc9pf&#*`^BSVS3jBbe z7^+~CkcVG-=G4xtEnUrxwN(;MKsg3)5XjgDRxl@Z?!tvDdmML}G6{9WSqdDZ5;E z@qc?E6nKI`!sHzt{mJS;R@1I-m0hCto-hDaun=g&<{7GFhWz11o7s! zmbUG?*0b5|s+LAmP__#JVwi(>Z4*LMP>7K4SMv>7y34>GRYCMtpw#N=75RxiksEd` zTC%HJ3C)r1Q2nba_2+?IBY>tLVHwN{1%ETE{#`2!8C_5nc&2QFDzG(ikQZZMRYWtv z)3XGsV7?TPV-EKOLV^D*s0s}iPhK diff --git a/recipes/icons/mobilenations.png b/recipes/icons/mobilenations.png index 7ae3c71f860b233d6a4b66ba630a0fe6ab5f2bcd..8175fd9f51bd80d7306ee7a27f936f82d603ff49 100644 GIT binary patch literal 388 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyIRQQ)u0VRFg#I!~gXIziKoW@d zsp^B+68bBFT$mJC#9*Za5J6;t>h*!LKye@fsRWuJ4ng_{)PUkZ?fT2b_4lYi)aWml zH2Byg(h9Usu_VYZn1OKv!{6TwdQ1!q>(qbmVqp09L!U)Igt3Z&k%8eiBO`<4E{&By z#mhWh978JNo=)}@YEj^EZRY4wIQO~Z-v7S4p%If}zunFM`KeoxvBINr(s4a432i0u z=bZirKPxw0xtzw!XA=2aq@mg)!JSAX4e?he=4-|5K;LV^YVBx(8Ua%u6{1-oD!M<1SpF^ delta 379 zcmV->0fhd91B(NY8Gi-<0047(dh`GQ010qNS#tmY1nd9+1ndEdZvAoq00BKoL_t(o zh3!_&YQr!L{!;c1k?-AloAfY!nm&+3E@9^}#vZE>x(zuxl+gyehq=%QJ}~gT>!3gTP+{Fbrl#r zktR7ZMA&3g94=O2(XAA5BLg@BYy(cP)t-s)F2}N|Nqn;Ak!Tm)E^;C!q-&a}bi;UJOE7&wP z+eAj%K0xAVYSbYk{PFSdv9aM|WB&N~>zbR`HaO^qh~|BM-hWe7)+#L4D=qZL$ok{s z>z0@4j*sMWbKOl()g>p?9UuDV=+-SS**QD;-{0nYeC2w3{`mRlf`il@An>uW;9p_Y zCn?u1F!s*R<8E)|czNGiT=KiT*flrpqon7Ah2(Q|>X4E9=;+{JV(grq+(}F8mzd{+ zg!tRr<#%}8OMgt`Y;ECXX6&Ay+eJp?c6Z!MP2gN!+eS$K`}_U&_UMR-^u)#W$;t4r zu-;Ns**iV&uCMgR$JQ$>^v1^Lg@*2^sqU$&_tVq$%+2`O+WheF-B42QrKSG&`0~2C z;%aN+XKDQI?)l*0+eJs+O-}8hqT_9D-c?uk*4F2Mfq&s+W&Zm6^S{8}Qd97=v-j55 z@2jlojgHzsK>Y9T-&tDr($n_Q()r%r;9OnfZ*bg7OyzcX^vKEh+S==wndEeJ(;Oe^ zjE&}ee%nJui=Y)pYIy?E|;_|$`;%jW$Ktbq< zir`*g@=&?C@wmA1ySw`4=jVfk`|0Z2M@ZBnBCZgAjUU+R>V>XDM%NJ;+r`uEt_q@?@o?At;_lLiASf8%g*>5!4)W@p+yKj2?r`se51U0>Z!P~TZw z*fTZSK|-F=mizz!0$oW&K~#8N&5#4LJvkJ_)5*-`vu)e9ZR>B_wr$(CZQHiFyWe0l zbMLM972a>EGD*7ooH`+G1%Xj%5p)Mi`D)n8chqQ%Y4;vx%d91vgO6EAs@|#?e{O6d z>6<&Lz-eq5$sOhsU}4FK%n?`&;<{B=Kxy;a78qhj39|=Ai*fY=`V{yY~Dh@dG{Y8?zss7P3t8L@5qSWf2W3Obp~F>Fu26o|@>s%gcH-oz z)78+P#kp)bdA>P<^?yr4{^7a-Dsq@W9~u7$WgNax(q{M{ Xzzd3bTpIpj00000NkvXXu0mjf$)@e1 literal 2005 zcmZ{kc~p~E8pgjW?&4CVh@wUjkwLNv0iq&MmLRJTMPv;mfv_)OM+QYiMFms}7NHi9 zr7%`-Y89oyv7)pBN+6JhBxX-Y!XjjW5az=(=Zw>H=8yNj=e_s$KF_`9o_nwQ`+69z z-?<(D07FlVYXF!b%dvJPIJ!1o>jjfRjI)n30CbdX(BW2r^;hWu9xlMc0aNKR8I1|> z0f5|t08n@d0OrA|LJ0t5zyUxO2>|G705DFu*LCU`0IZ<-dt=>(A^V5-?xXJBJG=)n za`4c{Xo9@9PTA6`Z2d`5-yq1y9EBlhmR6FIE7F@~qfq!qJVEhez2b-aoRCn4lk?=m zpBHCmmzI|F3k$Qqzh<8f8Zk4Udi;2CPQ?icn`~?2#gOReW3#=zdYx`zVq#t_)@!v| zE|(J&EG@k*F1$cB-7{u`Qqx9NL)3}tyQ2I8rmH){(TNuuH}|f8c%K&?zz#f3``clfmG$hKxAS~HzjJ?i0*Kde)PDO!3Xm~eds-sgk zH8t)ZpnUK^uhwuQa3dC$k}FqrO6AhxqH17p+{c%nl*~^|VmP}9vvbrm+Sn0W&biQ; z=P&g03!f-G?0~>ATRTNV<78XA^u|r`rDEOWBq#WsAf2rGFr;O(xuIeF`{Bb$j;p>)F@6 zA92wv%t^`B(vJk%&c)ge|}O5H#|air&3mNOJ4VbUOTTI8xy3aG2Pr*o+n4) z2xUvlxZf$p31`*&_wyo=@4tvO&8|m zaYMtI?ie01hT(XE8xhG3ix~Iy=LCloD6^T%Pb139!_{~Lt-1^>)}>&A)4`1S9PWMb zj^G=J_jGl}?l9?pSrT$1b;oM1Sp6G*duQ$zldalL4`kU^G-0x`_WE_+mWu9Q<=sK5 zKiGMb-D=T^sakS|b{or1Z|uGu9>pbMD|%aYj@f+cUgmxGF@6r)lTG;Z*71@HHPZuk zt?YpQ+oL5<^0gZdaYM?Bbq1~LANy~0D*eW6v9xL}#Ou2N3?!IwvEQH)M~48yx3`2= z*mmq)#|x(+r;CF7jZy1b2X?ML_xPLm(bN{SjMl~h;6EDDTRK*}hofIvV_e}iHnza< z*DIZBK2V;my6@)oByN25(39QFtSybVZ|da-3eQO1K}~>d^h!A`Y~xUDS)F`@=~RDD zZU)$N!D<)h*Id1jShzOIKBc^8qp1AsSCPESo6N#qHS9NPdVNJx(XgV=;Lq$$VhLh3 zD$qPtZja<>)*%tc;)Vp4fz7F3AR!*5pX7pq-vB8fb}#3)kgCTS(>=8>|}< zrCqRa7nOH;u2*+6G-TkneP>_ttc;}CZ9x@nqps~?b=zwMyso)jPZ67z(h~OD1^q6w z4*At^Zi-$fA52(bXSCAJDy!$F?kH2v=(Z@Bbj)NA%GMY>d0JEwupQ+wgQJ)&mi^Je zxA3j2pRgJ9ws}+=_AmF2-cCX2J8f7c^uVRu%VCFeEvxa+#;*M07rBbu>@iW?&Qv}z z>I(l%r%fzo=E*9%?)0igsvG-L7iB?JR8rP{=Z+1G=70aztHD>HL*xf!Y z`$b(7n9NxD(sS5fn9rVQbS!}s6H5jV4#>?wS~18Nf1&dqCWwS5$1Wq(_I+zX#N-Rahm=Sl z=cEvT&%1)9B*mQQJmdQp9Um-60SFsA6w(2SL^!-FIQ{}eHh-ZLNXjJua2J<5^^S6I z>C6{Prvp3zA}N6Y9v_yNL{3PKfe~?;r0C?Nbl86{21djmaj><;M-v=~c!UF3;B2Do kk05b)B!U=a>i~y>9Z+z8RqNk2f)0SEo3HCbm&l9%28*)NumAu6 diff --git a/recipes/icons/moneycontrol.png b/recipes/icons/moneycontrol.png index e0ba36a8c588382ebf7d779ee712d502d46bc202..776c865e8412219ef4a473d18f7364cfa5a5d393 100644 GIT binary patch delta 1412 zcmV-~1$+AZ3#bc_HGc(NNklBM_10 z#7aOoBH;x=5|n@tR(Rrpn?Q>4V0c8~B@akkUwA;_0fFKmA`wss5)uv(m?-veM+Q9c zMT|4?*zt^Krn^s{+8dD5n$}D!50;lo>aA9(y8b#_tvUjbN`I;jz@dk^?QSNw;ldgP zM`{2MtWi*;0>>2uVCgl^{EnA@29Od}CD%Wg-p%_SWqKcg2sm#N5OBosLA~}pNCv?L z92`;1)7U?@kXs#;yYh$rX!;q6Pv191M?8Kp2VzWL)tAA^?IrqSf9* zJn%TvyMRE>+<*Utylp?7MMSdz3K<6k02x}**Xnf zi%Rt25Pz}K8jK=bJmm-rl#>LM0$B zxELG6O8c-rmkTOkxxVt=Js-Yj_k9n)>r)Z}aOl><_uuy5&P{s(;Eo-Kj=u0{*Sbnk zix?v)9w0D^pey|q^Zdt4GhceYa>ct=$ z9Dij5O@K2Rrie;7ar!qeEk1kv{PQZzUA^$Db5FlG|J3aKzuY|(0e28QGKyA`k<42v z3);;V4Z*3!n&a(F|HpM%f+3?4Si}RvhB9S0bL*4I74B|k?&hLW3)$RqHg|yF zS@Dqx9C1c*tPz_k8bXY@r4l5BGL?{xDt{GGMS~de_!{x@>&1!?|s{%?Oe`^uHog(8+lh6tmNs2KTt%>ipf8=l6GV zth|d0UQ@TqvNJxm+-kkCrFCVZb$_{0UI@i168hd;T)6$h`S;8%v<JRy}2 zCII-L_9PBefCpS~zzO+!y`Cc+d6M=H+(6cT*?BALCkdlDi*v%k2RQg~f_)77hnqyP z9;c2er+&`--)WY(0XR2-Wf~>(uX6GQB_)1;jHLw{6Cm6KLUW?LI>(704eq83XL;`H zyn2$l$H0wptV-u*I=}n|ug^jd2KQ`$J0D`-VcOF-9%bo2>C8*j%fDdoR`6e_GLSaw S-N%jq0000}kPECORF*9>m{aApMN}J>b}Rg?S2+_;^GPe zM=etxxWd3t2FDG=)P<8A`6I9Xf~l0qS)@eH+_0A~KF$qyFeQO=i9q0};sSv)BM?(6 zAY9}!kVn)n3GvW$?z_~;q5E7+jej8Lvw?CK=mly~f1QEnX=UC*< zFGYsCp`sQSI5R??)aGiqafu_6D(}fIwr~V4K4f@9%zvSx$`-~XYs3q@jtquPr-$&a*dd+0cR^0rm)_g{ldO4 ze}3;nkA3=E3IVup&w~%&_Q=iKZsk39@4D}a7akwivC(Oz%5@kXAaFgxHkys<pz!#eF|~`N=1K`Rx$EmeurUiKvy`eYHxnH&2E)~ zW_VvwsEBJ=su2%-i1WVUwR&wRl#ILAT7TWE%hZZey_QnlnF?ME=Ngvf$hjV;c4N$$mjQ?R6}7}sAv{Eat`d?s~A*^SF$R*Nbh5Fp&7Vrmw9 z4HjD;ms%omac;Z2mFg9`aoDqAt&UgskRh^-XVQ#!Oh)r(+Cep{ zTU6=y&vZgoTnn9w8n1n?5mC+6U4I|9+t<%8V-3#LV)Bp?s?0d($mx6X@%zuL-Zd&Q z`xqDA(hmCl(fr){U~q0}aCTvErrSReI-4jo-dj6;`-$V9T{%4@wAqFDfcWk+h#40M zLFwU@P%%$bAfx2U?zrIr8MUU)M(S2dEyYHo1wun?sA5XuvWT1+F(qc=D1V{s;AF&1 z&47g3qz1@=>?x0g15R&*3pd0Tju5 zr#bw41cN1d^B_K&3oGg13;B0XvvH2pCy)LO?AOLqCcYxZ00000NkvXXu0mjfA`#ww diff --git a/recipes/icons/montreal_gazette.png b/recipes/icons/montreal_gazette.png index 802aa7c933d699b7c17d71783e735a903f7032fe..68e76ec96649cbdd7c2f41fbf39e312760411767 100644 GIT binary patch delta 1007 zcmV6c(KnI zc&v%I(u%m!jk?s0yVNwA%VeU&p~l;#$=#;O-6MOfs>|Q2%-%MKv$fLVxYXkrcB#D9 z<-XYF#M|h_+<)lF-s;cd>}$8%If%7xx!iB4$Z)C2aktnogRwD#vQn46A9<@hiM2e6 zwmpirdaukNd8>V~%~YAbRhhmijlP4m(1f+ng|*Rzx6&z%zgU~VDu1s+jkq&~voo2= zDuAz!yVQ`p)sV*ElD*ZFzSb&^z+IifU7o^4kGWf&!GBw@*Ib>!YNf^)bf_(XuwS3T zbgarQfv`iR(q*E>F_*{~c&(em*=D20p2XTRgtBR+#wd%uSDL>wgt94qu34M}LvJVx-4Mrqip<->=T#ug~DK(c!bv;n=&*S4+Lwr$(C zZD(xTw*A+V)XMalduOHcn*F-Jed?)zJS1))vwzn!$K8M+1w3=^Um!RE7=ZQ|fO-r- z_b~wNF#zo`0PQgV^%#KeV?kyL;K3Xm0dTRD3jt~jfQowR_oHV*Vv87wJJYm2HUYfb zSAYXQ{PscWXW`qYeO#9@)f#DXguL1OnB6MoxnN2h%SS-*N0Fg=2d}R=RU(EF@&hg; z-G5{S=86)1`Q#p;j?Hup)ajTxvheafpw)FI*$DZpB{AkM0)+{e+aDw_&dWF0E6gR6 z=;XXtdUw{aEP47?H3Gm4lo0S=cHiF(0#4f<#Q{A6_bUOufeOq&H4KB)OLNw``iU{v z@R9}*Wk8g)6z6$gSKAGZ94$LYxe)a}_-GytYN{Zu-~3Qc6|n4+7SMhN&SR)D2SqB$NgaFNx+pW=qA3&wW0$tx3FNFD?7me%byD#)mv3 d@c7;M>;RJDa#-D8a0mbZ002ovPDHLkV1hM(AI$&& delta 1017 zcmVBYUhQe5@pVttEV|C4H?YeXb~et|))5D1ovli@hm+t|^Va zDUH7>f3GTluPTnfD}b*pfv_%tur7kIFM_c!gRwD#vN43RF@KlHGK8`-gt9Y*vNMIV zGnvUVnaVVUvNVRXG@8mZn#(qavp0vdIf%45h_yMO&^n2=JB+zJiM2e6wmpirK8vxQcn7&k)zf_sNRhhn3nZH+> zzgU~VS)0IFtkYYZ!CRfdTd>z$oxxq5!d;%iUY^2Up2J_C!eF4oVW7iep~PaO$77+y zWTM1nqQzyS#AUSFW~0SsqsC~Y#%ZO-YNf_&rpIfy+kb4P$8NdYZ>Y#{smXD-*mJAO zbgasFuFQC`&v?Dvd9KWPzTSGT%zUuTeX-4ev(JLG&w|3>gSF6vwa|sN(S^6thquy* zx6+BX(uugzin!8^y3~!k)Q-E|Q2%-*Zb->=T#ug~DK(c!bv;&8%2Q$Cj&Z= zK~`YG#ei1<7X2s+u*xGTz-B(O0_^$`3UKI$D}TVHAEp4eJVXIb^Pvh%aqEXDP+}Ma z3S@UEGxWFOGhuf#%r_KFQeIX!Gc~fI zJ$QXvz;as$tz``J9M}!P3JUiZ&Z*wWur6*%HCs|0!|HzBMO}P8E7_*m7=qHI5@Xqd zzJDoAo0(TcOYH(FShZPjkr7YsLf2`rPGALQ6Wr{lPe@A6%@@jI&S+p;oLxCPp)@7B zV}5r%SOEjGPZ$rT2Y~iNlD!hc5+jd7T=F0V#(NkTL>Q_W0z-R~ioz>Zz*eWa*-;vaG4`}f n$HoeGm;wWNbYOxrcVhzpME9RSp}|h;00000NkvXXu0mjf>17P7 diff --git a/recipes/icons/moscowtimes_en.png b/recipes/icons/moscowtimes_en.png index 99ba9209ea566f6b13d1a44ff30dcce2dd8fbcab..5cd9a01bab93060f13debeaa16af07b71eb832c8 100644 GIT binary patch delta 186 zcmV;r07d_i0{j7xB#}8Oe|TQi%DoPPFc^j5(}H2GQN!B6(3Qx*N+QHn6Mt4>)UFgl z&+S_rxCVWOFDGw4{H{&82lBGW%OFf_NhQeo=t+(U?$nYKZpsYeiQvUL@*WwC3kMK` z?UDl(U>?J1^ o(Q5$m(x|JBn^y0d_Kbh_0XaM)Q+9XQeE-bpQYW delta 205 zcmV;;05bpl0g(cbBoYa5NLh0L01FZT01FZU(%pXiks&O9C{xwSyKaLp07l_sLzLB3 z6R}z_ctv<%MG8^YMCxTlRa;gJg8%iq@eIuwKOOkDy~YNrSXVK|*Z?QV8L7K%k`{nZ zd8?HV7iRMf@S`rL4Tlj`v&( z6SOmc8NZAstJP)NJYe5?_wMul>d*c4!87*5eKv00000NkvXX Hu0mjf=w?_6 diff --git a/recipes/icons/moscowtimes_ru.png b/recipes/icons/moscowtimes_ru.png index 99ba9209ea566f6b13d1a44ff30dcce2dd8fbcab..5cd9a01bab93060f13debeaa16af07b71eb832c8 100644 GIT binary patch delta 186 zcmV;r07d_i0{j7xB#}8Oe|TQi%DoPPFc^j5(}H2GQN!B6(3Qx*N+QHn6Mt4>)UFgl z&+S_rxCVWOFDGw4{H{&82lBGW%OFf_NhQeo=t+(U?$nYKZpsYeiQvUL@*WwC3kMK` z?UDl(U>?J1^ o(Q5$m(x|JBn^y0d_Kbh_0XaM)Q+9XQeE-bpQYW delta 205 zcmV;;05bpl0g(cbBoYa5NLh0L01FZT01FZU(%pXiks&O9C{xwSyKaLp07l_sLzLB3 z6R}z_ctv<%MG8^YMCxTlRa;gJg8%iq@eIuwKOOkDy~YNrSXVK|*Z?QV8L7K%k`{nZ zd8?HV7iRMf@S`rL4Tlj`v&( z6SOmc8NZAstJP)NJYe5?_wMul>d*c4!87*5eKv00000NkvXX Hu0mjf=w?_6 diff --git a/recipes/icons/msnsankei.png b/recipes/icons/msnsankei.png index 67a6983f6f0be6412ca445cdf518645f64c3c60a..c995ea57715e6ae6a956bda0777d0c9924660eeb 100644 GIT binary patch delta 242 zcmV=mzwqyl2h2Gjy30Bd>m1GIq^09zTrQp7FR zgBTP1)4G+-)pS8?N&tQO3BJSQO|F}pV1;jt$LX>(Vx$dc#EQ|9`Y2Ns|00Ns@@I&;}slDvSopyKhGHgHd3-ML0{F=h)Xa zh)_RcALTKxjR?olt@KYNqVC*&E!SfbF^Zq$b+5)IVib}*=|#leNM3&&MZ`LndVk(n wFCz47E?Cb-1a+Rhzh^@PY`wPyvIGF|1qYA_kgad=`2YX_07*qoM6N<$g6hn2`Tzg` diff --git a/recipes/icons/mult_kor.png b/recipes/icons/mult_kor.png index bc241dccef4c747d259f8e404b1a160ec2e97f3e..95009457efa6d145d99a1c953643c996d76cec2e 100644 GIT binary patch delta 429 zcmV;e0aE_Q1H1!}8Gi!+001a04^sdD0IpC>R7L;)|MFvC^Kx(f{QUQ*run_R{OjxY zudVvi()-}w?lm#|<>d5*gZtgw@JB-KFf91DweB%4@?>J}GcNE-NAOQe`NqWj=;-iL zPx{Wy?l?63@9yzgR{6)q?>jjB@9yzlTlvSv{OalOQBCeMFMs^z=I~8Q__ww0F)sV# zx|D*N5s??gcMpq}n7Df`&h??65FmzMHlVDx}~{rdX&y1DKxDT6mp zs{jB1IdoD^QvkcF0B21VUNR%|9g!y9_*VD2_Sw`!8P7WK5y(I555Pec()JqGRrnv; z`ri5|;&A*e<$rm+m;q2^#q5m$004wZL_t(|UM?4lq>|^Z)j`NwAcRd2x@{0TDF{&yg#G}8;Ru9y0>X3# z!8{c#mL4oz5p2Ej5Nvk@+aCn(ts}vblLI||CfMcbfF^Rg6YTMHKzY3t@ke0apO5Sh X(HayHDiMc100000NkvXXu0mjf{#4=% delta 440 zcmV;p0Z0D41IGi98Gi-<001BJ|6u?C0gg#TK~y+TWBC97KUVzr&mV@L$B)^AXl(Kz z{O9#+QLGXmP2X26OaGLTv-r&N9{{%^F#~@u;%q^idup3<-gnvJ{ zul&K?>E7p}{8cCtAk9CvZw-5AsQ3MyuI7Ji2K;&YMD~N1`+rT4Jca>3_wMn1XRP-P zB#F&{KQCTLe(?9c0+UBE;ODU;&hJbOKf@%^4FCn$hams+$ntOlew{mO_0GcN1F|Ft z!wmTI{+-~*$gmUW@?ZmgUA|=W-rC|dx+DmL3;=58{}>m20GmA60AO4@qB|Xi!3Ml~ zDf%JM_YyXFxPKQQfu|1-JQ#+1;n_2(558X4(B-ruUH83%?Nh7C-BK2JFEm4>7`^=>O7CHv_9A2><={i}A;< iodMY7VEE6|C$a!^Jgk3OLj>&r0000W@fk$L90|U1Z2s2)~TlZ(9A~PERcF_n3 diff --git a/recipes/icons/n_plus_one.png b/recipes/icons/n_plus_one.png index aac7b1e0dd330df60e784898d563e3c67d7850e0..04c99952a257517b064e50a38d38a97bf8b18671 100644 GIT binary patch delta 268 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 diff --git a/recipes/icons/national_post.png b/recipes/icons/national_post.png index 096439a4abcf77436765aef8dd1c875dabc4ceff..593a24b8e1ade659cd68bf97cb1846cd075714c8 100644 GIT binary patch delta 711 zcmV;&0yzEa2FeDIBWVG}P)t-sBOf6D&ISL?1^>?l|Ih`kj1TF&2>HkciEtC0fDxvL z5KBKB#h?t|v5}bb#|Ir1Jp(M2qz*R>%9l- zz6b2S2kgKH?Y{@^zz6Tb2Rt(!J~SQi!w2%h2SGO+^TY;3IUMxG2K2@TM?4!yJRA1K z2KL4V_QwYI#|HSv1}GyROh6m@$p-t%1x-O4PeL0~L>f~?8dZNs8d^&kDI*_VP8nZL z8DdcwIE6di0003%NklDI&AocyC9G!raT46j ztI64(cdR9_{mOrc4G09}?AJL9WH)w;&?Tdty5(8vJb>0{k(A(#

4iXeXeOJ!>Ek zSezHHD&VSget?GGN9#QBST9Lwqg?f+AHc!WiwF2R02FWWGnn0vsKOK?vCFhD3#pj7J)*)8ac>hgt|GTNq}{jNGWn tFu*i1?Q=m6h7#{YB-B&D13%z#-T(q~J*>zc6}12W002ovPDHLkV1j=JHC_M! delta 678 zcmV;X0$Kga2I~fpBYyx1a7bBm000XT000XT0n*)m`~Uy}#ZXLCMI#>|Cn6sxBOfUv zA1fsvEG8cQ2K{p&kIUGkk8%R7G zOFtV-KpRa#8&5(TQbZb4MH*E{8d^&kT}>HYP8nZL8DdcwXOWF5Cy8(qig6Q*aTAVo z6OVKglz9@Bc@ma-5}11un0ykMd=i|05uJb$k=-E=riT!xh!CiW5UGg}t&9(o4FM+u z#h?t6HvuOD-?RynVF4Qh>AVP&e*qPL@WKc2!Uyrg2lB!P^1}!7#0K=l2K2=S^u`AD z#Rm1p2KL1U_QnSG#|HPu2KdJY_{awN$Oih!2K&kd`^pCV$_4z(1^vtg{>%mc%?1C? z1^>?l|Ih{h(FI;zwVeO}0Zd6mK~y+T-OyE60$~saVC=?jvAYlx!LC)Y8@n-oFtEWu z5rb92nQ!>LUVt8UJsj_Oub(sVO5mSJ0tp}y0w56~5;0^X1kRPVZUZ{kx>P;Ov+b&p znX!du6ihg62vIV)XpL^UI%o>2GAzo9&e0dJr|e_{vYYZtMn_TwxwYZ-0syX33CG|~ z{RpU+Di|75Gw#tBIF%_`M&$c{Egk^2oWwH$_#9S;P7*(0^-j1=&om2weNdN{`}_mQ zy&BOZyur>{I>P219#C_)m0xx(2Ik|W!UxU@sE;q0=$jMu0Cw)4HAZQNtblrH9ipg7 z573#8j@f#liD37ds_Xj>YO~7_y^LRf1Uu#bYzA9uq&|#e1jbdO$XpX7YeX-4A zn`>7doH}h1gY9vRX2Svey4|*<8P!bfivbc50{{eIL_~j9_)lPfZv$No%M*X33jhEB M07*qoM6N<$g4^Q}RR910 diff --git a/recipes/icons/navy_times.png b/recipes/icons/navy_times.png index b73323725a5ffdb2970433beab6fe73acaca1332..9cfef96f330606f28f8f25698c2965ecd244040c 100644 GIT binary patch delta 10 Rcmeyub)Rd3@W@fk$L90|U1Z2s2)~TlZ(9q8%#$gfs}W diff --git a/recipes/icons/nbonline.png b/recipes/icons/nbonline.png index 6141897c243a4fbb181412e6543c2d35219c7693..2e7c9206743956cd598e88cac71c25a1197750f1 100644 GIT binary patch delta 882 zcmV-&1C9Lh3A_i8BYy%GP)t-s|NsB~^7H@x{{Hv)|NZ^{`~2kq807#N|NQ*^`uhL- z`}xSuJP=L{$L!pQyb@#X*=^nZ|<^Ng11CqL~>Ugr%c z_NcP!IZpPdu<>zz^_imdl%D(6+VE<5^_ZgZdWQMK$@ZnL_Mxl%<>>zO_5b?&@Mw7E z1t9c{nB@Q$<^UV>h?VO%P5tig^_!&ggpluJb@6h5``O(1xxnZZFYG>2>_Aiay20#1 zR_r`c_NcM*g_GL>4J7uYtM6WI?_6!@7%%z9&F2m(@N0Vd(ADirU;4<-=MO9Ryup(s z0x*B+AUFKrFd z&eHqa-u0QI=_5Sxd57sHKJj&g@_C2xa)Iz`d-%D*>pM{DCO+#mO#J5Q^pTwU)7R)3 zGXC@R^M#Y>8Z!R*`syY>^_!;V0UY+AtNDMx#^(tn?pA2{y~ON9SM`{o?`3rFUU2xk z!t#2E?_zZ45i9t#y#49x^pl?OX?p88P3=%){`B_f7cu|*{rRNb1ONa5VM#F}LF46Oyj3pRhn6d!LtoDFhIStG&YQ9ZAO_ zE)p+#9udGs9^sFQrpxX<1h^n?-vz)wkdGZ4h}}?9>O=rdvIgLk0dTJMq;eqb{)<=I z*9d6d{bqq~Q{v3vfh#N9pOediGd+Jw7}R&(01u2{Dl|WMi7|lsvtC@{E-Qy102P(y zs-t73;>2N5)zsG2i-Lfzp|PpCg&Fyj9{KF~m#;wptiQCjc02b)XBXAY0~dXIlw>at z`ueGT4jdSmlWrUQ4~EEL8jOsN$pZixpO`dZ0AQbT+Xeb*$;_EA_yTVY2vK7!eJ1baVYAGVpaghp2%l$IcAu5!D2x#pNQA%df$i<)& zMH~oGir|byCu+w(E<;`2;&AX_iQ|TKa1wQiu(Uz%q zIBG;2+q7lX+S1c9u+sG8nX%)J`SrB1wNvi8ZXxp^+9<&dM!OA%bA+m3j#a8KPfXj* z7UbY@8Ifu@+o<%5w9d)R&t9oQ60pZZ=7}lm=Q&kqq7fCpy#E_*x~`;!+cXPU_kfz6sSZyy-t5v>UX(enk%83+ zztnKCW!^6eZN4ERP~^)4u?M|2SgT3l$w+JAy(KtiGCh4McMm{{MCS2HP5gfI$e7$C z5YB@P#LKNlNoRc;fg#3v26dtuI2Y2_G+1ZmY)X}({wh)^f_bI7lfM|6uIno=*sZoy z3GbMr6NKFOoiU(>xH`_UxC-L-`~-me(7!`y2y|cNfFy{M7)zRS^;$*@O?VkDayU7h znCJ{|AI(5eQFp z=iYhg8+ZkMgv;r9x#QfKF8o-Vf5Au4Bwd0%iL{a#CZqcqcixcT2fq zx8>V2ykg(sh@P|y>Py#VZ#8{vB2X+3i&?{1^}4CW@ z8Si&r98NZbUa`(bd?Uzn^IR7?CjKtur0-b$gCFIY1k&cr!4H#9`i%y^$&PWiUq9Y{ z`umG9C#R|QbFW{m0mDVC+jvIgz Ln-p`0ec;qT$821% diff --git a/recipes/icons/ncrnext.png b/recipes/icons/ncrnext.png index a663796757265600bc460cc31706bd2141665c63..de2c860060cf487646ca9c6884061788aa3e3c77 100644 GIT binary patch delta 514 zcmX@ex|MZ;(Zn#tsQ+N_@83UU29S#^g3P|mDF9Rn6uD(=`Q_;GUr(R?diMO^pFfwm z1TS+7U6)tAuAp{9UH3AVz{Is4wwL)tzMa4L?b78RH*fvCcmL;uhd&=ay38kfMNs_L z^XI=_y!`#{-JcI1|9<=S@8{1eB2xeU{=F(Ge^pW;AaBX#$wrL2^^Tq{jv*Csy_2sO zH5&-HdU70#I9SF}ICXu2|NFh)@7?}i@72OQNnqZ_^S5QsWwe%EpCQqxm2#&kd}S4@ zxv`JIX1i3uE9YnJN|+wmv}vvYPf)s4X_Cjm6T#D(p7ARBxZk&a`)zj8goq7~KW}fG z&%U(lul|fjb=zwbz6S4HX(>=&q@#9&xj@K6*<8TOr{Fuw7yVSd)~2folWG~b^_9$Y z^mh0>{3v;#Dm-O6x3SMtfjPl~-7z184!ss{cpWBVs}~a4lw2Toe{qN>xAlXZlGX*& zcf4er@qUGB*D8S=#uu?2HjDqKt!97VkrrOPB7yfn*BiF2hvI$&?O^&)t-skmVrI^@ z??xL+j;ZurdsocB-gql7H=%6y*|Ysu4is>tIxk3hv+(JJS$bDa+~^igv?_jdRBE5G oX-?(Zf=cbptk=DC-gEq8V4WY8bb5yHLk1x5boFyt=akR{03$ma*Z=?k delta 521 zcmdnWdXRO3k=(z3|Nj5~4SoCN%bWt2xqwLUGPlrWK9S3OqE`gPuZT!p zm6X3Ksc>Ch^}2%E4RzgH#+DP;df0wBdi>k@i{CC?{&Dlx&wKZOK6v=^;iF$qpZ$9F z{MYm6zh1oj{qEhL4 zhpA_c{4r+kziTG4tnpeCdGr#)rq_jsUYU6|a3#*!w84RChSZPhyYF^8-Ecl2^{-w& zqAF}r#Qp}wPt_TWv*W#%)w3BJK0o4I!ty|Al1tyr8K*xzER@T*>lK@(W+v_R>NrDK zd~41OpEoPE?$u$q@s9hgL80gBeHS;Ily85}Sb4q9e00Frf AegFUf diff --git a/recipes/icons/nepszabadsag.png b/recipes/icons/nepszabadsag.png index 4ef76bb91d82ffc8e351f02f4a17a7e20d57434e..a8aae4239ed9bab0c7306fef4394037b6d3ed148 100644 GIT binary patch delta 252 zcmVASSQSHLB~g)a8-G_5LQMuZPzgF#5hAOI^7;1o z{rvrrWP_Axhl0measU7TbxA})RCr#^%Et;rF%U!18T<8~-h27~PYlE+n=st1wUGoz zQi;ZFhVbk7|A1Zy_!9t~6L@9-bUOgh7lGRdK$iqu4nTJY3v^BBw*~6LeL4HyTR;|Z znPBe$NJT)_1f~Z7nGra)0HkyPWJO@S0+90$B$5x6+y#hGxFqoa0000}c8Buxo$NLh0L01FZT01FZU(%pXi0001WP)t-sOawMf1vgCwI8X^X zRuVy16GB)OLu4gUZ8BSNH(zr*Vs$-ah*^4zU44vRevxE@lxc^NTpNGh(c0hB+~CyQ z;nm&h;pgt>?C|OC@apdK?(+He`2GC-{{H^||Nk`8ld%8*0C`D7K~y+T&B{p*fHMYsO{J12{YmaRW$mxK9|123G$R1s68KC2_?!Xoiomk~ z;5~sb1>kCs0=G-M>l8hxXTi4px~G74?iGW)0icqAjs(VU0NM~3UIAzx0O&xVcmU8M b0zd8x8(T%DanY;|00000NkvXXu0mjfIgx&7 diff --git a/recipes/icons/netzpolitik.png b/recipes/icons/netzpolitik.png index db469f309f6d4233eaf5cf14cfab7fefb422db28..7e6aaae15300ba4f19e7527bb8da54bdf521c97b 100644 GIT binary patch delta 433 zcmV;i0Z#tE1iS>0EH?lD|AVL9R*cca)$jWJ{`>s?{{H_Oal(wT;EuWBqP^wX=Joga z{rCF){r>*_{{M-q-;1r^k%lBQ&)@Q-$LHGU_1)+8rN8D^jM0Rr-H5B-#n$h}*6+yJ z@XOlqo5AFt#N}9x(pi|(TaeO`xg;uGk<($F)nc91W}wz*m)38c*wW+k)#dcp;`G-|ZEq{Tj-5+$r`1<|&`~EY5$vA_``~Cj?{r)_L%T18a7;wS< z|Nk9x!&d>+RR910#Ysd#R2Uf@!39DCK@dg3|GKlgfhg|o?(Xgo+#T+J1twI9AeZXN z6rzC_0s$#&FsmbH!+>DMrY*Q&#TGyeTGf&T)g)%SC^nlLD#1eu+JE}&L#Yg8D?B)6 zwx+*ZCp7Q-zgEuOTXgGwH|AxV#O_-e3V-f~(H__Uqc@ b4Z$Co%oWv-auwYG015yANkvXXu0mjf+zkIX delta 436 zcmV;l0Zab81iu83ELs?F!5VSG9dpAUbi*@&$vA_`Jci3nkk3|((N~PoSdG$InA2O3 z(p{0$VV%`toz-Tb)@PU2Z=Kk7s@i&_+kU9rfvMetr`?37-H5B-iL2j>t>28X;EuWB zkzyndo5AFt#O0#B<)p{wrN8Epxg;vY)$hgD@5a{e$k_19+VRid^3vn;)#dcp;`Gs?`~Cj?{r>&_{{8;{{{H{| z|NsC0|H&FjcmMzZ$Vo&&R2Uf@!G%HsQ4~edBj&Z+Yj=y?jvd%y3p%5K`~QEzhii!- z7cHd=@%|TqfXK()N6GmSAXxKN4<6X_Ef5FZzvICGCHFIwxNjV0;eR6wv%UVKC=HoG z3$9ro?z_?rQy2DchI_MVy_&r>SXn!XS%E7| z`26uRE&#Q=$}82%9lHaun2ui5e5G&{+aS1D$ae}N)U$1$gt>Ocy*u|2gI->EfR^~3>k`|UL0QDa!Hn^ zkyJ{OD#wa)kbaWfAVOwzhWQ@F4;$!zJ+*LJWzx7KWBc7?K1P$ljiXVnowYDF6at zsQf=k8Nty;NFtHIXg~l;fJqS)he=anF-?+~2I#_~C_si9$j~xcP9bmr`~h$VN8Aoj z_INI`w6ugPC;%&166RuO0356XRxG#*XOd@D*7q?aF*FecigtCM}9sVl&e)} zJsf}7P?5)q%Wa*gD|`BHXUAR0I29LQTzARto<-{MX`@|wwNpWS%ChgSs_Q&Pgga_u z!rTO^vebpyNBwoWfw74W%~~th-Zd@0=ezrQGoPNGQjHd^D48n$y29n?MXw>oZ>WE) zk{}EQLKT70nM$?(!RZ7~q3-^Pzr#OtUwh53R+V+TR&^IYP4k!LW*R#CK6qMo<9*-# zrH&Cqo^#-6?X8rufa)v320h;Gw#Ac`12L}Z^;3Sy?L0Ch%2PK?X~#CE%vx>zT>swE zuZd`4cS*>3qt;=Eiq(eEC%dcn?DHDpM`*ng*L6ku*Z3x-#%8HYbSsY({^kG4Ce8L< zyG?2~dNBHRO_j-d>9el-oF?K-&*Wh@FMf7fwf>-Lh2e7#!Bj~;AvqYI5hs{?DAP%d zlst*6=^Bq5!~9J2$1CUyTZ43E%hh3T^Soc8)2a-nU;T5`*Va*@%WgC+;GfYPN&6<# zjQ?4PN2^cdWueJUug)!z$Lf=3i?5AXYwwHL*wsSSo!#{g`%;Vp%<{+l!u*!;8ZX)^ zn%=eA#m0Dz-5n>)183Gz!XRIx@@B#&a{nR1H@GID(0ab@f$6=Hyc5G6YT4dnE{6^n z&vYcc^SEJJ?5-3P+ziroI&XWsTDe*K+sJFFiF_xcMf*k--+u(z6!kV`JpaBqWb)I} zJmsU#{F%kC23Kw4%c7LmH@F)^xyu%5UToj>1jB@#_xTzNwHJ;sDP4rLd s#=kw8v$(kPxK2O5E?1sdl$&lo!Dx6LX2woD`~f9GgTocoLGk(j08}!e3jhEB delta 1369 zcmV-f1*ZDl3gHTn8Gi-<0047(dh`GQ1t>{GK~z|Uy_Z{TR7Dua|Noh@yS=mywApb0Jye&cOVe_sq}i-7?U9A3#5ru^GPfL zun>R-K$5HsIDa7#5ddl5H}9VJ5_NPl*g$220BMsj1TGMUV|ssJVF zsBN!MmbH+8_`YwHwzeo>V?Li-6AU)2HAein*Z)zaqubGOi`CHh4S^LCGa@2ljK4FU zOukdzS5-Qf$?s34j>Z#-{rTbHqgq?9De_5>*ANJJbNC@~;lkOxUwjc|yND!7y{LVKaU z8PLkC0uih;IRL;2GZm(3gRrUv5v3N?JNig_WU^6il6$+|=zSviYqXeuJ)KU+C)zqXIyxb;tl9u70jQ|9 zkqnj&lX2rL=YjzLT)I5GE1S(;ny^J8k;N8NFCe#4SatDM0)$AcF~s`5W5dgYf zHw>U0;M&;OSRvTh3=pBH}FH{Wav91~$_!sDDh}SeD!uBYl&Bh)>QZ6MJe%Jdr)8l$ruj ztr98#OH0W`jQkk)5`B{a4QHJx0HAQ{9Z}?uOeQl{GeEdKybM(9Wn2eyrnn8`7m%7H zA}wXl#)$bEfL|*>DA1sG6KE8XgW}7l4YclzL?TN8G>J&nIsxM5GJqo^xm;gmmw#E$ zf@mbNLs2~d!iQjuveZ2Q5QHu)a${Ej$%AsD%W>1<=Z~MyWa5~*$TY#2h=@Q^dE-Af zI5>E9I^zrqhr`Q5+WMK~{AzPh zg35YX5vz^yzsno*hUa-F0GLv@v_xECPqHbnc`bD7N#ixy32L<5mL0yCHkML zMn2&mv+diIvYv%zSFuodJCRBqMIEJhQZd`^>5j(Iwr$7icK#oltOEsd$S=yqR4SFY bkpTY!$mTBtUKiqQ00000NkvXXu0mjfLXv}L diff --git a/recipes/icons/new_scientist.png b/recipes/icons/new_scientist.png index 65507ebe324d786bf7e396f24b94f99727364339..20a4961bebb443bb2d2bb3657bccc1e8ce23ceca 100644 GIT binary patch delta 498 zcmVeVq|4!X=-e6adUHY zcA1%)qNAj+u(4HDR=~l*!NS7){QUj>{r>*`Sy@|ygocfckB^U$xVX8yyu4jqUv+kP zc6WJwet*Qp#>dFW2?+_q!^HOX_V)MpsHmxKZ*dF^3}InnvVXF(wYImnxVe&(l#`Q{ zla!U0n3+~rS)81moSmMYo}i$iqFr5Jr>Lo2USO!Hs;#ZAuCK6OUtwQgU?L(SS65tN zVPj!pW4pY)V`OJNJw0Y;X=rI{S6Ev}NJ+`c%FE2m)z#M6*xB0J+U4cu<>uz+=jiC@ z=u%TtR8&=2S$|yj_xM{|T@euxet&^~e}R#@Fhl?V0KQ2?K~#8NHON(x#V`y+QMF_T zA2Z`HGcz;u{ueSzQn{rM^`W$%%i_`8cAXPA{y4LiSx*$kx4!5fgB6;6@5V!mM952K zYC@8Xv>Or|9;|bCUv@U1ULF^B>62Yd1{s(ZqArLPP-{>)$LPA3_vs{psR4|O=cdO( zj&)JR9ZaJFnAJ5Bg<-V@<75{MYQH4xVrK#h%!jYY$1pupfhn}N_P(%89D(h?l%Tzx oQt|sQ*O#l@O0cv)zma+P2Vw0JRb4Q@XaE2J07*qoM6N<$f}`;NRR910 delta 509 zcmV_~R#sP8S65nBS6o+E zT31+GSXf(FSzB3ITUl9LTU%XRTwh&XUtL{bU0z^bUtwQgU|?ZlVPRrnVPj!pV`F4z zWMyY&XK83@YiVk1Zf|jLadUHYc6D}mc6WJwet&*{fq#F2gMWmEjg60wkCBp-l#`Q{ zla!U0n3Lo@sHv!_s;#ZAuCK7Lu(7hTv$eLjx45~u zxVgK$yt}-;y}iD`!NI}8!o$PF#Kp$P$jHgc%FE2m)z#M6*xB0J+U4cu<>uz+=jiC@ z= z$jXt+8z=X%vg88??NU|c4HEaUv)Em8_p-G8^7Hb?&-miz{rLInG&$=Q#0V+qo2mQS+v+zv_Nc1l00Qv9$@=N+K?KH70001nNklNdls`yDE%8EozL6mw*C1 zjAkG?F2+4T@(fTw2+0t}{a^vOxx3vSGcx!<*f%tR#O{L}ICKOQ~%`{U;;6U*1GUN7~Gp7IKQC@Os>AogS1_7@A+9pTWN zxTkS)7Nd0iuQO-=JbC)--1&bWKmPvk`O~S3@3tNMaNy*xmv7c?e;^3-0b`Q4y9+}H ztE>l*!(QU)>&pI&U5s5yYpK)XCqSVDPZ!4!iOb0e3X%p!22BU!#W?=XY;Bw_D9OP3 zBYRdP`(kYX@0Ff!6L zFxNFQ3NbLZGBmO>u+TOzure^%(0MH$MMG|WN@iLmng$0X9S5)EJqGHK1X&TBpH@D6&0Zu7qde{ z(29!8f`Z+vtFuHzq8J#pNJzFyO1Df*w@yx(0s_2QS-e_Wz6f4kzF%LY9v+du7)YB3 z2g`kZr63@tBO{y&3aTqB(T&?xd5fPIg0Um##6BGOO_51ht{rdW@H#e|4IpzSR00003 zbW%=J>Gk#aSv;Bm008PqL_t(|Uggo%Zp1JY1kj{2*TH4RGBYzXGyng8)X8qMeW>!- zR_aK$$JTkeHcW}ne*s-~e0~UcN-f$mN=>**sd2q>D#&y-1g6*)aoKZAoE(1=JSLeX z0pe=84e|>>vio5_v$BPQ&BRkKH3@hwSpJ;D5P(FXDe`!YIxG<%Slhq{+Q0T>-A24EL}FD`)dIiPUX0%^x_ z#^n7~gEw6(lG1x^uzxD^*S{?L_knbP8FUee-t_skM{@}8-^4J?_awq8J#X8yln^9;F{2r63@tBO|CMC#owe zsw^z3EiJ4vGOaW;t~WQZIy$jEJ+VGMvqD0%LqoGfM72jpwSP!Rwn|F3OiZ^%z}c@ zii**VjnR&d(vXnWnwr;~oZYLd-K?zMtgPOzuj042*`|NsAMC8iGm000AYQchFp_4W8! zJemLi0QgBnK~y-6<&qw;aCIKyyzJCG*)qYu5T@F-rNmWa#s=C%AB7frfkkEM7`((VjFxsV= zmCZ=weft;4Y?0N;*R%ZKA8)CSqNw&jSh1pE42vddf*O2ph)f=@wy`cw*77v z0hp@WZ5vQD3tygR8zqMiSl;-s2*C8C9cPf+HO`;1x<3$ukmTY*Jpyn-vWS4>%_dpv zj6u#h*N5>@06JsNxo%{ga}%vItNG^o?!N$h10Ny+Ai3ekJ4BKW61(3WJU@M(?DsYF TM0@)J00000NkvXXu0mjfK}Hxh diff --git a/recipes/icons/new_york_review_of_books_no_sub.png b/recipes/icons/new_york_review_of_books_no_sub.png index a30ab4d154473a069731a1258c2fb2ea4b92e62b..4ea30041387aa8e57eccb87e14593e1d8bf9d348 100644 GIT binary patch delta 524 zcmV+n0`vX#1kwbMBYy!DP)t-snE?Tr0Rfl+0sj8}|NsA)0RhZ_fXaJ&m;nK~QBl8P zVX;0wvqD1W#Ki2-(4`+As3#|?EiKWFjkQNdqZ=Fg^z{Dy{j4%Ftu!>D6&0Zu7qde{ z(29!8f`Z+vtFuHzq8J#pNJzFyO1Df*w@yx(0s_2QS-e_Wz6f4kzF%LY9v+du7)YB3 z2g`kZr63@tBO{y&3aTqB(T&?xd5fPIg0Um##6BGOO_51ht{rdW@H#e|4IpzSR00003 zbW%=J>Gk#aSv;Bm008PqL_t(|Uggo%Zp1JY1kj{2*TH4RGBYzXGyng8)X8qMeW>!- zR_aK$$JTkeHcW}ne*s-~e0~UcN-f$mN=>**sd2q>D#&y-1g6*)aoKZAoE(1=JSLeX z0pe=84e|>>vio5_v$BPQ&BRkKH3@hwSpJ;D5P(FXDe`!YIxG<%Slhq{+Q0T>-A24EL}FD`)dIiPUX0%^x_ z#^n7~gEw6(lG1x^uzxD^*S{?L_knbP8FUee-t_skM{@}8-^4J?_awq8J#X8yln^9;F{2r63@tBO|CMC#owe zsw^z3EiJ4vGOaW;t~WQZIy$jEJ+VGMvqD0%LqoGfM72jpwSP!Rwn|F3OiZ^%z}c@ zii**VjnR&d(vXnWnwr;~oZYLd-K?zMtgPOzuj042*`|NsAMC8iGm000AYQchFp_4W8! zJemLi0QgBnK~y-6<&qw;aCIKyyzJCG*)qYu5T@F-rNmWa#s=C%AB7frfkkEM7`((VjFxsV= zmCZ=weft;4Y?0N;*R%ZKA8)CSqNw&jSh1pE42vddf*O2ph)f=@wy`cw*77v z0hp@WZ5vQD3tygR8zqMiSl;-s2*C8C9cPf+HO`;1x<3$ukmTY*Jpyn-vWS4>%_dpv zj6u#h*N5>@06JsNxo%{ga}%vItNG^o?!N$h10Ny+Ai3ekJ4BKW61(3WJU@M(?DsYF TM0@)J00000NkvXXu0mjfK}Hxh diff --git a/recipes/icons/news24.png b/recipes/icons/news24.png index 68bce32d2fbb5feb1be8f14ca84eebe332f2feae..45413c372d32c1281c7c29639df20103f7e33e3d 100644 GIT binary patch delta 1912 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 Z`VTOBr!@#lRR;h7002ovPDHLkV1n0b)Eoc+ diff --git a/recipes/icons/news324.png b/recipes/icons/news324.png index f39e1aa486e879fb12a8629ce4de48d607392c7d..68f6e394569fb6e73224d1466ecfaa44f988e0a8 100644 GIT binary patch delta 1440 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 diff --git a/recipes/icons/news_busters.png b/recipes/icons/news_busters.png index 2acf74fb69d355f7085251b69a2a5a0d0c7f1b37..91d5c85e86fc27f9e0e10a1742cc8e6b972aaf89 100644 GIT binary patch delta 485 zcmV@{uop&Qgd|dm%>fsp4yHK2MaZhEHM@|P?0m4GqZ3RDnt9cO4S&&s(|y2){C8eT zE$J{2Ay^Li8j7$S2TDQSmV3u$&%)1&7N*?506691ikbj%09qpKgkc;CSWU*hiRV2C z=PmeKPJ#s8g(Uku&;THm_|0(D_P<(1R80tx2fE)>#EP|gi`JA>6{-~x+|h3nkV^Kd zmDk517H8jD(nO}Wfq#9S*h{l&$*StB@5wYXVz_yWimIjJ)NFG@y8#jew`CDnM`Bmi zl!5M=2?^`~=Q%Bz%RT>|8hZEu`|slIM+ZdRRsNtqMj&xjvr5oi8IS+kDz!4IyaWhKji?*D&fkdtZLrkQEak$={|4#VmEFb>0O zFVEcZgl|qqCm-A!e(>gGcetVn$-K9ebBci1i00af$QjLS@UwZSN62QXDPngPIiS%v zZwa=NtR;rBlsE_M4$`B^fyJ+UvLRvDe@lexbJ3`50xSaPMDSo9O@zX;DbNf+M9$cmFI$*zB^xsM z5V8Sh5g7mkO?Qu2N$RR36PXc`A4KE;AYsK?XC=AjOcG>8f zlDg!pG)Ew7&Lk~!S4b@+=Q_Xs3DRR1Esj6n94&1!)UI?5M!fhB%2Hg<9H#7$WE ztV~XRbC`@z(sYnNbQi1qRPEBqG&k-P7CtQDs(Wm4`CqL`Pv6KJv Y4osvyqA!VzzW@LL07*qoM6N<$g5X04fB*mh diff --git a/recipes/icons/newsbeast.png b/recipes/icons/newsbeast.png index 9c3a01185c42980d0b33c783df1fe63f77e585b5..c1ed81a1790617cbdae8937b0a98d1659f4e15f7 100644 GIT binary patch delta 20 bcmew)u$*s#^2V0mY@7_9u6{1-oD!MFdh=gi}I5QJ3cy%VnStTavfC3&Vd9T(EcfbP0 Hl+XkK!A5P> diff --git a/recipes/icons/newsobs.png b/recipes/icons/newsobs.png index a65008ea04e73f559b2e08d4269d6d2b66eaf34a..7e8a58c91d8585ce360636cb67b48af06b80f353 100644 GIT binary patch delta 347 zcmV-h0i^zo1NH)tB!5CsOjJdg005Z)0GR*)nE(Kp00960|MTGB`S9?}fPmScpub>X z#&B?%006T5*9OfEuZxNXi9-#0R+23y`_PY&$x3cw@*hQ>PD+%Bp8Xae<0E)7+ t4GWDU5k_LxW>yH~@BhpHBff6p@eMJwE@YWG(YXKs002ovPDHLkV1luYo}>T( delta 351 zcmV-l0igc&0*wQZBoYa5NLh0L01FZT01FZU(%pXiks%j~t=D@)0(9rYX;Q8?I|NsB<95qe=000GaQchDKFhGF7 zBm4$00002sNkl^MLix| z)b|ZyvDKY_iO@^59*%_@1ql6EBt?J{4@KRCorx77pNRsvlRmQKA=|N7CG#bNOST+L ztI-yqbtz1Qvms?5l$VD86tSC^nfUAKLOTslDg-Hp_lO)=Nr2b^rH~B(30Ekw(Dx&z)vT xEsvEf4eQG!m!*u6Jz!lu-T9;L5sf diff --git a/recipes/icons/newtab.png b/recipes/icons/newtab.png index 42f269097a5fd80d98468a476e1eaf516e648aed..a563ffeb1c8aedbf32968b9f84d28ebd960a5818 100644 GIT binary patch delta 557 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 IM6N<$g0;7ETL1t6 diff --git a/recipes/icons/nightflier.png b/recipes/icons/nightflier.png index dc616b95cc4f6ba02809a4478fb8c36bda1faac8..623e9968aaae7ed429b83935d90ee0a4fade1b05 100644 GIT binary patch delta 199 zcmV;&0671W0*C^TDFXli0Ff*tfBt3yqa|ph00001bW%=J06^y0W&i*Hj7da6RCr#U z(Y*-*K@dRUX}}KL*u-or5yV0^wgo%TK8#Hck9VM<;Pj#l%yKhE-U~klW`9I9B(jR| z5KS$GXM_E924{e=ba-JMI;^pV4o56b4qK=dC=M{Gz600yK#H#<$S?RmGGK5$aOVV2rqk2KF+^ix@{ZFD zOz(_h97G?aE)Yx5u_{(eVV`thj%+n3C8Js1_mD%a5#jpa4E1TJP_G+Kr)D>r}v=qF{NnkZ4x&V6-3)F%;Edq%j5Ha zVb%BZb7nROIi#PPn{r&wDOnP*cu@OpnQhc3;qq~Qs zyEtf%maoK_vBjFQ#;w57!qD7RexKXp>fh$=?eX;X`1$zx`}+I*H)oFi{{CZ#r&@xd zAXb58il}Fcscw<2Icbmk`}~-(#depnxXIUYl&>OJgCkgjcu<(Ld6~0?qPiqmgeO~t zC|rh$roD}*zmu)Pl&->+uEQ;0i7;Y{F=LA|V~nS~&8fZ4tG~~zz|b{jjW%YExyskQ z&Dp-q+6_l{!_nOiNOzME0U2k=^C%MRJzl=k4X|@agXI>hJUG@AF7>nDX`a z^Y!>nd7DvtoeM^GRDGZP{QVbCd>&MQSAU>I%=KxLaRC;80QN~lK~#8N-IGO+gisI$ z9|H_u=>|PCDr>#+V>VGIcL-S;01)>FzL4N=~a05Vb3r2PgM|TcLcM(c?6HIy+PkbI!fFM?ZB3FYWSc4>4 zgeO~tC|rguV2LndiZNr0GGmN2W{oyxjyGqHIB1VKX^%T+uEUnE#F(+gnX$#1vc{*p&8fZ4tG~~zz|gJ0(YVRixyskQ z&Dp-q+QQJ>!_nQu(%q8~0UfPn*-{$S$=k4X|@agXI>hJUG@AK{P^z!xh z^Y!@l`1$zx`}+I*`}_R-{Qdp?{r>*`|NsB7YjqEkaRC;80R>4!K~y-)-IG^S!cY)} zZ%6`bAz-f*Q6!253&e(C!Gf{3AT|`xaQ^?hnaeBQ`_6gYZ)eWT?#CEYR86N2(KS_J zUVH)iHvylwFPM9T!c-DQWtte!m|qO&j3ntEAcq`f8Y5V^D)re9iaCZ(iWskLcGWG!fb?mlZ!Z-`s-9FGUao(~rhcyf9}I~?XT|ds4FCWD M07*qoM6N<$f^qyY4FCWD diff --git a/recipes/icons/noerrebronordvestbladet_dk.png b/recipes/icons/noerrebronordvestbladet_dk.png index 05f107580a440e778ae50dddd11075d6fd1379df..c531c3367664939381070bac54b85e49bfb4ebe8 100644 GIT binary patch delta 234 zcmVM�r>*QkDu>>!={w1_*OkNph?qdt(3-6zJ7FpnYOUzu*Cz zPigi+AqgH*$p^;-s;Uq4@jM+H@T3*qfove{Ks8WrFomQUKv&Rl1IxGoE8s1#O}sz} kmKLQ7fVReOKFlAy0lf$XIWmX@yZ`_I07*qoM6N<$f`)Wy-2eap delta 273 zcmV+s0q*{W0;~d%B!3xnMObuGZ)S9NVRB^vL1b@YWgtmyVP|DhWnpA_ami&o0000p zP)t-s08MrfSbrd6hcIrCKzf)^g`r@Osc@UJfTp{TvBRId%&*7Pzti2%-sRuv?(g*W z|Ns9egHrzh0056kL_t(IPwmb@8p0qDgVDcsuuuxk{ZH#+K!06K^#Gb(^1VgSm+&*7 z07;URfU*N7pR;sf5&<1F405h{bfEJh+o&eyOAiXZcz?VEhwUM69tV-%$C-|H{@H_xB XhAASSQSHLB~g)a8-G_5LQMuZPzgF#5hAOI^7;1o z{rvrrWP_Axhl0measU7TbxA})RCr#^%Et;rF%U!18T<8~-h27~PYlE+n=st1wUGoz zQi;ZFhVbk7|A1Zy_!9t~6L@9-bUOgh7lGRdK$iqu4nTJY3v^BBw*~6LeL4HyTR;|Z znPBe$NJT)_1f~Z7nGra)0HkyPWJO@S0+90$B$5x6+y#hGxFqoa0000}c8Buxo$NLh0L01FZT01FZU(%pXi0001WP)t-sOawMf1vgCwI8X^X zRuVy16GB)OLu4gUZ8BSNH(zr*Vs$-ah*^4zU44vRevxE@lxc^NTpNGh(c0hB+~CyQ z;nm&h;pgt>?C|OC@apdK?(+He`2GC-{{H^||Nk`8ld%8*0C`D7K~y+T&B{p*fHMYsO{J12{YmaRW$mxK9|123G$R1s68KC2_?!Xoiomk~ z;5~sb1>kCs0=G-M>l8hxXTi4px~G74?iGW)0icqAjs(VU0NM~3UIAzx0O&xVcmU8M b0zd8x8(T%DanY;|00000NkvXXu0mjfIgx&7 diff --git a/recipes/icons/nordjyske_dk.png b/recipes/icons/nordjyske_dk.png index ec95fd360ff747a6ebf4e6aaf42f759cec3d20cc..b75f4b0a81b7d8bf1ab4e55ac296474634cbfa10 100644 GIT binary patch delta 3143 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 delta 3156 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 v7X|4EcHGE-wzy00000NkvXXu0mjf18>%Y diff --git a/recipes/icons/nos_nl.png b/recipes/icons/nos_nl.png index 78b6c7293f287fd20aedbd429d69a45b0dfd627c..216a2de4eaed0b973ba6b2627b213484d78e34c2 100644 GIT binary patch delta 222 zcmV<403rYU0{H@vqkq9kL_t(|UWJoc4#OY}L?;VbS+YUG{V!Un#U!Y$=6zXXBM)HX zrt00Yhp;cMukeksK4!dCD5>=s3r=0*LA%B;))txWh(9>Gd_Lz(jyCmQ;~&Q8CTZ!|uA!0&nJ^a%|B zqUJvhN(j|YwH5(jEMJ@9drWS&!i@kc{n@%&R#y}YgDhfmZd!~ftb{G``B}hA9u_WJ z{k8W)rX51I)K#D|9T2m18=uH@qy=OMGD)&bvQcEK$zjpsCKrZYIdW;~wWkkw;Y{{H{|{{H{}{~40d7?RK#lFt~D&l!==o*5$?lhGNH z(f|Jb^ZESGjFB0V&i?=ZpBN)tReFamK$ajc8;{QI_WPe0C7&21=Eca{?e~#@8zh&0s(Z%EQ7?IK$kyg4Fe}ykaTUK|;e3~)< z0004WQchClh_k-CH+#G?Z6l4@ca+i~s-t07*qoM6N<$f)*a5p8x;= delta 326 zcmV-M0lEI21EB+uEL8vh|Ns8}{{R2}|Nj2||Nj2|{r>**`TXtn`{u>S+wJ$-?f2B` z_S5S1#pCnE z{}Eo4^dGaD62QFG`Wg1pxZwhuY>pw(A)8BJS$-j+dzzPlY5g7%eNxi_rtOW0&WWu9 zo7(Ll2z0)bz^bu6JPlvQGzBoXWz2PN+fo5jZrASgSULh2qFNM1Ng@J6R2AQs4kG;U Y1;1VmN>ZSs_y7O^07*qoM6N<$f}S0yC;$Ke diff --git a/recipes/icons/novaya_gazeta_europe.png b/recipes/icons/novaya_gazeta_europe.png index e7d65296ad8fc331410da8435898a9c5221f8620..f97f16877662c2e7538c456d0ee45e7327eb86eb 100644 GIT binary patch delta 2306 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 delta 550 zcmV+>0@?lG2A&6yBRBvFa7bBm000EV000EV0q%=7X8-^I8FWQhbW?9;ba!ELWdL_~ zcP?peYja~^aAhuUa%Y?FJQ|T98U_HQ?GkN(kwF^<0IcH?UVxEehXxutM;kjxk?$uS z0IK2utK$Hyf&ko(!Eib%G$FuWcscmAo_%1o0E<3T~D&enx<~yQZz}6Q%%R< zDIKKEk@T_>1?{xjmh=#=x-6}KzDb&I01&6$o?Jsfd75@7a^ z4?Q$|lnh@Pmy5I${(2~F~*?R3Ymq&pic@phr^&Pg`8uILGKhYhXjMx6*7Tt z8g^Ci`}l`045NjH<(=ch54i>MvpjbZNzrQQmt6U3pqF+Ba*Y6pW@ze$U{{Q_UP=*3Gd2f)f0yuQu=kDO=@oV$PQ-1&e0Fy~XK~xx5UBxwG zL@^LV!G1j>>GPtQy~g`r(2){XQp({5!y$(IAVC4Fk+H_J!)fv2)sBR#_dF5u!+2NZ z-({ZX<_t7sDyH_as0GtQooHm(yYK?bXhJ&!4!E z^2cyn<+o*?=jAog=1Hx*s2;A{(-J4;r5x|Ie{@dLbE05BTpdJs81BX`I9{YlXf*lAy12m8!EZIKvtG;-5cI1{&2hb*O6q0000< KMNUMnLSTY)nx~xr diff --git a/recipes/icons/novinite_bg.png b/recipes/icons/novinite_bg.png index 381255a1afa8c97e5706e583ec9487bd66250f7b..c7641c4351b868fd6290abc5c2c1a6300ba0526d 100644 GIT binary patch delta 1234 zcmV;@1TFiO3hxP!Eq@IWq81we|No>K9;O~5ry(V@Lq@hnO4XH@{rdW$5*4Z{Evqaq ztS&LFF*U9=H?KB1u{u4nK0%%e4x$tnxlU5LQdPTDSG-qQy;xhqW@*D{YQ=4D&xeVo zAS2?nw&uaY>dMON&Cc%C*74om^5Nn3>gxCM^Z4!U`SJ4l^MCZ94ic#*DfQ*%@Y>q9 zN=?#@j=x@EzsJYHUtz&wWWr=;usA!y%gd=KE3!O4#&2=PadXGV$H;Vc$#{Cpe16P+ zfX#t}qZk{}ij1>BL$g9f)|Z&p*4EgapW30K+oh)5r>Nemt=_J$o(Bu!x47fDy5zdM z+9{&(WoOQ?(XjI*VwpAPVw8^tuQmXP*e2c;;1Ai zpAZ!G_V)Md?7LQ3sV6M3H#)vrUHba^`}Otw_xHYBU%tu7->&U&%|nM-{0Tg;^N`5wBfU~;o;%OadgLYcDbCK+<&I1v3q^wyuIYo(&XgifofvjI)&4PsN&(O`y&CZ2}&e74YI6Lpy*w2}o&&bHoh>G#? z@xg_M(bUwz#>di*kkXNp(yy@h?CsQ&l%+^Z)y&MROHaL-nb(?}y~)V@_xRbKpu=ix z$R}&NhRn%Ndhh-+oU1@oHA6Od$kvE>)fQ3 z)b1pj)e_hP1mvfI7_AoYW_KF6X3rFWiv<57Q-A7@aY?YyK6@Q;9wWg=qp+;Q&h%B? zWZg;Nv-i{&X4;8jqXUx7<31Fa38fyAv)bJGu!PhRv(h1Dm!0Kye`b3x3a>y&kQCE= zr=vfFc7cE=jGRW29F-2LYzuhH(6~3Pg5THL=b)D4Dz&+%>$xW*+u8D+O9jZ|sHLQe z5`UWc;@+ybQq>#Ibfp6Chyju@g@(~DR%Rmq2exf(J&cmTdvAFOQiE#7uwfJ1$E~4^ zp8lBx7$}g*mt%^0{YpVyVn~yD6`Y5S1Te^QUM6BOWy5rim5sy)GsDvR+!om7#z0H0 zY&2G>F{ttHfKT0KTJu|@V{8~ez_SQ-=zk~7`PSFR+k99I7&QvnD4dwWKnb7=A+)uj zH|69PSb7m!yMLHgNgYt9w_{)e(1Yt{RAx;ZP3G(fLd~>VK1)EJuwal4Ks5%TQVq=v zWt|EL!_h1klaP;sMGU+Ypt|v;QB0`{)g_e?6lt_GWUY<|;0y*_nHH1^JDhZk#($); zL4yyC=pU(|ZVmui>;v*ppa!8jJZ~W}C;CjrYK8g$w8n?U!u`lq%=1u;r9}>Taoxtn zL9oVqOASDjPXsT6Oa8>Tg8`FP3j`96c=dmtPyH`m=&-;@Toj%>KS}^@b^h_o1}zR& zILZe>lmx>1*u}R8-~E~Elt?6<084&*a%gORH4O}(`1Q2>^w}TVZ?nO|)v@3z wmjr^i&~&@}S#Ivf&CQ~suXme{495$gzZ}MX-dJIUTL1t607*qoM6N<$g5@EmFaQ7m delta 1272 zcmVvwn$62 zN=>*+PPj}@xlU5KoSeE)Q@T=ByHr=ZR$07PS-nY2y;xhlnVG%G$i7-#zFc3vjE=s^ z$-iD+9?6&(Q7B z(eBmO?(XjI*Vyma*znrg@!Q<--QMx>@$%u}^y1?6<>vP4>h|{b_v`HU?CtmR^Z4!U z`SJ4l^MCaE`uh9z_5Anu{P+0%`uhL>|CcVzP5=M`y-7qtR5*==llfN?Q546KC&SBW za1;$J5=cq3al}ZFC`~ObkwQx|Gh3*P7Sggb(`>_~1uZbqkPtLBZQh^QJS0Hqea>YA$ScXCWy%VMz37?hIoCHA^SiSF-;x1R zQl*yF*Z2BS@R@Ubb1Rk=!gr*s2+}1Q*3f86mXF&+q1pMhG_cSvSCmPWmHLME!E}>W zTYEUKGakl(&UKxXN>x>L%~oeT)tEIWbAQ}pfzuug2hONdFHZBE327Es>0EmdRuo;{UrrR9} zoA^=Zk(837fAVwoUOWeP^m>C!@vHt6E)%H?hGcVQMNY+I0_T{fn&m7#B zn}1wVh6DJ@#TZ=6Gso_@6SJc?9{z?-+-@?h7p~r<N5cAIRNT30Pkfq?`1XcX*&AP!u#0F{Nvi|I{@rK z1?n#V{O8~5G64J8&Feu0>p}+XLI&(Z2<$})>`M~uQzY(KChlS{>O27JJOJ-zHt=UR z@MbmZJpqxA7=PMj83Edc%LD)&74aL2UK?-~e!(XVR!TyJ`0z*U3)1NxI vf^V5Jug7#x%|l}?7JBb?w!Cj#_sabP$JPs6zOpr&00000NkvXXu0mjfUIw(= delta 351 zcmV-l0igbv1DXSnFJS9E0qZ*e>O27JJOJxC0P8vc>Nf!DH2~@}0O~XV>M{W8F97K; z0O~FP=`8^3Km+VS1?xct>p}+XLI&(Z2<$})>`M~uQzY(KChlS{?`1UaWi{_+Ht=UR z@MbmeX*!XQ7=QZD!urv|`qIPu*v$Lc&HLKV``gg`;MV-&+5F<#{NvjE=imM5t<80d!JMQvg8b*k%9#0E=&q@#9&xj@K6*<8TOr{Fuw7yVSd)~2folWG~b^_9$Y z^mh0>{3v;#Dm-O6x3SMtfjPl~-7z184!ss{cpWBVs}~a4lw2Toe{qN>xAlXZlGX*& zcf4er@qUGB*D8S=#uu?2HjDqKt!97VkrrOPB7yfn*BiF2hvI$&?O^&)t-skmVrI^@ z??xL+j;ZurdsocB-gql7H=%6y*|Ysu4is>tIxk3hv+(JJS$bDa+~^igv?_jdRBE5G oX-?(Zf=cbptk=DC-gEq8V4WY8bb5yHLk1x5boFyt=akR{03$ma*Z=?k delta 521 zcmdnWdXRO3k=(z3|Nj5~4SoCN%bWt2xqwLUGPlrWK9S3OqE`gPuZT!p zm6X3Ksc>Ch^}2%E4RzgH#+DP;df0wBdi>k@i{CC?{&Dlx&wKZOK6v=^;iF$qpZ$9F z{MYm6zh1oj{qEhL4 zhpA_c{4r+kziTG4tnpeCdGr#)rq_jsUYU6|a3#*!w84RChSZPhyYF^8-Ecl2^{-w& zqAF}r#Qp}wPt_TWv*W#%)w3BJK0o4I!ty|Al1tyr8K*xzER@T*>lK@(W+v_R>NrDK zd~41OpEoPE?$u$q@s9hgL80gBeHS;Ily85}Sb4q9e00Frf AegFUf diff --git a/recipes/icons/nrc_handelsblad.png b/recipes/icons/nrc_handelsblad.png index a663796757265600bc460cc31706bd2141665c63..de2c860060cf487646ca9c6884061788aa3e3c77 100644 GIT binary patch delta 514 zcmX@ex|MZ;(Zn#tsQ+N_@83UU29S#^g3P|mDF9Rn6uD(=`Q_;GUr(R?diMO^pFfwm z1TS+7U6)tAuAp{9UH3AVz{Is4wwL)tzMa4L?b78RH*fvCcmL;uhd&=ay38kfMNs_L z^XI=_y!`#{-JcI1|9<=S@8{1eB2xeU{=F(Ge^pW;AaBX#$wrL2^^Tq{jv*Csy_2sO zH5&-HdU70#I9SF}ICXu2|NFh)@7?}i@72OQNnqZ_^S5QsWwe%EpCQqxm2#&kd}S4@ zxv`JIX1i3uE9YnJN|+wmv}vvYPf)s4X_Cjm6T#D(p7ARBxZk&a`)zj8goq7~KW}fG z&%U(lul|fjb=zwbz6S4HX(>=&q@#9&xj@K6*<8TOr{Fuw7yVSd)~2folWG~b^_9$Y z^mh0>{3v;#Dm-O6x3SMtfjPl~-7z184!ss{cpWBVs}~a4lw2Toe{qN>xAlXZlGX*& zcf4er@qUGB*D8S=#uu?2HjDqKt!97VkrrOPB7yfn*BiF2hvI$&?O^&)t-skmVrI^@ z??xL+j;ZurdsocB-gql7H=%6y*|Ysu4is>tIxk3hv+(JJS$bDa+~^igv?_jdRBE5G oX-?(Zf=cbptk=DC-gEq8V4WY8bb5yHLk1x5boFyt=akR{03$ma*Z=?k delta 521 zcmdnWdXRO3k=(z3|Nj5~4SoCN%bWt2xqwLUGPlrWK9S3OqE`gPuZT!p zm6X3Ksc>Ch^}2%E4RzgH#+DP;df0wBdi>k@i{CC?{&Dlx&wKZOK6v=^;iF$qpZ$9F z{MYm6zh1oj{qEhL4 zhpA_c{4r+kziTG4tnpeCdGr#)rq_jsUYU6|a3#*!w84RChSZPhyYF^8-Ecl2^{-w& zqAF}r#Qp}wPt_TWv*W#%)w3BJK0o4I!ty|Al1tyr8K*xzER@T*>lK@(W+v_R>NrDK zd~41OpEoPE?$u$q@s9hgL80gBeHS;Ily85}Sb4q9e00Frf AegFUf diff --git a/recipes/icons/nrc_next.png b/recipes/icons/nrc_next.png index d0250e4ec816e20902f918f7476582444b60c8ba..5c569237ea83d0132c50751f03589363c91c961e 100644 GIT binary patch delta 740 zcmVASTru zBi0)t)*2w%H9P(H`2G3$*eNgAC@$PWN8Le3+(Aa%KSYsAGb-FWLEJt=;$30ul9lU_ zlj@F<>Wh!+ijL=bd=qD|!TU`v zKoo}Iv`8U2NRbw6p}>dL-Cd8nySv-pZ?j>MNoID)Ro@G)@?_0~$xO6%#aynad9H6L z3;^Nbk#r&+Pozit-ND!enAIm2VK%$as8$C2{*WF`<;$x3)AdV_K(%J(AQXrdBrpVE z`SKymf5s;uWn>*74B+`K8q{4Vi zstfn$OInkjG{=u#`MUEMUIgk>=vlDT0V4pc=lSn8DDAS}D-~`m``5j5* zO)%O}0BY@u6QoSgUsVF?<5N3`YGxH4)SBNOh(ISWzya$0?=1_22>5wGy;|QzC%-2! zy?N6PLhj)6fpbW@8(6s{Pddtj7dQ45k2S@CcYiOc{x~hK2GbjR>ZPHT11j+8yK?bS zdNCQSUS*ayRd9lr$Jf~xh%#*8-k)ukSCq;Mpt=0eeG{M}UGU{cuWg1Z1wCzKLK&Zz ztx_!MX)_iZ?6uuXC4KG(id-h>eq_m&BJ82al{J`Sm#+{DFb6wZIiHH^A?wrO+&cx_ WIRHXsvpX&T0000L|^=|3% z*OR8co-p}U-^5ql{ja+Eo>$g9E30}|Qt`B?>}h`Squ9iUQSlGLq8|iD-182+>*{sa z+2f9r`&~!(JC1I5>|O5I0g>}edX@my-VMD-L0LVbo8#~hu^+zFVt?{ZCQS={mGn^{Bvhc z`Dk%1N#0oEdNZUgM@-%EozK>wEVKV3kBp9B zr-1OI;}u^H9DgyLbB2E0j|x>q$u0Z(e|TP6o%Clx>qq_m54H^tB~3O?d8Wn5kYcxd z-Ojen8Z3<4PFz0M_fmXbV2q>uflikdtFxmXaP&1jXsn&U#bo$ZW>`7BY-iH~g zJLOq5YrP1^-q}S5TRop> zG%k>~V&yCo*;m1I$+ph>UR0pLN7f~|-*~%boD#}%*MHg+8vDXNpe-d__|^26OJZvl zrv7U-T+2A`g6U5W{W%VEbg%Ty*}SdI;o2FYU*Bd_&*m?@>~vTty;_-b=>@CrCv6=% zoYg}7@2r#wRM8SFIh_8=m7&n7voUwAQFDsUQ&A(QBN1^cReo&k-pI5*Z0GV1(pROq ze2*-;w<*KOFE99sU}?cEz4E!R}$=}i8?dBMCI(x8>b2#hs?qs8UoyDx2#H*3Lq4W6l-Rt5he@T_Vr9O;XK#g6X z#;-YuSNHn(;qK<)?d9C+;bfwFCw@mtkz-1eWvR@zDSt}p^6jq9yLzyWtIfE(*TrC< zct4C>Wuki=cz-`)p?Xi2YVGv!{r>&m?BuxB!{F`ZRG4lzhgC+9Vwb_EpT@4G$+B*! zgm$fqiMX2m{rz8`cXF$T>htb;uZ}y3R~K_RrOC5srGHhLZ_eV@JBe6ip?c%)=(*Lz zOq6KM;L|mRQzLstL5^QMidi0bKx3kNIEYq!u#ZEIUN9MUJZh$ZErCp<$go6^VH|fq zhqsv`dP97%kaDVrD}YNVen?N2X;7AHW}|&@sfCjn0v=s1f=v~4Ilb7&oy4qWqkN~! zwf+A7&EeJW_42dPztZH{tj@T=*~y>9t?cve;_l``j$h*K=A_87*5}-=&%4^`-}(Id zki4P(|Nc&uX-<`BDu7Cix}MYI+SKLRlavBT5|X{6@b>e&*2b{Vyu8=PTb*>1?gBi2 zNw~A?0005ONklee=K3CV`8?w;v0W-n!w%E*K{ zVAJL;k`B@w90p$Atc)CnzJC5jfyRv7kufYh!o;Z3n9;Bbrhqvn zHqO)x2u#hBBp8zo7%ey(P12AQFsSfb$=cY;$!X{1OE4A`_A+wx^-n-jFwu2?(&Q;q zr|Iez&zvR9sH!tZRhm(HE|LO4n|V+-%wHhPs4uf{(PChrAt_+eS|aM`xXj6E1@}r; z##O7GmF3q7tw(kPm+Xd(3T|!+?ls$l7`N|G*tu)Bi^v`+gaQVEz5M(39}quy=hzglvkr>qFI>FjaYY*_aP``C`5OXAso|!)hoyz$t=rCb z?%rdA#Ls=S^rH9R;iJb-o<37~jw4C&unRJ=Ftdx{PIL^sEKDpzGdBSMx5#+vwyVJ` P00000NkvXXu0mjf&Ko%+ delta 1580 zcmZ{i2~3ko6vqdYT}2HjB1KS8)D>LlfzX4a6uECXWmPD>XqBT|3Wb7xNKlY1$RUUX z4`5j@M9>X#h~jSL@BkD!LQp8QAmtWRj+X8R*=&f(Chui3^M3#Tyv&=Y8%0|(4Ir>S zl;lnVpz_itS%?Y%Kq<=G(H4}q>W;!2eX5hUI{^Fx03;>@um~R|egYsG1%Q+c03ja$ zEp}n;QA+@ntG(QO?R9z9Ix$w-(U#iL7CU1sb$C{YXp6o4BSw(Rp+wgMXB>5TLF0^^*1Gng?t5>l>$+c%Z^mwq!qJQ4$7Uxj(*iy7PEE{B%a`T-Yy(lXb-pi^lx>#Kxm&<48zu0AlNM|Q^#S#r6XR{PvYr%1w z^pm9YAW}w<#l>T^BL2|irCr2ni{k2Nr(3w5^dr34$aV^h0$`KI zN-BYZLIe!tI@^(aaj&Q}eSkkK4+cn)z}laKHmkYiq7X6esRa%bL+33OCj*S0CvtQ)qECP9@bfcIv1< zzVP!7g-~6Uqt<=t%SV}3k!p|X^B^*LzD*b_;l6vctHp=&Y`WFqAes3~KA4rvbSwx2 z^2uI-z-ohv#uXui`D?q|yl=nHqGH|zJH}5|?p)tONYyFOuCtyYPKDCSK$^k!ii+2P znau!Gbo-giQ_p}Z)oqkko!Ey>9g!x6f!eIf`Ps0>;BkQ<_eAt;sYn%(&ntbypp6?V z8I3LVH8dlZ5~-fzw9V*hoG}{bi$)R5%;7kMLJ?3Xi)BOq6#?|0B0Q4D zV#NKw!lfij3M(8|1W}O;E-!>b2hQOkp>+3glUsVPH5kS2>`*Ch!sm;W^_1(K(LFXMu*cQxCDY1%tit9 zEkzyjJ7)D#0)a_qg)+GSjmM|~&Uu*8TI2IC6TqTznJY*~*`5tBqP+%jXNA+bP#l{M zR{KJ-BSMJ}{XM=ja`%NXfHuQpv4?ROv^nuseg=%FtugXp@#z3%YkRradK@nKuPOO? zI|BwQj1E5lGCYDC78#0Ugv79@kr7eI)!A6D4hKm^Q?N7?6>rMGm{Uy6ad;dKL&4%1 gSW{C|bA~C}1eTb<4c?X1$HP|uXL}F3a$9oJKZ^yz#sB~S diff --git a/recipes/icons/ntv_tr.png b/recipes/icons/ntv_tr.png index e8609dee69489380b8fa9464c019b63afce0ab35..9c80b2f18dbb1fc10b21603312e383ff6c7a3439 100644 GIT binary patch delta 2080 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 KMNUMnLSTa2k?DW{ delta 2159 zcmV-#2$1)z5c?32BYyx1a7bBm000fw000fw0YWI7cmMzhElET{R2UiM!Fh1hQ1r#3^4}WSx=g&Y`LhR&a#N$EINl^6iaZyH)wm^XQtgyjtujBcF!oVZ~#H`ovOn{2PdM9B={Q z3{O+-x_`Q6w=ZmF4c|bZDSOpRTZ$I9Ry>(MBKFNt_Ml*0S*F9AJCZD!cmrjV8fS&K z$*VQUmU~O}1q+ME@?ml_f+iOZK8K=TVHx;*3UC0$7pSJ6ZC)OZ*<5v}+2oj`%h&~s zqgOR32DrQm0n{@;i(=lrusZ(i1QUODf-!Gan}7AUUSMn(CNv&|Fox_ADaK8;88Whf zRALB_3HUj~16SX4eSPNx^LI=czs|XOQ&|NM%TMPjfu3C~r>_HXwlVtW>=yC+lQ-?YQATSkZWAQQ=!l zui^Y_R-B)G{oDfZ<#=Lkq9qqklcneykA7Ng!ea!fC=;hSQ9I0mE4WMp+_> z_NeW1i_-;Vw|>p|YAeUIt-0eb-bHQjhGVEqVfYN2iZK%jWB3%ChSq!OY<~n> zo&qtXQvzZD!?`@b^$gob_?a?vKg1V70-=|uaBM$m&sd_mbn4{A+L31NTao$G`Yvl~ z&5^{F^Ae^93k;vIx#!ASPOIh1v++V{eC0x)q^nhSH(p}TCfmn5Iyw0kMcgD>LfV&1 zr(e8h(zMpPo~^WckJyTI%ERVkHh+C)-U!oXXECJkG9uA&SSku+fzAMgGd#exG&@6K zWXj*sCp2m>DjhNm+QO(;-*0=T=<%l`Rd+YhKI7*)<>jV2VrkXxd67YCr0MZiA_xQ6 zT=!@tU!MofjW3djRdQ^yyIG@yY#kHJnJHabSi1(bu5+A9+p5FFnr8r-(Tan0@3sC!Haztu{uB$BA_g=Y{7QHwFRxg6;0d? zlNEUlT0-!UK&sgHJTZ;?xBYFOR0H##%8*MB*Xd^{OTR{?3j z0>tJNaLah;39Q8=*WxMYjDvR+5(?0K4x5+4X`GT@As$AWHGwGU=W>I*%wtON__Q=jf-yb!_ZMGgv97@c?;2|g*ir`=zIIQ@D0SvYlCeIGhl99#A z8$#^c;Zjp)v3rOg>wk|Q(v{<=9);E8SENtiPG1IYZp5i-p1_LRcO5Aj+dRBsw@fW3 zy?A-6_2h3hcW-`v>U>{SU>hCr`*j+zp@Dvt)tcHG;kxyPnbV3$dP_<7RO1gtiN!KG z<@gv}Vlm6-QITcQy)ANkP8xjF9^zReTD8I=>EcDrKDhnj+t3wd|TM!dL-@eIa5T7ywk_{7w8 zW~pf%4ad%_$XwUbY3Wc<6tA7mPNyQ}7zozy09Fm<1r9m5d85|qP7UCiBhYgWz!D%_c^6xF9;gvN+ zD>R4d=w&#^@DlL1!~RwjV1&;I0WqPV6mEUckUOrLlz%?)o>@QnED|m4y!P}Fpo>2FSwd5$f(DPTMj4ck0ftzB&A(DQMp0UA1Hykn_%bTX!7~D3 lpW@fk$L90|U1Z2s2)~TlZ(9;#XDxb%+Sv diff --git a/recipes/icons/nv_ru.png b/recipes/icons/nv_ru.png index 05d70f12ee60db44df9dd73b29324bbbdcde79ab..2c8efa5e9691393d87c74ae8be9d4a9f0db90411 100644 GIT binary patch delta 10 RcmZ3@GmU40@W@fk$L90|U1Z2s2)~TlZ(9;#XDxb%+Sv diff --git a/recipes/icons/nv_ua.png b/recipes/icons/nv_ua.png index 05d70f12ee60db44df9dd73b29324bbbdcde79ab..2c8efa5e9691393d87c74ae8be9d4a9f0db90411 100644 GIT binary patch delta 10 RcmZ3@GmU40@W@fk$L90|U1Z2s2)~TlZ(9;#XDxb%+Sv diff --git a/recipes/icons/nymag.png b/recipes/icons/nymag.png index 0a70ab0d045d738fb0dade287857e6c6a531b664..de57e56b620d66198ac3902a73f8ead597fb5be6 100644 GIT binary patch delta 10 RcmdnQGM8n7@W@fk$L90|U1Z2s2)~TlZ(9;vYr;b!Z6Q diff --git a/recipes/icons/nytimes_cooking.png b/recipes/icons/nytimes_cooking.png index e93832934fbfb256fe8452a0d5998de7f93a0c58..dd4e5e6b6c1f693c64e457adc6824c43e4c86e6a 100644 GIT binary patch delta 893 zcmV-@1A_d+2doE>EPwz1|Nj2|{`>p>{QUm?{o_9<|Nj2tJ}3S8_~Sq+=37YZi+=5l zf9{Wg<3B0mKq})xEaO8h=2t`GJ}BctEAzLn{O;`i`T6R1XZOs;?1OdZUQ7M<_33R~ z@~xx!-rDJIUFmOL>2YA`b!F;tVd`>X%Og7|9H}kfw^R}+#Pde**YWd&X`rg|5;oj(J zSms$p~`|0NU?CSjO?B!8Cof@%{7i@ui#brkw1C zc;rbm@uZsORY8+*0TqAqv#RvHw)DWc^}o31WKr;;mhhsN_|nbz*wpyh)#g}4@THsU zd1>o;YU_Ata?`sL#K=j7x_ zGwXY7^R%q%d~NG~ZtQ_`?1OaVNi*z(b^Z7E{rLC&`1s>JC+dH5W9^7~?TLHsihSyH zWa@Tiy*>h3ZvEbGDwKv_|=l+sGjJ=>^Z@Oy29lJuj~XZBK#X z1eXcmGN${BA2ks~^0*6VjIlc1=uYEx5GT)kT`m9I3oHYX+wc2r?r#S}cF@&9B3b)n z_EPCZ9;cEA)-odXIdc$|z?zyET;-6@0E-$FovVMVW4wQ%7O~|{Jh)dOpf8^(oF5S1 z^~8g9kAV7ICio;E>Kn8XmluPKhe3kKNB#w1NR!2c?P!p~BT5@epq*)`u^$b_!?K!d zupl2Z>D1u1eci zn3KpU5okT$1PC4?J=p}7AI~||AP?Ii5%K#UA5r!4-FZlgHDs!(^sKy+^?$`*5bHjz TwEz!a00000NkvXXu0mjf$;lJ9 delta 933 zcmV;W16us72g3)DEPvxYC*wXR<31?kKPclrDdRvX<3K9oK`Y}zE8{{e<3lauL@wk; zFyuxt^qB1|x;Ze8hbUg>dQ z>2+o5abfCmV(N2a>U3o4c4q2#XX|)q>v?JGd1~u=YU_Jz>w9eLd~NG~ZtQ_`?1Oad zgLUkLb?k;V?Tmlzj)3luf$oxo?v#k{mWl6|i|?9_@0ySBp_cHX zm++;V@uZsZrGK08rkwGoo$;xl@v5NltD*9%qVlby@~)-xvZ?d4s`Iq0^R}(?wyyKH zuk*OD^SQC~xUlrOvGlvM^u4w8y|(ngx%I!e^}x9G!MgRryY<7o^~1gO#=!Q)zxK$& z_Q}Kc%f$A~#rDj`_RYul%*OZA&iK;J_}J9=+12^p+JE`q-1^?y`r+RC;@|q?-}>a? z`sL#K=j8k4;``>~`|0NU?CSjO?ELQR{PFPo^6>of@%{7i{q^+y_VxYu_xjnUTz)$eR1zSls6-h1)^ahcO-;t|1GH5D`8 z?f~e}@bb3NQcsH%yka|_hh*~9<$VR9)K+40T7p-BIeRQRw+=`~AU~+8#@%5+5^o7) zF^Ek9RR+MW>86+>eCakq?*T@@ZHz;&bp)Y$LFmcD2)GV&NMAz$DI7w2KU0b3+aj_= zHh&I>jr|6IGyxxl$jpH_95LTw0)%HuyAj5AY~Q@PVRemeEHNNlrlFli7=zo}S4AhKJL{$N`rvXf`hVnLi-q zKEV7;-EoA4j~h+fd^Owx@4J1h_(CILnjWt4;nsp*?0?f2MjN9(v3Q|q00000NkvXX Hu0mjfqYx@^ diff --git a/recipes/icons/nytimesbook.png b/recipes/icons/nytimesbook.png index 741976ef4d9c77c8c21e058b1b2fd550ea390c05..635a8d66562c46ab4defcbd68c31fc3fe786886f 100644 GIT binary patch delta 975 zcmV;=12Fvk2jmBkBYy*{NklE#p`f0q(EDVT*y zlT3O2uDJGEh-VYB{4$4M-nt`$Y2qHoJ?;y?Q9G@OFpbK{gv!0cN+wWfuqxz21?vv1 zV;3hco<4hrN`DiOp@~1x*qH)77{>2s0|Bw?eihizkxQCbyphHT2nrB@JQ*}`!Pgk0 zhr;8*eCw+PYMw>2L{0>PfFdNuDSiYjGa-_q>d{K!2NuFYQpN6uWN-C1CWjPBTpV%5 zleEwU81kU&z}bBMu}`5CCsVPrXglSwsp?WU2U@@P&VTqr>91*@?HV;~ZtL7xTgvml zP7d!UvKq=Zy)^P9G)8Ho6;Nnq!IV+z@#URz`rF$S4T;{0;fS!$XAz{R1tQFXN>Nwb z@D=j7{Jw=ncMZhF$$GN*O#o`~XO_^+sj`gqj;@ExCmIe#&kctmB1ul_Q97W7F2EKR z7_4p9-G6_EZH)ioovkn;8q`%#Lau|p%&Y9C2vGS2k|unR5;qv+=*81S0KxDV$#JLc zi%+U6C;!Q8VMk$s4Y92jbOf?s0C2d%6D632wC|rqPNCH@>f&hRqV81fL z*681~(8(woDRhXS077cgtq^EsP~tPhAQ1g<=zlpziPl-xGC#i{WCXO%GTeHsWn{vt z3j|6-2yjXP#B}|8r76%;}Gdo2wAQYNI;2p`e9NJI9XTX!D5MDQ!JLmpnu5gtNCcvk+RDPN#ruZ>o@MNxDg)F zKiR9@>=Q+Z5kU(YK`}x#Va`NH23X4&3ZVeT$LnyemFM zMp*%*m?kwqcweM(#U0P=lSBTu!5^ZcqKhk%NFs@prRbC&$N-jUQaRFhwJTrI4c+7# zWJSC!_|!|#dJWZA(Ma>kI>4g}+XMcZQ}?_B&G?13t$+2KXoXej)9ZP6!O`d!_jCRb z!+_xuEUXz?yEWU)W8aldzD;Tgw5;0v+x^gA#}EqugQW(qh31F@bM(Y#*BWMLwzZv1 zDK9x@K#nYsq6U+rr(yOd=)HRU5oXLE57jS5+Pjk}SsY-g!8&9|hoZN9-{|TZ!y~?? zC*~_7XMb1?ck>iAc@Pgq7bp+uqWxV+lZ=U`i@nN=r@3=AX9~&)^?;!&nBuvF)e4@9Nplo4=EG8X8pBD#6!Glre9#>yx&0Yi1p zlVhS%ij_B^qe;dB%@oo6t^$zXl0QHiuo$Xyo_~ysN-5ILFBZ_z5FLp$o^Z-8F6!x` z3#jXXn`xpNha41*%56%K>r=>4G%V(n9Zcz_3qrC5_RvTb))8zMjmwlbs=kGt^W_bi zJ;k$hLmz!z%y^i7IzQqKz%Y$BkIc$Q<1*S+*{Hn0?vV`(TLFUwa>R4%(tESY1Axag z27kE59}QKKHf1u<_Jgeh7z`{ld*HhMCm8QkPSFQgs<5|{TkaP{!*Z{NlY#bX<|ns5 z+S~cs;=?UxGnz>X%67n`x;tz)vR)>GN{|E!V5sTrXO$@V%9RIRdu7bfMMI9;XNa10)dVN42wfqH% WCr9Y>t@?HV0000X$mw$grL_t(|URBQF4udcVg;AWWwHvlt;J*K(ZdlFS_m7j@1R_@3 z>oirsB-1xu*6sPwDh-X!*eXP*(3SNb@CyfUKdt)#YJdggZ9AaS4y9;LV?u%+FggAq zEw6z0fU!YQpKnlB_Chz{9q^|4g5=g6s3d4(F^A^(km;Ui+!Y@w_X$DZ76sP;0000< KMNUMnLSTY+KuQV# delta 159 zcmV;Q0AT;C0cyJx^b0OVei zb4j&RfDzn}@_cZjtQoFgPf7&w?$BE^_(dmRyR69s?&DeOp_B;;s`8xh+qnXy&AJo) zhi!iuyuUDZG_v>w;(-!j0j#Q$Ioz;GAE#&<6X$25Hw|_n!WbH2>mMoi2|+Xo((eEO N002ovPDHLkV1j(`NSXiu diff --git a/recipes/icons/oba.png b/recipes/icons/oba.png index 66c8427f1bdbcb83274365830c85feffeb576700..0799036f09136d52a7ae8216defa4a24db4657b2 100644 GIT binary patch delta 508 zcmVIXP)t-s00030|Ns5{{p<+<|NZ^^|Nr~@`~3X;=Kuic0092} z{^}0^u&%BzDk>o!9w;LtDJdx|Cnq2q8m~r}CjjgS01O8WlO+LBe+>!>`~3L({r>#=`VI;U77-Es z`T70(`4|li{r&zJ78d{e`~Us>8Wk1)|NR{q8Q&CSE&u=lEJ;K`RCr$9lhtm-Fcd^* zoMd^+%*@Qp%*@Qp@cWHxD~YPDsy?>o!G1ZK>w9g3Kk&B%;d54nX__8E&_R4xqPL-} zv_3+`e;c{HbWYP1I-seem34(qUftrLw4D!lO2+!_1LMPD2kRa*jDo`!YU`-hNS|IaArKfwmeE@mS zvvK2434$CRx^Vli0?0cl9K;rCz6v(Es)XW6dX=!obP1W=1q@I~!^cyg{PU1uW6u#z zPzY)=vMc`!o6escfgXMF%D+b+~|lLfl~0000k9zu3;^p70P7|I>n8y02mtH}0Fw{_ zQGfgU`}_R(`~Cj>`uhC*{QUp_{rUO*`}zI-{r&y^{{H^|`}_a>`~Us@|Ns5}|NqW6 zDSrR}0X9iQK~y-))sxjy13?f)8+Qxt?(P;WAp}|6-9vyZ3`v&r`#fYNP}pnwVXCI; zOy9oUJ;EO$2?eO2a{^!(MjW7=C+a7OnSX({)_%8nd57Jd2Mh<85HtD!h;ZZa9ukQG z3hKorWuxOQk|zRTqUfs|Cq%6bLsFc!1wc)PlmiC(qIPCxP=ut(uBEU@7I=!H`_|8G zU{Yk3umVEti*!Q-eObG-YM?GvOnVA4vjkoV__ga8pdckV-SOWg0xtyo+ST_!&wp0{ zsI?`4?3jeUWJ#cy5}8LSAjrK)J4DcZ+Jz0=NuzDEGGFF+`(zUsl!BZC02-2{)NIt+ zX*y_4L5s-Lcr!$mHTxs-#=3r+mJ-5r}{YgnQNM>A~RM zrP9wnpO`F_j`jNb_xt;-*3$xjcW<|{=kxL=lZ&$0))R?=#ed@9zTe!8#lZrBc<}i5 z-R|v2qn&xYw@|2|G?|h!nUVm2c4@S)kH^E_?(HCuh#roHz~J2>kcg(!&$-;$^ZEI% z*VIa-o~hK)6^ep-y}0G_@q4|w34?ntmyglt=8DC@-tX-rk&62K{GHCrjK;zWgnMVR ztp~grY{{Q|yo|lQlz*?=R5{ZBeg?(bN ztD?}&?Dh3KotE_a`3Hk~B9Vzqrk@Ihd^(+$@%Z?J!oFp)tyrw4kH^H5zX2*R7K(!7 z@$fgBl*#1cmdeL6nUaLTy^hAhM4_AJ^YV4NwJw&AE|-u2e|8OqeyY^blNkak2^@`u z5Qu;A`1g}c0zwId!o3lPel<) z9uh&^l#zDivWnnLpPu#REkz>q&f24AgkWb5klm-RIxY3~_bePr_4fl+s zkUnZOP&hWqHWw9_gp!DflaN1|q>E2+PYniB%s^?G^fHvF%nZ2T0wZS4CKQ!$VI z71b~&mDm|bISRDY?taYr@0oHH8#!Y@eZ1%Xf z$N<-DgQr&bfI6oa^)is#09c)M0d_a`$PV`I1NMsm%Lf`$o1!1Oxdk|SEF4X8k^`7Ov`tb47=PzHsHGcn5 t`IGcp?^n_9TKk{BOtE{HNpLs&{U2x=Q^Mje1cLwo002ovPDHLkV1l9l_6Gm} delta 1539 zcmZ{hdoNoCYavwx) zrIp+&%1SM{W)&fqwClFA~?o&-;9y@AoVsXp8&v^3jk0G09xMZ zPfr{IK*7$|if#sk0s#AgYkZVYpdNlrH(pFkjVGlh?3G;K5(z7;b@`ei;mN|g$uAqK z`g`Gl>8a_(g_*^Lzh-BC8yd`b{pxg4u12I#E$pgmaY6M!-@@{;TrMA(n|;{b8BzN@ zpyEk!cW2+!WJO<(L;lZ7!T$RaqjTF^|6Z2;F*$LivYeO_j}8q~4haaYsq7pZjcus= zMjWi4me@Enxcuo;&YKq8jW`tSXOxwa-_fQGM~D586f-ZAh1OJPM8MxmuIF{MeJu{& z903=0wFg#|tAqsNZ^X8J7#7!8Z4CD3mX*j>Rz9t+M!l#}4Gm;JDjJ*rXq$T*6C2q& zGF;N#r6msDDhjF}9H{K?@p zwbUz}+#N5nx|^jMb_L2vo>^QxdMATbQsn<@neDyX5_0wp!!{me8uH310yIFBF1 zHq?ezSH!%iF}cZ zTUxsRw&TZ~Oo@#G7A~dlL@o$WuE&m=YNsn2DF;c9wlLd?V>qgJHU)JWe+^hvyx8RZw# zxM!^Ou%jNM=W=$uRm#D}5U44wuy3Ee$c5Cbe`b0YyNTYMWftmU{O5%8?y9EL0@0iz zOiiCUgF#yyx5GN24Mp?#*;`7w+58#JpbRD+d)6G39CDC2VxkJ2lbm(~X6dgm&pj9 zfNjV!@5sbZm2szu66%n1>ZMS0HGHRm9-Ds{YNSEhxnZ0zoB2h5a3r|mLj7A2o4CReI z${tbDSsY|pU0N-8%T+CKDCn;YJl<_- z)^Qr%b#|4ZCNpG2BUj(UGdsy+GI%V=&4+~~Kq3$iVhQ?K5`j)Mfb<9u+28x&zZDrUUvu!~!tOi)!-oYdJsE74HG|D@yMUqM^l=BV1{lPX zf+gr-i6qPkA{FDo<9S1Pyg(qpan@)g5E{qzVfTbjB8p)9nk3zW@97Fbrd&6^C(DZm zLAD6$3y8V$XZSCeHBXoYK^`m)+k*#)2BgivCk%nK)(L%H41#{*FnJzpNPD3U8bNlf zL##QTES|qN3w&M+@9o7tQsQXyg^o2Hp#V{jOd=AgB(lM+hb@H&0&iX?WX}m;0RoMd zm~AwH^c>f<9PBKCJLe(``2f797w;mMjdy4Gaoo6GzW9G{gJ-%^^jNM`CY4MfF&R_> ofvj)9G@!Ur$pjLENMYfS2^_K@<^3!QaR8ReW;UkxX)eM40I2Y;DgXcg diff --git a/recipes/icons/observer_reach_foundation.png b/recipes/icons/observer_reach_foundation.png index caf156efdf2500ec8497814fdc5a8ceebf2010d6..561bcd8d54214e5a156793751eaaab2011264975 100644 GIT binary patch delta 1043 zcmV+u1nm2o5uFH-8Gi!+003az3AF$K0aj2Hq(sz0iWF z!HcoOKYO1{gry;8kkR1fn7PdV|NrCc?1ZbriLS%l_Z3`|;>gx1-rN00F|3iMEZko0-bC@k}meS+skFv&Tn6t6U)}+DFQi-Xl z!_;e;wV%Duw$0kg-R2ZsiQ(w*o4n12tHUX5l|Xx-fT+OK;Oy}5@GNeY-sbK#becGJ zo9yiV#ns{vTZld>xq$!x0C99uPE!E-E2vbhuKl?vv46oz&*er2?ZOp)=R`Q63UTns z_uFtnqgIaYP_qnTg?Y`t>N0Bs9D{r&y?CpMbZ(^&Ge}qI2aQapKhTujwe>n=9$Xk1 zivh=*+1@U7pHg79TAXD~HTWWDf#b@DDiYuy*V7I@Kh@s200067Nkl z5Qf(v1b@f^L?mi4y&FO^V44j$iQ`)09y`7F{r@eilWrW%c=FvdJm22YJ=(jyJ$PU& zSWwKQpKj*7@n=CJ_v0&pTrS`V3kneXJ<(wxhMDkk1vxLC0Pz_)o)E(h>l26l~)l_Gu!G! z9iJg=3@?*sn>Qck*^fn2NPP0fkdPE6a3;oqG-rc3?w9BI|*A@1r&r% zScMkBu{bb*t$G}a3$;C3mpUG2q%L^$Sfma|>ozX6t-GzD)PEu}pa>!$!zMe3V1TeC zfsn8S2xOgi-&`n>;2F=HGxN{;oRjl@-`{)py?gKP{_gz+!NI}7!NI}7!NK{zL4a31 z`gJ;bY1zxD+`9Id=I((d10&OTwy-0P=C^uD)j>3H=>ZII82(@lgn=S1JC8*sz8q{0)e2!8bSJGGVZ~f&%RY@ zwIm`!&_VYcw1|$tpi<>?25`Hm{?n5W%8~^A!>-UZFTO6;% zI)T_9%6VC0Z&i2Vvxx0MZuaHSg*5bty+ik3uZQxjkbek>iU@5)r_&j2yO@3B%kOiq zz1evxg*?HcQDtNzVG6o1s@p|Dt5SY`dN#X!FLYs`&rd=VopsWrodu)I!IIel-T~bh zjXje0)~4Qj1Ud~}2uZ8cfQ~`=Rw!h$iVSV!v%hEE__d-X4od99alxLqKMrzF_vEmf z$zT{@;$$xZAwR+rytTR%^`gvrcuFTBxmbG90 zkozO(0BC~_;$(SSPuc9{0HBb_O0;7WP)g8WKHAxW)v4)^rUy~-vL~!nIOvX=uYjIS zfU9-c?|2OvJw?#zlh8_Y4l&T`Q>FQr!0H!z;x#TOKv> zqkm33C^?W)1ow4!L;Rca7npT_&YyxrqB3FRebPWwDq6cj0~gws0)NG8QONj|+O)G@=33L)?=Kt~x6$ad=Qsh@W;D_BW z7mTHqHf@RzU2;8T{qh6nbE|fwm$k%)xjW}Kc8ffF2S**SH==ksT7T46wTXhEM8N6m z#5Uuj?(SGh&=Yw^DP&%Zl&HYIdti9^={z*{z>)DWA}=a6VBYf_r>L$ zTOdKP1}H~A34h!BW*uY*drQmS%R8bsPM08{miqiccG}>emVrI+j?9GT>aN&?xDcjj%0|L0f6NEZbx*&`K^(g9oZ~CbiWnwhkxclQvCAW(LpDn3qzq&(V&Sa zl*$=^xnFHckxM�r>*QkDu>>!={w1_*OkNph?qdt(3-6zJ7FpnYOUzu*Cz zPigi+AqgH*$p^;-s;Uq4@jM+H@T3*qfove{Ks8WrFomQUKv&Rl1IxGoE8s1#O}sz} kmKLQ7fVReOKFlAy0lf$XIWmX@yZ`_I07*qoM6N<$f`)Wy-2eap delta 273 zcmV+s0q*{W0;~d%B!3xnMObuGZ)S9NVRB^vL1b@YWgtmyVP|DhWnpA_ami&o0000p zP)t-s08MrfSbrd6hcIrCKzf)^g`r@Osc@UJfTp{TvBRId%&*7Pzti2%-sRuv?(g*W z|Ns9egHrzh0056kL_t(IPwmb@8p0qDgVDcsuuuxk{ZH#+K!06K^#Gb(^1VgSm+&*7 z07;URfU*N7pR;sf5&<1F405h{bfEJh+o&eyOAiXZcz?VEhwUM69tV-%$C-|H{@H_xB XhA`H(-QFkMvN$%a~%yq%FcGpInb3KnFUbp)G61CNac#k7N9UF*G a0tWtURSDbR delta 313 zcmX@dIDvVBiX}_Bqpu?a!^VE@KZ&di3=9g%9znhg3{`3j3=J&|48MRv4KElNN(~qo zUL`OvSj}Ky5HFasE6`@5qJ2GMlDE4H<9{aIdq8FEC7!;n?9bVRMf5}%x}&xMgsYAJ9C3I(w!Zs2lro**zm@e#)~;h+-HpcH1K8;wj# zeG0B}PF?f#6kOvjDY36_5MexeFo>t8u1Qb8+a*S3;kIASxvaaH8FETPId30&v~D%f zCe;$xh?11Vl2ohYqEsNoU}RuqtZQJVYiJo_WME}tVr5{iZD3$!V304T^BhG(ZhlH; YS|x4`a?Vqtff^V*UHx3vIVCg!09)i&l>h($ diff --git a/recipes/icons/omgubuntu.png b/recipes/icons/omgubuntu.png index fd6bd08b310b5594b6eb4544e2bc8eaaefd22366..7a91702e0880963ce1f01af10e5873336016968a 100644 GIT binary patch delta 186 zcmV;r07d`00?0UD(;zPhe zd=uOYX`{(0;9^lWbit8So)keNDKB(tzVp_1AA2IRa>YCVZGE6DMlroA^1(XS(Dp3Jf!hs=lY2j(sC1=xgq482eeLN$(Nqr-4Q>e%JC`}!A x4sM}?PPP~RTQ5~@RBEA({jl7is{sBw8FJDgq2@?PS002ovPDHLkV1n+EP*nf` diff --git a/recipes/icons/onda_rock.png b/recipes/icons/onda_rock.png index 42289019c5f66c5703d2caa8ab7fd61282b2ea46..3ae9e27f07651e15d63392c8e41d331761629a5a 100644 GIT binary patch delta 851 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 diff --git a/recipes/icons/opindia.png b/recipes/icons/opindia.png index 1a4e4c37145c0a35ed258a730de8f70f207a7581..9e35f18d2f42e7c0c6ec4a3619d554b43abd72e6 100644 GIT binary patch delta 487 zcmV+9xXqf1l8BZ2$lPhDk(0RCr#+%2Ts5K@^4Iy>^IW+qP}n z{A}B{ZQHhO{HxQc>YAD)bFTATbnWVQkvlxRe)H~qzwxT?m3y}X0&gMc(q^qZX<^Vc ze-R!T@8IN0m)vKZsNZH`uC$FXgWQ#TFt^iF2M`-abD+5*h5s`L2^e`}ZYvts+! zSwN?%zkGW7>?&59HjGwMy!-PC!R)A`txoCx{(ivgC+ws?M;W|%{{wH?MK(#_zWm28 z)(|5Hzdr(>nNN9$P+ciPZE*qHdC9@^pZN5Z&k;3q4Uq?XS5XPug~`FQ7x>QXNmR}a zM2%clN_lJ-DhH2&VCJQse-+|sX%e9*H=pgo<=`T4hBbsl$iX_`0BeXMNe`^n=+TiR zEdv(2_JmlHXfCjP>ZnuqN+N}B9;Qr~>9c{=GD&3wm`=3o(4M{fEtXBQyIDvk73&}t zt(Uj|4s;b}F>@-Zv=702%tYj8J8TYEAVOSXdImW#%wKbK-@)C1B|B~MBR(yK9O+gZ d@zs$#i~$|*Qsj`dD%}78002ovPDHLkV1jll^d0~J delta 475 zcmV<10VMvZ2(So{EEoGy0sM_v`&$qFp?3U#L;U>w`%wS@0Ff~k1I*0Ku~{7fvv~my z0e`M}(ZB!z0gXvSK~xwS#ldA;Qvm=4;6t7>y1~Q*Q8B?*z)r+KMFG3JJ80|{1OrS= z#12qVG5(d?bGLafo1b}Kbc84McOO2A#(zZJYPcU8cTe#*j>3zAE%9|R0G@_C(B<7| zZ$U_Y(BVtBAHy#AqE4)bRcP+K`GvIeyMGgT6UU_0fiSsVvw!hoR}an!m6bn9eL9fPNVmO@}Tpym+Zr(ia%VDZo^Y9fRR?=zk2u zGbw@e@f}b4%$P=U?io29xX$ zli7 diff --git a/recipes/icons/opinion_bo.png b/recipes/icons/opinion_bo.png index c25db2aa33effaec3f75db8168c19e940c575319..5a3bea7b2e918374aa3301f854d694ba597d9c0a 100644 GIT binary patch delta 677 zcmV;W0$Tlp2HFLXBYy!(P)t-s077;mLU#Z}djLdw+5jip03zG~A^<{qCqi~7LU$`d zb^tILM-~b%q001sRcHsaVG9yBE;s6y=J#SY%aNhtN z-vAY^D^3+ed#@)pM0l$!PUZjro-S9;95v1yIG-+6#3Di~LU(F7XKFiYk2PPCe==N? zGFq4}SKI&`v?oYijiU$v00B!$L_t(|Ue(jZlIt)K1<M%b8Y_h`9<*or+9DMtUQGGv)~N?PgK~y zTa@&9gM|M$g{=!*25$foI(R={e_t37MDPNT#KFhW$(ixLhZvw6K(-w2vu)F~Y&8UF z51>Vy3?aE3AKAmN)>)Z0m%njfTn? zEiP^HSLpIalTwzb*AtA=X1uCE2LPoS?*uvr^j`^--rz~r`LF_uMfWF1=?rl2r00000 LNkvXXu0mjfsl*dm delta 718 zcmV;<0x|vC1%n2VBYyx1a7bBm000XT000XT0n*)m`~Uy}N>EHxMF2u}077>FLU;f| zdH_Uv07QELMtv1Rb`?T*6+(CwM0gcMdmKV{971>^LUtlTcOpV~Cqi~7LU$`bbt^)4 zD?)cJLUk@eb}~S8GD3DpJ#bPzZ&f{TS3Pf6J#bk&ZCO2TSynx8UpsAQJ8NnssX zEXf@}mne#;58Qx^mme;#o;L&+(1V6vwfaY**9Xo3&{3VeFX5m|4+>gsR$gXN7@Pqp zcw};BprhL>J2nYH6kFdvK09208%5yI1Ng#;l3{3?Zsg(+Z~$VmnJ1)BAcQryMX&-H z&*_BGVme)RcH3>MPu5a_NTux%^8f$<07*qoM6N<$g5EzK A1^@s6 diff --git a/recipes/icons/oregonian.png b/recipes/icons/oregonian.png index 2d2b43981102bc1796640cdfc77fc49085668eab..8df926f0009acf7a5d9d517fb9e6ed579c0e91af 100644 GIT binary patch literal 778 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabRA=0U=#`P332`Z|G%=l!YD!x0h^T5 ztAW83T@vIM%(!r4;_Ii(y1&`y{uNcteQSR9px|R|;jQjJlkXcAu0491HG1ac;Lq## zxBBwWPu1jZ=KSdT{`>R(i}DZE)5UwkcXv7(d)|F%f4=sl?cva`vZa#SyRXOcZmzy; z-Eb>nDli(nJY5_^D&lG{rWZFk2poSX%IGL!`Yl7JQ+4b2d)4n#_y7OS@m+qx;hjIK zD}SGPb4N$>)H^->iyfO*pV|86zrw^s{p=vdvO70;R7>2>9Wq$qvsuvG(EVH#%Mz2c z+&+WvmxDh&YBD$1%r!W!`K!XqcCmT-nvIn_ILA< z7l9)_WPuynnh$At&2=*G0p3Q;0R*tBgd9%GEi?j3$P>U7;4HANt(o@b?|_bkv|Zqq zwq~|w043xAFa?|kHh^i|Ja=c9MOsVZd8)g0hHpwAb=9`6tD~&kI;{4Yj(Z)GH@)#EJ>qE z$nI1CCFD`uif)gDOWK-W{`z5!<`qq*0sxP3pHM|Rz+B|M#8%n*U9kQBXPpIbIOhwu3j){>KuCGF z+#dx2K86573$9ZMnYHznYXDdY0elZMYynPYylh%qvs=LYFR)*jL;>C>B=pAaqwAui zt$9+w9DiU@TeBGgc$1JYP`U8G>V8~_HKDDUDrgQc6TOMf?+R`%<3m2EggoT0=W%<( zu(oD};`*$uIq-Aa9|1p)4+2S@z#R{`1@zQt4lof9#8>z{FbbTD#qR^xv^6hlGzOT_ z)~v>Z2lfF!3d6Y4JFbC1EU#Vxw?hxAHX?1!x_mcVk3AY!LIz)wp*h?UE%>PhcS3E= zSD=sTYUq&?vXo5ZRjYjT%y{{Z4g=b?cqEY<)3002ovPDHLkV1hmf BR-FI< diff --git a/recipes/icons/origo_hu.png b/recipes/icons/origo_hu.png index 9857bc78fd94eb1f0bd769cb134068f491496452..0226c633e56f173960c2952c5e03a3031aaa96fa 100644 GIT binary patch delta 212 zcmV;_04x9d0r&xsEPwz1|JLN?LWPM(h>WAd#o_Gi`uzO<{{Dusv#7_(s>sT_)6~4v z)zstU)#K#V+FQDvf}LQNQjJaqo#7Crx^*^y8r+H0d!JMQ@sjzgYO1ARH&NI8YHfYN*y$9{btyng0N4P6UrRW-r75 O0000sT_)6~4v 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 diff --git a/recipes/icons/osvitaua_ru.png b/recipes/icons/osvitaua_ru.png index 3f4da96b63bc272ce513472e08aecd551bc808a9..19a7b2c169d2595c3091485095d21ba619447db8 100644 GIT binary patch delta 1332 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 diff --git a/recipes/icons/ottawa_citizen.png b/recipes/icons/ottawa_citizen.png index 7ff3ccc05b661a20f55e9e93f3869c4a6f48f292..6511d885ab2aeb7f62caf78b0dd253d83d67ca97 100644 GIT binary patch delta 1130 zcmV-w1eN=^3H}I>BYy%(P)t-scBqt{(7m3~zIdsYoY1|d)WfXT$F$qcwcE{{&%D9m z)sVxmqtn5p)54|H!kNsusn*4+*2bI8yRF#BuGq-3+RJ&Wmz>bNw%pFR-Ost*(7WEz zz2DM&t(wT>+RW$O&gb6L>f+t*=j=`?1*T}5c$F0}Mt=Pwp!LF{$ zz^>KDkiV_3*~zcj%CODBu-VD5*~+lm%CXJDkif37+RKo_ud>?AdaRjyteKL+u#&{F zlEt#M+|84^tCPjDx6#M9+Rc=^tdz#Hx82W`$F#WJ(0`T4wYt>Gy57*b-qE|$%9hEt zyWi5h-_e)Kx0$}Mz2DQm)y=-%(tN9#zTniq;MBn3)SAq>cd3<{&%46m)_ks;o6o#{ zt((N+*Tm!3ey^Oy+|$P6*nh5^$K=_a&%DUx+Q{YG$>rL~<=e{U+so$LfwQ2@-q)Sb zzRl;|f`7E3&*g)9T^V>f)Zzz18dE*XiQh=;Pe(=bq5M-|XjuwW5Tv zpx^N7pV7YK@$BRB?Bw$7rz|yCJU`cMz5TFwo;7h*0*3oTOCTPkyQ->&R5uuZ@IAk-+zNEB zKt3`dy-Dw|{klxc|*52^`(&3LFEby#-3cP0V9s?F-{m zdY3CuW_rI_0%U2MvlzhR!PEgy-vtPL&tysh!(Iq7aof4z zZai{O0(Xp1+?wC2_GQ-CaL%g&@d1 zItue0I6rgD)(=22q*4($wxvZSU56kKfn%I113kPU$T>PLCLuC|{5Z3-^3Tqyn*gQy z7PaOUt@bvpHj7%D1=nTK2{|w!r>j1fUZ3AXEMPSk_O{$%67PWawcceC@3LC%_O%ub zwA}-qv=_5GN?D{*PG=dHg6Gr!;?v7{aQQI39O)__VLTjRR3MBB0kcBb{fJF{B`5IUXV)nQ35r32l2Z?;fT0E@rvX&OZBTO?RYQ#` zZlijrQO#{~;5BJ@L^Y47;WcZ9TL3M;RnXt6<&(5X8=ynlbtCQikq*6ptQV3E!cK#b zBH>UBB8ovwHHxT4G0j0jGl^lN1O_!p=(1tDUQ9Phy3C_pW+_9?XP8GBU>TP(!fU}ifvU7YNdmqHVwz7 z=GZht4qC2W&b4Z|Q(EqnjyI(nehTRM4h966HjFqJ1%TZsw3|eeS`nz-EPiH|JR6tH z028A#e zd^u-%Id5B&r_Ut1mXzUcFvl+1$AJF4h@5 zDpWKkA|Xt6A&qqcI*AVVI?wnjp~&fYjq|=esasMn`Jr(Kiu^-T@=N@kP~;G1IMfsz zlfU<9!q!NqM?Q%AYK+OV260dOQkYVZw|D0bBw?6zp9c9Alk)VQN%{WXMb76^FS;1c z&%4&>;!xy+&HlL1OpMd)64|vz0G~!Bo(gFWP+`6&KA`07l5ZjU)}vkb!D?{7ClIIk zT>!CTqFlp%JAevH=c7X00unDsv9gc|R9a@#7m5{s7+oCbA4Kc(Sl{015g^B-)!ID2 zn(hxjm09A~UcXT!TaRY9jcP(u-X8M`a*OpiByl@K@j27I{#bnEl>fJi**UH>)&V73hTX>Re*cRD> zb2<3N*S7)5Jd3@CPp2jS+`zKQ)88T);?4Vo$rPN+3>AjUkc`B$*YGTQ^4tJ5An;f0 zl`#@24>^7&Al#*B<49%XfB(p`|Ml9P)UPP{shW$ hnCFh>9SqwELGOeA&h`2Dc@FrBpyjvM>W@fk$L90|U1Z2s2)~TlZ(9qAoiCff@*l diff --git a/recipes/icons/pajama.png b/recipes/icons/pajama.png index 7f0925100b7a821b684d60b1c5cc123c5b4204eb..2ec8819351ab6e51015caad691d89efe04431fc1 100644 GIT binary patch delta 495 zcmV0o8mpc}y0AsNuw%x(k#Q|5==SyU`uh0&{QUm@#OmgY)z1F^|HSF$)9>xJFr@DvD%#1;1 zW@ct)W>|(FPstOIn)Nn|h-)gi5 zOqxIpqM+A^M{SE9z)E)}=mpV0G(kzqITGi6fI=n4q{0vo(F-i0+*VR@8-Eor{aqp~ zVO!^NwU{eZjMr{p2oIDHLX|^|Uxy@yPyrl5moferbNpY3U{ptW))Nu{@NG7X<^7Z) z>~?f|d&Z9v7|V3P5Wd;%_UD+4d=Jxthx)yxkV3-CN-O)+Y8CP%rh*2*s_6o&%TL0A z*gE-xoD->=6sepduA;!`x&_4MfW z_44}q`2GC+{{H^||B<=RlZyc=e@4k>i~s-uW=TXrRCwCWlGS>`KoEt)hRIS0(jdj% zi`CuT-QC^Y?Zvx+LV<3%=G^4$%>PYhXNO^mM40;#29y=T>Aco$P$V@zs&9ZwF;wj` zC=70GDvvZOUY=4cVw|%B3CWImIj_>B>LFGt`BnU+8wgNBtdykz5{b$Ie>9-n7z$lQ zc;de15e?XoS*?var9kY34JbevMhIbckJzVvH3d*Y^7;$Jz7CuJ2mHheLmcfFY5?Gx zjbgt&m;!9IwmCXS_T$6`Wwa)rO(xTQRGh<$4;LKN$2yA&0S}8!^id0?M0cuyMw{>L&wWwSAJ6cYd$Xakf0 delta 797 zcmV+&1LFMt1dR)jB!BK{LqkwWLqi~Na&Km7Y-IodD3N1eV4mRU;^fLCz`#&YR8r&~ zlnE?zElZy)+0|IRDfvmM3c;1dC8@c^3Z8k%`9%f!MTsS;DL}PQJ3xTfCBH<$Jux#+ z!N@?BM0XPepm2n+5aELmLxAMcqA~~ztVn=Cfx&>mfgyk)k)fVp0mD&-H;m$pE{vs& zD;V!Fi7^E+O@Cmzz%0xh$-I#H4T~j92g@y1J=Rv%du*0$)7ZYThqLeIP~zy~_`;dU zd4bE3YYVp~_W~X%o~gV-yp#BZ`KIy9@Gln75!fN zRMPyQbwGQPPO7ezo}k_<{UZho3~P;IjBQQiO_@z!nq4s8X|cp|f>oV$u1%b6pq;C| zwS%#vj+2_Rl8b_?yqki%vWL2-u9u0ojgPx;uwR0Ien5TT#Gs|YyFxC8z6@uIkc+g6 ziioa=nSU9(C+ajY$|+LtX+~-x~l9= zg??p8)vg-GTCcjP^$#2MnrfQQw#u~?wjb@3?keg&-K*Ny*nfAT&7|3r|4xmcc6f%y z%>G&5=OoNMJ>Pu6(nTVRTbF!Wmbv`i%8*s3*MGRI-M8Ly!?sN(o40H=*|vR$)z1C9 zUH6>b7qP0ll{*!Ux>Us_&Wcs{JRSunm(C+e)V<1_wb(rzfS#W{pa}qKL8B?4>Sph2oC@N b01^psNLh0L02dJe02dJf$|mzau_0Xo9QckK diff --git a/recipes/icons/parlamentni_listy.png b/recipes/icons/parlamentni_listy.png index fcdd1d8ca03b90c90083aae5635c79b754e348d6..88e8649c19a10c132d25ede50c271d0dc1d7309b 100644 GIT binary patch delta 227 zcmV<9038490_y^hEGqy1|2dSYYOcP--{s)$@&5k)KbNaaov>A-v{7?aNYGc)tCCg%rXp&;`kfol%G3BN|C@(ybecNxO@C$kF`jyp(!0oSr z(F^D~Xjymwv4gLLJDl{|6~;GMDr{krU{_%X(+kuTIxq{MsNfyK`T4zxAuPCGqC%-^ dr-Eq9!5_%J2FlJhvkd?M002ovPDHLkV1g#Ra;g9T delta 228 zcmVA-v{+5IU;Y;z^XF-s5AEFNI8dnj zL;0Al$8eiT4DiAa5+gLG1&O~W(Rf0_LNq2wn25#-2^&$_AYuJ&k@dboA`}(7E*}j< eQ2|gS0Kgkyk);sOWa0|?>(1pog2;sOTZ1PS5=3gQL};|&ty5EbJR z7Udoy=|@WIQB>|z+;s*=nCMxDAEaoXJ<|!@aD=+6XIOjDv=aIb#Ht9u3=|)N72o35- zO6o{U>PbxMN=@oZPU=ok;t37o2@dO2R_jz(>s43lS6b^>TI^X|;|vg!76CJV@r8%+ zi;VG$jq;S0^OcwMprQ1oruC?)^{lP-u(9^Bvi7sI;|vk^xViVay7#)f;Q#~ozQ6as z!1%$!_`<{E4ifpx%lXXB`OVGw(b4+Y*y9fr``zCA-rnO76a4Av{qFAl@bLZd@%{1g z;sFKX1PK23_x||!p?`}^xpQsM>*;}8`1!^A)K=imSU0gg#TK~#8Nz0+fVn=3IC2k;GM zY8z|Ywr$(Cd2QSJsyj)hO?PMazU=?SoSydgq9IbhD-ThWiP0^Hd`5QzO>$;v-+n~C#vR=gv%(H{i9%I!6>j=7tFlaG&H zlG-kC@MiKJ0Tu?bj1+WS0gf<(*iF&v6+0nYkX=$xR$5w~h&ZUta7pD?5d$WH zMuqu7t-m7W)AJq~U={{=R?ol506{6}I@qt7?WN!WiZRB;88e1Ye00000NkvXXu0mjfeRa_p delta 883 zcmV-(1C0E$2gC=EEPvqu1K|J!;s6BV00rU!1>yk);sOTZ0tey)2;u|?;sgoe1q$K@ z3gQL};s*=j2o2&14dV$8;|dSs3=rcC5#tRJ;|&ty4ie)J6yp#R;}8_%5EbJR7UU8a zPSoKNlfZWP3lWd>P}JW zP*dwqQtMGv>rz$gQ&#I#R_jz(>s43lS6b^>TI^X|>{?vxT3zg0UhH3C>|kTl&gocwG0ULkytgiO2u=cRA_OY_|v$XcKwf42Q_P4nA zxViVay7#)f_q)9JzQ6as!1%$!_`<{Z!^HT-#rVj{`O3=q%FFr7%lXXB`OVGw(b4+Y z*!$ev``zCA-roD(-}~U<`{CjIHY5R{qOMo@bLZd@%{1g{`2$x^z{Dq_5OeM z_Wt+x{`mO*`T73)`~Um<|NQ*_{r&&`{{R2~$5BrF0004>NklE*%n;?`PG%799*i+d- zhP;L>aB%f-u_Z|a*rK^vqUrIvAb@`m@XQ^V+t^;6Xbc7fP`8@QdS8nHAZo@y6Sor& zegG!Zq@(Y=oYwrI3~&NoZ{l){*k7LUYRF)|0AX|h@BEhCh7;dYd5674{RQw<-p8VG z#eF!vriEQ%?~-@mYu)xRP(p!p9Ra$(0sH?9M4Sm6Cv+%)8rK5_kl~zHURHlr5iJ*r zZ%Dcp?W2GKSqzblTx#Lu3fogkzXZtb{??15z(EOsKotB5Jk(na6Ce|Y>LMd`UJAhY zcz?Z8YR^z0J!JFz^z@>MmCw+dm=NV|jaUh=tV6Ym6@U2$P^TuR{tsY?0BPYC7e)e@ zPLkRSZ?P+{oEgY}!O?J!S7Te26P7&X;tV_c_<3kq8$^NM1kLlW`2>)GUv&OmM*RQ)002ov JPDHLkV1oKm+qwV% diff --git a/recipes/icons/penguin_news.png b/recipes/icons/penguin_news.png index 5b7656665982634c2ac9ca6b71e5f9a040e196cf..7051de0942639f53b530a45a9970cb7e15987b15 100644 GIT binary patch delta 870 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 diff --git a/recipes/icons/poche.png b/recipes/icons/poche.png index a3f983db3a645bd3212d172c73218fa8aa84cc9b..091d27d9243e8282be10a3b1c347e74f8253484f 100644 GIT binary patch delta 393 zcmV;40e1eB1oZ=uB#}8dSpWb3a#xgeT9%J`s-23pu%W}rzSiH^=j7k(>gMnD^Z5Dq z`gdKJ`}+KRW1P{++>Capxva{-w9nepT9n4niv9kL_Q z8=I!q=qR8yp-$M5=#V5km9ijJJMlc`6|wipqvadegR{4M6C2L{G4t`~IltlZ*N2)! zR<_OCnHQ+3;9>56IH9iw7NnDjcJdRb7ZsW)e@L7fYM^035>VvHVgw2nM5KNBvid@k z@#O{RSzt(nlmn1?SQ10ZM&Olwo@4>{z)2QJ3;qDU7Zyh7RT74@(C@&Q%P1ojbU-D7 znxl40y?-9>=K22uzjiukQvm>000000NkvXXu0mjfub$RW delta 422 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 U_V@JO+>0M3Tc0lNk5?G~08OQpMgRZ+ diff --git a/recipes/icons/poughkeepsie_journal.png b/recipes/icons/poughkeepsie_journal.png index b80f4c2d0698d587984de7a6791e80daa78eb9f9..e90d2559331b3954ef49f1e0c6ecdc8ce59bd3cb 100644 GIT binary patch delta 2069 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 delta 443 zcmV;s0Yv`T1Kk6VEPwz1_y70r|M%wq_ulvS-uLg`_wUyC@6Pw;#rNjD_vW?t-nH-E zt?%BY@7|p6)|~Iwl<(Gz@6Lqg&V=U9d*;q_=EZB~#bf5hTIRi4=Dkwhy-MD-O5U|X z-nBa3wK3kUG2X2y-mM|ttr^~>8P=s4%U3de0003aNkltTfnBhlm`Ncu zP$;!CCf)#<(JmR#M5?56saykltNn=0qTa?^;OGI>!nNS0JYd2{77y*G5j#u*9`{e4JI{<>Io7N4f3)<`oSAf;?b; diff --git a/recipes/icons/pragyata.png b/recipes/icons/pragyata.png index 6bdfec22352d7320dbb2bd39568af38e0ce41b1d..eaecfb16e568fafe5b0aa31d8a39b05e2b06237d 100644 GIT binary patch delta 10 Rcmdlgu|Q&i@W@fk$L90|U1Z2s2)~TlZ(9;(u-cdWH!Z diff --git a/recipes/icons/pravda.png b/recipes/icons/pravda.png index c7975f2f0173015b47ee65d4fb925a1e6c67ee22..7d48281dbdc26b159225ddbd6a16aadb23b2349f 100644 GIT binary patch delta 1600 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 diff --git a/recipes/icons/pravda_uk.png b/recipes/icons/pravda_uk.png index 63ad2b1e9186ff1aef3cec05df9ff5f31cbd1fcf..cfb46d85e2dc1f4c25fc9d8c1b8bf931e1f59183 100644 GIT binary patch delta 128 zcmeBSKFByhf|rGvfq~)e-A6${%3xxmNk-$^*ZDxUkEe@c2*>s0gab?xiZf2CtV!Cm zaU&D+>!P-IQf*QTUaa9}6*#8-ms8*v|Kt|S1h(f&Y#ki(Qw~+-r;rDm gE7BJpXpm-L2oVxJdVz0xC(v95Pgg&ebxsLQ0GR+UY5)KL delta 332 zcmX@e*uy+Q!jprUfq_9omp>Rtv7|ftIx;Y9?C1WI$O_~uBzpw;GB8xBF)%c=FfjZA z3N^f7U???UV0e|lz+g3lfkC`r&aOZkpoH;6Rg?Pv|NpO?+4c^|WlZvRcM+JniB|{6 zVK4FYb!C6fCM=?B^}T>)7f>k7)5S4F;_}>HPp$(7Jj|i$AFt{B|DXFdxK>A2pw02n z0rPcLE59CGbWMoMFot1oD$~pZHI4yC%B;eUUc4sxt?PzBma=*1nv=U$C2ao_r)GOJ zZ(dBLgcF6DtFA oZ36=<1A}}yo#!YTa`RI%(<*UmkaM0A4b;Hk>FVdQ&MBb@07^n>eEs0gab?xiZf2CtV!Cm zaU&D+>!P-IQf*QTUaa9}6*#8-ms8*v|Kt|S1h(f&Y#ki(Qw~+-r;rDm gE7BJpXpm-L2oVxJdVz0xC(v95Pgg&ebxsLQ0GR+UY5)KL delta 332 zcmX@e*uy+Q!jprUfq_9omp>Rtv7|ftIx;Y9?C1WI$O_~uBzpw;GB8xBF)%c=FfjZA z3N^f7U???UV0e|lz+g3lfkC`r&aOZkpoH;6Rg?Pv|NpO?+4c^|WlZvRcM+JniB|{6 zVK4FYb!C6fCM=@Im>ae)7AO?v>Eak7ad~d9C)WW39_CQ>kJohm|Id9JT&p81(B^pP zfcd(rm0u4ox+cVB7{jnPm1*XI8pnVmWmaKFFJ6=U)^$T5OW8bh&B61M+|Q?osq zw@bODLf)op2h;Q3s0gab?xiZf2CtV!Cm zaU&D+>!P-IQf*QTUaa9}6*#8-ms8*v|Kt|S1h(f&Y#ki(Qw~+-r;rDm gE7BJpXpm-L2oVxJdVz0xC(v95Pgg&ebxsLQ0GR+UY5)KL delta 332 zcmX@e*uy+Q!jprUfq_9omp>Rtv7|ftIx;Y9?C1WI$O_~uBzpw;GB8xBF)%c=FfjZA z3N^f7U???UV0e|lz+g3lfkC`r&aOZkpoH;6Rg?Pv|NpO?+4c^|WlZvRcM+JniB|{6 zVK4FYb!C6fCM=@IAQjkm8YmRz>Eak7ad~d9C)WW39_CQ>kJohm|Id9JT&p81(B^pP zfcd(rm0u4ox+cVB7{jnPm1*XI8pnVmWmaKFFJ6=U)^$T5OW8bh&B61M+|Q?osq zw@bODLf)op2h;Q3hhQ5^2e#bYtMd zVJ>I5AMX9moeYTx&hzJR&cD>Dy1XBdKBc&abN-~Jv)%$yB!AfoFr!}a%0J4w@e)ibCkCYcS_OEi601^Ojj>hs7cz|q^ivTXfmcOa&TH7Thk`o|` zzsu$7;})2Y228*v|7c@ZmAnG-vxdB~77H0GPT3Ri0|q*P zxTFGPFMs1S14SB4^h4!|GHsKjtnLeNM0T%&L;}(e;5Fqs4L$+?DWUS|*hFL}q1TGU z0+xXy0~rE(CLJmtVm&S%Td{q@7zO6-AYv2PQJN;m!wxS$CIL~v%nP0w3b$GX34a2}{{eh!RnAU8gg_+iX0y=Q-2BbW z&WbRnY{|Dz5|E?*rR^%@Oi%H=j%t&0HmmA#(|I0sBjK!N}uN}K_FZ8R1; z0I!nx tgDCU7=@pp0gOmUzeH$eaZ002ovPDHLkV1i17dJg~q delta 865 zcmV-n1D^cF2KNS#BYyx1a7bBm000XT000XT0n*)m`~Uz0DM>^@R9M61m&?}eSfo_sz(m$k&t_$7wzbRzbbr%UOfe`v|gIrVDkO#!Z zGA7v8YtHH79ywP^t=w1&Ui89nuddGA`Q|&{d5oADr+EcV`F|=EvQixeT?Duig1*g2 z-m?n?u<;m3iEh1)b-O|c23!XgfwnK8KE#<3R7J|=-^z=Ne-qqDp`gN&R`xP@mPDkwEz$}C+suzdmeh)nK=lj4{0%8Cm z$V8$*LEOm;zz4bA?*f}{b`H2m=J23En4e#pG=Qr^^lt(${oN-(6@XqZ+LbHB`T4rL z(Z&VPm-%wvD}H~HGg|;6A@pp2zZ902_u|~#h6B(O1Ank=BnR;2(BFULo>%&KEhS`T zW|LBBYY6a(7!U)mVv0EEC`bg->NL0v-U{z|PP@;+as36#60zps&(YW3LSYH=gTf=L> zfm*Zo2GdfhpxfIpJ4=z4f7H1^0Na1@;zoOK?~=sW_I7L&cr>!-H$!4%_VQ&Ugi}43 rOyFo{7Hlxw3X5C{Xh8n=r{jMC7A=q+gjoik00000NkvXXu0mjflTwf6 diff --git a/recipes/icons/prekshaa.png b/recipes/icons/prekshaa.png index 1062c2d4403f2abc83f2b9ceec56ccce099ca58b..6c232180d5b79bbb6d517dd11536a871deee87f3 100644 GIT binary patch delta 1013 zcmWlYeNYs27{`~FP(iIGe-qW3woYvDv#oy2Z`y%0UQY6q7-l#c&;+vZC8QHkMknOF z!%Lg<2n4y{cJ~gLWMrrq#*Qn&4qt!?jUzQXFfR{+1VSxdyt}>Iwwdod|2)rp=Xt)L zd7P0`ORLvuLUozxs!fu>5(FVxae!xi918NR#N!~3C4munR^*Xnk;O<>M&yvhv62;u zjo8m)pWG6#)QtTc@|qc`2~&WOIP8_hqg$1lp;AvL2#4&^2RbsFgv5*dpd z(0>dD1i09U6b!H*hF-J06RZsjeMuwnh#uw5#|`xSmsp(!3KReU!fil_5DZqrAIqV=49=Nw1_o8R3o`m@&a%1IlE9AK&cgk#|VQw6Xel7108I_5CDER-Cj%$Riwj5Ci*dtNfLOfv1DcMej(Fi00|~* zSy5(aVFz_^;5s zbK7M%ni|Z*|`b5toOo zd&d~jS7k}~aMx2h&E)apKZG%-qVhYRpZ--n(r!E%Zrj^(M_Uztb^!bFo*(oT@ZiRcb^`j&bIS((#dJepzC;=Z%+3N9Nl1 zt))#(+PAJ5J7RO6aOm{QkEUGg%~N%+%CT{gsN+(N{Xq7zjy{d!M(?)o_FYcfjp9^s z1F41C_*}@x(+6*Y66O4g&-!9!M!$_y9XlPi@5thrJ zKl@sK&q~mpY@M6&7 delta 1179 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 delta 30 jcmX@ee~o{FvM>W@fk$L90|U1Z2s2)~TlZ(9;sSO6e?|!A diff --git a/recipes/icons/projo.png b/recipes/icons/projo.png index babd621eb5fa17d9e88380f8469129e33991df2f..fb81abf6d63829f0f5f2714520b7ad37feb4e238 100644 GIT binary patch delta 9 QcmX@Zw4G@}!oRmk&(SB_=(!Wk7q9-K&HIPWFXO0gyc}FkviI>3ElPvg^wW!8GCb_v`~GdM z*69bs)gk@0JLpZa>F*yw*W7RFVynZ`&uTsHCBcm=(0`V&anUwSGk;UJf1A}hUxH+1 z?6lRq{#b6cmX*3dq84Yb)%wQTKghi1oHJ%qa`Dbt8*gVxFt3Q6H>zXcQcBK&YUYWx z_J=o~j`lcBn@I(;38B<|<5^CK{z0!o9&iSjV%m%w#UDSzEDtykj4_l%Ay|wwV6t46 z8~`vOet%d9+>wTvOw%%F3>3aq2%K{bW0|T#GEk>fT(Ews0R9&FMaLU~3V4_T*y&?l z*x&9@k(0Kj3Z@Flp>-OfC)Cd=#Te8+8Tl@Him0EdfD)T5{RkbTdiBA;-`j4ff{vhH gdC^^D3iLer12>Ezc=%Ds;s5{u07*qoM6N<$g41YNqW}N^ delta 662 zcmV;H0%`rH1-1o{EPwO!^XT#W-FvJ?c(9#+v)h+==Is=^wZ+<&fxLb-Q?BX zSE+2zXB;=|hR$JXWC+}zmL*VEYE)6>+<)7;I_)Wp!-$yuG-#xU{ypw6nCVuCT1Dtf{7^rhluiq@$#vo}ZkXoSc}K zn3k56l$Df@jgE|sgMx#De0zL*e0pUf`Q7v1b_4rPb5c9{CpKp9qZ?^woOxWnbe-q^j#%g{ zUmx!7r`{?$w`Pl>>y8%wsvQ-dj+fJ}^YHfNkBx0-On+GarFx&dy+8H*xy9GkiC*D5Lw+AwEilR^|LtMNzPo&#d&!3cq zO)gcFL}8363W1_g(NFBvyb%Li%z{iAhsqd3Xk9gQGX?$-tCB!aC_xaxLO&C}@jRmR zcLGKTk$;X55^8agP^PL(A%q}|?NsbFZ}WE)IKBDMJ(^NtJg>_)IKEdwygd1^VL5k>_n@e{r2_ku%*#2645Xe z>6?w+W>MHrHr7Nd=8u2xxv%4YZPPg&++t1BIv@W0`~2|k*ikv_rI`Nt`1sw~?5UmS zkb#pD0Vgx>xUSt}PT5jB*Gn_*vZ&HC7w3_I{`&grpp)TuWbCV-+Eza0i+cY1`~2|j z`QqNyLn)JN0VjXYi(c*k009L_L_t(|Ue(gIc0@4@1W=FtnBOupgqfL{nHm582a+Z( z*4~qP)#?^=q#>O(Uc~&HLm8mWF-28XC6}8hY19+<+VDs`Ig^~%^^5$#kRb?K9<3As z%taUWfY`Yuin~M?y3x~X^z{RXT6U`#v{FbTV`ZIjcM^Y7(*@cBOjXA$Lgp_nEn@|% zYn}ksf58UxH@CL2gWWbDECtTK>!Ns2rBZOD*vBV0#aYcHQc%|}uUtSc6CwlGxQX31 z?kf+Egi1jgPub_dVLYIiA@%ZFpeEmoAguX3z4TCTjHhpN>i2*H>9al(1_#o2H|qIM t9zs@_c;+Lk$`{hgeY1Z`8?i~}%?}!MB3V-#Jrw`|002ovPDHLkV1lfhAvFL1 delta 818 zcmeBRZD60ET+g78>=ES4z)+>ez|hdb!0?L!NWWlUC^cYUc$L7wU^Rn*LA+qju0R_G z21dI8pAgsUUWV6wjBogw-taZK?rnVC+vuvj>SasWD>jN(Y?ZE9%U`yVzhb9+%|+{3 zfBpNN>)))H^Lokj*Gp%-SvmLZ`Xz5x&wtugbvG^Wnv3>T$9nZ^t~!tFbKh@W{paKR z|NsC0xO?+yd-?MTjqi7?`*!`}!}64?4r*7OH81G!zgfND@3*gSH!QuVFLcpBq)ucbisRG8TV6vGL!pU#}KS zzGA2He%qQ`VRlcutN#54n)vqTqkDh9e4gyUs9rC_B?Sx=2F4_BcNe)LE(L8MhrPtp z*OmP_Gq0GS{-i&TWPsAro-U3d7N@UHx*m1NL8Qg}$W@lkCN~ofp+5u znTYhY`mYmzyqIxNz{GH#5916Ee?P{VnUhu1Hf^49_)pWu9aq>cFT3igGu!e>ph5Sy zuFYLrzY4u&NC`d8b}Db_uAQy9Z?34Y-NHHshp7H^;RQU9HgW8$LL_q!5W;&;l2Z&?z5 zYl7=8|NVIq+BvMTw~qkBPPN1}q9i4;B-JXpC>2OC7#SED>lzsA8XAWf8Ce;cTbY<^ z8yHv_7~Brr-pEjZtRpu+B{QuOw~kdj{I)gwz3>+J08?d|RE?(XmJ?=>=c{r&#_{u~?}{}mO|($d}C-QeKh;o;%r)z#J4*VowB*Vx$D+1c6J z+S=UQ+>!npeuWo2z^ zYt`J`*1^Hn*4Ed+z}ME+f`NgEhK7)klGxeVD<>|NIF*&#Yirxv+n1J?+}z&W-QL}- zte2OU-Q(ll-rnBd-rwKf-{j<&mzUvRVBw&k;i979yu6#5nv>808v~u3os;eXB!8ly zq3FiOE+Qi7=;-O`>FMd}>FVn0=;)-Rr0afusi>)|tE#lKv+eHgx3{+MZ*RD^x9{-q zxVX2vz`*kH@w>XZ^7HcZT3Yk-^Sryf^vftyEkEZf&~MP{!TjyV_xEp}XI#J4 zy#CPY+V})N>IOkjtKZb>r1*pY8U|L9wD#!acKhzaNUDm82VwAuq8tz;@?e4{@WGrH z=PoR_<`YE8Srvr`CZzPu&5?JVUPq`A%~r74+IGt zYdl)uvL!XuGfL{_RFy`8SmGYow@)x%Bw={(Av7H`C>e+%K@fRX0uEdpot=}b3J632 zNYw!bKU{nK>e<;n;{gE^PaaF#cjEQJ{pCU^B&@+1GQi@U(qvoYdVgTQbb8$l;Y;X~ zP(G$VEdyLCXzXXs8xL;X{#L;CQ&`${Km_oJRt9BbOh2Y(L{HNVEzlL4*|2BT z9pUpwyu~dfK#7%x!*;#4w$llE>X3cC?I-_*b?V%lu ziV6#}Gh0sEwzyX&MSscQ*;5wZ*8AL}d&M@B@vv@7N>X)Bqd!gBbdSv87ec?$SLIe; zV@&!TDfQ#aDn^t=M(vkNUN5%WEYh=>UP)1OSOYjPFvtKe!~$O30so~=c@|N?D*+fd tC7{QkHWu)HUJhWL`01K3m=^>70x>YJXu|#g(f|Me07*qoLgdz delta 1188 zcmXBSdrXs87zXguS_UdXbk-YPz|Uo!tLX;H)&xijluXS4HzA7#1gwgN6&mp}NJfz{ zV1fyhA)6DZPy!aiE>(+^tt}uRowWUW6AFcnTPa1vT1)RcAN%Eb-jkE_=Xu?mf7nuc zfCDzOkDe+7VCxR7ICQGS0|376#20Bv03s5JScxPe2|}zzAIcbi&y_xNpXd_Mnv=l!lO!9$@?BzoNQSk%)a>Xk~xy}fz}qgHD)8Z9(AsguccI-OoB z)x)XRzkq?pF%z_j2}53KUQx6_YL9??)Xxcap&CSis%$TyWO+22dwbj(u zXEK@U#kjCdbF;JcELkiTvl))%l9J`+WvkV?0=>kn78I;P zuP|$CYp-D-+iTnD(>9y!&6_uNCev=Wzl9tQ2a^eh!?EsIU*9M^oEuzDm(%HTxe&y~ zak<=NvfJf`JRXmiMDlvPkPiSKk?13ldEA=@ALaNdakOC2X|`U zF-Cmap1lDw`At$f0Lqi1{Pf&nG%+xlT^{-G@ppoI*bTQLdWf0p?>ct}ND{8BY#S_< zB~>9ooKxlHpX^V0xcH@4F#MOkP>`Fg?6~xgJBM^#-}Ss0gkqbD2hjuq5#TP~*aAv} zf(_iE$&MrK!GOVI#p@mVZXupdX!eC3rygv#G^M5vUk#v_=dY>TE)YDCehYgOz5JK< zqFraH6F49UO}LeR=G%*S;)IJozKo{-N^F!jev{#mSCrVt_uUtzo*kll$rCr9s&jvtmc) zKT=p;t{Z!w;i{CP6D!3bf_vhk+@f9mNGa_hD7Fe-8=r|*!&h2jTfPs9;8;_3J^88F zIUgAQF{MKidxpSB?4n;;bqoItbALT7-&wift4@iC0^f+MqpbQsk+yxae3lckeLp2M zP4FVE6n}Q>zU1~ZnuD60tC202wHeI{%3yl)DBE$1#j;;!O&5r#B{YFGy!pL=!CddP z!pyhV6RGpVoZUC`J@)!zUUAf)0T%N zXaGV(ZhlH;S|x4`@5*$tfEwIDHWcTlm6RtIr80QtCZ?zQCZ=a5XDgWLnb+%C=vpcO z?KIIfFx53QQV29OQ^+VODX`MlFE20G%LFM0VxV%p{G#+bZEt}lGDv`o49O_XO|r6b z$xklLP0cH@vI+nyEoLxW{(rYVni{aBR#q9QnduoN42G6Q{0v2{KsDke^$3HYio!Ef zN-{udF0WDK0IHEhvdcF!H?^d)Ae8~^Ed7GKbo)(Veqe(T=Jzmvv4FO#rWO3W@*# delta 157 zcmV;O0Al~Q0=EN@BpL)~K}|sb0I`n?{9y$E0004VQb$4nuFf3kks(k4&yjMqa}s0* z0ssI2S9(-fbW&k=AaHVTW@&6?Aar?fWguyAbYlPjc%0+%3K74o@X$EPwz1|0E>;khT8+GMiFTQUCz(($do2-rno$>uhZQ04RKtl9Fs} zY@eT>prE9t%J8eJtF+Dkz`(%8#l=WS|7@NAfPjGb`1p{J|Ma`}4*&oFm`OxIRCr#^ z(#a0PAPhxOKOl4Gx&Qy81w?C+=R}pd>D?Kf1u}@Irm6%u0)HFzZA|58#?Y;0_7Z2xSX|A2sikdXh7wf~Zm zlAoWSprE9t%J8eJtF+Dkz`(%8#l_On(%#+9?I`1t?-|JKu{0{{R3q)9|UR5;7c z(n$`&FboAy*9oP7fiSe?-~Xa48ZtVq5F7r|eu}KvBvm*IiGP+&kdPLTO(0(bX6`3U zl}Nw^05CVf0|0>TbRSf3Ui#DxvO8!19+0)ZHmU%}e*dZH07SlqrvfZd1Xu?sfOF1? zNE`SBfFHq#b+Alqn&5@-fb!J=m>k?HP_BE9A4RdB9?UfZtehN&g@twjm`$8c2X+1d ayaB3fC!LDnY7N8y0000q$gGRCr#slWT8+KoCV)P%5P?6_tlt zz$%D^T70!-{{Ig%z{Z6PiXnPF-H?;bWoK4+0Hj2eNEJzB;Ue_fpkp{5BLb#4D)i+-u7vhA)Z-Pl;K?t@tgl*7zLUV`cC+zSS z{2ms*2g^xKFjJd2yM2C>2SuLG_Q#X)o0?qstIT3#P&^CrpP^}H;r;u7>AsVAtJ~j2 d`^$PhXM+hAGtHedshGe delta 1111 zcmZ3^a)WDviW^J1qpu?a!^VE@KZ&di3=9g%9znhg3{`3j3=J&|48MRv4KElNN(~qo zUL`OvSj}Ky5HFasE6@fgu_eGK#P$FG{|pQh4OdPKU}5|Je^S%@kcsy^>lu^0-CYex#>eO=j~vk8mn2^KtDXUV|8WLFguQ4*9`u24{vpO%@Es!&o{kg8CTTfo3z z(K|KNd(#~QjyuI&GLB3vmkQrZ-0B@V&9YAFXQiFodGDm_H;kN0E-Uc<{r9uG?#R=t z9(hK4sy5{$triXUI~I0k?M=1%xs}qdck!j&n4KBX<$Y1lbGN$akFe||SH+JXQrh<- z?8*l>2SJ^rX^PV7J1eKMZivXRJ)l$EmB0JuGofM;j)cW#P3=0rkKC;_s%&Y@?G>$x zKe#gFt*RjV&WD#(TFnjLJ(%pe4|=)V5{gp1gF!s#}_-%Pb{dhKAqq6kB3k6Sv25PsQw>TBYqf)P;{t{`kh4L1L%H zE8(sR*C>aMpW$K)8iE!I7`=f8tBiccl?5Z^RGQAKmKm+OPNJ`z7m}-HWZiG@b7#cw%Il zq@z*&XF}ESz7JAN;w%?e#Bw$yZr{GlCxNBCV3MWR#RXd5mlb~zv?_mhWbd}ilW#NU zXlUfB-Afv`O2P`tow9QCuBz^e?hg4A!N|0JQKa!3Ni$^$USGCpmse~QH3;L~ zo-s#wlUu#jBh|8G1Dl4IeQTzl_}TM^bB5fBrzaa$W#9XKW%quTC0peG zO)Q`D>8+EW3pE)Cv~;r8_X>&x1Z)U?(-r&fulyOO zDMBJw3jaTu^iyr~&D{le-aG9psuT&cbMICU(6}eQ@5ue@Ns4>{Qy0qEb};W~y3gG6 zh+*X<7YBit``ApQI-NLHG(V9)#bu(Q;AqScvbOb>i@BA}yL&#bmh?u=lKOg~`Cr0| z>B_=YYdpE%v#k>=^YKY$tyM8o!oCGhjTBpt&UyUis&qnX`VPgDhg06xeC$$a zf4G1@&6sJ$JJXYAzfF6>_gOZ1xBUrs^C?d>uW-AH-HcnX`FP3YV!K)GZ-l2>OTCR-f06aqS(`ba>QWZRN b6Vp?JQWH}u3s0tkiUI~tS3j3^P6hKC4eXc`$A z7bPVa9v%RLgbh_y8X6kORH`47i~)3i2}wjjRCr#k(?xRwF${oFdAHpZQ-(m9nK?xW zmzhyp{{Ppml}u+V+mlPagGV!ZH;Bt`EpZX#{~LL5Wn9LPlvU@Sx2wW5BY9?1~gz1 zeXXh>^6Uri9SVrkVym}5K_bgRc4Fl~c>y%)S#x4fN2;5$H-4PgJU>aHT z49sva$G}7(Fhroc`mZtuS_s5J7M#@lMaemdWLte`^crI-uxHUN2 delta 392 zcmV;30eAk12CN2EHxMF0Q*FE1~De}900 zfR~q-u{aotow;#fD3PYH)UTMtXL3h!$1_Z1&kekd+A{9oexc zu*gONwwMZ}laLkU$YUsQe*;T6si?Vw89wL=xD&zhAoqKNSSkEy3gp206hI0zz)VFn z1-f9-m~bG71v9nL6c}Zr0W&lO#^?%6(G=(tQlJSIRpNyM4m1VgUkVE>3pf!>|IBaTrAn mo&AIST=aw)F*6PgGcN!V(|o1u&Ph!G000005AWB0_*{hB!4_mOjJbx04o3hHUIzrD>zwNWqx34g8%?794AT~D^46PPyhfj zGC*Gd6*vGKK$)Mf;p6D=@bmTc_x%0+6(UGb^w&E8003)AL_t(|UcHk!62LGB!~h?; z@BjZbO$ukp%#Kr7wF+Z_%!G!*k{;ru`Ag`nq+^1e>IJ|F2T2gB$5aG_!H|(W(v%5o z%nJk+pgvYCP#TL0iPp?s*k5cQ{E0QQSnJGM2osD70b+St-R2DyQzNGZt;Dnd0000< KMNUMnLSTYR3sva= delta 234 zcmVJ5tSu#LhT4jD05AWB0_*{hB!4_mOjJbx04o3hHUIzrD>zwNWqx34g8%?794AT~D^46PPyhfj zGC*Gd6*vGKK$)Mf;p6D=@bmTc_x%0+6(UGb^w&E8003)AL_t(|UcHk!62LGB!~h?; z@BjZbO$ukp%#Kr7wF+Z_%!G!*k{;ru`Ag`nq+^1e>IJ|F2T2gB$5aG_!H|(W(v%5o z%nJk+pgvYCP#TL0iPp?s*k5cQ{E0QQSnJGM2osD70b+St-R2DyQzNGZt;Dnd0000< KMNUMnLSTYR3sva= delta 234 zcmVJ5tSu#LhT4jD9l3-9sjEM<<1_=>}P9@?rK}9qXP1K+<8T>FI7&qA1hMi0n9E=VeiGx912kYp% zkFH(UzF*$kzP?iQKR@j0`{dr!?>3V>45Kncu%C@G31f9WvBMD>mSX_F%nBRpUTaUe)ukaf?Lnik+|iXy|5 zXDnBo52ZwX_$NB*b2W~@wWCK4I6rB6D_OvhY(QW(8^L6?f-pA&NR_~}bI*sX_B6gb z_`>>uIp0h=BYy>9QGp1Dtbin`V9v?*ZgKRMiE}~Y<=s1)gZ)=ZXTpGk5`uXdAj}}C z2$(w*tS#-2E5UGUOPwquh$!3{!Q8wAbKVK|0tpbp0R7z?0|UJk;mO%_tepX|V8Ecr z0HUbE{o#8H$1h!6=iIxaIpFMn3dsm#?xV1WL1KOpNq=SWRVb*bY1IkLxLmgT*RGX- zmLh#b`W%w7ay-7D+v-7ue;Lw%IhUF7Z144}r6_Jc-Uub|*z;S@=q13E%C+Bq{|W-6 z)ibJ4ea_d@R;;)Y^!qZGttz_!s>&ZwK^rwv{JhqcCD+${+B`-;i}4T%9MhMQQB=~W z6S#i-^MBoAPUqviHk4p6QVb>mBeQ{k7>)onP${jb@zCh8Bm2Y5$x>R@w|f7fMqoBQ z*#AV^zSloi7>ni4R&|b?J6kjDa^({o6EkI_z`^F6XV%jdzZUO3_zuQ6)&*gv{yWFN z-Zkms}ZyoP&Y?K&* z(igX#x_a{Db}1Aw8ZfXJ07(SENMdAWMS!7SE>|vnwc)#DYv%2!27G-z8^qaQDvqUF zOQ{NqmS#bq;+lp7P5!R7ddi%P0#+8-tIxz*O#%0rmO9>oW|JW2ZGP@2qb0-tUm5{T zJbxgGDpKTm{T}diwbx^Wq9-qZd*5LpD|d{4zx~azhN+vb0x2R1A@{AKpwsyzXPmms z<}wu&--u?<Hzy2XBH1IJ>0y zk0k5Ddr<WLqZ7-e_thRDY1Axq$h``v>3S88GK8^2oP`3PZt&(JWvf zi}Oa@?Wec!J%3+M1v?Pu4vWjz_uM>n;w4~~vcNhO6K@P*@5nM)0?e~utF3S6_2)+Y zHwR0?Znq6%1WPtmwey$rjmh6GY?$^1QYpodU19I=b#>H8iV89>OK)%GXm?NLY=1<7 zS*{sgYlIcS2+EJ!UT)@P5(q$414PpRRe>M`m{7&Gb$g56eZKB<^#~!%Q=VU4r*F90 z&eqAA7IMlrIL004?NI_sthFdYn!@>rAa9f1F)?&!3D0+&q4*0WYh~fBjDn(W(2RP{ zpRHGr3I;O?XI505(}abFSu62%!GB~385LqWsc=)TGs;aOxDkfPoCqOg%53QSl z$DmY9&ah5C(>J(0UeWr+qon8L-NhKowVfB&>Dv#38p4*`#iLq@bxZs$e;(un5(E+i b5(NGN;G-DD;vH7700000NkvXXu0mjf8sve3 delta 1328 zcmV-01<(4o3c?DIBYy=6NklTTE0(7{~wLH|Olaa+8Z7h+@EsDB6^k#F~m= zMT^ixV`F1$Uz#-W1x-w~X=!V!wn^`4QkyhwOkdPDV^frySg2HDRYa{e1-TTEi_3Cb zF1vfq%=BTGtu?&!!X%S3CzDBj=lg!&H;1&=n9sqO=dl2=0DrImFz*1y<1aJbiRel9 zP?{h>5Wt2BijIYv9EW8q6~BB%2eZ(gzJD@gAmDubED=CNh)PWEkBo_(1OTNZMh6Gd zEZyo(^D-s^Tz$)DVyMvfL{W( zCP)QjZOCi+UkW-dRF+Cf2q=P25~h7NrrpB`ST=|lXldLsdAqs5KjMw!&^M4KKogV% zEKB22NB`oXYgae69@=+$vbE(olEkbDP=Hc^ErEco;eV4FStTVG!T{sl-5HPW+{r~K zMByj>w~k0o9rXNKU4}WXkJ->NLCNu<6!+a*dGy5o<3kt%Pwm`&F`NQ#Utjt_)0F}O zh4L9RbaqW3#I9a_e`>;$fq~Z6r=x90pVt$~T_O$D~41rxAz(+f@Q=gzza0MUsFqd|Ft)9>!dlr|X9$k7VK*1Z35DYkomFa}~dC%*o4JMcZa4_HyAqNZ%K?D|t%GkZPGJF?!8f(jlh?xk> zx0i-Dx~r>7WypopO`98r8ZH;vN<*q(X&FQz!wG^2>065$hTFQ9foSdq5wak8&8i2B zva%b)0E3sS$`olhT*9AJP+S`li+?(5t4b7Un31O68Q}c}*Z>TZ z5VJPBE&uax%6seUUKB!ug$A<*2@PTmO&XdtBG=^H%lrP!D=dTz^9^;juhd$x5QdeM zk=Zp1@Tlv-N&qAb!-`szFfutZoHSQgBW4wsn*NZPz7{~28!@5=D7{~f{og-usiDpDGtPz^JqKzZiwS-DhB94!0mxd75y21u= zWSvtJkumFxAnv3>5VUC%N6YTFC)u^zZ_|3_op+w+d4KQ!|8G*IsorzeVG{xPKm@b@ z8+as?`PxRq8;Afv(kCgvCQ1P)#uSP{vx%7hVCm))b26I2{7Db%z~5mJwDgwBeuAZH zQe_w`+b~o=v4)~er3^(0kqSx@uo8&`4j{e(8(}z7Uo;e<&yL0NVgX4a7K%0zA0G=y z06P`~M`DxAp>RFunl39u4T?16(^6rJ=A?ZarOy2&Qj1>$Snvs?62nL z`?2FJxCMct&6oV@ghLV_T$z9(4McC}pa=uDPP~VlIPxH2i`vW|uxQdmekxk)uWUkV@# zf*~uw;s`u zCXM#5R!LP4DhMv~t#6zW$kCowP)0W2LK7GRdi}MORQDFNb54UjWV%&*e3eI) zMP}c3k#u8jbNOH)qbA@=-avOpU%@8UlV7={fFOd|j&uxmsE;|dYano)=@N^c%@CMC z<5JMyYI7MNG{{H;_QF@__YVZhAdRzLCpgpKvv{zNJ3VHKA_vrf@9oWpKaP40=NxUk z3mq2OTN{)$_?olO(RIxMb+Y!`td}WA%$z^OyOYMsOxv5Lr?8{*!7>_nue17BwnBgC z{C(4WkGTb2%OE3cEEZ!CYnNvXjf3)FR`CnTY z$z=u9fv~6GI(8Y9D|LzZdAYXUM3K8OJ?m-puA7nXGeJ%@N>N&~W)=E7A-?F&nno;y zWLC@>s5WqlhIB~o@+#GBS$9K?{1XrQr+d{=xsTh*P^7IF>Na!ruIXZGk+~?>y`g8t z8mj7j+J3M#;IcA9P3qK+%Cc0yKaTMsNA0xtE+XajF<|xB|95QTyC%*-=;)Tx@!W=pFd?{ zr~qvfFVVk_3SYWFaFjhvQMczermI9>=XUwf5r1`s1b2C#)5X%5?q;M`yw;tV%g{Ge z*#qix5TotY0K{FqT5hS4;80m#R@c%ob2p>6d{v^==Vo#Bc16#i)g7A|HBC=y&!DO( z9=SN{P%@QhRy(0LqMai@w)3KuM=SlszUv%H|4q6DE~()5hwNfuOWxRN<9c1*n#!5= hP46NFCEm9JSN#<4=6~MqM##X0_w@7V{xTBrUj delta 1112 zcmbQoyNGv!g~-QLV+VvY04At%J8QuLmcoiSrMq_<75uTl6s)Xe;~+F`}^K)MdKd0{u1%})>ko+6d=M_St=SQ%Lrx9pkvQ2A*WU%u`NrNU()MoM+ZctS*6XA~5Oc$$h%=t;Qn zdbMIP>xMrk_O|=S?*Fco`0e!Z>C1Zf9M?6)a7wNE`ZK)WL{actg#Wz_wmv_1d^ml` z{du55Mwd>6e37$WhR(+;{U7T+H@~<4`RCW!M?2^5JKO85a>6_Jii)m|%7ybi67?@m z++4!GK1crEe(#;ece{>E-4WGwNovoMWm9@ryqQsI^7EbX!`HW$8~X<{nQjV_Z+PHm zxHeWWv-#<{o(LsR}$3<)c1)sOfyeMdLb?ubeX!A=-A-n5S zIYcJ=u99a>oo=B%Yv1je6%IX7^2=_WSz{yN)$-YjvA^?tsMC=ncONa|m0I|r;lrkY zn54HY8z0&9^mfdcU=S%|@1{Hu|!sIw)>*wU^3D+#r0#ta(B5^BGR9Z%xnq zH2wek&uVf18^H!80kw=#UTdtf)_UAt^yEpgXYd!LtAW)QV@0-J*gE5X{$-zz`AcOl zv|bOs?#n5ew99VA)<;@i0oU(3>~7Br-P#U)E4>=!~An|Ic zqr@-o^Y>naxXLxu>?)9Oo7|^1$7oEXh^U%Q~loCIF-5EsFpE diff --git a/recipes/icons/real_clear.png b/recipes/icons/real_clear.png index 929d08085da7950a79fb9e0dd135190b1cc0d3b5..9f1dfe30b08e97c31c5d896c7bf7a25844d6c68a 100644 GIT binary patch delta 983 zcmV;|11S9F39$)~Bat{Hf8dOa;EIdihKAsbjo^)r;Es>sj*sApi_ic7)GIFBeSXv{ zF5P{8&j0|?007Vc0MY;e(*PCJ93s>rC;$Kd)D;}g007MZ0Miv5(;Onw6&%w50MsWd z)GIF3E;7|JHPu2y+FN3G?9vq#+jDf=O>lD26&2idcHDS+ z(Et_G930(&g5ZgYS4h008T{yY0in)Epf1*x2>o-_igT)=W

%PD1 zzrgLn!_`1SlOX~mThbgH^4Hk&*Vpsc*wsWt^V-_<+S>Hm+w|Mp^xWLlMo9PMw}7{dijXKu4u7a+Vg3ot4=w3tg08 z%R)x02ySj-G@XQChQWA>CSfI-#6?mt1Ohk*f7aGw3Wqx>%%Jmg$ImO6d!DF_IUr~I z#>0OHc^4+hGZD31LsDZcSG3wkI)T7F1Q z1^t3-pm6mKlxZV=uO;P=qWX00Rg82`3gm?R=!GpZ0b}KQ*x}R8?IT$IiC!V6q_102 ze}H3hKcO}6R0zp&$W@$Ni0~w;oz?HxV^>o+B2QL6F7@Y=TXHZ}8FR+n5c#@rK*g7( z(9`-by7Asc;)9!V2(4l)KQ7?Gw{=Au_GV*-B|Oz;$_WSuWCA&(KQ>XQj_PIL^jN~t zKYko8cYYpLClBggkG7_SkjMvg3xlTwe-f5;RMbB!U83*#l|yjM<<|_K$EdKf$ekFv zQ&YJLc7LxxH(7l*eEjrCGU`m^<)vlFtt>ooba0kOkugqY4Q;@<%0wOt{y)tsmAhPw zFEC>l4ob~nUFNO%J{X2=VOm?Tt-bAVY^P^CUfcQq15$LaP7H5?r~m)}07*qo1w^hw FV1gJ|1t;EIdjjEs>`9)HXL z0L=gZ&Hw<<007Vc0MHy9&?hI+007Yd710$H(G?ug930UrE7AY}(f}3G6&2DI9MT*d z(i|evA|ld2K+^yK(*PCI6&2GJ9Mc>e(;OnxJw4PF9Mlye)Epet93s>lC)6S*)F&&{ zD=yS4GSn_I)GjsDGBwmaJ=8!z)Fwbe)iO2JJwVk!Le)Y<)kH+qMo893O4dwH)=p5? zT3XjkPS;RUlM?|gFxzl)+jDf=b#~lybli1z+<1E2dVAb^eB6VB-F$uBeSO`1e%*e5 z-GG4IfPvkCf|GCo6$apmir|WilZyc=B;=o<EzttLLq* z=d!Zrw6y54vXj{X8yM=ixaz#U>$+uQWq-1Xnz_vGaFFN3D>G|yJ`R(od z@$vig^!)Vn{Pp$y_xJtx`2PC(|NZ^{|Nki=T5FSw0wsR}3rR#lR5*=eU}OKaNV zHrG0O``Lfw$HnI4C1_{?6(sA|Z#miDGIM{UoVmWrqU9w`i+2}Wx9vaL*|ub7tP~$Z znu>VeNo^^KIVXKpq!#Xwl93EKUMVfUZm)!_E}fkG*Mhza37tL92ONi97Ypkk`4YoKN!wdPP& zOO3q(C>%fv+$2*^#>h$aoQwm8tBO~hiqyuv5;9WiRuBafPq@kjpKLV>@;uoqVPhp( z(J6l|y>^eBrG-8?Bvqtmp7d0)II^j`K%(PVqO@GZe0N>B&HGg>AaRjo(!XN+f-L5@DEk8dt%UlGMVqZ6 P00000NkvXXu0mjfA~orz diff --git a/recipes/icons/real_world_economics_review.png b/recipes/icons/real_world_economics_review.png index 9ccd38d2f3734514241eaf07f05cec3aabaca18f..da1b8a275f375ca3e874ef75f1153d74385f8a0b 100644 GIT binary patch delta 401 zcmV;C0dD?)1o{JzbANJ4L_t(|US(1-Oe#SXT*O$D{McJjV`8XDOz3T>jlk@Iz;zcJ zp}Pu0XQ4l5cZr3eGgjCU4JzFxQa}e1k?T0ngXiNoX_#{M&70ks6+X5OQrW{jvBp36 z*#9HKkpH`1&jw;EB9zp6quZ;-+{{FLVpq1Gz^Ho0kw1_se}BNo`5?O-*_IwwkdBdH zB3^C=M41L@R^Sx@9?3&Qgtf((;EcyJCdiD5og&dCBrSj%&DF7pR_fFQdSswLGxtQq ztrAUu8?qs3GE9qPD^D{(C&gpxB_mQDNgjCIWgGi0D==g|kfU#)ZdhP~^_MuMInaOw zmax7F{gG`tV1G%n#iuT+HL|4(UK+QcvCTt?R8E0yqZJd5hkl;if~~nHe07C7o!v|N z){O#RJ*-Vpp=%Sp#RY2oxDeh@q%PR}9V0}R0jWIw06H(>fcyw0O~K}>Z6tz_+kLQk vgF_B#aP9#(T*JDk&!DKyDmTmC_zu4nzw{~`Lrlm3015yANkvXXu0mjf5%{{8 delta 407 zcmV;I0cigE1AqjObANbAL_t(2Q)N;+Oaf65e2AeYG4@u}m>6mk6MCDgjljGE`E(Z> zp}Pu0XQ2i<5(`5|te_(rRJu!~fDR-g*KvTry6-@vVY{81-Pt#DLa5q0&|*h>;fnC{pR1^8RaqJSb?0@@e$3;J;DBE(r>LG38 zkbzLPu?Di#kMIOu);;PphhKR0<%nR9MpFjJ3<_V0=n9gSK!Fz4i3sE>#7nwlAW2i_ zRD_CY8UrONGi^MGh;TDOQy@bY)2!#H(AtuhGu>gC@_kHT!1F+yWS%0Pt9tk)3tAl)j%F+QZ4HcoY%tM;2xCNFCuRcMJZcKESmniV#Ja|QlI_mg04EgzNkd>e>b-a^Q1}O_AO~B$-%e7FM z-0FeFYb%pg40e?rwAV4XL4EuUY9q?cu>buI-xa^~DiH42tcw5u002ovPDHLkV1o4( BzF+_V diff --git a/recipes/icons/reason_magazine.png b/recipes/icons/reason_magazine.png index 11b0b6b1bfebae5a45bbc77bc8b87a7431c4ab3f..b81ec6e403d7eb2ef9c4c9d3d747b52f4c0792d6 100644 GIT binary patch delta 9 Qcmcb>xS4T+%0$P001|Kmn*aa+ delta 36 scmdnYc!6<(iWEz_qpu?a!^VE@KZ&di3=E9LLGDfr>(0r%oTxYn0K;AjPXGV_ diff --git a/recipes/icons/red_aragon.png b/recipes/icons/red_aragon.png index cb968d6e716007e1bc27d7232456d0f5a2564f12..892ebb0c3e478fb805fd16b3224005b297d322d6 100644 GIT binary patch delta 279 zcmV+y0qFko0`LNmEKdFI?(cVd=PNPiDKF+DDf674^sBAsCoShHFyia#=PWYjAt&Y` zDB}te<|HcT8X@N!A?F<==N={JCM@I_ALl48R>ik&m$=aPQg)qE1FPNq=U(F*nya3n~h6k8|0e^<86Gl=Id;S_u2=fwgcHX2H zoK<1#iTJ4U-5b_`UPrP%B(3br5o`S+&tb?GOQ+mewevOD>nY5wC;~miuED7L6X*sG dVgLW!oZqa?2DkVq=F0#8002ovPDHLkV1me9gBkz; delta 282 zcmV+#0pf;I$2_U?lF z{tqXTiNSGy-F|arPl#L)zrqGd68op^!~_qLhmznzTen0(u)DEoddA&zf+c9X#weGK z-~rZsxnXt{DPKQ8#NE$*rbHGF$;E#G_s}d zJN~}&-6Nk1Uw*zR{>ukXBm6JmEx_S_0I&cP78PJV)+hn+F@G6m{(B@;i2=YuQ-pZr zgNUlnV>S>~i9b!G1tYT7<}-JUl}9{Q2*zjtDurR{3Sr8d9}bsnW>;E9ni!IJ;%G>m zkR26#3zZu#OyIL3VrB$EFsWpkD8fX`A_JflP=HX;m>mcvD0>tVq{qZ%yX4LD4gdmx zS@^Q4YOAQ4%zwt2#JaK5LvltCm)$T4E2ZbjmXuAHe5(QaCX2FhL@xE_O&g-JW@v31 z)YsQGK4`RGp{zH~o@g@|^>4)eJ)I8%Z-t!I*+E!;zH{!~<<2$|v0w((JGf%Z_c}jG zpc*j5z_}>P(w6YoH$LpFUn&q|Mn(@F{r1IpbT5s6_kV*9v(+iJmX}}H`sB>H%M*@H zw25>c4Z2sX&2Y|Oa`6&5wt|sluv`I3HS3LyGiTpJ8EPZQ<^U2s;WyTnSKpd){02+{ z)J%kka|8&4aOf#75GEfS{_$Ow9td_77=&xeZzBt5Q^26U^jxEHk)5qjXGf18eG@(Y z(b$paw|`C&fKq}CfL~ZH3JxBBeeme3-1KGyS*xG-L(j1mz^(RHtNk(qRbZa?4wiDF&_CP(nKoJ%TB~n*GOW;X^1$dG zk^lf9PCSDWDifE80lPdI#BqEh&mPX;aH+lBSlV(VfZcNE^;&qgg3{#vptnnIj7o6v zl6^)*Lg2&#mvYv`#avbxN-u1jZ+5m21+i7N^{U31bP!!j`ac6>5Vkzr=ki_ zNq;5C>A1H$8ry}4OH`1Ed=+w zd)?@obGpLo&5gD5@2{?1F8Y~P5M_h?bampKMRFSL3XM>}zMk7t_z z{nqA-A8o$$aS%*9NXJnx-cRDbV~rRHA%B7B83IrZ6M`pYo!BI!JL&KalbM}_YOh~f zS$hXnvnnvz1JWaA0So{rkWX3qvsr?P9WvQCPlx;C(XTUosnm+{TL?I@AKx!BBpxdz z0F`4W2u=~Gny4rY2q=t6NAcZiw@Vp_SzOGn*S$LE?f|=5*hJ}xY8GITYYPDh1Am~3 zDt3C*+Z*(DxYR&|sQ~G?yW5X;v`GXu0X!jGd4#75K&9ajq#t$&Op!wz?Uq~gsGONb z#qkbrk;|0|0dRIV09@=|M1Y>c8tF9dUmp%{Bg&^4S=LSCd*jhqu&L%p2nVpxy{KR@ z5tV5u5<1VL!zVx7yY*Rn`PDo>uxM6$exny%&GmuwgusA=JE~nkK$J$POoJr&Buud~ zZ(siuWx$+qfk=;SX7WK0cBBl+=1*Vo{?+xJOBt}bpJlPtM}L*RFKYF3Fxh3+kp?&l Z@K2^ospSlqH|hWY002ovPDHLkV1lW>Q3?P6 delta 1302 zcmV+x1?l>N3aAQ@BYyx1a7bBm000XT000XT0n*)m`~Uz1+DSw~R7l6wR!x%}#Swj( zRn_SMFSh;DX^`6A%Hb}R_TsXLnmD&9=UuEV?dgrYx_&pEfcYi+r{{VIeAN|b@?chnf z0;oQes1WHxFsk}%N~8rNq9+3o5wm&ld!fxQ&ia7aow+c8h{%o<|?RLF2*RRAcd2Jx&! zY(Pw)HV)NDP=6sXLy?lPDk@M$u-jrMA`qz}F@4uL$F8sHqc1{S#bzPVLllMq6$E3f zH3MV%WQ?^P;>1_uYBiQv+HKXw?1Eo}P^R6(3zz@i8~#bE>8kpv@$=>E^L%#P)brFH zNDNJNnsuKUU3_JKbc00{s^q4pAAA4rrhX)V0wCrz#(!~Dl>;P)m;W(3d>MfEOW(}z ze)sljdArQ-B31{)HakSN}TJ*uspr@-s#Z>(;C5+5I2hpN>D0rc9FIwqZwrFcI6HN>fa}C}y`9 z>t3}c-8*<`F#0PKL$$Z}>|k`m*xtG=n?+H4vwvEZiiS+K4M0SM$T-)H(iFu>vG_`4 zO#(5E&i$pgcP;qJhq5=gHaPc!HCbz272o@2$^s$+9{mPH1ZV^dbE>f}=6RlfUROVC z%-P=XMsM$W6p+UK(es_`d}}I;Z|BpS5?Ab?>>%MW6ah>CD-i*~=Vg9pU1G*&y-WSk zb$@1v2G==UKd&$6<4=p(e@Qvb29~UB2Uzn2L}F(gBsTf<=5qdpZipHTZgl%sI@x)b zjMe~J&nBNuCr94r%$X7cvV9g2pd=CuxlT3&bT&Ee^sjch&seqqy8X)+F8vLv#;(~X zk!pTYF206n(m9B+l5Ml-OixE8W}Ag(k$;bmy8Ww9|KXo&qLRUttFMD8P1hNetMN2H zDe_sTmocX?dXmRv00LA*n5;2b2;*XTt6bbt@tc=$!^4-E9J_vd zH)rFc#uwl|kp<#<>_18JTa3tLl58a8XOrW6d{fthk2e{}GCwI6UrMZ<%ZRvDe1LXY zyc7^(>$oFLna@63EWSRwX8@?GNq?E&UM*Kp&zu4Vh(tT>MVne;0-T_!>+$`c{&V}w zH;4OwuItkf8k_W{<4@}5)L4L1K|Gs2?XX?1cME_tMuBJUB*}W4#%ieQdLrVD z$(XHb8$Rv07tw>xHrBv}W;R{i3C)91nI!0RFWT&YVu;cZJs|LBLDr%l77yzLCIA2c M07*qoM6N<$g8htq9{>OV diff --git a/recipes/icons/regina_leader_post.png b/recipes/icons/regina_leader_post.png index 83afe621ebc1b85a12793601cdd6465f6e1f2eca..ccca989ba9286333c373a2f691a23a83bd8f5615 100644 GIT binary patch delta 1250 zcmV<81ReX93ib(*B!9h0L_t(|Uagj0ZyQAvhUc7_-PyIB*pBTcP0}{Br9>rZ)mB16 zR9t|NfQxd$4HDu{Ak-hkAK;Q(ETpjx318r}6w6mEtTie$-bSM6Fd{U3UGaY4?d6cEo#Pt%v}a9z^&R z&1ADlr@Ie=$~>_#m3dQc0gz#O+u{-;hCVelXRMxa6GKEV?QNl#WkN*Ts%^X;9lsdP zy+&bKB!%R$YkyNVE?`8Q?mbelGJcby&pHfdm*e^MsCC|yo3Ji9KF81$fFeph*@FmT z4dB>>NRCvdG-q3@(ZYIAJ*kX^8j-^a;(dAHQRk5tzkgcM7oQ6XN@0B^UbtYJ=X6vD z{xE|0c}`gSVaN5i$0822LXby>h&ZSmi;kVQ3u|U}QGe+G7!mg}L43drsdRTk{w+dH z3c{_!nP~1zv~VG8oMl_VERYJ!O8^i{#tqx9ze6-3VgdsONUVc!W-*#ygC>-?5M`Ks zF7jmgjyfBMUc*#mf<&3P9yQO!3m1d>3I(wURLUekh_xH+KnIbT6!G72!^Oth>Pgfa zs*a2W-G98$DU_3rs?Y5o8Z#C|gna=_TnxaZ2-FgGi*Y8VZIqMz{t!)y@Gk9aWPd$s zdts7kp9quow$o%!kv=M5a-m6D>`FO_p)u_|CgNh}_}>5yD6A?nk`6MGA7&yt4_&gG z(?>7W+K)Qn@<-v~`}Xu_rg@oy87?hl(0>RA!+?TW3g?uzD0RgaYNl5po`_)= zPqqzv=5w+&yYeZQm%LY0yTaA=;M5lsEvjHvAOBE}5+d(NM`S9l^9M%_pZ2)C>^gT{ z@39|tCE4`dJErw6eBpSBZ8JNU6d&ebFys2s*6sbtrNv}`S?0aPM6FaC$+JE-07gX2sRl-y`>)XF%c1F8d^mKVFr+;6G z<1r0nTy797Tn(C+6;aT7%bvVunpbpbIXv-+ZoC5%$Trt5>bbW;5;x49>*(4gbhvyv zSiDB16Hw39H&HUVAbEZ!=Mf`lR}{c^EDJreg0!bdd)!2zeXc>^R2>}Qj4_B|3Jl1-k~Bs#hO(Iwl4sF`cnDiaOW3|55j5M-!3azA^e1%dH?_b M07*qoM6N<$f=s7jH~;_u delta 1282 zcmV+d1^xQ=36=_wB!AsWL_t(Y$E{Y&ZX87vJ@;01w|k~%#$$WzI5CM85M+@cA*8G# z7D!0ImL=jh!cVbB;tPn_kcfal%7Z9RKgKiD?swI_EP5O}jumVxz3En|yHA~aPn~lq zzk3_LAhsZeD&F6v>Y-{$o%qrhIivv@B7RSQSB|>Op(Qd>PJcnh*SX8K)s+MSGl3N6 zCPXSOUb{}6*n(J^0vs%iuE}7-7ke(-@>QxL#0EqF&JIMN`I7|{WZdjokgO3~o2=RH z$XBV$wtPNTUlTFN;LJGs8UVyBP44pDpmzb%0BoXxSs2Km^!bj=YL~$C5VamL_+oraS`0?g^icq z{5wt}L?-OnsPFq5z8Je~OR9aS5Zhzl+M|aLWPLUD#l*%-s_xw|P*GJjf$3ee$*Pny zmuP^zj8fowdq@z6Jg%XfUXBh{>RwqpQFYn`6V8>uRvxTz+6sibGNhnA3U2oxl19Co4dHh>ZvUyOZ~5}9+@ z(A>VnwI$+{_afe_)%yoFcm9hH)uXO;C4c~iIG4U6Vn9#V z>URThK;Zyn)s;{OsfevhpZj_S0NOwWD(7g41%DZ7(s1J&HW6&sjenQ)5!pn_EnXP; zsx4Q7;S#X-Af{%#m{+gBe?PdEnGyO)!z)f6SeW z!GGE($erdAJPQ(Q+Fgsizz*!GDC! z0b*@t9Ie1~=ld_7Lj8f+se?NBGPrOHK>h5OV04q?b@hc-d%SwfEPpDKJ3P1n4k3*^ z{YiaAL847+mTt=IPwrmj&Sl9Tt1CFUs`(Qw8RGbLCbtzMjzEFbQwj&Bw@Ef}>3;)` zSHPjvGj&r6hwvHqKBk~YHj&+5wS1xV1nNP|@dkVbmr3>rz65bQ7gXG2I3Cqo zccP7NefErOhr1t`WYbUn(rN-V9AA>zeTr5!f1=$@NTbcvPwsJY73w7YN9*1BfXiXq za4PTJu6F;f_x>dt6I=BqOhjOH8GAtB0D!uTIO2uHh0!&T5=d2vNI?Wqsmb=>OMqZw zZBlSZka^&!ij5Z!hAIIVXk{=uoE6Bli4R|ri2zR^sI=lC3gL3JuGQ=l85~I}3l$D~ sCK{f?07$iUc{=94cs-NP9}Z8$e-_=U5dZRDasU7T07*qoM6N<$g0`t>Pyhe` diff --git a/recipes/icons/respekt_magazine.png b/recipes/icons/respekt_magazine.png index 73cb5e80da77dd6e637bf9f0c8e0d97296c7fceb..4c1acc767217c56e7c80810fe8c9dc8a4d37e033 100644 GIT binary patch delta 272 zcmV+r0q_2R1N#DyJ3LHGO#lD?B_$;%Cnr``R!T}r5D*YoS64AHF+o8=M@L6VNl6$O z7)wh_85tQ)PEJrzP*hY@9v&VfBqWh{JbwcN1J~*7^#A|>1$0tQQvgB7`unjTn~wkh z0GLTcK~#8N)zh~Q12GH(P`O`E@4ff@|L5g|1h`vd&f&o6!7vDjNNMYYSgnYZagi8B z+C`(u#(|{b;g`n>fbg0E5au#CoVnLB$TRLu1WdTM0Du?3A4wM6 zOBvjr+!fg21eUzc00{R&2FD9`DFdH@sr`3J1a|)m(s7;sa8xI006ZLcdhmGR`F{YL W0UY+qtZ(E10000-B#}8^f0ju^K~#8Nh0^V6;2;o2;nVprYMjLP{%>~421>Vd>GM+$ z!yxkU3%+sZe>_;5D)u6N%1WLcb9zIYZ{}(J>Lzf8h^yMj$-MOS=$zz)D;B_0#Y_ya%l7teN0=1^tdf3*jH19u>#;EUFoF1PW5S{$8k*7m-LWDWMs ikH*a^YVy^`Fa7|G=M){o3=}K?0000E2!d delta 462 zcmV;<0Wtor1jYoABoYa5NLh0L01FZT01FZU(%pXiks)G#nMp)JR2UhB!2xpHFbqY( z7XqM23Xu5kf7!P=)n;@|3f`;}!q>9N%#dK6e7V%2c{pGWE!WVb>uNkn3C99+wtQbuDyHN-9qCpE zoFX$MkE42jSWX^}>R@G@VQvmbPbH^rdQC7%^bS-9$@U_Zl*E>twzau7=Gb1Dwx?Sh zGiK+O415}9oHwqM2Tiu3<**shMsQ@qyv@dwH%3}W#4s-DNSUu@+j+Sgy|vjx+DMws z848Rqp-GP^iIifN;ZThi1*4(ztqZ~^4ZD*zc#3F$y~F6V0D5&FEwl;oFg3`ecRVMU zv^naCn9h)2`zYsAZEneF(nh)|6}{}1F*ab%Eg8ggPAyL5JR7a`@PX+7o591X&Y>Ad zu{S&UG|ek{k&4oWm2Gbx)`TeqPpdj!$F+@@SbXwIzSP~=^6Mh|tY%Qv_`{^Yoa-XH z8Q_vJ^QI5h-T1ANWAIIG8;J)w**E_*Z?dSxyN@6I0*vPr9m;BYq5uE@07*qoM6N<$ Ef+g$SaR2}S diff --git a/recipes/icons/revista_cromos.png b/recipes/icons/revista_cromos.png index 80adf592d67853b9b1ff3d4f8cd3f23b1b4e7c4f..fb9a6a4394a6ad0fb461e6a40c9c4c9ebc5a3c3d 100644 GIT binary patch delta 466 zcmV;@0WJRg2#^Sn92x*bq>9D>00F;HOjJex|Ns8}{{H{}{{H^`kxP9B#>>sc%FB_? zBnG;_!MVP`k@!dklbM^6n3|JD0k;J54q1|u0Rp0bZAnByRCr#k(^YrdFc^U0&sNlS zahs;R@=}CtDbtoR!ytvM3@$Un!0n*SjrxP97-Bg-zRRl%9UVRA(~S*b31N$ndcXP4 zqFqIxqZf<*UUg;Q84O}9;juvi{ea$yVfU83cwe4i=x-qh zx&Xdv0Drn)@y-A?>TiNKXFQ0=s;m3&PQVk$a-eO=_a400000NkvWt IM6N<$g77HZ)&Kwi delta 574 zcmV-E0>S-|2>b|;94`Sgqq>r-xg-X;zQDS_!I9%g29lVXlbM^7F#)#(Hyakjlk5Sa zf5b^dK~y+Tosm~_)G!oAk2ey;F@(?-0t8rkF)S_g5PEN>g8qwXH(&VV5;_IG}$M33&aCB&t{p;8|9P29^|?dcdssa<%6Ii{q$z z_6z`0gO6Ym7|6i^3ywK3uMo=2SMwmPXis!2t{f>F#1jzh>RovEzgGtY-uY9tocW1-p44fAsnPh z7liK<+phQ7Q)J{p(Eh%cM(l<~TsLwdL9HDs==D${|HNwW{uTg^SKz2CsSmjlxiIxh zrErVckDahwq~@c>ZTlt_IAP>A=t<|KKYwa%pH^!HO4*}UdPIN!4_g_c|3Lw}fQ&#@?`06Sx?MX}SQdRVdi|Z~i>n$+qA|?Ii z=K9FV|Nj2!BPQ~FfAout>LDck=;;0H?C@Y>=^P*IHaO`WAor`R^pcbO;o^VFC{r&vp=Im>L z3Q9mRT00Xe$N{@DuT*dhbRMHw_bdf$0-Wq^2v@)bQGd_H!Y?2_hm>8btH5{_Bz{5_ zm~;<`hwT`U>>Sdm0wjMGfc(1#q!kCC76a?mhLLUn-eTZ|nX;?}fJFr`FK_PyV@3h9 zd`{Wlmw^NK*9ypg98MxF)l3}NEn^y~?i0M0!auJ1&b1Q#v-E}utQa4#90X$k0000< LMNUMnLIPld+H@uf delta 542 zcmV+(0^$9t1iu83EPw7&RPk$V?ow6#?Ckl%!~EOa?LHaO`W zApGIs{NdvGx47_QW$ZaS|NZ^^YXzV;c``Ov+E;0V{^ZLli@^p6VF*NERBl^qC z?_Oa3`1tB0CjIB<>Lw}jeShv#R{ibm`NPEVZ*lEKNBrO4>3n}0>^Yr$gq3R(d`pnJpczXTl=<<7f>L)4fKtlcN?EUWU>MAYm zLq_?;#qn@*=^P*IH8}XTxAAjy?MX}NA0q$!{QTqO|NsB@tE}{rlkjM2``Fm)Eimgd zHtHfJ{pRNKd4GHU`uh35zcAz_x&QzG@<~KNR5;7c(?>%BK@5iBMXZ2b5wR^UYYPHa z6ctthMSU#@>i_>I9%ejkMz7?Q!*iH?$$0WldBX>SXj(+1(^o?81yoP>R_&0@v=UVKB9ifD7va5>`{Sw~ gqhRQNRu{Z}0O8lT|93Oh=>Px#07*qoM6N<$f@lyowg3PC diff --git a/recipes/icons/revista_summa.png b/recipes/icons/revista_summa.png index b1efaf3e921d78759fb8bd5fa4c7c7d0c306c251..193cc08e4b5ccba7f59f91074deb08b0b789af29 100644 GIT binary patch delta 960 zcmV;x13&z~3Z)8=q<_;mG|?|B&?+X?K|a<+Lfczd^U28c&CL4i>;C=y|NsB~{QUj+ z`2PF**Gx##G%?|Lb?1_h&@3p_J2&>%*Z17q_uk$3;NSV=FLxyI{fnT z{Pgtw_V(33JpTIn(Jd*}LO;+eC;tBa*GWd`m6X;){3`SF z{PgqHKt0t#J<>5P{rUOTLO%Zc`qDBk)k8nlL_yLrFV;pwmY+`VlaT^8e@RJ1K~#8N zU6bWw;!F_60~A`?>(w0yG$l`PtGm0LySux)ySux)yZgQGKXuvNl!X4i?C#7HMwsEE z=#44)yx?>Sd_k&F&ke*7pW`TkLXjgk(Gcs8%P0ikDgWyAew*(BAY{gA+&Yt0gvYmV z_xUG{jSUy~Echuww3>8LfBzsJc)IRkaYUi`=1Ld9C-*B%JTUqMb1QTn?f~p&CB7AS z73ht`5Z?D6V54~$GDJx0!_54l>0K#PlvDs>7O5q^5V%!CjiJhk-DBnjnE03*C`^$1 zlLKHvE46JU1)?KBA5DJ&y<+liNHBQ z8Gkyali}LAT_8C71!x&kSE`k?F706g0SeNe#RndhsFB|ISqL5~Ro&AzA8!sbLGMlv zO|k%hQzO;dsnOX!?A3Q}^4aV3yDroQmMd`HFuB+5?W+WK9==1R> z+G>3+F!?C8w9R70UklP_a5(+5xI_V1!V-J2qU_xFXH)k7qDk(MAS982$rRzMRfaKC zbN22RmgC2%5VH-+$VnBSBLfI;KgmXp4Qml;)E1qj(RzKS6sPrJN%N$T>d i>z`&}>u*Wdtp7i{0L0~%DtlM}0000W3cm`Fq<_#VCeSM<&@3p?EGW?|DbXz{(Jm^{FDub6EYUD5(lIU4GA`0G zFVZtF(ljvAG%?dPGSfCP(>F8II5g8ZHPbmZ)H*lRJ2%ukIMh8k)IB=XK04JuJJmlt z)j&MeKt0t#J=H-z)j~eiLO<0*Kh{J+)fCGMqmf5zVvN9{Otju=VUbgq%doLQct<18emNqRV6L`N= z_Z}$V^H3kPE;&~a9S%{*OVO#RKTTY=)s8T`H77wsF|_G60Irhh9?y(1E&yVNmLgPo zvlFh$t>+(pYH4YHd|}H>e+f>rUPWQ50Rk}j(0e}%^S|2b281LPh3Exf;T=DV(tW)g zuoy_8H3L(TCeZIv97-1zEY5ajmlf7}cb+0;uU?+yN-IwQ)^Le*jo@lxzpEqLXn= z8{LqV%;V-11F)x3E@Yl}OyPTUVqMh;L@|*7S;46w3+wsfJ+@|&gfM|ba8*x(EW-EV z$Xp2uksMGgz2|390u7srz&Xf)kg}tVJuFJc&H~6BBqB_jwf)KmUy$|sS_7F#0wM}4 zRv*9J7GR3tSr-6-fARoG#lyHF$c9$$dmUulld%9ylwt=;uioYMBV=(SD1KgKg*J35%zko{r~^~07*qo IM6N<$g1Yex!vFvP diff --git a/recipes/icons/revista_veintitres.png b/recipes/icons/revista_veintitres.png index 389ec2947105a46587438be5a983e9ead6e4255c..ed07881eaa47c9b82b34599d1f5964b071e3c19e 100644 GIT binary patch delta 8 PcmbQp7&k$6VuU3C49fz@ delta 56 zcma!>$T&e&zrZ81n1O-s2naJy)#j6CU|mbJ^!@bo;(vR4<9B!DgoNaVhTvmk z=a!b|n3&otE9V%$JL?6b4!qoe7hr0AHK_T1d}-QD-!-}m6)_v7Q;Mn?JP z=lbjG`|Rx9N=o5_gXo-`{PFSVot@xnYT#>Y<9&VOe}C$xrs}7s>Zqvdsj1l_BH(av z+d@LyJ3HluhU~Po?Y6e=x3})NxZFrc?!3J3yu90BV1MFwcj9__**iPpf`Z>?XW(LD z_~PRE;^O(_!+vdsHp3yspNx$-AqjW`T73(`v3a+|NHyh zP*B}eRN68!gnZVg0004sNkl+pl0S6uR67lm+C}zWn<3IW~5-mUVrZdtZL6!ZTCpD9R?-a1Z;ObLkE+ zp5je2GH};Cfe(S_0K14|V9s|qUZ;Q7h!4q~VSfxt!9^C7VuK@h;Z;NSzme0E;iU^M zuq{b{z(;%r%I-Q+Tbj`z)w5u}#voX!Pf;zG074GO>c^+NaPW{RpJ33eXvbSpYyjgD zPDt6Z-~aQ6kHOKZZaadFav5d^{q#Y)$_kt{4Bud0Bmj20*JClFCMer-jW9&;EGhj0@bWR^A;c?FWZex z{*~i(_t(JYN^$oPFX)4o{?=2uczXwUiuHl{lL;S-dQ=fWLI=(^b07*qoM6N<$g8oDXkpKVy delta 943 zcmV;g15o^w2gC=EBYyx1a7bBm000XT000XT0n*)m`~Uy}#ZXLCMbrQQ)c^q2007qj z0M`{2*Z=_76&2VV9N7Q>*%cMp930sqBH24T+5iCB6&2bX9NHox+9xO4D=XSEGTJye z+D1m(Cnwu0E88wE+cGlSH8tBfINLir+d@LyU|`%kJKQ}z+F7NZm$8 z-AYQ`OibNSP~B8i-d9)NTwLB_V%}q8-&$JVU0vT`VBcb5-)CpwVq)N9W8h_F;Adyx zYHHwXYv6Ej;cacmFJe0=a`u2n3(9CoamjM=%1hIo15vN zpy{Hb>7%3Rq@?Pmrs}7s>Zqvdsj2F!s_Uht>!zmbr>E#C~jtgP&+1d8o-1ptx_ut?5;NbV;kFEuXdV-Y9*@uSO`ul~hMyoJuVIipmYXF~l!iiu6CB5O+;3V&DZ8 z1GxOjZ!C{A^HpwH55+C0=*0J9wMUOU;Kl#JKMIi-BPu?AO5>w7u?CM2(tQ- z`i`!FPf?@#ehMos1IP-(;ZK?X9(MQZMV64DrH?@pM`Z#gJCGc^g%CdWn7RUKH(I7{ zgDVlGL=^JGxt-}>TWcTR7J>J)9$28N!YI!?-qV5+$_8-aLio2RA?10^{tvM2dKB`L RImZA1002ovPDHLkV1jcg2MPcH diff --git a/recipes/icons/ria_ru.png b/recipes/icons/ria_ru.png index 04727b6b962bd14fce395bcf4c50f7ad5ef8410a..8c47da83dea82110cafed2011ebd0c313caf328e 100644 GIT binary patch delta 215 zcmV;|04V?c0s8@vEPsHYz@WguKwy9XK!Bj2z#w3Nz`(%&`|x0Zz`)$VrpoYufWRO? zfIxtNV1R)C|Nj`}Ukd;L00DGTPE!Ct=GbNc004GLL_t(|UX9F062d?bL(!HxPH?#Y zX@Rk!Sg_zPU#TR2RRqAS^Spqn2f<{oC5X@lIDIUkZ4o}qt@ZqX!8{1LsSn Rof7~6002ovPDHLkV1fnWV=4du delta 217 zcmey(_?K~lPCbJ|g8~pGG&lfJLqh@x9cTce1qT)Y(E%{Hb70x25C4CE`2YXEf|~MU z1_lPkk|4ie28U-i(tw-_PZ!6K3dY_8j!aDkJS+!zIk(Kt_5A-oO7Q8kg9qciR7|sU z?}!w3NX^<>u&~<1XzKGdtS8LAzd2JE6tr3F(3v?sn@f8QxxYOVHM~*vjC1$fV*Y0y zoZkecRHnMzIR0$y*TsUH+N+P)HRkbu_lVhXb2|6-q#LRcKOVBKo4_)2+LqR}0RR90ARr(tEG)3Fu(Y(a0RaIE3kxMB zCAYV?Dk>@h0s@hhHVF+44G#|w5D*ZN)G!Jh92^G+2ik0Up_2{)AAcK3L_t(|UWJo~ zu7fZPMGcfBP$+h~-`;!gJ^%lo9(^IisGuAPDK3H8sE>#dgXH1QCn9bVLqcML>*4T{)>d@M!%fL} zWbz4QX7HxS*6V``^EQjIJ8Do)@PbxwN(NP5_wi(MNYnYo&NAMD?HRjn5Y&mVPp*SB n@I6}Zv-dZSzxDjh>uvu(Y7{-4sbn$?00000NkvXXu0mjfa~-vL delta 417 zcmV;S0bc&d1nC5jBoYa5NLh0L01m_e01m_fl`9S#ks&xI000010RaL60tW{N3kwSk z4Gj+u4-gO#92^`VARr|rB`PW^EG#UMfHn!Ru&}hWw70jnk-sns{{H^||Nk}kHFc5s zAAcuFL_t(I%Y{>ia>6hSbpo*yNQi0aprwQE4c!}B>Hq%;gY8&`aDI2bvxfC#4aO?O ziOBM@%rlY1Y!!{PkQIuwv1E>TM|$Y+$a~(BD`=4*&9Fk{dw(LPs(uBv?*rl;k&BomUVs|_yrPPP2`cVR z0pN&g1k3bgr{CX_8;_{)_M|WxXnn?VefdFrl@I9fuJt)9^<@J1DgO{v4*Gm+ed!!P zIe^2lb;#E^3@n97a)O$|;$zb$De7}Lt-hJmQyf%(_q}0RR90ARr(tEG)3Fu(Y(a0RaIE3kxMB zCAYV?Dk>@h0s@hhHVF+44G#|w5D*ZN)G!Jh92^G+2ik0Up_2{)AAcK3L_t(|UWJo~ zu7fZPMGcfBP$+h~-`;!gJ^%lo9(^IisGuAPDK3H8sE>#dgXH1QCn9bVLqcML>*4T{)>d@M!%fL} zWbz4QX7HxS*6V``^EQjIJ8Do)@PbxwN(NP5_wi(MNYnYo&NAMD?HRjn5Y&mVPp*SB n@I6}Zv-dZSzxDjh>uvu(Y7{-4sbn$?00000NkvXXu0mjfa~-vL delta 417 zcmV;S0bc&d1nC5jBoYa5NLh0L01m_e01m_fl`9S#ks&xI000010RaL60tW{N3kwSk z4Gj+u4-gO#92^`VARr|rB`PW^EG#UMfHn!Ru&}hWw70jnk-sns{{H^||Nk}kHFc5s zAAcuFL_t(I%Y{>ia>6hSbpo*yNQi0aprwQE4c!}B>Hq%;gY8&`aDI2bvxfC#4aO?O ziOBM@%rlY1Y!!{PkQIuwv1E>TM|$Y+$a~(BD`=4*&9Fk{dw(LPs(uBv?*rl;k&BomUVs|_yrPPP2`cVR z0pN&g1k3bgr{CX_8;_{)_M|WxXnn?VefdFrl@I9fuJt)9^<@J1DgO{v4*Gm+ed!!P zIe^2lb;#E^3@n97a)O$|;$zb$De7}Lt-hJmQyf%(_qp_eIvWOx$u(+@{|Yi^73?YZFXcvqjfE#qM)az zq^Nc)t9U7NWJy?8Q;}a4e{Ek$@bK_>cXFATmh|-Wo}HU+W>^%!yr2L809r{zK~#8N zosLHifG`jPvzs1j@8$o$gaw7Y$F~{DmPKlI-3TR_Pei>~kj!qvE?CGu&e3hxfPFAf z5*N7ob|h2JP(=Zcc}1*{p~^o>#?WXqhgZPN$I@~GoEQY{25B9G3;+NC07*qoM6N<$ Ef*3KZf00?WJz>#ZFXcvcz1G{nU)|Nr3{G%^4H0AEQ&K~xx5 zozBS)fFKM6P+UN91$PA7|Nl!Z203`r-sUAUDPupc)Cp5n%o0(j79Nr4Rvd+e`pyly zBAam%20F?u?tEBC@(ua`a7h4PAqf>fN@Bt&$-x!G{C@1)oy*e)5hQub6$1bO002ov JPDHLkV1hDiTqytm diff --git a/recipes/icons/rosbalt.png b/recipes/icons/rosbalt.png index cf7f78e949e7673dd1535978126f70df10e2d9dc..8839f1ad26e2833c3947c49a130f5f0072448538 100644 GIT binary patch delta 2190 zcmV;92yyqb5t$K?BYy~BNklG{wz0Y&b-g~VkCpTOL8|>%gWSw=AbAEfTEdZUfW`Fayk6+U_XZg^-IFA1V z<_1tGTtSe6LYg=O?*au43;|JM(V|QVNkSr$EfFfPe}#I2^qNu>P*sT(6J@GL51Q)9sU)(1S|VjZt)fMRs1eJCRO}|Sa}<>+9D*Z| zt)ie=2Y+P&rx7tn6c8yeO%$Dhy~nmQIx-5tK$y+!FduPE)F7GQrJ~dptu)3+K1|dp z5{l*)CZ34lV}oGi`Vt)V08J=Kbclcjq8hjefTbz=nuGjSw^3|e#Ks#ppuo#V-eoM` z#@pFwXHWqYNDvBG-*quTg=7`c3QQ(&(UFr7Qh$m#@svUFD5i$sD4io|rXfPqj9R9z z+0Ra%XZO=1ymI|62#_b>r}Ez{(dCqy39bn^yvy`A`~y#{Tt;b6-gbL=`k7tW)dq8@334JRxbUG1 zsCmGFH*i@<1}+LxwMps-DJC>jm}Hy<%zs*V8n~cfj&9$9 zQSe#FWgC}-q=L&W52YvNNZTeUO(|(n#uinsi0w&w`r!4j_mtfoo0{VH2Vdi~TA&f6 zggiAxtQP|=f@87`6CD(jXV6j3rCAOkWj{$1Wo~mczQ+oGDa(Ghh#h;je_+>roqt_? zeCq{J@@J2rHUV9&4jQcoz|iESLd)F4{g zU%dWvJ2-K`9@_nYefi;=>yW`8HSN#g+t`iw-DFqXxz0ZQo%Qzl?|#8n{$RN+e|XT~ z5$yS2|DT=giC4E6|B6j)o3yc=<96`nx9y$(KV&;z-foW%KVtX(@-F+{Q-62b)>ogc zL)ypN#y!{tkDg=KZNA1n_n+(SrvLg!JMY1Tw&@o?KlT4xpEACW$?{EuX0D$>cLrl) zW8@?ZHTnsDg6DR;f!$?%WC`SUfdT593dnGdpT`{LGCDa*GGVCM%TaxY&|eZd@*v04 zIKS0>089ZKhjjxhV3HH6RDa=|<75su^FDz6vr&B0GtU)V*t?ohKgs^qPF}7KpauvC za87eJh3_Yn2K{apgT+Fe^GF41k!EhfaZU&>)Ip~u5O2@OH-zzp+gQ}Km~NfL#mylE zfrinLCX-j{eTQT^HY6;%GQb zHbuly3K)P*-=eeLT6b1o!W9J)OaQFFKdfAFPT-9*uzyWsB=o26_?}O85mOYZ4Ak=VxKw$+d zJ1?d%k24PnrL_6$&IRo0*v-dq0hu{-rdw3OGNE+m z9_f@<&@}rDoPQaHpnwBVj_K&k#|FtFvm0ka3pzqKwJw<3n8$0A+i6fRl|cZ@&^40_ z0s>5BLENZ`t&pJaob}Umew*r0zJ=fuSU?dJKxV#D>YCNliY;(3t9OW61IDW<%IwG{ zH~~>Gpe2B#h5;)0oUNGKPUWt%3ZeJv-o^JX!1Ksc@qfMMr?5c<=kR<`1{4&f2nGAw z`&s6fF|Rp~Cq|xNN#7EpMw~cKAu|(2Mk&rHHFKle^VNOZ1c2U8v*4fR_4_#o?y&sk z-(y8T!Ux&OtuY?QIi|;$-<*$#@S%(v4j>|a3Hi{x@qIr$@Hi8{{??xW*l_wu+>l8J Q00000Ne4wvM6N<$g0&nnx&QzG delta 2216 zcmV;Z2v_%+5wj7HBYyx1a7bBm000XT000XT0n*)m`~Uz5Wl2OqR2Ufr!CS0m%`ll+jH4LCXi#%Bv{6P?TQx@2T9r`^wW;N-q3rYK zGwswje)Z)qoICOD-wzvyci(^N=dId$cJ+8-Eq~e?tJ+Yw@;54b8axo ztSV}bv^2(`CM~Nv@0@wI#QHUl4Yz*b$~*eqS4=*6{D1prMc1ok-nZ5wYbttjp_JZ` zlXp{-Iebn(jPJZB z>Mj|Lnjy*7bW%n&>57t_j7sUWD7xr%Wlcl@%jIYsMvHMU#y}g5IchPgLCeutL(8R^ zrD$nbtbfGPOZs^oqQ#CP6up{D6H_gU8k9DOEQTOrBHL)P)_e8LGqz4{fqne}2l|Ud znQF9z0*xk zx1%PHUFH*?I-=p5C*F3d-sgKY%_}X?Fw#%>=zse4`WD7q8ge4ztTGgJ4E;C`N^}-| z(`mA$P%KI@wrVB9(5jZz-sy(tecelsob=SaFVIuZ@}>AEm$$1eTGf{+imp@})4jHL z?>KVyV%PPz8)7D7t`40thG9uRYlDbRH+Ghpx@bbjuuz2>DKczKH$7ke+p~|p>bpnY zWq$*h?BU)!eg*&l0K9gT>o-=lx{%o`%Mgt+hQ77IqBN7Xswjp`Ete*xR}{4kcJ;gc zU;mUR|Mdl1_ap~MDFhhX2mqjIR>}$$OQMZx4c!oiMvNiGnSPOTt#w-Ticy;AG{Z|LtD|&@^ynXUBKG{~Q1S001-EKrE;`JBB60Kp7~u=bT+N**@Ru;(tQZ z#eT8aFt*X6&b>Rl>t6e?<;K$F{0R`;5a6o>+d%cYo){l}a19tnRZXHudxIlW4Sjdk>m8Z-41G7W&n?>D+kQ)9rPz1LwGN-wkX7upF04DF6V9qLc#k z!9FsE8@9(&cJjFF)@ez1)=Cg9^bzPn)F2p83X#D800000 z06;{5Cm;U?m@U%+Yu#=!Y4jlr19GH9B@i8fDCjUj2SC#R00000{KHqi?A8ZvbL~Z! zIq8`7=`Q+4ha@Voq<>WU<%ohL01^dL0Q%?w0002MBVT*ey?^~lAGzxmC))|*WKAFF zkbp!((szxl2qXf9F)Nz@2mk;80G@pE33q+wV{X0uChzvg)odb)MUaq4R6wI&46Igi z0;q&=ayVo25{^80x3fE^Ef<3bmO0qFw%5PE_B9XQ^k;ti*MEKsJon7=ZvD(h-16f$ z+HyjTrj$ZM5*1L92%wK8i;jduBO)-`zxsavvUr#dQ$TQd?Ft(E`%UoT^Dnyg!E4-n z+YPpy&{`_3P>@JKVniU3^ra1%wG*|302Bn1oVaoAdXwyw>}V`v?gMKVdu8<{SHACq z;L7{1bmPaaGk>?E4NVsvi9$968WIR-jjZ}bx#GlHJf)2X&Q~&}&`_W`)3#_xBx4Q^ zth|pItNkuuoeNhEd2ag|D<*a-fvl_}0fN9z4N6sX8p9j8_8Z1 z8U%tsLr`01Hq0oTw`V`F!s+FXMJ;NE5=0|03lu=2g@3NroLo2`7cX2e3489^bNJ6M z*7^KbhIghvDmNRY=l}o}ji#CO6K~Ala8-AeL(@YZI{A>x_g-#jgAxS*fIxvpY?++D zXw|36E5G^r(ExjX!o|OSXrnuL?18K=|B{?+2mn9=04bdwPCL`ixM+HjhyVZp5>Q|e q-R0_=hapbC{@G(+boM`f@BaZ&lUlC2o>^f40000B!5CsOjJb+6DfN7M#vnj<@G*#o&q49Gk*z|0KJ{bKEh%I-bB=5 z3|YS3`~qVIIFWS-&;bVlH*P>A$+ZF4LHN62o}ucXcG*|~rn)S!7!A}StX#kv7#Et6 zb--SnMIDR^DxiZ=L16Hq)$ delta 437 zcmV;m0ZRV00?z}GB!2{FK}|sb0I`n?{9y$E0004VQb$4nuFf3k0000&P)t-s4qUF` z;o+sFrOC<3FLAfz?DgpA=>Px!0000U9v<}c^kHFPi;Ii6xVYry<}ffY4Gj%RNlDby z)OL1upP!#Gakvqy0CE5T010qNS#tmYTt)x@Tt)#DltV!P0Dl0pNklg-k!=nx`u@mV2d$i zz%O@TvB0dVYX-dQ0~&xL10VwI13&=6KQ9EJtb-I5*m^OSb51N&4JJ zdoFfK)07_evxdMIJO3ED?2m`(00012dQ@0+Qek%>aB^>EX>4U6ba`-PAZc)PV*mhn zoa6Eg2ys>@D9TUE%t_@^00ScnE@KN5BNI!L6ay0=L@t0d!(;;^vs56<*wo0x%)}hT f2g`s+ph_+P>K74o@ZR%Yf3&(+!?{i;OXk;vd$@?2>_s}dzcCOQ&tx#3X*~0caoQOMhMC4`Gynhpf5$H| z9H@*j$=lt9qwU_*Djlzt_7?@ib8d(`wXd4(<85nHnycQ2M1fd}} bKP5A*61RqfSMnYMH86O(`njxgN@xNAtj|oo diff --git a/recipes/icons/rynek_zdrowia.png b/recipes/icons/rynek_zdrowia.png index 0cc9440ed1fe2c209384538b229fbd5d0bd9c829..a7621e3f388e85a761fb7f99eb20a5f52f193b7f 100644 GIT binary patch delta 206 zcmV;<05Sje0rCNmEPnuai~s-tf4jxMzrR13sBg2r-|q3h-Qs_Le?LDzFE1~j%+%lC z-*0bkPft(p_xKMF53kkRPol6Nh?)<8lh4o3pP!%a@9%O`Uxokx0Afi*K~#8NtBPVvI7nqc^cwe_|h8!1kM`=&?l8b@NvB)RQSRhMUFWQxei zCv>L$Di>yGOg{JqA7l?ch;SOAsn!KdWE6|WqrL?&SAMwi0>nlGOO7gEbN~PV07*qo IM6N<$g4S_iAOHXW delta 210 zcmV;@04@LW0rmlqEPwC!`0wxU-|q3>-`~&A&%fQ`uhrY1%+$ZXzn`Ce$4+=~>?7EgolB`{?VwQL0N^H%r^cuv|-^=yQT*HeVVk z5LFjGVc@2R-to|JKKO-W5Ih`%3X71tS(`yrQr0rj*6t8@QdfB*mh M07*qoM6N<$g4#i5i~s-t diff --git a/recipes/icons/sabit_fikir.png b/recipes/icons/sabit_fikir.png index c110b00a972c2d539e107bb2c6a2fc87288cb1ca..02cf9778888f48c68ad9344fa90bd2f1fba012b3 100644 GIT binary patch delta 10 RcmaFLae-rk@@swkNvM>W@fk$L90|U1Z2s2)~TlZ(9qCN`%fIkR= diff --git a/recipes/icons/saechsische.png b/recipes/icons/saechsische.png index a6982cf3da5f8d2b7fcf8ddedf580a848bf99349..23e1f309dc7d1cf73194aa92f005417e025ae716 100644 GIT binary patch delta 378 zcmV-=0fqk41Fr*+B#}WKf9dnvj>SuWzA|^YBEsOFx!aJf)qb1IVD$R#|NsBg>AJxK zu7dyo00?waPE!COIBbx&+wL;yrJn!*0Ub$1K~#8Ng_G%y!ypWVA&29G`~4qwy~gcs zl~jB`B1McD2Al9l+)ubfE;#oX?raCh8#uP`Njxt3;5y<*`6)Nff9>Bo$OEw5*Pj*f zlrNk!%^_bo%)rmj%s0Wcy*HqQh$EBKn8L@;Ag>(#@$0U^?ixw~Kx7yU}o4N}gd zH<}j}VG4TFQmh)$F#*KcEG`3;L?R_QnZ4iMZhmg}YG9Tlw;I~z*smYX`suA7@ACQo Y07V=l|5aFkApigX07*qoM6N<$f~a%3ApigX delta 412 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 L2SrXqu0mjfcG{;lrvk*cZfN;PJ|Rf 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 delta 1276 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 delta 3040 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 Zk1u2%a+K&C_00eP002ovPDHLkV1kH@N@4&2 delta 174 zcmV;f08#(n0pbCWJb#f%L_t&-S6$6b3d0}}hG8!`4HiNnFoN#0?kOgqd%Ng;JW7|r ze$Kc^J0QF~{yCzhW5^L^#X>v*&7_$CP7OVR7?+yK$K=ubD9E~-;28^QQ|t(0C6lF} zbBm^oU_m6R7EHXyIt0T<5qIOT@sR}*Qha&2UawW61~Ylq2qz`Wh{XA>TN`Ju@{T3f czu$l23uGR0lqv8J*Z=?k07*qoM6N<$f(_zK)c^nh diff --git a/recipes/icons/schwarzerpfeil.png b/recipes/icons/schwarzerpfeil.png index 06ce3d9d13f69a16ae7f7aff35fb28096db144e1..39d4900aef06c609c3d924e1977a7e183139e1df 100644 GIT binary patch literal 1803 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBgK_V5$l532}W{Za9iYLtsEcK-}(~ zDlj=-E(!7rX3$pt_j}@kK*e7!^&es)R5+%u)0u3{{wKXk*g9G=ja$|?$w?_k`p4q8 z7AO0e;=i`}6dOJZVk=@Z)#y-sPFs-35Wzy7(7)XKxlqJWz3+7j7IIYik^RcPm%^ZDVV>%eo#c z9+!6b9{7+g^Xa#_{gP_KY2LFe-adQT`EtU--G+N6i9OoANZLw(^TU*|gqSV*;hRGe zWmhqLh{>M($53FV;!F16q{vN&EP{BKoYyd&J$Et}bNAPaR~ZVWlGP4O7L-bUZPO6Z zE5v$X{f1`@IhQ$nv)>k9uU#!TO=n^V!-GkeiyQyk&^0W6Z@+kgZ@+#-LDu9g5;txv zPu$$V_R_J(I#v2Wgzi(OY>^`V54|BBx`q+Q%)EE;h-kmyQ}7Ng4cNZvfyM-7?|JO= zzMrp=u;HxQ&%>7bn$2z!ALGiPP|GW~bzej(Zk=|JC3D~7yYYu^i7#Pqu<s`g_a|AIQ@zd@^|UsSx^ zz^|~&cBy>AuYY%5GE84`&MjejNdLs8?hT%saxxB@ZLz4h%(s-6F<_CfL}6Boh-CXl z*V89hLb?MNa!a_gywj5ObhyOuVGEPZ1=+^JklTjy&bGU*7ug-Nu7kHR{P?bi5iOz` z`%J{Gp0X(f&tjjj+OI(P$+V?472LZ+88)P@T=MIb~KU`kf&?%+Jm#}-u`$D74@VgG1-85M~n0m5n2k*HjU$6D^ z=4;=N=catBW1O+rFo{vhTPS7CeJPzU+6QV{C;pN?@aCJ+%RBE2?&)WKa%-5(E*f@7 ziotKCwQNJhO7;i+b9YU&4`-JA#r-({vg7ps7tj8wu9@j=Y)~`l-|e-9FAjEG-NDP0 kb4@qn-p1vQiGTT{&$s9ZZNKppSgJ61y85}Sb4q9e0QA*j2mk;8 literal 2698 zcmbVOdpMMN8y;($NQ5Hi#yDj(=kw$|Lm?{0DOSBR^I|mT84PQMsmvfctZFJMUA2y( zP>R&T2fy!W%t|YfQq8}My2ojW3T<=d#~&Lo!<3%=$ zAP|Ug&{{^gZpO^L22kA?{mt8a-DE8bh>=AI6J_au1cdl;ge(vi#0QeVa1h{Rh`T^f z2t;4XWyZ*2Lf6sQLOu$Z!=U7Rk&X?4c>2gi0DCJagR#IQuD}ca;$jCJ#^rdyqluxI zP!SzW=B~|@fDxHtOm^m0HkAYS@rHTIX*vUZPzJ!{e4ao`lY7BG*`?{$bH`{n>=Q(` z)eHVvR7~gw7+oj^$9JN zg@gZ<@waFxGeZQT!$GMqRl?TYN21d_S(m$ib~FdnsiCcxaCMgg@EAgNDjyWcf*4+K z-4_&x%b^j896XML2a$Lxn}8&82mq3TC4xvYmW(41h-@;8L!P(ucYG`XhhOgKtBqdfFt4qDS@PUZjeAK0|abv-Zxk0`wN%wk6apE0s=CjgeesA=Br>s zvQQ?JCJRL{92SLzt&HXhIKp(P``jIU4w(T;xM?6KP$J~RJ|&sP{Tl&f9F|PP>!M9$ zlkmE7B4CkJB8#INC~P(dN1?KC6!;fD=l@L}S|w*}5O!?r=O@sR2bqBSRAFWDEKk%V5woyVfBu>y*p3s1Jv4>pdd?G*yO)x(l+7ST%Vc5 zOK*oRzPL2i-9Gi;Y`YJu=ZVn~%y1@)ge`~(UbUz*q~aInSx@aV?QAfMhCSQ7d6_kI z7#;DmEhpz=jh@UY%<&R^44HV}_^+pJD;j%xt$fLqT_!AVteOJpH_3c3RY*fSW;5c~ zj=kP&yoDxS-RB(KRPxIUR>}8ABdXJEiq9(JRCuM?unnYRjIPz!SoMBbC^n1A8@DHo zuCM3;7Eeoi)8K6veOpcAfPS4`!md_J{OY|i56mrWIA-Ms-ad_5R^E7+@*~Bsxx*lT zpjOYWtkI#r6uqX$%8hwwS)`ckX*IVnpu!YiQ~m7_s-=EvOg z+Z3Yg8YQ=S+sQNXOZw;6#OAsCPyf`|bb$SaW}e}6%ki`^EX$pg?NbYzWHpr@>q*gC ziF>*vf6wer z{^)?_l+}el73Yn#+AF6DChNy}#U&|>)l;gg<46ru%x@Wjn5$0QHBO~?2N>=*NqrQ1 zL3w_2{6!5eXENZ`jc&AsrNE`J!}P})XH^4dul(AE_T#T14wjv}Qm);vx)jG8qHEjg zK*+=gS2<6y0=u$x6Li7#nx5A-zV=MT#^ZVQWtyR@Z_RnWEhZ&DTU%!V)|V5-b_&R8 znOXkmLqPR=%65yfbZ@+OXXWm0U0%~zK_-@jYF1urSS@JCNW;(R49$%*&&DK9ywEeOF_Eu|e)I)KXecSGx@+6@_%^v-U>NZz8&nssV z)>gIPYBlDbQSE^rBJXG3Ox$e7`U!sK*~yByPPYi@7QH3%9lvF0DZh7i1wE+Ro>VA? zUeE8^p!e5on9IWzFYh^Rgy5}4mxrNuL`oX;R#!E3@3gPzZZ-2eZtMZr*ni6#_pUMB z!Ey6rx&`$lt7d((4vm3*;W%;^%|4pp!?)xs+wR(^9O z?MZKQB@vAAFZeFzJNH>{&l;F=%keCy8h_vDWc5fqC&IO`nz>`(bOXXGAWpx!+uX>E zRBwb}R&I7*0z|k_*JO7f%tw}4H;;O2Lgc7?rR;ba?iv1shYg4Q6?W5CKVow{7M^Kbr nF&*b%xRByuhkh55qX$`W;)fHf2A*D<`#TH@2xFY~-;(zqYE@qt diff --git a/recipes/icons/science_advances.png b/recipes/icons/science_advances.png index 9aefdc57234b2c5db40ca7f841af5a5a5fb5852f..cb4d3c9700234b98e7a9924969094e9454975f89 100644 GIT binary patch delta 621 zcmV-z0+RjE1;_=EEPwv~{{8j#{`~yZ0T)cE8j#^ z_RrJYG)3GrM(KWu>3@pY8741ysg^%imkm`n!>x`D`jhF0`o$ZyL)dwBzn4#{Qqwb%l*cdGDqpSS! z^3@9<*c>m}9WnXa-ud6+`QYN&9x?jl=KJUA`{?TY@9@?PA^h_5*&#IABR19zA^rFF z{rC9(`T5!)m8#g00q!<29WOVRI$6eyRo~w&cS8mJ=;h z6r`ifajh9pS=z!pU#JP|`qdm$aZrmS(y)~%qx2qB;6XX<9v-SYJ^jI69NE?rsS5aNRaP}WhJ2b%I00000NkvXX Hu0mjf@dr)U delta 629 zcmV-*0*d{}1<(bMEPvDi7t{h6)B_pR1RB%@8`TCI)dn2Z2OZT39@PsV)(j!m4I$PI zBGwKf)(#`q4dPKHDun+%Q4hFhbliL)41ysg^%imkm`n!>x`D`jhF0`o$ZyL?UtYIn4#{Qqwb%l@1m;jqpR*` zxc?9Y0003dNklXFEb+00!VE4OoQ8RtV|P1^OU!;-^+rruQ>1|9RJlUYYm;r=9qRu z&_*(~ik!v}y%06U2U8MkGrvPQB09m9QZmc42 ztS4}-D{!qVa;*SbqyS*20BNcsZma-Qq5xf{B5$_{s{mi76>h8c`TG@atRio% zB5JA?XQ>ryssLA`0A{EsZ><1ksV8!+9BHZ?ZL2bLu03?Gq*=Jqq`A|jy3?n+)Tq4G zt-#mA(BaS8=j7}1T``~Cj^{{U;Mw#VE3{r*65tpIDQ6=|vf zOrRBOt1EJ@E^w_ba;-9Otuk}10B5O_5CIho0BNZJPM{oIq#R$SlQ98}S{!Am0BWin zYpSNY(*R?qr@PbuN}#E{)vCPJs=d_!ZL6`v*|Njgvc%f6#oDyR+O)>oJ$SLV$lSTf z+`7r#yUN|X%ig}t-yCnO#M0u&)8oq4cxR^=v;x>qN!weVW(i~tn?Vbd(b>JZyFU$~L2OV%vs=$}2 zLL>o5syXp7(0FAEr`Vv{>UAHt3#CPW024yt{yIiha|JfI!%hE&ij*Y@z(g7yaPTu4 zMt3n7Xsy#`VA|{?2rF`K+Sd6$f3Sz=ZIFMn%~jl3isv4PZZt!h-qpN|69LW!L^a1Z z;2K`KAZoRm%t`&*j*VfLdjlal-HsO2x*(h1i_xXX|pb|McWM#o5R#;T171#oiWCtZqXzfcG#RX)#(l< zJvOV>&UDsB?!TZ+Bf{3vxNPPUwDo0#MZpd0oa15 zq(T5(J$J`$TzAC^fc-^UYyuTTG=S(v5Zegi8bQKcka!Oy@<0*~B=bND)C5u?K1hQE zAYA}51RzrYvVB*nv%h2jfPB1=kSp+vZ_ zQBna)g3Xp4FF=p8(SQOppb!lxl96{clRuM@i(5j9TS5!>$wEt7!-|^2V1IExf{oVV z?6}E1`8cmr-K0?S6>5QELeMoKR8ESNlVYfQ68&wuwfFITs87@ONYf6fv>pB02LrR6 zgR_c3UDuFK`Nv$(@ZY_oPx{86_N(#azptAn#Sll{ z&tmS^vN-1YC3Vofk} z*VfHx9TlwPg`1M4DUbM~&p$QUMD?48=&C=f8mLLweF%kP7qeadcCoj7EcmqOA&c4@ zLj9$TFmmHw&n2U&gd6l@Xoaue8+Chw=U@JF!;N#>;1~WEUELp=#a7WiEKA!`MpSuQ z3CctIYk?(mtbr&J@x;aN*b`GvnTv-59o?87G^&FB(Tplf-sT<>ce?4SxSCV8_=$wa z9CIicx}dkP2Y+%1^8DDkC%5N8?5K}0UKQS}_hiO+j#ebF6=Q_b>lsc87ZT^LrtO#` zCzp3M_C3qqbA{TC;DL^noXkfWFPCNd`I8dvua_ged&>s)X2YZUh*;j430dXJx#B(K#;#i<+gd+eP{!BsQmbnY Q>j2DIYTPMVWcs!L0X~5H%K!iX diff --git a/recipes/icons/scinexx.png b/recipes/icons/scinexx.png index e4795933532b79c61dd56e8f24adce541c73daf0..175060b29bca2641bc88dfc27fc2d946ec3bbd42 100644 GIT binary patch delta 209 zcmV;?051Rd0rdfpEPwz1|No#>|8W4Lo|)m_-SF@3l#Yo1fgRD$%>S@*|In)c;K%>( zm9!fyKwHQg!I=}#9(Oh)9 zI%XthRXMK&GQd-{4mf6d1i3aia&&B39%ykQCv7K;c~_mjo_pm7&gBO`%Yn>t00000 LNkvXXu0mjf$^>c~ delta 212 zcmV;_04x9X0r&xsEPsW5ca)BZqn??wud2ANp~Anr(a+4`-`()Y~T0m-UWKGZIR|@zZBbl`wKt_U9 zOV$1A*x*=H^SNS3gFw}GfUjqO$yegs8y&mW8}yjSq#c O0000e@Hj-6fCwc@7ls6a#Skom;JN<=uIB-09)kI}AlM-=5wRm7xQ1Ya z=pbE&5fIXuFcCKe_#oKfz5pf)hz>Z3I|u-L3E@KL*pakatK~|JcOxK(K#=HUf(Wlga*`C6%9~oW zOG}c|UcrB*NqjIYO`# z28W@+p$GvM1_ViO-&OSXDW5!1{>f2to~vFCss@KtuSQf{?jrZKw3>ywc?bwFCRd<- z0SHJ~xLB}IRE&}-lHw9!RE#*6DQ4!$n_J{F!?gt1&5T8pI zhDFHRI^^vg@{Uf1C?-yvmLaLBLB0J104O{WEAQx1u-T$BDWdEg0g)t(h*D2XNUN%) zH(1iUb<)~8_2i_Q$5T&FFHKD?eV)}Ihz3Q)^lXhtEW1;ONu`o&*AW0hDIO@r3sWdC zr4m!A1RyAY2z>krhz{b6EUgBUvRG0B)=euG@OPQBem-L@m2x03gO*Sh7dRShelf{x zk8R~Tf7g)>hP>u9UVP{6yteQVC?W5?2e*satH6TgYw1afk96VCAG%rm*wMz&Kf8B4 z1(odUOBv3bJeaj@)Y#kA;AHen>^IXv*39o{?>q+#8oadJRb{(Lwv!d_YZ?s3UzveF zmD+w{H0|1JI$hX9rCQtaA7mxWu=9rkiVhT+@2?-~pC)v^pwTQV62p^1b{K>-MJD-} z97%^~_#%6Co2S#Q&(3|E_KZfdUHA@@o!-oa$wDFvMqJ*hL+gf~4`$@-$XuEk$2jBN)Z1$|QC!Qk1j zl*ASMc+#o*IDN97h)8cP)Nt3LeW4C9#8z(m+4cMk(#Y{$hqpdTJF59uG-)JqZraVD zn(Zt!;83{9(Z5E(t={pe>RhF2cs0Ojpe}8_nX`${=2acF^o-P+tzZf__v$7*wyE_4 zCbqsttk^4+OmB#jVt!`4Aj`_)1jF96Kczg&Mt;LZG_d90Cyke)3X+Xa*>s;ffA8(X z&ZV>cjZHtsuOC0tBm1lH#@9)$agY7Eq=Z@WnBhtC&vWC+2D}d&4=^XwI-XAd_B<=S zfpKe7X#l$w+1}&1$FVgozd5vA6I7X3ddyu^{@!NefJ-(a_#OM;>Dv)Y|1?m;V7WZ3 zOY`2f!Rm0d*VRn0-Z)uI5XKlq#0**Fy)cnq4vFrawBCkYo3@!bAECHHF}13&9&YC+ z$I-jS_U^Q3p&p9Lh^?-tw9w5*<__8?R6QM0xYZuLJV=}F2whj5;wuly4;%e_dgT?r z>as}xVi(7RRDa$Hna7s*;8cZn;G+&i@EJL&hDN)XU}=w-}}2fzvn&Y`Qz=;6X?-QsbEPo zB`_8MT|@QJ7-Xf&0nikC?e*OW0Du610U!Y22ml#?JpgzC;0=Hu0C50{gPPSYN$*8LvO2$D!-6$w!FS^GA_3%c$d{O^E9*pq3{dm6qJikDme-JO|An)K&-qA4L zvEzK0itq`R{IC=Jh)8~PEI&B~_dswuPH^TYL0r5bK2eaAEJ#Wbq@ER=O`n00nXC&l z`83=xA?>n|c12iNEG#M({#+s~E5rT3ic7GPQtaAwtn@l|9mfr<{1$er3ahMA`&v;o zd*`0$eyxaJE2^&-)i;P9Hc&+kjiQD}Vg^IZXcjYC#LX?@);38;hoq}p()~=@^Gf=v zSK2=y9U7V&8lD>+m9bc|_wQvB6EZei{_&&y^Q3~qQJ|=T&sXpTN}*7RVM=UPIXkNq ziIgI-QY=?sd;aOAvm{{*_C zX=40nVYDoRWmdItPC1r<%5IL0m70JUgL@sWR-qOK&-8a@%RPzu`{hW^#g{I17LAob zO?fpw)y6a<6_P_;OQfzgv3W7YwAua$s*Ffxd=cBOXi}29=DTSxvbkvFPLXYsp7p6^$Bvlf zeXU{hdL-nw$amG^liM19NKa*>j&F*4*oI3>#pv4y(XAtay6#m};6=}h(%hSHV_kVq zgAdl;6xdOV=^J%>@b<4F#uyGY>1PGE5pvJ=M!g`Ut*#z4<<3%ctcc^oc9&bT`hQ{X z@(giLP4AcK?&@XP1H}08%FY;Y*FI<~)ao2=}Gi zJb<{8o{(ipK1@I8GHLba6K(g^OSVwnTrZElOT6i{ zd+Gbp0qwU?=7rb;-SwIUQ`I@cwH{+v8j>4z!*rvt?FoT{dFxYtrTd$2$QfE#8tBHX zB-$w0ThBSMp4LjG0-F|>{D#dNDRcKU3uT^V$(g?yR^%?nQriE>d2Tc3-$gK4v>SVr z=I-NhxGm|kJdqln?m)l&uJv>{ml-)=*yk+C$R>4#E*msn&>fv<$PDcw8896f7G4!i zrxq(!+dbX#OxydL6&9aDXgikO9b(-QyxLF_VnIhSoYg&Ys_RP>SBsaDNCF+BH|eh s0K8ax%4vF|&SJl-K5JIdPQ+3dMEE4t@RvRuRR6fUcY2WNu1C`U0v77v5C8xG diff --git a/recipes/icons/seattle_times.png b/recipes/icons/seattle_times.png index 6a7c4ca9825535cd8b42573da01631ac639ea09a..8af79a6c0c6cc7d6575919fc722743a47eb2c608 100644 GIT binary patch delta 269 zcmV+o0rLK~0`iFk*BuEoUFmq+T?JErpVFV-s9@Bzt2o( ziaAVYG(=rxexHV#vrK1;>Fx3K_V}Hw!D4xsTXmR{qq=d3rjet%8d>*>00001bW%=J z06^y0W&i*HjY&j7RCr!x&qoddF$@LJaZk@w>izGDBf$v7-+%e}d*NxCf8ulB^vu@h zY4(}uBUcCI0#Go!uYr8)GbXIh{C-hTlLlL=}I Ts_xkU00000XA~ delta 272 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 diff --git a/recipes/icons/sfbg.png b/recipes/icons/sfbg.png index 284540bad6da5c44bf73f15bd569720936417637..f157c30b6d299bee33295da2848e9a6c3829ebc4 100644 GIT binary patch delta 471 zcmV;|0Vw{u3-AMwBYy$6Nklo}9AfgEdrVs_`3=Db@>uebqg0Q%u1!$Or0T9Rm8HHFBJP<`VLGTV11^<7XTUb$8 zSXf-wx&AXyWPN}CQ7Xsg;cO_#={oQQB=n#;DZm#9f^#N)1}T`16j>Z6{{P>B6mHTV zAPN|{K!6=6l7I03zao%gRoBo^=LRySKon?!ZTlO|z!3KT6Hqw$GnlW=z)%5EATC>mSpx6Z~5GnwHTwS2t`%r&C$BV@E_Vx9o0l773ZeURR z|34R*zY(Zl77GlpY9wF!|NmF593v#uRGfNn#KrHI&z?PdL%rA}000vLbGclh)A#@Y N002ovPDHLkV1fpF)#d;I delta 1449 zcmV;a1y=g-1G)>4BYyw{XF*Lt006O%3;baP00002VoOIv0RM-N%)bBt010qNS#tmY zE+YT{E+YYWr9XB6000McNliru=LQoLFDlsac3c1e0{D7VSaechcOY3PW9-ObccIuUGBHK9`&U-iWU52hU^TzC1kH zVesq{K25UJOn>gr>TXa@T&c$|gCFda!A}5Io-2unK5>sZr=Lvl%{+r{O%@;OhE>!9 z-)QB+&A%9BCR_>EWq{z$2L0S)n`m(Y;Vf_Kjl zJM5D~JUR~$?82G*!QFR7F@{FEV5X*F%f174}ZV(Yxs1AzZrc1pOHhp9%6G0 z^G^cV%{Oc8+FQ=ZpZ7HBz%yYx^9SJsv@9r+1AKZL#uGdMmot)dV__4irXw>x*6Ki# zoW+aTka}3OR*GcwbR!s4hev%Qk3G3JKkm6USM&soYtkzU(6@Vu-HnaqNPIR;xEPsHD>?{MmZBdT3qkoyn$~UcFF9 z1Qp>R#Mm6SN+)L1ncqI^W-Z3~89W7goERF&S_Z>U3DVB6_3r52dxg(}wDekZEv@aV9?ly^XjYDs$7#se zjxz{jFK#~qc;90ed+0A#J@x&3{n|K^1S2zqkED^vY%l3JY4KNMBO_O>VgJT~)Ud2$ z1b^)4M+Fl2ae6|P?p&r`(fxn-Fwy!bAFaE9kNU;jmz$9Gb9RR~KWVo&g9BB(8;tjr zK&@ZD^L0dw81WHb5iHZ0005iNkl1wjPS7f`S*ynmo#1wRJ6D766_5Fb=z0%mwWrS+^{q)_*>*Hg?D+qSnE z;V^7A*Y&}!77fF|%1|g2>ILF0gltbeVcDw|g1$eyHWZJ?kDfU{QIa%~NKChRnh*LH z7`QO?MUt}ToSo5VG`f4>Qb|IW8-Ir7e<K_M(|mdWm!vs1JYWcBM=M*I)S7S zHcFDJ`(Teg1F@1MRRb_&gjJsBd0sLAW10nSOP>mbLVj+p*wk>atQp{iwAXAnM(~Hj z;c$4X1xGXkq?-lPpqtQD$pQ(fn9Jq*AZCQ+uIsw);3lB>vlF`aUmP7BIe!G4YHDDE zG}ySF6!I4TrXv`8E|pLB`pm1X+dlG_S^OyE^Z5_${G0#e7ZU<=xyR7U9smFUC3Hnt zbYx+4WjbSWWnpw>05UK#Gc7PSEipM%GB7$cG&(RhD=;uRFfeZ(Eaw0K03~!qSaf7z zbY(hiZ)9m^c>ppnGBYhOHX0@?kw1hfQ@8Gi!+001a04^sdD0Z>p(R7D=R@;A0HbV8y>dd9<<&U z7Z)cdClnMEAt50qCMFmd7#bQH5D*X}BO@KM+#IRW3JMA)Oja9efgZHq3`JE9L{teH zA0lRX5nXc-Q)UrXY7Q_u4_0a`EG!~cWegS>5=mGTI6@8%4u1;^3lL6VASWmqH9Z@i z!yGq12oe<_ARr@Sb{lew7;%dqjH(k zB|1PUDJdf^F&j->BUNJ_uhw(L86_npBZ8hEoWLR}D;*ynDL+LWmAD&(q99#vBU4}^ zV|5^xx*ev?9)g}Ag`y-uNhf7@6I*W$7#kQiKOHJ9A|fIvF*F~g%nm_N6dxiaEiWWf zVIZN#>2P7@0001=NklWYc~dh z1`yVW5Yx$K5GgeShbeE_wM7q{E;ryw`}`N+Lt#myR)jc^NYWIO&1nn8Qdz0ifO@0Z zYInMt9{K}n2#m&)=}eg~maFxSB<=zGTK14VIzBn2#54IE0C#zHeRB)`?*8GCegg6Q o^7{7v@gGUfeSI@Ozkm3T4W9TIvj9X&dH?_b07*qoM6N<$f}8=plK=n! delta 551 zcmV+?0@(eu1hoW^8Gi-<001BJ|6u?C0sKisK~y+T&5=!O6Hyd~-Kj|%(>hHinateD z%p{qlty;8NB9d0jqM{)fR5rA!C;VRG>VyVv2B}VGKp5Z(6*^n60okj2US(Et7qh+HFci!zB1z4BaDS_ zC?~Gcx#tk!0~OTzW4b0z5iU*9<)xN@(3U)=Y0}%*hgF_LzqRN)EPO>7+D#}FLQxdr z@whaKL?SY6e1CA456~J*Bz6?Y<#M>Lix2{BrjB~DPByZ$z(E!Cx4%OKyYh8#?&;8SS%*bR4Ro% z+d$lTgXKkTIyRehZ?xv`WD7+ss|>v)M^DwE18&>(U}@ z5%sE~hzAd%hhC&mk4mLiFCO&jRs28^JeERzS%h5#86IzD-tYbYkC_LDKOen+Du%IO z%9#S#ymw^R-46i!vifWpHr}87h6l;3)*XP=GXQJ10e0|e?JGc+0DNBrU{3%h(xX-Jhrk$8*U>yBjqbt0Z1G;;~@t zBBB}4^}RRhKg__Cu{IBbwg75t~%*U6L2a!23;^l-97b_`; z!B}b<2j^cc%ngKwI!*&ApoV4_3Yul>*Wdm+hRFDc&ZTgb11XmuJx@zNu(mK#yIik< zA1-rz05yumauTS%dn5FcI8%pzqiVjd`NTGPksGGf(h5W`8optfO^p-FrwVo`IADK#RmHCdQZZ>DB}u$imKskqS8+T;p0gzWC{04;y>^z(t2 zy}QQQaDwR$00009bW%=J01_@pagWC1`u*Zr`Tzg{2uVaiRCr#6Q(3aZAPl2zl47$j z$^DP|2(4{qcpdb|SeE4l)$n}l0fq5?st@+^1G`cmfih#1*ni4TY$^Ck_mA`7Mwvh7-Bz@f+j#-?!2!&I7@2#o+I&1ohS+wDWVyeR^$WXvZ`0nR~1 zm4N|785pgEz-63Q?4)eD1QB?h=x$0#Y;wMq+e<_y9Akc>cEIqUb_rYWCWW1sRZPeg z+}FdEcv-UUD(lX&?&ZNOPi}p*^V#e1>{lOW`SsSHmn8Im=mLL_Uq5B#el@TF0000< KMNUMnLSTXe>9U#t delta 370 zcmV-&0ge8r1FZv)M1KG+e+o8)9Yu{bS(s68re=Y#ftS6RtH`Oi(7VRj)Y|0Xh^dKc`e=T)W43S%7u_pRCpvH;ks5 z7|DXhy3qv#EURk4?1XHK7EfXp)q+mDf`5i7Y6=i{0{UA{H-fUoKBhGoMv$V#>}v|> znTlY8@K`B~A!}Gcl<{jSr1LuG5%T5UGdiP_2+)gL^1U zG%RIe-q2khR-j?Eac^zhnT@+!m~Q3zrG3<1{?xwvaQaWL`*;rt`5%hFAArCqX@zWc Qz5oCK07*qoM6N<$f?y%B>;M1& diff --git a/recipes/icons/sizinti_derigisi.png b/recipes/icons/sizinti_derigisi.png index 3db4f3fc8a5d238a2c0ce2764ff69784aee957de..388fc41b6d976932e08c8f141a6683eaedfd7ad1 100644 GIT binary patch delta 10 RcmaFCcbRX3@W@fk$L90|U1Z2s2)~TlZ(9q7gd)f<_3M diff --git a/recipes/icons/skeptic.png b/recipes/icons/skeptic.png index 88d45d03faae8742e5364495d3f5d1fac599b382..2ceddd6ad9b5b703b180a5d81277df269e143548 100644 GIT binary patch delta 642 zcmV-|0)73d2-O9UBYy#FP)t-sBOf6D|NkHy9UmJUAsrqf9v}Dj_hDXNDkdj4Gc^AG z{`~y>_V)JM+S)WRGPt(4<>lp7Qd7OWyfZH_*45R5fPo$w8~y$LEGa4{BP6x7v}9po zEGQ}X`1thn^j%t8DJ3TR`}@QHZn8q?d^DWcG=k2IyX3qhlo*5PsYW?iHC@|x3^$jUMnXkfq#JT z@9*{W^uE2lX=i7oqM{@qA$oXt>+9=MPf)wMx=&0^W@BXF;NV(USC^HRt*or>?d{CV z%sDkSi;0RRA{--AP*7lAUita?WMX37+}xV-Fq4{e#Mk$POy}=S?W)t|_Efp6Wh;c&=X7=V z42=4Ld6F4coCt;(6K7#mkVLQi0wRA=gGFmlD>3l8dZ2+D=~!c|nMpRZc!1V6orLJ< z?2?5{BmtlD6q-Ytw4&~dIg^AfYE^nIj+`=LNEO{j< z^yR!FXmu@F05&$snlZJdkvZer03y~R*ba8-mlXGb1Bu}f!Mrd@;0PIwtcFtKlhaa~ z5lfr{7a?m%%4KkQb$xStr^0_PIg=jL$8Z6K7_v`AS=Icx^zy2`mCGNWae0w~xFANx c6Ux8C56}`Ir|~aIC;$Ke07*qoM6N<$f}T)2g8%>k delta 1009 zcmcc0x{_mpaya_08uDyHq80#DOdHYF$;zkmO>Hn*4=8NYq|wxX;|OHJL%+@hGg(^l6=jH{3T@c#YRFJJcU-J6-7uA`x;qM%q+QStiq>mNUU?B2D@-^VvB zIONNhFAp9(hzbvnjf!5oX3e{|Z_k`QV`*xpsj8-?q#PU&@cZ}gpFe-5BqiOwb7#f! zF)Pftnx`sHg21sKjz|OG34^Hy>uNYZS3jd7-Hdn zbz-KqQ=&jy`ZL>!Im=r&eF<=i+}z?cO+d*z zYN(z`nv%JB?V?L@sl_4JH>}IQa_#EudcWznFF(k;(wngU{L8GYyv(o6xr>-@&E;U3 z`lwI9%9NqJl=&ftplj8I)r>v+>JknlD49yg28A`W+a5TP;WOj-G4&F~$D9n$o?O`? zv1o75Bx42xYxDV9k24bsITfZ6^^?s(K z%;t{S9w(O-R+s<(@FV5W|23Auac8av$o7Q#DbCty?JY zSNPxezgPw3G*^FmV$}f*Q`HjJh?11Vl2ohYqSVCtl2isG14Bz)Ljzp{%Mb$-D-%mA zLj!FCBP#=gwi}mXP;}(xr(~v8;@0rJHR}OTgCxj`;QX|b^2DN42FH~Aq*MjZ+{EONMCJ(l=?0GlUV03# X#05(}Ihjrc+QH!I>gTe~DWM4fzu~ny diff --git a/recipes/icons/skeptical_enquirer.png b/recipes/icons/skeptical_enquirer.png index 65d5a77a5ceae56f3cd810268de5ab24b0307c97..073586ae43f7499323e450350b7fbdefc32066b9 100644 GIT binary patch delta 2121 zcmV-P2)6g#71a=sBYy}SNklQaE<0 z1J_#gRp6~>c40wyI;}bW*BK`w7rsAiByW$JMoYpIHzvx6maMIWf+v1xHl%R6V{{i(mqd-CcAKI}V)`;KnS z!t19U9qD}Pd@`~;ZJAys8$qd|$ybAx;6ya|!l;mb1b>7V8D%13CefQ7Mn~)dIuhL& z$i!hO%8Eyg3{LS^{(kv{xf{NW_Yb~$)0+Qp;AsCxxaXsdXbm;ocx1Tiw|(i@BETX) z6WYorW8t_KGzS|{p#>mG0LZm~a?!(5HjmGAHFN3ay+8ih=kdW`LYdx4x{l>FbJ1Kj(FMZ=Uu&uSmBAP= zB2E&=e57b<4Yb$F@Z>>E>!-VQF|4+CohL zRDThcN&}~&(o0OZmivuiyxo5YXH!uO+d3xsf;(0;-g1Ao6@vr-^Y%ROWM?Y&tRDu= zDXYbzs+rJPD>6WC{$rGaQqI+B$7iu~*f(?xeWnCh#@j-5EB9YK{-%8DKO5`bIDdGN z7ob|nnBlJjU?FXpAaX0u@yBQ&2QEsY!GBYUMsFq3$xEa25EwS%cYLyB({6b#-aCiD zEkRaj^pqncwJbbYQaM0lo`Ngk${9>@O!TLRV1N)c)8G8V7dx+)J&C^C1XNXkZG<&1 zY}-H@02hGdPC#iFs0dn`K*5S@pj_yv5E4mckD96*ObxV1D63f`aDXi6q+r+y$A3Ce z0h!-_)i{z%&uvQKu0f~_{LBcjg)}JG74K`30pS^+FjMf7m4zo^ShA>cDovu)LTLge zaOJiHY1;ski2yT91tvmR>cpCl2|9q78G(ijbe8TD4glxj3_|_`D#B7>=YsQCJHNM- zjkuKpD-&MQk%mB6_m7i05qvD*0e?WsMAC}FvI!aif&fXm7H~nzxtAtTRI#P9a^ShW z=}5u06+A>x1Q?2;6Cia`hKEKC5Im%yWhET}lCz?m1hOiaaOdI*1GZxz2RGr!O^g8= zHKGK(V5H3GfP_kSd&twOLxV=CtdJ8sch>+F87N9Y6_l@BMCOe|g@07~R5=m%81-NT~3+-^>rEPGkr+n-V1Tby4dSU=}&ZvYBf+*G)2%qMO z{+j(>reTePkVFp5O~FkQifzd`~qi#dhobd{rC?@dJ|m> zhys%}M4*vXY{0pfiXhn?)PI}^oD~sVA-~8y5fxWJQ5ni*=I=k9{meG;w?jK(OP7AV z+JbkXMuMe^IDjHJxnPam;S>zlfZH1P458u~0T>XIASg3Z?bX-qI{l{(@yBDm@=X4ZxKM%H->tBK3zcx9MK4L~E?HIak4MvCEX^+ay$AO{Ze zLufOhR5VtuWu9F*^M8dM0Z$bgg4crq;M>mC=8~pkdW)%Y?4oNP!bPl*MY*aW66%-gOCqwG3uz&2~m`+3MCxvFdkT3?~ zOPU@FAQC$FBWq_RfFTpv+V6A5NUSo2mR^udJQ# z(?VX243O#vtA9QSN@it(b3wyG05;XZMi^)x@f9vi6(-}{95%KUHIz*T1a}Nmk1UBh60c>V|FSNPa9UOGm+^z zU@2o~RbckMf!HS_-^J^7Nl8o_8whkyL zB@-sZ`Lr4B&}8k$QZ}CY;MA(*Pi|Epx;o;2vkP1G527pS00000NkvXXu0mjf&oc!G literal 2781 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 delta 2104 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 delta 1556 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 a-G2bjMh;_g>jC)y0000Y59_xJY| z6&26V&)?tQyu7?JGBWM$?Uj|4o12@(#l-*s0RH~|NJvP{&CMbrB8iEKhlhvM)YQGb zz2M;Bh=_6~ZMwR; zxVX4NLP9k)HM_gJqobp?wzk&R*7o-HE-o%+v5M#GF31X_U>z)Bo0xSejHLM7*-Ut!LjsQ2e0^GqzfT$Ka z_j<2A5a1DJk4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKptm- zM`SSr1Gf+eGhVt|_XjA*UgGKN%Kn^9SVWJxIpTgH0|TS7r;B5V$ML6EH)^*e%CLX< zK7YsAlu)*(@)HiG7)A0fN-@zoz0jjjQ*EZsGVezsva??YO`0OHLH78`)?NJvRnJ-5 ze7|?>v(NebA78%M?0^5+CD3x!7K77)A)Q+cwCkm3Y*k;q;7Qauhhm56#jdhnLyu|y zn#HJRllgz!ydxDB2EB*dqT;M%EF9J?yq0e&qw&DvSxi%-@|7O7{l_W`@^vcZMGo%v z%{|#LrFQ;emC0L#m*2TM(cAgtrU{##N$!dCkXn}a^OxPFlb3faX=~rX%-j2!Z3ZWE zTXKV0=$jVC+f_*&!Ef3aUvEolFn;t%g#FBle^oX?aUrE`jLJKHeVpSWwo#_4H+Ee? z@}t#8&o6ov_nK(DKC(90{nMtZ|1XaVac%jrx1PaaS%|&aw7M){2&$I2MwFx^mZVxG z7o`Fz1|tI_V_gF?T|>(dBLgcF6DtFAZ36=<1A}}yo#!YTa`RI%(<*UmkaM0A4b;Hk M>FVdQ&MBb@0EG6_MF0Q* diff --git a/recipes/icons/sn_dk.png b/recipes/icons/sn_dk.png index d32b874ca22483432d307dd5e5888aecdd1d81e0..db03549246dcefcb6c83ad68ef209344a02eccf2 100644 GIT binary patch delta 873 zcmV-v1D5=s3aAK>BYy%kP)t-s?d|OVD?;t+bCA?CkCA>i{lBCpbs|E<`6gO6~3L z?(OR9?(XmF?ND51ousGP;pgw~@#E*}r?0Zd&egECy4Bj;*?-{Uc7TYCl$q=8@0X#c zqN}j7y1no1@5j#1L{eYu?(VO)xD_r!mY}4Rourzjr$$s==IiXo&Ctcp(8kTv(b(Cv zy1&=k-QC~f%hJ`UvbgZ<>h13C$=2bHmYlY{!Ek$l+TiBy?eChQrk)!E#Ll$-DD>Xx3R%g)cLvbfFF+3N1@l%1s&GDMoAs7zXCjF+A5 z?drF)3F?d|FB z?CF+8r||#)0k26!K~#8N?b6e-9YGX;;a=_0_CB_4Gq!Epwr$(Cjh9QWnaW9?A(j84 ztMS#kY7+Fn?6@sx#HNg8R#IjOVKGj)lOjTdPy_?#^o*J@v#Pr04)~w%XR&#W<3DQ;n6a)a^ z4jZ|BhtxZF?GB{cqa=>KD*~Wn_wGkWkNNWTTVWBHGD=Fz$`Mv1eP9|tVPY+%oQCRf zM14bM)?^$tC4xX1pw!f9(-$x<7W#i+QPf(DV2KZwF2hTZa`_6<2Ue~^u-XTSwd>L( zlGuhm;8~mg0GczX-@G=dw42c`I((4L{|231-7IJCzH~EU|AB)D4juLaaO9}xc|^)% z$4{I*W!}?HpE(QIxu^ly`3n~>5oxH_5_Ib2&(_`yT+=<$=M&vKu?c=<}iU%z>ay`%3x!q6!q_4Mb|8~V*OeOq0Sj_fk#%De6cCVrAPt8R87(TCRIHk~XSg_bCg-NCT9gLW59KwENe=S+^KJSy~oA2k3=litaZyA<< z$ppKT{HT5abe0)y#^C?}w(&s`Q~)j?03g2rfOSmDe+xi12>@*@0G?FIY} zudEj|vJasCZa1-cO@j9O<8p--LD;-z z3o-=|`~eYrfu$Voh)f2EzJTDJf1`rQDzm_J0%i!TX%+y2ZyAS+LRyIw^H3yHa(eES zntFasV@uenl%$eM9t>+or4xYYU4N%dGWi_YSpgj#lXY}I>=Zp5l*kZxc~PSkbadGt z4iSpP@;Rkqe(_>HtAt$*L68e2ytOrAX{VQT(D3-gpmbtre0uVQ zqJpm%YY-lN?oL75NdMtK}EwSZ!S0X^uaOaP3`c1mwb#n{9|{j6YWfWBtWK69d{@^oYl#!LiL{AlhH zc>1qMImujq#kayTb+jK>VnqGm>Wd ze!tf%o69$AWoxeE!3JwFX95EDvvD5|<&1qjL=B-5S;Di)Pyi_1e-Giuc(wPw+URj11YtJ!_U3^IE3nn-y_!B{_TG4I$Dr z`jHQpzR1^!DWg%{sPc(m_B?vz5(~Y$&e8|g-i%dtkk?^5zh-YqF7iM9I152%d9wYvv@A V{6ArRlpTc;AUNPSrQJXF>OUlDJ*@x$ diff --git a/recipes/icons/snopes.png b/recipes/icons/snopes.png index 801b1dd30c1e0b1cbebe8f3e2d7ecbca28c4204e..bcacf931da91b83cdcd8b14509bb2e9f0f107a1a 100644 GIT binary patch delta 1456 zcmV;h1yB0M6u}FSBYy>mNklO}6DTt1; z7(%E@A3Yqm&z1o9tK{4X^2rDW*rN$E&ETDnUQ#!Fkc9>FQjk4yB*rGkVschI@@pc@YG$M+HcrB)P7I`6Y&Wpc;*9ry^-otZ9=-$`~Dmi>*13t`yL` z7>Z|t0DpK%UA=U+Sl+LduVoAkW%?t?dtwx9)&#_JZyyH2aby|r_w#IEGuSVl5+F4K zph=i}*+Af_#(@o1%}r&ql5qIzCo#a}Lsz-n`MUp@i{|Y)s3=S}mj~lgart~5OY{pq z<70R)EhRu622bQR34k^^)0ySo&Ur{p9`3(!k$*i6kXYBK2L2+zIYx+yh-=(WEW-ZU zsS(XHGqZ5>Mi;L?3hCMkNZs{J>MfAER>N~{VVeMO$pEa^BRg&W8hLw3Pg9EI|zW8zbXLmOj(K#z^F4a zGQa{H`!0NjCrfiu#ok@_UB4Q8BO8lYc9agd8r?)pCqY2_#`|c06ve^T|_={lTL* zXBpTXCqGG>u4JiMJa28Xq5$RMXgk8`exguK?vW@_&4A zDyJYP6V6#cW3IM$qgivI8ci$qaLh`li{zD_`Y9jw;*=pEiH{A#n@cC)?9cf=5Jtvx zEDxU3cHcw0*PAt$sDz=zNS%bNfAm%2o87svm!{~}`5UiJz&(*8_vXNR<~c|`pM=|M zJ$+7v8OLf)h|b6UAkG25ADV?FFMp;ZDg+$@0R z_1t@^Xe+y4>(N8AI(hEDLn(8i&eva_-OAN(MNe%FADkM=Sk=F_DiH2Sk~cj70000< KMNUMnLSTZ%*u{+i delta 2622 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 delta 1251 zcmV<91RVRK3C|0VBRT*CXF*Lt006O%3;baP0000WV@Og>004R>004l5008;`004mK z004C`008P>0026e000+ooVrmwks)G{Km>pO{{8av`sC;N-{Sh^=lt*S{`&g<`1tg} z#_Ecc=Y5Fhdxzz6e&JbX+&@s?QDEnJh4Qz({`~y;;Ns+JcjRhz^u@~j?(qKj`TOVT z?3SO^8ZOZR7u-Kj-AGsKl%3KD9o#-o=YEOR8!qg@mg{qLo%(gz&U1{~WtO6iG{_te+^`uqO*`Ra|9)fp_-AvEG) zZt=9b{qghixxd{-RMQS4(gz*W5G8-!Ok4B4!vFpK{O<4YueRb~ZPp?*?3khV*4f%N zN74!&)fX%Bw!HrP`~CL!`sV2AhLF`6Ezt!U_R!SplAGK={_$?xU;H3Lo^u$@R+5)fz4Dsj%|8z}F=>`QG96%g^O*d+(D30xSdj z>+O>>0x$%>DCc66X96sL00Cl4M??Yt0xjME000SaNLh0L01FZT01FZU(%pXi00007 zbV*G`2j>P86fgkJ5U~^h00Ik1L_t(I%VS`G0!Aig7FITP4i0uURu*O^Mg~;C$i&IT z%_9c|JltHIC`uR^dHML|Pyjz4FC!yDUObwT%KuKtvdBfqEU##*V+OI` z&fWp6Ku$~)sDM$N4ve*OV41%W}qP?oe5gA6qMLPEpBBcQ^O+EK7Dm0@6o zGNZj?>|^8N!R!QOLzrQ#3~VrFqE}L~UrK6PI!chUF|fm!8JSsO_F+lcIk|aA3fLKB z;cUbFg2I@jqL|{6(lWT9ERq5_x$=t2D*I&n7^iAOgaUSd6f4tfG-~VYlj<7~R=^zD z*aT%Xx3t=~={3O}3G-%q2TYTEr&m&T7u=h$fa>mnMTJ~bZ(CnKJfL7fJz?S`xJxEa z>6;1ejM5YBppGcUgE4*;WUo4YT!$aMe!02FjZSad^jWnpw_Z*Cw| zX>DZyHZM3YF*U5huWtYV03~!qSaf7zbY(hYa%EwEbY%cCFfubOFg7hQIaD$*Iy5vo zFgGhOFgh?WZyqe?0000bbVXQnWMOn=I&E)cX=ZrlE4EPnNJdEf+SFW9Ow<&G!>)?A*o21DtsqjwJ{6K`c9#Gj;X<@a(6MRLps7cS*QM^ z1dYJdBYc2Q1(HiU?|ktI+zs9j;DFV^>g0*TR-kogff=CGjp2c1!!Q((deY*Lm%k#O n`+@i+U)T%qS{hbz-B|kv?eYe%TA*?w00000NkvXXu0mjf7l?+N delta 290 zcmV+-0p0$@0>uK5EPwj$?f2u}{Myv^(aO}))bzWt^tPn*sF=97xQdI4?2~`&jC$mB zT;yz2&X3YIe#S94^WS8h%A{9Lna9% zGS!UH5h^B^OrTj~GbgUaV=RJ4=b|vXwuq&*SDF%osP+Ppd5R+@;mU^~xwLU>Brn#H zfdrtf;X0rzLRI%+d-Tv3=tD4mK)x;$R5pWc+*u4J24Dk}%SZo;{YKws!6h=_tEW{d o-h8k4s2+H-&>M5VRIghLelE4EPnNJdEf+SFW9Ow<&G!>)?A*o21DtsqjwJ{6K`c9#Gj;X<@a(6MRLps7cS*QM^ z1dYJdBYc2Q1(HiU?|ktI+zs9j;DFV^>g0*TR-kogff=CGjp2c1!!Q((deY*Lm%k#O n`+@i+U)T%qS{hbz-B|kv?eYe%TA*?w00000NkvXXu0mjf7l?+N delta 290 zcmV+-0p0$@0>uK5EPwj$?f2u}{Myv^(aO}))bzWt^tPn*sF=97xQdI4?2~`&jC$mB zT;yz2&X3YIe#S94^WS8h%A{9Lna9% zGS!UH5h^B^OrTj~GbgUaV=RJ4=b|vXwuq&*SDF%osP+Ppd5R+@;mU^~xwLU>Brn#H zfdrtf;X0rzLRI%+d-Tv3=tD4mK)x;$R5pWc+*u4J24Dk}%SZo;{YKws!6h=_tEW{d o-h8k4s2+H-&>M5VRIghLeT7PJ->v=tMw z6Qjkg4XzCes|pFL2}R091*ioCrv(1~{sN`~1EvE3r2zq?0YJw<2&xD>!#fMC3p~R- zIKenS#y>T_H7~j^9l9MHxf~g{8JyjmNX*N9XGbN00001bW%=J z06^y0W&i*HiAh93RCr!xk5vu?F$_bSlsn9{%y9qvZ9y7!HU7(!?IeWgpm)$Z>(=z6 zBP*yD)eKVv5Q|co$%7oA6KAk`3=VvNX~uVOd|}y)9!P7O;c(9i)w>AmzEamNa#>6$ z<&-~Rz9v|;0X68H)^NI|=pC{X))IGc04ky_K}nk7UJctzx4)48Z^;iVP!8h{D)8_C O0000v=tPy6ce%&53dgmt_=&U3ks_W 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 A82|tP delta 776 zcmV+j1NZ!*2B!v)EPt@Du%-V-Nl8hi|48}y`I3^7D=RB>baV|34OUiGM@L6EI5_C& z=z)QO+1c3|8XC&V$~HDO_4V~9CntY@f877KNK+2mAl^zW;gq|M&6n@iH

D z-rmp8&kzt0z`(!(0RgeGu}VrxqN1Yu`uZ#^EbHs*xc_a0goO3~>$v}J9UUFq+}zF0 z&1Pn1zP`S%uYa%o|N4Xf0QmU$IXO8hDk|ye=^Gmx+S=L-3=F)yyj4|Ii~kWQC@9$2 z*cllalm8)cad8R?3jY86H8nN#^z@C5joAOMetv!z7Z(T!2#o&}si~>*^77Qw)NO5T z1_lPJ|5CQLwogw_|NsB+@bKZ`;S&=R!^6WqK0f{Z{ePC0mMe%Y4 zdRz)T%}RLmQ}l&`8LO}=$PzVXfPj2%mwGG;vP&7@fVl`$fq@86!j-cou8o1g*c^)$ z<&q2xrGFd}t*UGq`MH)@6coBLFgVwOSaxpS*c_<_a)Lcyd9th~c5i~+z`!7x5}6h+ zjMbYBaWFr`aTQ}z;OQX;_oz@H)}ZDM<#1(GwuNKFIXgvNlp zn5YVCaUj>oFg4ZR4=u@cbUE?|^0GoWR^2@|Lo(yw005%llN5{79^3!`002ovPDHLk GU;%$CTu(08^wc@t6=E1@0%F6QJ-}mh7_dVWvNJ!6zhuNT@yH!=OJUq#Hd9gh`pAHVo ze0%B8l+?@#t#&B?=7Z?8g{Qmv@#&L0*1qG`t zEZCl&*`J^H>+7c^B;mBQ`0nn!S68w>Km7Rk+oq=5q@=7dF`Nhpzg}LyUti#|vf;C{ zqZ%6Z=jX(0YsGDC#cpn;9UZVbI@zJ2!)a;r;^Oq=3A|oUU zvn5R((i$EC3kDhgn*JOiG1PXkR|Mq#3aA4v2NGw55f-G!I^qwY80LjoWYzI!e*9Cp zLNG*aCm5ink3s+e796N5z&0pbNM+f(WO!r5F@FZJVw4BH!1ZnpnSrf04bbM`!7T?i zln5m~ELg?hy@Cfx>7~taM;d5Guj~?^npN~~ovsIcL0rGsC8X$?8Y7Btr-C2hvfF=X zcQ&D#pK?lb?e+!dIIu8DzjdppYs;fVLbO9ev_(a=MMbtqNViK%xPMMgxld2IP*A#4Q@d4F zyjNGeSXjMUTEAXizh7U!U|_&uVZdTy!DC~=WMso=Xv1k~#A|ECZEeMFZpLtM#&L1S za&pIWbH{Xa$aQted3nluddhoy%Y1yxeSOV=fz5-1&V+=|hlkIIh|r3P(Tt4Ijg8Wd zj?3P$->|UYva;c`v*EO~;kC8mwzlKAxZ}CGdMOM%ggN0(CyLD?b6cl+S>8l-16Su^55U{;^Oq=1(KqUy*=vHWrq3;MwUnlfDZ3%%ZxPRXJ8QT zjrIpSGD|iSp+FyMLvJX9c$-iY#1|#P9dHHgwJ;UpqHd-zZ)ztae8Fjv-_iq8pvS>l z+S{82RuHR;P~dGY!>8B@c9jf}Y;`gQDrjYIhJPvGhS^Zh1`bIe;0A_+S}06`0#v?H zm4SgN3CxrLDhQT`DPWSe@eYmgv*ZAAm{pv@JajoUOii^IcwlbO0h3PBxDtnQlYCGb zXMC+@Vy0AJI2VI&f^5E_U1Dkh8&m;bIY(WhUS+hVSB+p<4hvH=OF&?upI?5y7*qjc zu{B~`5pQmWdTE|gh?9e4M2c#KmAiXNiU=0#+=Lj0BLDz7wUBU0LUtAa0000aJk(&!FMLmb8y)nPxNn_wSOK#d81v z0BA`>K~#8Nb6EJ5M1Wn@GTH}|3Opu a_6y#Eb_H4wyBGlg00000*3;SEPws?@BH-e`1SGo@8A09;`iOt_us(v*Rb=?py9%n&$yu0!mZ@i z$?m>@>aJkurcmadLFAS(;D!;~VKUfOBj9*X<&JFVntQvMceIabnPxL)W@dkprrZDk z0BcD^K~#9!bvC4`=`Y002ovPDHLkV1j{$c0T|B diff --git a/recipes/icons/standardmoney.png b/recipes/icons/standardmoney.png index e081d27fa474f809a10703ceb69f4d0fd6687771..119e30378b4ba3c7d826655a82e5057b9eae6486 100644 GIT binary patch delta 311 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 delta 347 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 N002ovPDHLkV1kk8RI300 delta 2295 zcmV2NuQ~)opi=YXVOeH=_HK~ ziJImQXX0qo$)nXaRZ~&4iW*`SU6F?u#8r?3Ec;^j+{ZcnzJCA!01Z@$nz(`DGF-@_ z4Sc4>busHp(keyF1SYzi&H1p#+k4slEN}jpR*DP&00031AVnXciJg>dxo|OmKEt+{ z28O42qKDUB<^4m9!wUL7$&zVS&*8!Ye18k?4iTD^00000UM#jbmMnKl=gC-cNin(GWikYrhIa7b67#0Dk}g0007z0sw%dNU93|)|B_+eZ0ssI>lkfoL zRop(!bqQkW6F-vu_`Wr_7+UzrJDa*UVJALD1=F}plzhe1@qffU>WlwV(V9(o6w|09 z!udomh<^uuXx}|Zk)eTHw2|U6R(AOth?#m{?Oz{5+PAQ$sdI_W^lZh>fd1w_V3lQ9L24juXZS#|fpV_k2+=aer@?3Rwg(ZZ2gf&EkBD^6# z1pxu24c`fdHb==K&Tq37VvOnslf_tzeHbTP zfPdv^5svD%59wbvrf1>Hiq7qX^U;G7=qj8Q`8t7Y`eo9t3vQ~bFWRXyxn2LtV^NE% z10XQo!%+ZSKv@wbP4$HXbO&*YFlk`rTm!3e1FJ5QRBxsIH04^#3Sj~(MBXO!B9qQ# zdTYYn^zHFl+ zpHFfoi{cV9IE5L?h-jhC)SD*X`vOT%gU9HU%~^{Zl6p#Klc;x8_WVc24l(cYLw};A zg9HeR67)lX8neGb+kL#-+wYW z@UvZ2saFVpDJwrabi>M{v(ruE%AYLkhDVC|}qgwLEXQfv~(*BZoh8J1@$Diz&cj=@kL4O%AnFKz< zV4%|`naheRa2}IgMkCG~;qm3NPZg|(KJY7C;~+b_!0Et(Iqax&QOOsfq=DrTe}%!4 zD=XZ5gu_p$fr)un_KK>5lz)Kow^5asyO_hnA8^axkv)T7@J^Moe!KXm_FYh}U3 z+cfL*Z&!5tfq17g&39z`LXsf^0G#RT3nN)p-tl{X&^xqp_20Lq&&_n=Bt960G7{3H zl}{zz?k9?#@a`h6DX0{D5G!9j^Si%)(PU5W@zV!~J07~fdUDN;E%UeBq9|^lOVsm% z(c$mf_dPTu0Dl0GPqa!nba?-{bNS5u?N;6n#4!QgGK#xpusbkW@=PE`fp*y~UawYb z#qnbY+g#hp7jE5ze^GRTsOO-f{bk4B7)2m20{{R3000000000fvPfe~Vb!*4h#sP~ zOw@bW_`#c;e0i_tGZAS)CIJ8dfHZBW)vAG335ci$Dt`ba?LZ6FQr;}}tt`#GG0F$7 zye%ujU33OSvvw=mJB++`$jCDhje-#n00000fHbBrl2+QZ$(jI2N#~Odv*xRj!Ihg- z=YB&meXXc6PV#-D`@ZM&>l2z!#WcE%00;m806-%!Qrh_K73xdBrqeniKQrvq-VY@8 zNuBASzi=_zoJ*D|+UrFuKFGe(w7i0hc{vVJ4qzi@l RM6dt=002ovPDHLkV1nbRQ0V{w diff --git a/recipes/icons/stopgame.png b/recipes/icons/stopgame.png index 5a52b843904980c646810f2bfb0ac5d336421026..f4e971d7691bc06804a7512d7e8e76164d95425b 100644 GIT binary patch delta 794 zcmV+#1Lgd=2Brp(8Gi!+002a!ipBr{0iIAyR7L;)|LY>?}v{{H{}{`cYG{{Q~| z_xJbW;{N~t=prlk;o$%N{_7ko_SDtr94r3#`2PO=_Tb?5(9r23EB*ie@367;)71Fv z?C2*h=rTL^;^XQZEB*NQ{`&d-`1t+z_xS7V^v}=s)YSOm;(zBWGW+-V`ttJk;Nj;& zM*jW$=rTC$l9Tbp#r^sC_~+*LFSf-}m3&{`>p&)YJ9T(*61Q^T)^PT3qtY&go!f^2^Kh z=I8tN_33wd^ncXU_1f9(ouB>r`t{)8>wJFs>FNIV_x=0(`Rwif`1kqj?Ed-s@xZ|9 zb9C~>#_A#~>XMWG|NZTspzNQY@298lw6*GVb@9Hw>wtpu($f6;`t;e^>V<{)>+AOB z<>+T=`}p_z^Yiu5(e=;I>tJK^(bDzb-}Tkj`S9@QT7O;j)6@I)_5J?+^2o^j`}+Cu z^5-Wm@4mkF;oxzr*l9K9WX8!&C{{H{a)QwI600BcuL_t(|UaiejlXF27 zh2gdPD7MD7ZQHhO>$CZwn(|xz!ss)qvWo5j)!60O4EV0EER6yqFAQ4A1 zkQD_45r5UjsyfJ)mLsbXmJ?M7GnKZbSvpmyRXx3S&0g%&G`nW8m-y#O9KsBZv1JB6eftc3)%D$v`vh>}Jq1onr6ZYzSmyeOB; zmDfKI4i61;Od|l4QBKDoi7wMPfQb|?rxYoe=6@h{7Qh^rVt!%KFpO+wtav=0H2~|p zyx|3~NrDC3ZnrIAdk5I%U{3+CPeO8_Kt1H(2<%u6(ob}prgCrwcJ7pe3#gYQoLARi zH@A1WNuC_QJHP+KM+Z#wDf5jlkbV7@^6EQeKS(6dPso1#zK{DG`Ue?w71o&lI!yHp Y>l5ZQ;u^l7>i_@%07*qoM6N<$f+g$xtpET3 delta 813 zcmV+|1JeAa2Dt{18Gix*008_L?V|ty010qNS#tmY3labT3lag+-G2N400QetL_t(Y ziQQI9PZLoT{?6Q)>4Qg5P}1}zxYW|?*@B`XCmKn1Quuq^{rAwX(!GIjG7#o|TbLxJ$);o9!*-KsKL#Jq9$o*9dq zK+$6}EvG*g^ezpMNu_9fLiZwj$Q{HWK(w|Lbf=Oma;21OKN+ks@+#YACaSB=V4xC! zKa_t+Q+8E)DSrW_05+@J~Ex}6}An4!E$Dn?QL%5 zS75fc!(U`~=CiLeJ5TCGV<32@8!w*iO3n+yU@&Vcx~cWe;rsOZt*;g>Egk?f%ZYa) z>uq*)Xd?p@ibl&$8VFZyUAp2wiAV$*Ty1Ugt2cgWMkrL-^ckVtB&)i*V12z;fsGn}1KATZ7lFnp(dZ`Dm1P0st9gTl!WmEatN8>mo~d04lzap9qbPB}d2D$v)fI zLyQ{(4TIMR1_1dbmV1BVxK6?ZpuTnm*Y9$rY5fx0Bi|3g#wfzR(~WyAb_2fWsyu0zz)=fbAf29HXvrs zq*9sLS-9@+Qitq7_}EcBG|t+Zip2o15O!Oe847tj;D7Wz$QPGglCq>T6B(ve34mPR zSUr8VFEjz3VnTjBqSvJ`}_R-{m;+OYJX~K`1$y7aBwInCgrG=Zh}mo@#^$aQc}yy%k=d0mX?=JWu{tewOeJbU0-fO20l(7*f2e_hhrar)V&MA- zVn=Y_XJp(1Qt*&bp5c-G7h%R5nx7wWialb`y#!LA&jhmUv@s%ql`sWU7~4Uvme$dce3GuKN|Hg3Sf@fc(u+1rIS5FbJ-O>$g4zQzFek3P8R|h5ABhFH~NO z;W~&?1u3`#Vksa}lsLmnnx;v73J7K#-Y*2Q9iskxY z7FN2}ur|xAGqe1oxo&i2<*fWsOZ++4T}Lv#Vnanz*w@w{-tP9tbI(1`^LoGEpXd49 zW5qmPP#%SVxyt19WB{%o51Nt$0stTbZ(|Cmf3jp4UErmuymSDHRsv9R5CAW_Rq_e| z6Bz)f7yw2k0O9)Ye#%G!AmG~8&HN2xV`JUj-FCZuU|?WqXsEZhx2LCPe0<#Lbo%}N z;o;$qj*kBR{<5+%wOXxGsSu4uQ(j)))z$U(?OUJEH#s@!@pz`DrY1ZSo=K0zVu_B9 zh9C$Hj);g5iA1fft%HMu0)c?dW;Zo8S*=zkig?>ie)d;277& zRmC_R85zmr@!Hzj+-~>j(`US1^cK_gl@CKhLs=|VNl6I?yGWuqa;&W7=poy&1GkSO z%_oX2QivVKm0uKUns&E4`jYeoGg!uhFhr`j{-dm)NjGzmQKJ^Q9lNdEKpxrg=3sB&28Y`Kfc&zE^+8mq&XZ~a2+__!fw5mDgh3WfO z#(snq{-|HRD4`l}#|k+OxHUneExfs(Tq(7$T7JFZ&AepRw$jGsuNDfPu`#*&2;{DC z6FY8Ts3rckYFHbJt9{*Xdp&V$PXnac|(b7dW@<#EI`0eDK+r<7mi}_TAy7J295R zvID+HcPZK;tnKrfT7xi7a7HS&dyO^TGsLeipe_lIuH?_pGoar{kEcc%7b^$vqfbAH=qBr8Z~#grhq5vk7uUi^J~ za`;f)Jm}Lajz)qr{Kmdz*Z33QXTiDHef$G1zc;4MCJKqcZb}y8OdM~aRDi7Ld%o5$ zd-i*0{_*?RQH&Ms)(Yc>9Y&eNC}&9X<>&U!^C2)44f+C^C5m5;dEpWbtsX!w@+6FGGK1IC$9~<95V*mgE diff --git a/recipes/icons/strategy-business.png b/recipes/icons/strategy-business.png index 2e1a2b17d298bc30bc274219efb2d1a00f646745..cfef7ee96f5a8d77a33b0fe45c5cd19b536dd936 100644 GIT binary patch delta 8 PcmbQsSTR9mqGt#I4Zs5V delta 43 ycmb=J%Q!(rNwUN>q9iy!t)x7$D3!r6B|j-u!8128JvAsbF{QHbWU9?X#c%*hK@W`p diff --git a/recipes/icons/substack.png b/recipes/icons/substack.png index 2bb5cc80d7b83b9b683313d448b4070ef106832e..e7ec33c9e2f447ed93692841729963c5efce9b58 100644 GIT binary patch delta 146 zcmV;D0B!%$0?h)D!+&*2L_t(|UhU7h4udcd2I1fA+FS{QB;5D=za)|iB(5zGqT;Kk z8NeL{F0KeT7cg$Yg@7;FG`+y!7W5U+$;~IIUhi56+Smb{PkRsyKM$7oANGPQz%0l% zK^bBpEEho&DXgfdnS(AN?4YExflhrK@KcQm5QR+FwofQ501E&B07*qoM6N<$g8CIc A761SM delta 151 zcmV;I0BHZs0@4DI!+&~7L_t(IjpdRz4uCKa1Q*+2a?W5d|NkTf5{Pt_MZ?t7tY!hZ z5J%*UK=~nx%+^%C+zX=>TdJ9U9RWu!F$)Ey~r}k{JiTzdpL6Vpws7WKV)s# z9!@5Q?ml?>Bx46)l=m;6zT_o@1ow6vaqiC%rAK{p^wxb;0XaL6@q!X>-?I-8Cub*AL>$Q3A_=B%x~Ne+2?RYtnuyya&*MIT&hm z5VJymCT+gOn%@CB{t1_Xjhna7mYq35kmM%(o~du*_s=yvAq47Ffa^Cj_wVRR93P!| z+izWnf5~R83$Gqt>pQw_Ey{oe$xdKv$Yq=GkG-4mF3t{rv?Z)p$BzwfYUP1tIPE9m z^(lu35#}waltoT{tdjsZZRPZxGozleaz&RYw&>ayJ)B~vSNwSO^Ix-~rSty>>NU5c00000NkvXXu0mjf@Hi=> delta 777 zcmV+k1NQvw2>l6=BO3q&XF*Lt006O%3;baP00001b5ch_0Itp)>5(Bq7qF)mx2zi+ zFdG{%7NngPw5k@is}`l6k#sX27ObNdt)v)#e-^T-8bVGOkc}3rqZwmp8yYSaxUR9w zTmc04+Z;!eD*_&WA4x<(R5;6plVx|?Pz;8z+azmB7h*e(9cF05DKob#^O%{L8QL*3 z^Y$-iUnkCbP8YwQbgaj+B)=x~<@pF9v>H$jOf-uf4OXWYVmWMowiFeDCZNoZ>f$H6_8gRY zBVOO`p7wyp?SAGNx_cwmhZvybFSsvuM50Fyx%{4v!&2jpNGdga=l+w&83%+wdH>3p z%RY>8XkX`1*MWMh=wbgHxqS~&0G}Jo_-IG0WAA=QdqvYp(!In&6$od~Mg>e0EUPwe zdpbyZ*4pxa!1+s@t232GtTr6m_M9x=#83rLdWdlR>DI=UW}F8sF~Z$aLpsG&JI8Z8 z?}`m4)DOk|#BN0z0Fm>Q-A{Oqyy~5nAEUlm@+i(0D=2%Oj5c*$mCOA84xy0Y$QbQV z1C(W>y4Ri{H}{9dU*7XCMhopwKoAgR=6?o*KXU1R0O8(g1Id6b6mD{2r6QWN`xkS+ zf@J(7`UlvwWh-gjC2ttv`iX#d>MQ^4Q;wC_4J{Nv8#cBK?CegS7@c`DAT1<5XS346 z%Lmu{k8NM4{0jyO4h*-2-S&yV*xMQ3;_S!=ds4bKetcwe8w(lLFmi*52GQx2T{UkN zMXMrzQCu%T*)Rpm6+CA{MN!M?npW5Lx`(5Cdiuv(IR7;(q@Dj?LWOo)#u7OJ00012 zdQ@0+Qek%>aB^>EX>4U6ba`-PAZc)PV*mhnoa6Eg2ys>@D9TUE%t_@^00ScnE@KN5 zBNI!L6ay0=M1VBIWCJ6!R3OXP)X2ol#2myN2g`s+ph_+P>K74o@JZ2+RkNBV7T=P)t-s|Ni~}00960|Nj2|0RaK{`1tbj^1r{o{r&y<`T6?# z`s(WH{QUgy@9*{X^%WHrsHmv4w6vL-nXs_1#l^+@`}^J9-O$j`_xJbb=jV)!jLy!^ z=;-K?qe258A0LtBegpsg{gYz>tA9Z-J!Aj?0l!H^K~#8N#nV-f95DesvtOhzPl>$ zH8A{dK;!NAT1ih`t^!oPts?`M`O`85>B$JdM1BzNs1~p@w>_Nzpf6Kkz<-k$A3hl` z=OcMU?Hb(lW#Ib6S(bvwFz5Y}BkXf{gczHh>6ROqmUu5xkb?K0#|YvZc-n={aI5#v zCe!a2_&z1$$G4DG??PhtD?+2+BJfoslkl*Kxs=-lh(^5GnT2ue4?h@!ixLJiOnwIt z`sN(m@O;FH?e_r~1CbkoDSx>r{Q}xmSePDx*8_+*0M67PVm*XA^>x}q;}tzT`zmRr zzy-Y-t>Vt<;gKbUHYvHC&q9@;)ExdfM%S3L;M&6vLkJQD1;Gj~uOy11btIfV!^U+fQ!!Sh(JZq&?Y@_9X*%wVD)qxYLKju5uXkHI)gRQ8aE4x=m zQ%CB@)%Z!yUkVN;>;eVFq}wl=?Zf-shidyfLJqCg^f+3ZSPEPc@e)=L6-A88A;OZh j%&}xq_(z-d*Y*Da9Joh#GC6r^00000NkvXXu0mjfiPJZz delta 726 zcmV;{0xA8>2h9kOBYyw{XF*Lt006O%3;baP00001b5ch_0Itp)=>Px$$WTmFMgRW( zzrVlz{r&m*`2hg|`1ttp^78-x{{R30{{H^@`ughX>iqot@9*#D=jZkH_51t#6%`ez zsHpe%_a7f0|NZ^6w6u(jjNRSc(9qD%&d%uQ=$Voy%uK_~%nV^>X2uCKr8>z-olTT@ zeI#GYHdoKpta^?^&EozWP=`ZGO%Pr>H&hdN>LmUbVDgc>J#4tG3;+h-+h3$h{3*=^ zg@q*1d}V@e8vr8Gt>6F=+1p&;(0`+6?>;!s*OC>$yh=AbMY=YBMs~qNTJ?S^Gx;e! z0MaYVLt35Gof)ZS$kW@8vkZ7ej|b63H%ERCS$HGS*F_EQUsK7rL)pz!#Am)F>9ffe z=zgbkvAhW|lX*+a>vI@O+?%8eD#Asn@)Hn7SEI_*S~7;d4I;?_lu26D8h_Ovz`R21 zOVjjn9Jo$&x&=TxB?jzWZi|k~c4Fm44nXL<-30*3SvxVkRd++Gf!LEc0O&1;zOT`m zjWt+*@n9GO(;*08%<`(DC|X`smxU^5D9L-;wAl6ogS|(hp<~T_>SV}qe4-12JDc+9 z-QD|rqp57p7(Z_K)1IRpk$-R%>`nQV=D?1b{NeUFBXdIWH-#s%5y1tGxqx5v^-b*y z9Bduyk4Mc`UohI!S-l5KWnLx=qOOY>EzV?C&=i?%5&qR@{ptO`0mV8|X81~9ApigX zS9(-fbW&k=AaHVTW@&6?Aar?fWguyAbYlPjc%0+%36Cgx@G{a;ABePT>%h=S&#LUDT#0SfONT5nC0O}VJbn-$ql>h($07*qo IM6N<$f_)WaB>(^b diff --git a/recipes/icons/swarajya.png b/recipes/icons/swarajya.png index 74debb2b0059f6c9ce3fe6173aa7047e1e3c620c..216227196c2791ee2ff91441056f7586afdbe27f 100644 GIT binary patch delta 20 bcmew^xR`f>^2W*KY@7_9u6{1-oD!M^D8A?k-+9s3@HpZ&AY&Nq$X!i%%iA#D( z5%sE|2SE>_hhC&mkBZAE2V(NkN4AHWU&B86I!uz0do8hnbgWew}?d6T)$K z(Od-BdvN-Xbpim6CC!xz?0&lV1rL&4YnlLCmjSl#0{q6(_Gf@T0r=VmU{3(@@v}E; zbJ#c)*KQ@4Yol2HxG|5LTxWHq3WwQx{%+^d-t-_hyd;)ximoL%vwIv9Xhb6gQYvUh zOD(Zkjj6^JLKq?GYNL%JaS7YFYAxh?%0KX)5~}j)Axw_1uH|-%%0HXObid_Hk(#A{GZT<(0002nNkl^)fTC*lhfQe`bs^_sUASs5NX%zA)u`(`CfuN%n#26YomEB6-L! zwtfg7id$fX-?WlK$k#`$9g6k6F~txB9LH1l9H;j7u6eMbHJiicD(ORbfH8Fl>{Em) z4do!AvK9%wSg`h3_1Rkc@^GcSkPwnjk_Dif76R34;6`x*6w|`I>LpORAeRST7*{>d z0jI&P)+N#NL9C+z=wJZt2P?=4wAk;0=udk`{U{` z0AZL`(_vV7dj}3T)wvW~YnlOjpmDGlfCJSe(5JQorS%%a#>r$Zq{r=$qa708z<>lokCnx{>{qJCdqF^>NJ{_ybdO-)VfQla4B;Pj)-TU%RYWo0HNCZVCBZ+~xZi;Iic*x1q0(JU-1 z^Yin~&COR=S4c=mfq{YP>FFgUCDql{sHmu9WMq4Ldp|!vadC0U$;o13V!OM$MMXu8 zjg6h1o%Qwgj*gBxIyx8_7<_zu)YR1c{QT|h?Jh1ZA0Hpy-rmZ}$}ur9iHV6KA|f<2 zG)hWJ`uh6r?tkuwhlk(a-yk3$(9qE9>+8Y6!LqWlOG``p`};gRJnZc3-QC^2P;xZ@ z008+(L_t(|Ue(jJZUjLLfZ?+Tmhq4o!pzLf%<%p%k)0yaoisaXk|KZo$+8P4_z(Rq zuumtH;K)5ssKDC=LRAG`LQPYF`DH*AFkOa-3o)RcI)7W=g$UeDu8#T=a5y{|o?z6R zURpC{V88+tX|Ol7Edwk1_=5nYV|`IJq23;Qeit)F4jw;!`4vwE_`X5`wCX0V&JGDPOi+KmXbh~qe<)d$RO1n{zOy%PiY zrT_@memoqwZ!!h|){=AwQEH|cTm{oZoaZ_fN@=B&>j&I#6x+qyo4@!?bzcY%F8|+D RG28$E002ovPDHLkV1nk6Cg}hG delta 596 zcmV-a0;~Px1oZ@vEPtV)p*lJ`>r$ZWQlZ=1+Z-Gm{p|H+Wo0QTDS?538X6kP%F6%! z{=2)oMMXuOot^dd^^T5?CnqNu7#Qz#votg`^YioPF^(lACDql{sHmuWdwV}WKmPvy z@9*#6;Na*qkkQf6TU%TG?e+Zp{O#@SE-o$~A0OV{-v9gkXn$yEN=i!l`ugkZ>ntoR z9UUFb&COR=S4c=mIXOA$>FFveD*p5M8yg#BWMuaC_So3iadC0U$;o13V&MRAL_|c5 zjg95yi+p^1)YR1f{Qcqpa;K-K;sA92|Nrpt@G&tliHV6KA|g#q zP49NJ{r&y!?tkuwhlk(a-yk3$(9qEIqs_s=!LqWlOG``p`};gRJnZc3-QC^3c5+hy z009a~L_t(I%VY2*02rvMKweWEuL3n~e+GOC;sOjTyzwcB;bddOr$9>9!T_HFD{XEA z0|RLs3glRM_?Rq!3c?I+twXRVP<3_Y)BwsGNSX=TS$| zegsH?u)3KgHWTC&l%nH7X1IAoCWc`5g`R^29$&x|;0uRAr@)+DMMZ_(9H#=$U7% zAsh;P9Zf+B3@lvSaVZE7;^wxnuyB#V;Rat{R<0luF%x4(UtiuR(cs`<(I{TDd=cX3 iq~H{)OVhlG6#xKA%oD=2^2X}`0000y4IP%J_! zg##8z37V7{7+3z-YTCh&j{i$!#M7m7f)G3pd(7tVqiVr(50yU-bBopO9ByFf{~W}w2~a&7yApe^7^a%N(?{m~PEQhh#R5`QxUJgF+)5w(ZnJ^Gcx88t_{NXTyHw~3QXIjN`F zmkpFh2mp;v0IGX8aTPg61W@CutAq{WUywBbQG*+Ve@I=lCp`QBjV&M8TYZ#100000 LNkvXXu0mjf9;u+t delta 380 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

~83H^KV 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 FV1n}5)@uL& diff --git a/recipes/icons/taggeschau_de.png b/recipes/icons/taggeschau_de.png index 03e9fd3f037bd4f09837cb324b16b2c83c2f6de0..1f785d0767c3240bf03e8dddffae0d803f2316a7 100644 GIT binary patch delta 1743 zcmV;=1~B=P4&M!sB!8w!L_t(|UbU86j8#_|$A4?>ea=2}=FI&H3J z*g`HoTh+*PI5?VVsj6Gzq>abWYvN|A&lE zst?b~iHY)=kG*rl#Y$+yVvAMT#8W$0w>{BAsO~6;B7YV&vwG#V3)J+y&KJ}7w-x_+ zaq9O+r+)i)Gjf&DtH$%v15Y&#tgE}P$#1DWJ~4_*PVU)(A>U%xRbfXJ@uQ`we5O-`Rm_byJe}JtxJ_!+D?yiYMPTXW~gB1 zTq6{nn|~fiX6sSWXZ64svdshmXNS%79H2%fnJ=}pre0m!uzkl`ZspLBeses$;3jhQ zqAH1FqtU)?*FV`e^@n5J;6{^;!AVe^!BeDD@3?i zFju&@F?HZ4>wuj{{1dL7sS(05NVFi3GWX6Ab$?}sgmqKV1Q^MD1wUlIjE?(NU(L1c zd-fde?tkM{OICbk1&tY+Gqt^K&%U&7#lc|r8Gr91P6we^)H*PsuLDo(h-6oEJ zchyQ3BBURTc0LHKZgtLI@e@YcGGRHE5r5K3TajA=Y`ijnQ$>7)LdaO4^^)c^fHDo~ z#I^0i1--x#9s8FaoM6?%KzkKoB~Tzx!Cawq>Y}g>VFi%*r8p|UXjFJTMk574we#!0 zUh4kdpEe&F8GD;<2WU`DGL`GLNWlZP+dOi;dunAoNw?JJMtq1{) zNSwMf3#?pH_feQ|Ui_+%EI@30nuetvrU)E4Iz*I?G6{wBM<$3Ol#31l8?68;b0ln} zP3U7Zw#iL;Z|^(t$6tP<`~8Y4Nq-%MAc5h6K0F@%d4zmJ2Q#@3b~PS6J$EUrCO;eL z>Y%K>t1}oy=z#(3*ar9>evwQ)Gh?w|M;Q5w_b$Hh%`g49C%Jnt^c(^FW-xs7Jo%Eu zRk(#0`vmaD-zJcPq#aG?ut95r63R!&n=T+?;u2jCk)I@R(F$qhbN1$U=707-SHH7| ztq1&`-zgJFv_W}2Y5yfsdO*t5^Vn6}td{NYTF$H(>fI zp-&WwMT^abfqPQwwN;6M6)A4zz(={=hbD(ViMQicsFY>s5^9~BA~%E(03cZY0|1Df zwaq-Vg-d5B=I~vll#i`9oqx-W>$Ydi!;9p#DhQ)T^8V2)<&h~D6{5U&dz_N+m5B@! zX9y|?c|*|m5}y+K^sb@%5zZgSEfcs%ORRldv@g+-K}GmJLJ;_u#m(kQWVOtpRJsO+iSav+eiGKxn6|k7U%IsyN zMdqSBCN&p-&iG|Fkgi(A7((miX~~0uMi+QlKzOC5K$tM?vQ{{+lQy zm0@Xr+`cfxtDr)(!WfK32x@A<5CpNNFd094HZ!J=C;fIT(S_ zu>%N-xCKhH@piw6xJd0UmLU5KcR(w&A(13igOkB_5DAdnISw?S~vX6}js lqvL#vlnBWJZY%gA`42^xf+AL$<9q-B002ovPDHLkV1nAzS-$`P delta 1795 zcmV+e2mJWo4U-O#B!AjTL_t(Y$F-GPjFndv$A5c&-*>)m&fJHY;W98VFqEkvghDBe zn%X8rDNR}nTBVm44aTIc^{MtvpG@n6i8aPHl4v50ZKJ->h=NjS8!ix?iZvLO)-q9^JP zw7J|r4_~T{6p#W}eiDGvvb{N2yF3`|OD3z1C!1UR+E_8V+`w~f;D@bEswMcsmc{u8 z`cEFYc6#@jcz?2r@7@_eDKWpt>6_>E^{TO4dNJ4j&`{9hnb9b^aMA1UNiSCYd2N$P zd#ShU-j&S@H_h)K>VALMiK&k+6L^UK3{Z;23!K3vqFj$pUv?aNAHLgfb?YCE7k7V1 zs?@}!^fUE*UQBgV*SET`(82BV{2lAwdo#HD-UTx5$A1H~W^Om@R*PUfCRw0`cc z>Idh`#Y+9J=k0hNrR==!=BG9Xt_~)z#yxE(r|1a~(Bq5sPpmqW=-k1J1n%ts))M%9 zY8e?vO&09vxS88NwQIk*Fz&5hv~aLv)lzTiyjK72rIQQX6E$zt&ObR$zKiZA+m^wC zFk;D;m4D^2QZiA-b#4df>n2G!HDYrmK#fgMuJ!Z=&u{G9{`AJ1wA_EeygHu#BlHR# zqA7##Vlc7mc04#V`RgOMWMC~phH}Kj2-$#Q6)aJ@ysv4`&TYWfXW#yNU8*Ce*#1RI zm8D(5-j_c6;4*d`jE_yaEv-U07Kss>MZIqS9Dg;MpPrBF00CA~Zh#=w7(=Q$n%T2+ z+iR~Kc;#mw@Yn<1x^9L?>27K3Y2Nnu%1`f0o|0jR%LdMCZC98mSz5cn;*nhLqm7e1}R!Ar7=&%O3M0Eha2`SMEArS&& z`n$6L%64YGfi^i(F*h;vk-wR<*I08Ou%L;wkth(Tq*SRLKQCNM+5lYQ8i3>A`KS~G zLSR7(fM)*}e;oelxpJ`io>Px5z5(#geqx8i$NAPv7NMgwwI;cm6DEx>jLVEhg@4l- z)BrL8K__Vf0s+Rkly3kca2uhKovqo-Ev6DQv1|pk8Zb6dCrY$6O>E0fX?44^IKB``b^Xp8w24;+7JK%@5PLc#*9F2M=FBXLXCGknz|xv_^%P z6mT&Lpi#nekuGVBHMq7g5$$^G*ne+-^4PLH4ONpyryzlmia9W@e?LaCa}oK%yU%s) zJ6Re|n<-9d)11^zbR~~fq|x;HbI}?gj)<$YbdbM{))##lg(WXVb_R zz%S3Hub!h=lZ1+}^30F`e( z8)M;0I(x}oA&t=u(bj&Z>VM`})fd)yzu6Sb(%*;Hy(bDU@1GdC+Q7tw4H|WuhAK6- zUL>j@1ejS80Y-EF5E~w+QGcUaK&5taT#lTcShC`~`>Q|xuyWsAInXABHDks2@MwK( zGDM~5h_FG}AgtL~S#2p_nwh5=FEZ)Be>QcQ;e#x@pL0h6lF+=rPpDKb=pQfH-{m!N zj1VL-DnTd2A#q3)5;llp>oid~Ls&vOSbZI!X91+-+(9Y@`c~r<34a}u*dF?e?U;k> zU?Gm^>ZYq3$0d%4qZvTd0Efak3S&r#HnaAD(aa9%aOE^%g@FzDS?VRym`Z7;wXhU& z3@oRmjW{HZNh0EeMuo~H8U^Id56&v`EE6L?N^Oe8Yw74CO^F()6r->fYnaNDBw$Gr z;*iP&mjUgWmxv?G)lyEbP56~u`^A1JHYHBe+-2*A<1DUZfJ*@Mzpg_ ldON_Kga9F@z37w1zW~T(jX3hWbld;{002ovPDHLkV1hdhhBg2I diff --git a/recipes/icons/taipei.png b/recipes/icons/taipei.png index a29563b26256534cc55af4a9163a9281160d095d..8bdca073c85e8a2acde594c3d7f1b2ffcff4d84a 100644 GIT binary patch delta 1282 zcmV+d1^xQU43!FyBYypp#2_5IbcsGWf1dJ2!)B;3 zhmwRCOH_ha5RZ;hwzftq*RRv=#s+COPItBDd-XcsCE{Pl#<;by!6IdraX&$zR=(|8XHnmq*se`s@ zwMeAg*`dzMmz2MHMT9jk-@W^oEs?Z^!;a;!5nG6zn}b8ROqNk+eVr7t*3PHQl>lsc zhlFTrixw_ipr@0Q^c+}1y8yY!&clcF=fngppMO40OXK6THapurV(V9~(9)?>GA1_G zmRw+ef#Y}dJ)fGQ#dGH&;n$r zWSk(F*V*1C{_w$`2IFiYc5Viarr4qq1uDXB)$3Fr9;O<)hR~KiavN2u zlz&DMrN@p@j3D9M&WqF3_9Ylg+_%JvQo>=mYT!iM@{kL_S71LXX1o5z9W7~#xNK~n zhUP4jY`ac4&m|CR!vul0;y73|vNvdlmzWMal*iG^fX}!lCtSJNj$XLx$DN4aR{Ot% z`}ok+JKPH&Vb(mL3OaD?Z?mIWmg*}h@PAg_yY6P|zfDA7LhA1EqA03wLi9<+L{_g}rL~(k?Wecp?thXT zoF#M!^|RI-1&`9$|CS7_PC=dk;j5VYyn1qG3oBZa2_C0{!u4qbEvS^2*{)lJ`yd$^ zK?JFgx{02|6T3FE*vcF`mzAtV zc~B~kp8h~r+rXecsfgC4{7;O&QG4oh*>8&IQ|QEH_n$v1Axf_+{Yt53)0l^T#7+6Gf>>_1=`XRUQ?T$kpj#t5eWX`+BERIf2&VP0J@(l^#1@H`5VN}9J%0A)) saA+IP$x+nWgRjrdo_%)a)~y%+05Mi;UC!XX!vFvP07*qoM6N<$g10nw$p8QV delta 1594 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 Hu0mjf{6Gnr delta 1123 zcmWmBk5kfj9LMo5FuF2zZJpP0)?JpPB+;XxcX z?b7iidI;M6@%9l$UzWNdh&TcZL>!0#K}-lrf*=+Y1hAnHE)>cGf+&25$`7InAesPT zz!at^geeM*Ln#SpXaeXNN)jNUvLqoa356vMVM(cMDV>`^SO_5w< zj?+{zHB|}uCPjWFS8L{J&3tWjg04!SyT~iJC@i?dE~sJaYqZvyxx-~=|EPTc9- z+}d((xqvMf;NEh(T^^6y1Lg(K;{^rtd41b_wVSvLeD0Si>|_!1xvHpvI5mL1+ku)1 zsoq$4myoQT8cZCIeG~q}p`GNU* zXm9Uj>RqRg>|Led?;?b!hEDY^#r8=pvb5A>PF;GDS987MnT*@tpOW+B#uY60#vq0n zh|OKSp(6*M{`(9jPlTt9Hu|ZKQ`(As_9vaV!?+oBEca+Qby-#(-iPjOO0zu=$STkIJnKKo)b9@92}1&9jnR7tph5ISaxyG!S%aEF}O zgm+F zH1(gPukw=aO`?wLO-E)HCSqBjA2ieB Tu3NL%=Ia0c{@Wu&{`~ya11I|C>F=AV_s-SU0w?EfgW4ND`{(QOv%d7d$=x$h+8R9d z#Le6+OOd}7A@<7A|NQ*e5;N?Ene@HK{O|DB2P^A>llarx)(I@&K3kFXEq{~WM1%kU z0O?6YK~#8NozB&c1R)rN(XVJ5pLKV4cYFWG+CY;fFx`Ez=cbasASwQ5i*D-CB{Q_y z77Qfiw9pMkR)JN}Ku!s8d`izINPpuUzzlbRJz4vt%z-Tq;Yb#-a?(=`IFb5rdWJ_aRj^9! zQU)(m449i=puj_k`m~70of2>#tyCiSr#ZX_#U9CB;!!B}go|hXl!{?vO5{a>c$Gv# zf!;9hq$&j;G@5+Mqm61`v@vpM_bus%P8o?`Kr{)fixfPC00000Ne4wvM6N<$fOV delta 665 zcmaFOe4BNGNgV%iiOE-oC%etnsq4=Xq}PEor+y??1e53Yz@m@~z*`UOljj{L*S^ZE{_EMx_p?`iKX-NFPTl&Seh1G19mtsE?e4;q#$A&FvL>4nJ za0`PlBg3pY5H=O_UCNEB6{L8o49#^de}T&978Nl&z*cd=&*r+tG>FBq1lmz ztzuS;ObfsN`al0@bI$QeZ&KQRFS>Yd@z<*GN&l9%TH4K>xozXxYf3@l9#O&i^(UDo zcul(G#@667XLzrB=Dj z8s$~N`b+oTH+c}l9E`GYL#4+3Zxi}42+C*4a{^6Ekle9tV~R-49v9+46Fl+^>+uZm6n#4rKP2~xVXQ+zrw=8 z)z#J8+S@)pJ~%iy;^N}(@9*&N@I*vJ^z`)g_4W4l_B=d1_<#8LNl8gdOG`8~G*D1b zpr4<_!^1f_IqmK3?(Xh2H8o95O=@ato12@csHkIOW8~xGe}8|8iHT-rW^8L~>FMcb zW@d13a8pxLT3T9QU|@iNfQ5yHH#awmii&P+ZJ?i^prD{#U0tfGs;{rFetv$$#Kd7? zVb<2x*VfjirhleyZ*Sz})I_l}^=H}*(j*gFyk7s9RaBpu;PEK-i za&vNWb8~ZabaZuebo~4Kb#-<9{QY)zc6fMrPfku{Wo2VxV%*%^-re2a-rnEe-{Ilm zK|w*|)6J4#7OUS3|nz`*bB?!m#q z@bBHZ?WN%gfBn%(1bt*VotB*x1_I+OxB>UR_+L zSx87o&sxvV?Ca~$(9qG*(bCe=)YH?{)YNEbXoG@-gM))CEiJUPw70jnhK7cShlls} z_MDuYySlsi`1!oNyq%n!`}+DtMMZged3t$ye0zI*e0-y$qokvxzg6uzlg9!lf3ZnK zK~#8Njg#Y-V_6hOUsV@7m2_9qmD=~d*eTn#-L`Gpwr!1V+cy7~sl1hyO!s`~*aHc3AwoiOQR^hvP~*5gvr0NlZ46`57rfwYvkf7cU7 z19kWF0+SjYO>gZe*zbkWiP8GVe^mHo!ORSQjztQ8PR=`y8HXWtWaNA*Twa^u7ZrED4{-e< zSCoK0R!K0Sek;Ys3IJ**Nb4*BK3*YbgDFALr8Cei3;?;mOZkltjoX&~Ixr|kSSPLaQUE@+i(@b2dLj#WO3bMNqhiLpRf-cG0IU^q z(~mpz%Wh;tq1fvJvjPq4f2CYl3vhIgDF1;HyT55A^jW?D2sMnAvd0bZmrYKoVx5o^ z-KbM-Ujry+JmaJcc!0{MrELiVPBFa@GP~am0%pYsuSq$j1}K~^S~B_DCNEY(@2$rG zjEVu4SjDwWa4nVMIF)mxtpLW~dEEtI2o0*_h_(C@yk91&7xUBbe>Ka&`(Y3W4JyGz z!)7U`dO2tHPxI1h5od{Dir1F4AVb(+;e!{RTL}l}=T#9wAFHj~GbChB2dWC-Qvt{= zb)&6Y6XvCWv<+4gh3xMqWNE^Lm8%#htP>ws2~&Gzy|Y+TE+TE#P`$qX`HeR_BicuU<9hYX%t?;w_a@K(YP_li=fBbO nbS|q{lkE#f@BfAOk@5cr;$f`>M^4`r00000NkvXXu0mjff4s%& delta 1365 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 Ry-@%F002ovPDHLkV1oX6Qm+62 diff --git a/recipes/icons/telam.png b/recipes/icons/telam.png index befb1ed900a32a6ff6ac12b3d43e115672391883..89525f84489c42de7dfc11e9cbabeb9cdb218522 100644 GIT binary patch delta 289 zcmV++0p9+%1EvFzBowz$OjJbx0028XJ3&D~TU%RTU|^A77(ZZOU|wEcKR-WcXlVcc z|28%@IyyQ%Jv~4`KuStVO-)TuP*7rGV%OK#{{H?mGczV8CO$qsUteEfU|=aJDUr4$ z14BbYk>DN>Mn*<0EiJ>t!!R%~YOd{SlL!GIe~n2*K~#8NRmMef!%z?g;a|*7MoeaA zcJTh^@|#(xO5dg%fce=mp_DySGqNXCZs%kqYrl*fWT nBNfZzzUAt`um_6v(b0f6ov#{cP(4N%00000NkvXXu0mjfh75go delta 306 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 diff --git a/recipes/icons/the_budget_fashionista.png b/recipes/icons/the_budget_fashionista.png index 8c0266c06b25a66c4cfe3c90846b0f9f6b4698d6..b6abce50ab9af950ddc0921db0227265ea19a8b5 100644 GIT binary patch delta 170 zcmV;b09F5&0)hgNV1JECL_t(|Ue&-cZo)7WfZ^YEocTRR%4{QNhp66BAYw&<10dAy zrbp<>JNpLk@y4vsFvmormq24ALPC|GssbB(>qu Yf0wThpUF%E(-6oBEs zEoFYMB+RXWU5VWdN?eV40AqAVkKoDGu{Y4~5tyqCo|i6>7(q6FwfHF+N1CtZSPAnEmAP>TtgG{W#R^Kxdzi_wIUn?#G(@Vi{HUm?=1k(}4DeS6KCHHQuz+P80COH0e*#f#6KJ7;Ta`}gnPg9i^zoH%ji%9ZNs z>K89w{Qdj)k|j(2{P`0Z8M%D}@xSi5%Zt@>NHZr;3E zTwMJ8`SX`AUuI=xJ$v@d$jGR?y!;;TuiwC+;3^673x3V?tnztnO~V^zezuusfTB-4 zT^vIy;?`c;T6Dxgg!SUYX>aNlrLq|V!RP<~J8c^mYwpdkwhJ}idF=F$CDYDUt2IZR z`>{f@SgUeOUEAhhxWcqrls)I@RJ&lYDRK*LmmQF*PdL*w zU2;bG!6w#jMUms`3yL2!9BEo=wZ?<k4UEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10f+@+{-G$+Qd;gjJKptm- zM`SSr1Gf+eGhVt|_XjA*UgGKN%Kn^9SVY${uQC)U!sz1Z;uzv_{OZ-cUd)anZ4dh^ zcwYwHys>nH_#WPThl3_f3*5{FI#{f?a)_BRvI>fD8+vf{ZgX^;y+DkyxpPxu^#96S zHD{(nd1X>hv(L?WYI9icaNm@Guv|f9*-9^un-PciJ`pMY9#>=8tNc&l!h+n%8}F*t z%$t#@E5os(;Iy@C+(#dMUx!W}yRQWg8dXZ~2|O3mxa`^?)m!jDLNehl)Aty~BSj(! zr9k}AA3K-Wj1*Qyv4%~RRRVk}x))BHZAi@0zhx=G(${dPfIU-SMN{R> z84kP=C5(EpjmAITK6Yi-Ipm|>Bpu;Q^5Zp?Wj+;oYcc4b7|4hyAJvD`1H^G>)-$BgTwL6J=&Fi4sl)@ z>h{l$&boere{OcHB7=yLA}~NzOI#yLQW8s2t&)pUffR$0fswJUftjwMWr&f1m5GU! qfw{JUft7(lzMRf;6b-rgDVb@NxHZT*Pl*OOQjB_<{&1^@suGBVZG)c^$r@9*#P^Yde4V-XP%hku8M78Vxe<>hQ_Y;SLG z=H})H2M142PnnsSo12>f006kSxVyW%y}i9cLPF=~=g-g2+}zw37Z>8<;^gGyTU%T7 z^z`iP>;MM`H8nLhHa6+$={Ptz2LJ&6{{B2XJO~H~K0ZDvDJerkLqtSGMMXtMMn%i1qBQY43CeGzrVkdl9I^C$e^I000RRK4h{za0iU0r zpa1}&p`p^!(iIgI*4EYl0s^C>qot*#-~a#^7#JBD8Gop#sAObhCnqO_goJ8pYAr1- z*8l+5*Vo$G+A%RPb#-;!-Q9S2cm)6edU|@{;o*FIe0_a=etv#}f`T(MGY16)g@uLc z>gtAuhVAX`4-XF^At5(6Hex%b*1z8hMf=E1m(4n}+*LZJm~Zp+WQJ6)yoGoUa48b+yB`7+FgSjSYc| z1tu?mL==MYLKeEw{XMhcUAMUK+?)ddgpUY*D1Qhb){I^=XYb>;yW?Az0sP7{u9tqr zGlhE4H%+x5j#;PxL?L_;(dLU51DRV#Og`M8GnpAgqBTQV_$0S8?@wXGqN5U8Blu6C zw&7DV3O8Q9r(p*z=$u&Avlc8OB%xo{*q|A@>0~If0L`CTK)# z&wqi=$tD)TmlR&NpQRCHB05gb{79ALKJzoyJvDj(sw)xi_>2;r^MavN#B-0MaxnI+ z4soO>^`rP-U5ogp;{J4rHPVqE&aP(-6wB&%Hy7pC2vsjEY+2m89dzBR+xAA`@Z`-l ze%<_q`Pfsfx-gzJz$5RxnlDe!i;wjk3mGP6&+M0391uD*SxcK+8 l8o((H;F1P#TLW0q;5VYBp0u$FwG#jU002ovPDHLkV1lre!Jz;E delta 1015 zcmV87#JBD86hDd zB_$;$CMG8*CnzW=DJdx`001f~Dl021EG#T7EiExIF)}hTGcz+ZG&D6eH8wUjH#avp zI5;~yJ3Kr*Jv}`>K0ZQ1LPJACL_|bIMMXwNMo36VPEJlwPft}U3T$jHmf%goHo z&CSiu&d$%z&(hM;)6>({)z#M4*4F?4*VotD+S=RO+uYpT-QC^a-{0T>0N~)@;o;%p z;^O4wgww3?CkCB?e6aG@9*#P^Yird^!4@i_V)Jo_xJhv z`TF|${eS)a{{H^||NjB=n)Cnw0m(^3K~y-6V_+Z_a3KK>4orCtAqg=U5aQ(`XdX5* zQ0(L9M==D*kv4X)Ha0Q0vbDFf&{L5F8h~U5mk>-f0Wxl!jQyF(8`(w1P*~r+;?QqKR3a79J&w=C$T{DKjweqMD(R zFn3YCuRMdiYyQ+li?XeN3J_+16~xV1)E1y7#=zlHvuIJHzb;UW3#NjDALz{ZnTvX& z%oRZnUbJXJiZjp|Ja7d-)ta%h7IlXk$pFoYnYw6tuA4lPE5KI7&so$NW+27DpcUD+ zXn#h5yCN0^-Qk8n1)5PEi>9M0fLjq}2(&^ydeWlld9KJ-z#Q2fVX6SMB5}^5NvTe< zNREVfbM~V4ARS;J+Y~KY)Dob>kK|2|0?mXui<$z}xOmiq+ZQd$v_uXlNKn@;S~R85 z&(S%oVsd|hhZ33}Ad$6j!K?}W6T6E;t#D8x3liOvfYDvm(An2rndEJP8r_HlV`gcC zkzhEX05!=8igL3<#Rb4Xn2#M<0jG$#FhoKCCdA0X1_P|DEX<4y7=Q^0Fe1xq695d! l1OP)90l?5q05Fsj007*qx2y6|ilYDk002ovPDHLkV1n3%zWo3I diff --git a/recipes/icons/the_federalist.png b/recipes/icons/the_federalist.png index edfed2e9bb13832f0f48f40633569ff3de7ac6cc..7a4b9a748b9d2aef960746a8eb89f2fe19c65b8f 100644 GIT binary patch delta 912 zcmV;B18@Ao2#yGlBat{EfB*mg6%`dCA|f0d9499y{{H^`{rxK|D*zQ0{QUgF!othT z%huM``}_N#pr9NT6^)IJmzS3S92__}IHRMZq@<*#rlzQf`Wn~6%~+>kofrcJ3BjNWo2g~6=!E>YHDgHCL$tkZf*bo6)P1L zb|MvfA{Bdkdwe1leSLj?etv)=6@Y+%D;yk={u>V*A|j9{6)qJOlarG&D=U*I0UlsA zH8rB5q7@t*A{-p0rKLSRJwQM}s;a82tgNlAt*@`Iv9YnTva&)#LbkTHx3{;sxw*f; zzrZd4NE{Wz!^6bH#K_3V$;rt8A|lMp%+1Zs(b3V;G62%j($&?~OiWCZrU4#*P8=2K z>FG{RPVMdOQc_ayI{@$R@9^;OR2&uZ^72$vRQ2`s_V)Jo_xE%zC;9pLS1Tu1S65nE zT3lRQUmO(x(Z93+000PdQchC?ii`31`ThPD{Y&%!00HnxL_t(|UWL12MIJUJTD^fcZk-ps z*!w54u=rX56>^mUjBAx>t=*!=omr|6n=fBfjnA&zs-&2ESMHu zT>Rr2mh@3DTufkzCuHdQT`11E_6IN}X$-woP#$oI6eSD|ZGqBDXR3o(J3uBu_n^51 z+R0ErvycQ?W!keHewvLcJ@rBoJc9449%Sp*9E-jlI=+t1g%9i12>W5M_* zs3KsEwC~Y@^tx5;D0uyU6pBkAM3)CAaJeUS9$Bx?A>?wxN?&wmD+{3C(4|99}V0xo)3^ssakrPd+Gt$KAv3xf*DR z9TYea&~ZpP0pRz|&y>iJJKxi@Agh2Eh2YutL89^9kLVB-*8;X6%{KS94jj;E)^9rD=RfMH8?mpJ3Bi) zJv~4`Kte)7NE{VNNJvafOimmXPEJlzQc_eL6;xDIS1Tu1S65nET3lRQUmO)>Wo2g~ z6=!E>YHDh23vF#~ZfYbpP(xhprD|lqN1atqokyyrKP2&rlzQ+9?7?d|UF?(aJQ@9*#M@bK~R@$&NW z^Yiod_4W4l_V@Sq`1ttw`T6_%`~3X;{r&y^{{H{}|BU4tp#T5?2y{|TQv-^N@%Z`u z{ucd9^Z)$cdoj*1C$R>Z8B6%hlb6@LUHCJY!c z2Sijv#htGEdwtlz0Pe9~>#KWD_c>n>q^gm@KPQ8lR1i!-8WksvDM3}G`$9%lkOp8K z03cfj)Mb&a%K`wL1&|Ih07RSt5Qab)A`C+S#)$yP1b~$TEn*$Q!^biA)b?=M#q}Ya zmF8BlKma&tWA8$7GoHP|%70JyrtbfGwsxLGw|G?|AOMW4{mr2ksolCQoV{~nxMa`j zX|i{KzNwf1EAOd%yi^RbA%N+!pjVZjjhF%uNgre7$VoA7&MY%ijt)^{z7&TN&_Sgq z_lXs;(iqdzvqvJ7DmLUvdgdf|zb_|8=t**i*dkzL{&y;sr`LRG?0@W%Rw~KSRbq+& zob*%a*(v?U4s}QymC6rdPy!<9rqc7eJZR{*7X??{BPgo9KnUo^jY#K9F$s8pU4 zR|bqk15}c+QW-0$9BxQdz{v@!SCUGS>OH#(0Yw0a9x9Xbd~qS53>evT<8^8KuBWNE zP5{nId*OpTuZgvj0DoEc^e5vy8Rz+@e~Z`&0F1Qxk7tI-(erP4x!f=Yz)Jg-OH|LB z7rM*$NdQhZ|Dr1MyNfTJ4D1>t$!{$c02ofM^ wgW&?30UMV_1FOq@QN}`E=H_3!9oiQ1FmOi7J~ua$a0a>B)78&qol`;+0G8W8qyPW_ delta 287 zcmV+)0pR}e0j>g&7=H)`0001UdV2H#000SaNLh0L01sgR01sgSs6VG^0002sNklu^auIp4nDYW z@6r#dpaamP+w8A+HO1PWt0rX*-1YlI z7@TvFtn9Bf-hu~!3pufW)_x7||0dv^^Qr%K4&d+TPFhUlPi-U+6Y)FqbKxGZ8AK#u lo3oEn6EFc2Fc^XA0&lky0JL@#9nAm$002ovPDHLkV1nQHet7@@ diff --git a/recipes/icons/the_insider.png b/recipes/icons/the_insider.png index 08fa048687e1d23582712f2837a98fd706fb0e4e..7b2b65ef41c64a5f437d18b3720e93f743410554 100644 GIT binary patch delta 169 zcmaFEw3BgyL_G^L0|Ud`yN`l^lx~1eh%1m55D-vMP%tnsaBy%42na|>NGK>MXlQ7d zFk!;{`STYnSg?Qp{`2S0-@ku<{hP&4fEwIAT^vI=qLUL2aNm(pxMX$tn?!?TzvK+P zri^Kzopr03ob9G5`Po literal 364 zcmeAS@N?(olHy`uVBq!ia0vp^0wBx*Bp9q_EZ7UASkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY0t>0)3IFPO6{&;}^Ml;rL1!tlSn|IB_MkF&rd zvY3H^TNs2H8D`Cq01C2~c>21sKW7sb(Gz>uYCQ`m6z}Qc7$R|b?s;#)0}4E@7quBV z{h5R&1aMAlU{QI{VAaW@lCZR!Z_3L_Z;GDz+7(2dymar>>br9G5`qEz+V>ewR5smw z=df!1bk2p`9M0JpCEnA^bF0^;p8UGo@9CS>>^r{(ObO zuC&>Hhr;mj_%uSFOINDu>-Bnrz!@l#`1$-+VXu{(&En+o&(rCUn9Fs3zOuL5k+xTV zzA3I10001oNkl_c;I~^U z4R|E0^+u2aOih=;AB5RX_Y2AbR3ego?E~UW669#gqhPj(WF-rF=$jn~Fti|6BRymJ zabm#~Gd**N7E2AH71ks-3bO5v+#hfxBu{7j>~g)`5k8(T53cnF9^?oJgzk$q00000 LNkvXXu0mjfA_IYq delta 618 zcmeyubd7a_N&sf30dhz#-Z7hp(Dg6<+_YR=fdd17ni6y9+}HtE>l*!&%@FSq!80v&Sx?!D=Xvk({tOy)zDBKaxfw6z~R%! z`vVjj6!kS&MAC%Xv;+iQr;9gdh@?+oxI9Usp3TEi#KZVRLuMooYmoq}(wa{SUW}J} zy98LhcupyJv0mn1m8HO`Sjxt$u(VV6YF6gv&97e=z1X<Skaj{w3 zK{f`L+uRm{>9!R>r>T~>MwFx^mZVxG7o`Fz1|tI_BV9uyT_fWVLvt%5V=DtgZ36?V zdIJON-4QELbmZozWTsW((qUlW_3VKNP=h4Mis1aTlJdl&REF~Ma=pyF?Be9af>gcy zqV(DCY@~pS!mC0eN`ey06$*;-(=u~X6-p`#QWY`_N|G5ED&{=?J7^b0d%K!8k z&!<5Q%*xz)$=t%q!rqfbn1vNw8cYtSFe`5kQ8<0$%84Uqj>sHgKi%N5z)O$emAGKZ UCnwXXKsy*bUHx3vIVCg!0NIG%pa1{> diff --git a/recipes/icons/the_manila_bulletin.png b/recipes/icons/the_manila_bulletin.png index 733643dc2bcfd7bd8e1004581e12e286a280b702..9a8ed156a1c84fe6ed687d0ef62c13d3f14f3671 100644 GIT binary patch delta 589 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 aE&Df&Ibq@bZ{m^w0000|x3P delta 2329 zcmV+!3Fh{T5~C83BYyx1a7bBm000XT000XT0n*)m`~Uz5)k#D_R7l5tR%ve($r-Nd zuI`@ObJ^o-e86&6l7<+6FzUH3kyDCq2BO*mgN~2NrTkn0--Q8W;KdP!S#(xaMsMqV9^H?kv4u^5E zEKApQLP#_c6-4NkAxkn|Ri?Ot8~!m(6PTK&#p7`y1ULXc5{Y;`6-B`ZppN6MeYDFqYZqk#!%K?ptpISqv4AK0)1T7*I&+yQB| zS^*FcnaZYZ!haf031tvMg)3~^rV@pcppqzceJyR^2m$K>gh*=pr`c>m8gvIQ@;?ql zLRespZnPS@K{yYEL-9x~K!C!vdfjqtg)v!@pf`-8)oK+2pjtE~6( z;OOY+$jC@%XD71dk$>{}{N>A+ySlnS0?a!TcPiBiaL%1O7XYi}vSr)dxo%Zc89enL z1Dj1)(|^;`)7{;@xw#2z@Ch0O2Eq`b<>lq;*RSIf_e+;9!4B9uH$OKuH8nRkx3jZz z@!~~@mn9|apUHV zjY8qg_bZNJlPE!s!XKvIk~IH7AS5A%&FSw67j@|<0nQ(&&0yf zu78ePJREILWr22h=~VnI1x)oLG=cS1Urxw?+bW)dm9!kE-nrZ z4kCsgNFu3JI^UP?9~j7H!JP(}1A~J--97PWtTWdYjzj?Awkr#v$g*UjKuxdK0E7{+ z=6wnxZnr0Hzk2lw5e~`%?wX&UhwHq6xqt5J%Ig5LP$-ltC1@6jM&9L1tJ!F^4BJKw zkVS}r4Pi)Dgu$z|O6}pphhJR#qE@ZJ_3iB)0Dk+;w_kqs<;=4gluxZ*TUc1&g71|| z$OW(yjzHT8I-A#mh=fLEnI?-yHK=1)hN6VqVlgJm#p1@Tn}7c8?>~Pw{VbVCet&ZI zED`)~6MwyS?b@A*3D@LIV&&cPlgCfIJf<=<$aUn}Qpr}MF6?(n4>vfL<(f7cONdHb zA+oAO5vQhUtQ88gbF zMoktP@fx~pHBDkTnMCT7v!f@EAD{gDgIhQMq*tqYwG!xcc=)jMJ8m@1-hU7CCx=f3 z+;P@dHxN@qIGfCP`ArlPStPrXpv-djxFOq|DRtF#Sd>Nky9d&#VM&z+k{_76#@pSE zt-|+Kqo&3Z&APR`w7RjjVSXr7H4$*%+}eHlY86gtPxW+kxA63KWudS%T^h)x`v$tB%D!|o zOoUb5+w zso}vcByth0;?ab)v+Uu;+CDq?gTXY}sy83p`f6?ZE)m{LCJG}7&VM$ESH$>fK4+w{ zZi#YfueiN2H8b_}*$Xuu6XEnGO?Sf^N_+dr`PA%EVP$QySoyeYj{B;_j{QU%I>o>F z?)K{2150nlk9^b-L1D@jC&d0(AWTL^iDZN-#IBUKo)R>x;x=wq@6(H!))8&$O8- zR@`W8tgknlMt@s6Inddk>q-+rceg96%~Hc{MEo&8H^06n2!)qqiiSaD?y7<-u_(=C zh^&xiom8te+tvlFrHbG&!-#}5tOx{~QMF-IYYum{NGOy_VagEOa%&BA1I&9Z997UH zgjn~}2T6oMq560FzR?rAPOKWCj`uDjg&k6)z0 zqQ^=$`kwbDd;i{I>DpL$1-Cf3s+z1t6qR|pI<~L~XN|7Ob-9K z!WV!|9;M;w!#!6)gzUJss~ZYaV)2Nks{aP{#1h#;A0jtp00000NkvXXu0mjf`{s1< diff --git a/recipes/icons/the_marker.png b/recipes/icons/the_marker.png index f6476bd3cc5f7bb1a85a3fe449463511a899d6ce..f3a43bd57f56e033c4e3de7bda138df73c979fb8 100644 GIT binary patch delta 824 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 tEIvCq(|c delta 1241 zcmV;~1Sb2#3E~Nm8Gi-<0047(dh`GQ1gS|xK~z|Uy_aolQ)Lv#f6wg;D}%0Vgu!5= zxYfQ$`-1SiG#@Y$_Up2(F377(W;_g%Cd&!v_T$-MG8EpULez@nk_x%6Qxqs(8_Z%f6iVWcJ__BZv zz(!yd-~{XdA_SG9185d#QgA}|y8AQnt8OR2;qes!+kuyWd6_r`t^;2Idv&k7b5a5v z9-kT5A@Hsm+3B4_LLz*ul-i?v-O-E%I6S`jz+sG#-yH-5Ijxl1GEp?r1UNjt`#^pK zRdUzl6B<@Cqm^vB{H ztXht5dpS$yWR3H%tY9|BUMnLSH@v&@Y#iTOMnla?Hk9P@aIu3!Ri&JMa}DK-W~awM zQKy}&2OuW)~UHhVU^P)T+XWB0or zbz(7Vym7RZ@K8MQYTrPN+Qa9unl$P*I#U9GQ-34F?ElMulefNY<+<_#YAOoS)*oM* z$M2W=2qlq^qB|7jr5_+aIETa5%AMw^1xie|K78+L# z3V(D>iCn%D0fWOrb^SR^CdJ-omnQ6Aoa&?~&qmei^v|=V&`!k?2m4N5OygD2&>O&J3!{3#hMN z!Jk)jdc#9JR+5W(%=H;F&@_%*Q{0RW(|`JJm^_;qlTus@MJY@vp_A=8TkFrWZG9nN zl<&@CZTxbfm+G|(sadxO5y9U#fK^kt=4bO|RVg3-beW$oj=Z2~Quew=i-HzlQVodB zq!AfT&-~GNSxt(#7<>Z6#uBv?DA595cMSM^GUUL7 zJE~zQ+Xr+_R>UOGt5Bc9NTdQ@cMy1I8U?(s?{;5L2mlb_5W;uUKz>|=&qi-jn$ku) zs1v08pza}BfJ)tK>>iSPLO}O!3JN?0oS6dhb7HjR*OQG}>Qe!)y9;C^aP%IM{{U15 zCKyc81i(lnge@5J;N4V553m(@T2lAAdnbAzli}1ZLakD~0%SBZh;UIU_KV2A!0t`| z`?I^PfKlXle3~+nvyznY90($GplAk~1e!+u6Hq-j008R%0PQC$?JF+rE;9fB|Lq(i?II`t{{HM0 z740}X?K?g3dwlVPh4rnj_Oi3~xVZY++5GD2{qXSr{QUp@{p}ST?II%m^78hwvh4sB z?Jh3zhKKTxko2LV^rWTqrKa_*t?d;R^{%h*aB}T3HSIMx_J6v%_rSpU)z$0(75dxT z`{CjI=H~4j9PT|p{qgbcKtlfW^ZxYo{`vX;`ugrwSMO(O@NaPKD=Y3sM(rmj@r8!% zLPGM3i}H?-?LtKIkdg9{lJk_6^OctKmY4IHne&;N^q`^bL`3d8J?s@6^rolur>FF% zsP(9+^{T7&tADKZtgP>9YWKmx?n+Gcudwy7vGuXC?oLqdP*V1^wDz^O_O`a}QdI0B zBKN+(?pa#yT3q19W%}IQ z`rO_6-QMqJXZzve`{LsJFhv2{Ojxe@9*$wYVc}n z@NI7W^Yid-Z~pW2?Hnim_V)hw_w4`x@N#qi`uqR;`|*2w>lGFM{{8WNe({2XoC4W@ z00062NklGBgJmvz5_vunlKfHAS<(amsPk$P|Kz|*E!UhvBxan4g@B1(|xPSAH1AynD zoR3bp*^~f>FlR>L`LZ(X5T@GSnd||FN+?e_&2U1KglPV;D|_Y!AQny9FI%1m5`0%g z-d~SXf&e3MuF>R-VTf538t(~mvrY?{0EI@R|r9O0QAVJjZ#?}YeZk!%D~*P@Z( zY@m@e1j6XC7qY?xR$uhyad}FFR@ZQ8Dq_qCi_~DS%ZpDIjxRtO4={EfcylmUh)$mx zKHnVYMj8U#NCA%Sd#O!r=|kz(b;*-4juB;z4^V`Xq|*xkt0a|uAiB9Sytz?WWr_R+ Xm*7ci+LSAL00000NkvXXu0mjfyU04r delta 1025 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 YzujA~PR%z&5|qC@UHx3vIVCg!07T%gh5!Hn delta 1047 zcmV+y1nB#p2%!j&8Gi-<0047(dh`GQ010qNS#tmY3labT3lag+-G2N400YxWL_t(o zg~gY_ZyQAv#(z6IUYAHtAj(Kl)hfcONJ143m5?f+kV;WbJw#jr@~M^bFK~=<4gCkg zA(c3laO0E%2g0EOiJTfJhg7Cb43${P*m816?49ATGxm6~<9`@J`$oIZ z%eJquzJ?|T7zNfnQtkb=GNv=%908vLV;P?VA?@!iI&vmJGegwNTwqN)(mw+}%=jKi ze%Eu*96)+jFn>*N0Cxuk;r_U1J2`2GCC-}f=U%@c-2B|O(K`FDCQaJ(6oQi;SL5=~R0Qa2&kAU~+ z%##NN#HbtZ^VY{J)XKl|%Y$|9EG`fU&}fjC5YeT+tXgY!cXz2&DtH`^fDa^TPM`B4 zw7kkT@_*@|LIFZh*M^%nuJh%?uNfU1B}tNYC#`iCu`vcA1gEE`tgNiCwY7y(DzMY> z4p5Z9b)Z0Jep0kowGAY3(_si(Euttwh9NQ>oMD9!_DU}TX2~;mCfSH+@u4gwr!bwm7Sd(3Wb7><5*bEHrYec147bQ1%b##L{cIoh%oIcglOAYu(`rm zjymua_8|lgfiFm_*@Ui&#crG9I`yYN(|`E=In~-R-|y@&GBWay^xHFZAzWTa8jXgu zT!(l?38V$dKAj5YqH6)?bKi2M!q`Sgr(F8h3#waJrK}(^)6>%fNA5vat5uhT7J%5axrnZsH@q!_2K(GH(tu+bWq;^->>I?1H1GbGZ4)* Rlk@-p002ovPDHLkV1f}A;_Uzc diff --git a/recipes/icons/theeconomictimes_india_print_edition.png b/recipes/icons/theeconomictimes_india_print_edition.png index 65f0be8ddf4b6eac4b76db54de323ddc767497cf..f6ab2110fea44f68d002b1de01d08670a49a3e2f 100644 GIT binary patch delta 878 zcmV-!1CjiM3E2pcBYy%zP)t-s(f|O}007ni0M-B;*8ncqB0Jb8J=rTj|NsBj6*bo! zHP{?D*8nHi04vr271=IA*)l}e05aGoJJ%dC*egKUCq35yBGdo?)&M8kJxtm{P~1>l z=8cm4@$vlf^8Wbv{`vXXB0bzpS=&Zb)&L^hQeE9#X5C$9-+yCm-)C>%X>jI>kkbGF z=8u%=prqFoINL%}{rC3$_xJwz_t+vh+elUa`}+U;`~Uy`+e%f~GC|)fVBA?+-Be%QRbbs!V%=9_-B)AXS!3N=W!Duh*)BxgU1{E5 zY2IRM-fC;#U>R%KGD6>FZ{KEa*8l+DXK>jyM%gt;**Hj(PXSR6+BizuJ4)t`lhqs? z+CWT`uK_9p*BmaB+yQBS+dE9#Jxu)a^ZoSn{q^oBL4UH+eA*=L{!%RHU9eg z|M~gYD?8XLJ^%as|NQ&^{{7b?GTT>KXy}=6%APVsw4o3*MwsSC)2=NcKqUny^%;{=gB))Z{9dkw)xiW9S1Kzc(e`b zU4ZF}oA@$06wE6+!D}})kl~*b{YcN%@6Q9H3(&xuSo3s9#CP9>yT^o&`GXQQVZ|k^ zxdQYwFsq;O85$#h(kEEC5a~)`V?2ymRp(*E(m;F~p-2t^J8)Qe81}1=2`y5cy@Eb5+#&}6S!@b6-RQlV03+CJB&83JX-3R226(&=P z{@76P;%kX&Fk>R>ej1L)qrcSk6~L;wH)07*qoM6N<$ Ef*Vb~e*gdg delta 921 zcmV;K17`f$2!sicBYyx1a7bBm000XT000XT0n*)m`~Uy~Lr_dqMbZEO(*OX}007kh z0M#5E)&Kz302S5%9M%9L)&M8gA|lrS0M`H_*8nHi04vu3F4q7u*8nxw6)x8mHP;n5 z*Bma_95UA&HP<3C*CIIAD>&F3IM^aM*djaFB0bnAJJ=^Z*ncZK*egBQD?r#XK-nif z*(*TVE<)KZMAOxr+C+d@*? zL{8g8RNF>W+elU0N>$rRSKC)v+)P>APFmbhT-;J!+*DfJRbbp%THRD%-Bn=SRbt&& zV%=9`-C1MZS}tYXTxQ)}XWdi|Z za^G!mlR5!W59W%H=8cl(j+5q(l;)I}lbQi41L>folg9yRfBf|Ni~||Nj5~|9|f?&Hw-b z=Sf6CR5;7klh11tK@`WoZ+3QOH%-i@rfq5r+7{8^S^RNSym=A4^eXtL=s)3I1Wzg{ z;=xmembOq)e-HkGhzjP1tZB(+caxdtAz4i>_9i&Dnfc6{_daiiYWVvQ_zwUH0FIJK z3;-nolNjV}1R-I}1PBKPN&z-80DRARFDhQAuXLUv0eI^ta{QwQ%mv32$=#MZOP~Dt zf_QUpHFAo8`>u3para^CWAl5ZE?#ZCl-Km5MpM*He`6t)0MgdZ)l#o5BCDT1Xx&}u z42Q$yr`V)(FykCF6A}4HbMBYVTDjyfgf8zbNgkK!ScWC)UA$qpxAF`STcrNiMtLxf z0A{3i)UPgJi*uYQ7`)Y)s3A~TsrY9E0y!lqmx%%~F09g>)6ZchdjtUHHvq!3CriEi zDm!a=f3_Dc!IeR@qqAim&Gf<$U^&h2m*F!#@oi&=*ZdU09lgB+Ucsp*~ZxB#@Xk`+UKpu+W=9G$3EESqP^9u#M;Q&>7%~Z zrN7p$#oGZ@kN{MVvdP`6#@ff%=KxcVuE*TP*ygFj*j|pd085Fg#M!XO+_lT!k?{|$=t-%=60XKroh;BqQqU2xj}!d%G>HMZJ@>2=Kx2CslwRD*yg0b z*2>)Jw9DVd*XBumtFy}9vB}-A$=$5R+p)>qv&!9~zSNtz(?Wl&4_%d{zSXeC+hdZs zwankf*XJH$m;hCeu*Tdxd#M0Vi*lX7w9DSf+v%#s*{n^*+ONmkvC7@D%H6ij;KSGD z%G&A0*XG67<;vUWZkoT5vCV?0$9$v2s>a%_#@Yv2k^oYT07-`nU6pBwE1>7f2~bRft;<^Sy-_qJ>Baq#Fvs5P78-qASG-~=~;dp3?Bi^m;-n; zRaHGA37?XxT6EB@M}MiRn1YmzG;x?-oC{N@O^56})66TF4SxhosT}4O6)!ML7STr* zmnK_+-4I;9BAA@K5>^HMet&Q^?zE6$2~|>6Erc+P4WJ9DrV3XcdrOs=_F8K|j~{+0 z7gx?&opZ1QAFfjEpq8RU5VeCXy#_h*lboN=qC{|I2+b0zC7^3kHF$+#2%!XU)zFxc znR%vkH+$5Ky?>b&NZQxfa^T=0`br>jI8uBBXbl`|HID9OjjJ|Yu|&_}%aVg0 zg>&aGv~%h5l{(xM{=RWn?F*#CwT>Gd+`N@vcDtA04h4y|a`)bSp;_K^@yJZWZ0000DE!08xzrRgjYb0U>|J*yzO7=60XKroh<9+2_aE=gHdX%G>J3 z*yqLA=f>FP#@Xk`*yhLD=gQpaw9DVE$J?&Q+poynvB}-A$=$5R+p)>qv&!A3!Pl(B z+Q`}IqrTRpzSX6_)~?0dqrcY1*XP97=CaA%tH#=^#oDUH+N#Fdw9DSf+v%#s*{n^* z+OEgkvC7@D%H6ij;KSGD%G&A0*yhF8<;vUWZkoT5vCV?0$9$v2sl(W<#@Yv2k^oYT z07-{lj<#u+y8uau08os{+2{aNjsQ!ElkNd37^=kCug2R-g|JtPw8+@#08NUh!`QIM z+>o%TRsH&tIR*^Gpsc}8r^}TMx5w-A`g|V1Lw{edGskaTKV&G`u;FBq%ovk+0B$8n z5mNz`QbEQ?7%OuPA32IFZ?p)<2!Lq;j8#MsBEThyh>x>ukUySm!bB0uCNK#F1|TaL zV6p`$oH7-dHhso#U?yhu++nr_m{T-&9_BB|U$_X1O<+kEz%sy6E3j;NaR~}m6s}wa zMSoQkWwp8nYjYH#b-;Qf$l747(#B1tz~(JmsS0JfURJ)1a$#L457=H&xkIno*;gIh zRUIKk>A=z5d!muZUSMC;@ApUd{{fI=8YxzT+B(j;rX8ThaiRKJdq6U)R0G{CYrWZN zCdEqafgWWw`d|WXdmv~64XDTfaT7S$D}UhVI#T!JV@yfd0~(h}u?B=y5y*9PB)U82 zO4uG~Y)Yn5hpG?b2umJKk$}LlrskI8C;Dvm!ikfK%2O1yhEKQZXGH4kIkNK??AN!6 z$EUoh9PEqmQUVwWj&y*GSA80&; z7_dinK+*vCV=I6sPlZ2wZU>|V5aKUTWdM=^ynIFW`i<=!(njOncH*7cxc8L%@KOBv zWM8FbP5RvS5cyBk@f%n{rvm<{QUj<{{8;``N{^7ZXS~~0ZxDZ`{4fkT|M~0w`}6+% z^#1(y|NQp;{r>&^{r>*{{r~;{{{H>_`TqU-{Qmp>{{R2||Ns8{216a;29QRN4T*X!wt$?-8`f5$l}3;y~A X+F&DOi{*8-00000NkvXXu0mjfx)S4| delta 380 zcmV-?0fYXj1*-*+EDX7_3;D_h`OgXd&;^k=Fb4kVjsEG8lNJFUk>DF7{`=tm{Nw%m z>Hqob{`>R({Ph0(_5b|#{r&m={rdd=`~Uv@k#}j6GyzV3{r~^_|Nr*?|Mma={rvm< z{QUj<{{8;`{{8;_{r&#_|NZ~{|Nj2}{{Q~|{{H{}{r>;|{r~^}{}3O-=l}o!0d!JM zQvg8b*k%9#0LMv0K~#9!Wy`~|15p4)(SEjV+qN0ow%M`M$@%Z#_9T@(TveND%MT=U zdW{oo6os;XgZJ~VVrZIX2&AC?G+=jcfBTF;7Qz>AV5+x&?14au`QsyU+Bb4hA^_Ki z9I=C*8M?@V{aXucbZ^CPSP;J(fv4wtU+(U<30w|fvRaV5tT1)(NWNZ0g16tt{PlVI zunumiQX#XMJ2peX*xF=cX|^(S60NQet`f)`FAa1nO^lCr4qp){94#*_&d*KH?u0>= a(OM1kqEk)oCkXxk0000km&bt|&H|6fVg?4jBOuH;Rhv&5D9B#o>Fdh= zj9rXf$|CIdr8Nu;%onOcB1(c1%M}WW^3yVNQWZ)n3sMy-atjz3D(1YMd$#D60Z;4q zx-Pq>i8+>)a~vkcroa1S_s_N{gIRHlL)G$AJ+mYi^y$95>@PNl_2K*9^FMt1R<=NG z*#UJq@8Cr;El)Les&qL`TI!+A{N3eng^irM(wgm)N>s(qt$aS?mymFBzH+XI?@KPe zG`(1tRW-;_OKUuJ0I3r~PtwKBy*@b%*sI z|D9J~CHWt2<)1A0id#Iy^K+2Y0ma~|`o2_2!9$K81EeN}6^jR43S?`kxYuw*xJLP^ zl%mnyy9?&6cfGnrYiM~6UZ3YvLbZuB0g{XTgF!_rklL!N%q`4 z`RWr5bvZq6{*JKnsX|#?{U7AF9?~dN`QrNavu3)o(Zzt%%cai7iaXY>d75atY0jlD z=ML|jZgfk1QB&;!dtbkZXSZB$c~4A!_s*pt@zt%`9Ut#2UtrPEd|UfJ{9P&Et__ds zuU^?SKV#3k6$aa{TnX&o{-Bh-Ej(JF{pY6thbmVo@l2fatn*TD#aW9PUl%S~+n_d6 z>Z5`ABc+>N0ktJcS3}nCe8~7P+h5c~e$Gh+*Ug)^8P`ld^4oIHo!Oc%f>W#JWZj*{ zf8}dh?C!j@w^v`+OI3-aiD#@oc*A9O)^==E^+N_)`m3#Zo zZKvqJ+tSu&Y;XPfw!=)~*SgbXt>5mPkZ`|k^#9t0q;%ayylvB3fBpr@?K)l7_U+7x z8Sd4!|9BVuJu-b}*o83GX?qvdhR(RV(Dq)cwL^7jiqE3IXa3xFhz|Ypt*dOmfwfDq z;bpB^-80=T#+=#G^(<*wpozHg+C@8OWQv|W>a#dQFD=Rys6+#(B#ay-B0y7Cqnly^ zvM3a05eZ79KyCp!SKd9m-bMAa!QU7!&CBoSx75F2?vDKY<8}iuyNi0dIEHXUCnq*A ziP`WtDoF&)3Swi>X=7;R;xzXFDo`zPjVMV;EJ?LWE=mPb3`PblOH((;RP6H6E}a}(23^$YUS z|L>Hx14_$)q=WO*O3D+9QW+dm@{>{(+%k(&%kzt}ixr%M&0g%;)B{wf3{nRyl&qY= z?sS2;)zDPez*5)HBE-np%E%1p%BY>Q`!@oW>!T>oFAB-e&%tK8f`)Hma%NshesPAT kSnR?_$TshkdXIf0TB5CD{K;%l)mTOZ>_u4{X z&mz0;?>n2+1c?WI$1ju2n+#!=zkuq|%PD+V*-Wh47IlK z$`FDu77UW%^9DjN;07M(Eyg!kQAD6Lz|RR(bC7xl=nPD@=zo=gmUfLT71fj{fq||* zb+uHrN%hOMtq(NoG>dkTI2^MbDH0gtQtYcl9$xf@G`UbkdAbDW=llsB>3d5uJRIH9 z5B3g72W8CA3w!OjDqJxPHiJZ1SPs;Z0p6BDB_aud$iUeyl00efK5VX!1{4)sQbXgo rg#nbsq^WX&VIB(CTms^Mzn$d^l)SR%?$;bR00000NkvXXu0mjfL=lCn delta 298 zcmV+_0oDHG0_g&fNPi4TL_t(2Q;p6+Z-PJ&1>hl2&LE9kDbaGJK+2W0Rj$U0dsPtj zTC7hhYX7XjS?-3?LcYqo&$T56b!N`556_YH(FDL3#yAIUY=fEC9C%ECYRy+Az$sV9Jb1b-$)&kc06Zf&Wkp&|{h zXU)EEq-xBVrjwyA0NO>C$Lm;ZclnwW30#s&Z0b~Q-}I6+xlm=bKZb|b;srhFM@uq1 z?c6E|Hx5XLRl+a`d*Qe$nlqej28pP&9H)x(i?vJgS4&GVl+SZ@_%5E%t-?#5*0GDP9i7czrm92PA@*I_Lm!UH%QJ-_K4X Z@&PexnWi7R`+5KX002ovPDHLkV1nsLN?iZ| delta 172 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 U5Fpu!y8r+H07*qoM6N<$g4d*{sQ>@~ diff --git a/recipes/icons/thewest_au.png b/recipes/icons/thewest_au.png index 39448f0a59defcc08528e906014a2fe6e37cd6d8..e66164eb97ee6911048c0af82065df3708865d5d 100644 GIT binary patch delta 491 zcmVF_m# ztP5Fy3}1;9VTc@Li2!7lk$@q8xYFOY(ciSu-?Gr&y42yl)#1R@-oe%108Vw>>F?$5 z_3iZd?)CTpOL6%6`~XR9`~3a;{r&y^{s2a4!P(_^q`G*cxp$(ta-X$qo3bWznJjde zF?X9Uc%8=C;>p|O$K2-0-spd_%!szl9cqyqZkKAHyJ?}lWT3rZpucL7t_vs7-{;ih z>eS`#*XHfl=kM9*@Rh&QpTEwbzs{h*(WA%N@Adic_4tv_8-Fi*pfG-<8giZ_bel8x25P@e;oIVf_F;XwVpOSd|K)MK~(eP*|)P@1<7 zM0=+rs*UO1rhGtLvG&i&FMjM_4JuEf2toMr1!)-iyeakcXF&j4&Ax|eEy*JKiymPN z(hmKt)8Rx)(5oJmB*@F;*_d8yZ?X7)wIR!Jn6>V2=P@ivU`O09S(mRDJ+adH_y!084QINo@c| zX$x6^3}1;9VTc@Li5+T@8*Y~xa-Jk~ngZ(BJ3O{{H{}|H0Yicci*_qq%pY zxN@JhY@4!4ho(b?r#^zDNP?gvd88<8j}2&*4QiQ@To(lZ0004WQchC)xYS)Z*X%55a`*u4q6qPXD3rnHC$B!-~?!YZ-A0=q_dDP!P1QrRT(GxL7SRKoPKr3k4`|@x>6pu_U5D8(@I}j*-u3py yaUWDCL@6jX^=KojN~~{<$u@>$41Pku#M2Xswo{+^-*RjK0000M1=t0U8Gi!+002a!ipBr{0Y^|wR7C&)00FIR|Ns9$|9}9jZ5RJ^2E20y zwQLK>Zvd@p0j_KYwQLXVbrJt}4d!wT=5Y@1b`R-s3*&JM=5Y?}bqw=)2e)euzGe*M zaSY~h5AAjm_In4mY7XRb4eE0h$7mYNYz^jf4CHYQ<8l?qXnzdnatq{f7RhK7$Y~14 za1_XA5Z7o7<8c$sY7@|E6Uk>1+;S4pYZJ<73*K)M(rgpTXb2nR;ZW8!=4Bu}O_IeV`X%O&s4CZna`+XAkdJooY4d-$W>~s(AbrJM=6v$`DrPpr& z002*PQchC<{s9EL3o6Vm{>I0u56t+WBKG{U71B4m{eQY0&tCfYC=}~=!uz6jW-{*} z-Pfc39lZS)wEPjP``!zq;3>FX*g3fZSJFB~Ps=>XN4>;s$)F+t008kxL_t(|Ue(IQ zb^<{V1<+#90C9JBcX!w72IB7e|LaVh&YZ_Ue*n4bONAkxi1RM6)Y;Nsu85r)RIxhf9e|=5v25zXl7_Z5Up))XzyTn^9R-C5cM#8 z>71bYXqVyTH7^Vd4l#V|yr6~|KD8yNG<(j@t>NO*3)k8j)D0WA+7`r}3HO#TGAiMT z#j~~q8XKQ*$GaOov^5Y-PED(y2j)k65HquL8ALwS*AQ-{bH3YG~ xVFGM!Z6k=CkO|=I?Mr}z&>`hGIzFNQ;TKEsk*MsU!$<%C002ovPDHLkV1h4=PI>?U delta 724 zcmV;_0xSL41>psd8Gi-<0047(dh`GQ0;owuK~z{r?Uu`L5>XgO!I+vjGYkv^BqmLa zD|O+*jnTMpV_dp)*JXJPh2d4(3gJ&Nb|)!5Y67xo$z@Jies^WB}GUD>VfBTlgpi5Y=l-yQ+tB(ouXu zgDZzb<=uaWLhTfOPa=Gr0to6A(@tc|xE>*djQEs+DaqHMNIUr-5hT`Qy${gLxJT6^ z%$Lx4DAG>097JL*){gM2-z6?<1 z-uu9qUbYlK;?|Y^53oLUA?C9mjM7?uUT%r^ZGfuzwHJ&Mxs}kacwYyoa!X@iTvez} z^>2XnrDxijbvE?_gq$0Un>V6hToqR-fZ#3_gb%@C;gxq@dHxN01piiQiF3=!XC4R> zwPp)YP=7B4Q}8>4ymV6;QrAXgoQVDS_YYF2OoCw|{xK9#P%i+#;kKCHc$3xzSIzQ# z`%x(00l+9NK2DMJ6~HKM2OM9+j3qBkn+kzX)>`Xl9ugm;NCl1=W~94dn79YjuVwan zVnl4j-a%m^z8xSHIBl5m-RpG#A%PP6Z1wmhTTLUD?`{AgId(=FGS}+>g8Hx9f#Mg% zdVHa~0fdzQ?Dh~}7GLQZJBnXW;t!PH%wv3Mz<~da!SDxkWnLrNVWNEi0000X$90dSGq>9FoHg121NklU6JgSd zq5xd778uAs51NRySD8I>wy*9TFWX3lkY9 bhL7b2j>QuM4Qts=00000NkvXXu0mjf&`wR~ delta 253 zcmV!bwCyR5;7U zk--kaAP_`f0V!LhR-OO@+`&S{u^fz1F#CO~QqAP+5)0Vst!kyWUfcr*s? zwNJQ$L<(y*8!eJ*B*dBylF{o1n9Q(^;51%d6EY^Pzq+ZAQ%ZoJ6L*q8r8R&!q~``+}&AN(0@?}fa|u1s3HRh=W|@N8Xfr18~BSP`NbV3$%0k)WaVE0 zQ_gvM1*nOCExF&V197gpJ4n*-Fz-D;w+prSx4-!c#u5_*FU;8R00000NkvXXu0mjf D>{x5p diff --git a/recipes/icons/tijolaco.png b/recipes/icons/tijolaco.png index 556555c622f7f2eff9b343d676fcee74c848c5dc..9a72b0c1c6c4b3985630e982bd179fbf3bff39da 100644 GIT binary patch delta 10 RcmaFPafxGs@W@fk$L90|U1Z2s2)~TlZ(9q9F?afVT*U diff --git a/recipes/icons/tillsonburg.png b/recipes/icons/tillsonburg.png index c734bfe0ccd7754d853ce5fd05e553a735e81829..45265eea24182009efd50bbaa2fbea6744aa7e29 100644 GIT binary patch delta 407 zcmV;I0cifq2et-~90dSGq>9FoHkbvRo12@Ok<)@eEGQ`}C@3o@C@d!_D<>%|C@Cr? zCn+T+C?zE*B_=5)CMYB&CnF>#A|oauBPAgsBp@LrAtEFoAtN6kb5uL6la&E~eS)zfKPG>X=f3dt*O9FEz9`7DF&=K!@Bnt2ZFo5d)4}4n;cmc-L z*(WqNLf#;-pre@#dVud5jxE&ZR0@3V4)qVupK`>TcAx+n`TR3S#KKzG_oFh&8lV9$ z77LGehh2)tSDV|hSZ8Z}2pFRQpK+c%LE`VAaU#0vF^B-#jDKaR|{|gdf?N#RaJ{$l5002ovPDHLkV1j<$ Bs{;T4 delta 533 zcmV+w0_y#?2FnMK92x;LqqO0B>yt-SQmW`Gc8 zk{PUYav%ug9JfsXwlK_r)2U4bhdIC$CLB2S4!Qu$IqG$!MRAV-JAduo!+Rd{xG92gmgg@%rdjH#!nuC1*sDJp7cX-k1GUtC>fVq#}xWi>K0 zac^%2Vvu?qA0jL{8HA9PlarsGo}!?kDkdkiZWp$-wcOm?^ndj98Wk3om6k6(B`hw4 zt7#ZWdoFx=dhP7&fPQ`|DP&w(S{oM_j2$7}+}s$An<5+>{QUeWDPJ*5D9Fdh*w@!b zFdh;|Vr*(^5lUxuNg5e!c^P|!gn@z?aerfAVc4|$9{>OWQAtEWRCr$P%>|C@Fc5&@ zj@dzmGBYeQ^MAf~{{yX^s;$y()eGz|WipmNX-eWiD}arypa?KMmokbXDObM+6ad(q z^XcS_tIj4^*zt>edbZixBUjQy#_OxT{T^j1FPSRl72j>rEFeL`c^GT z+_Ze!7*CifLkeITU&^2-l3}%HgwjmS kOlTnRs(e-7CJhes1M{yqk}Jho?EnA(07*qoM6N<$f>N;pUH||9 delta 616 zcmV-u0+;>w1c?QZEPn@LkP%8}5=UYfjhh*4c^Pqk8GD5pgpe8)78@298y6TH7#SWK z8z3AVA{-nd9v>nsIU^q+CL$v#DPJlkCn_mqEGa51Eqp94gfBfMFf1)GODHumGdnjp zJ~})?K0ik=9!PsGNk&IYfiF)?O;S%#S5#G8Sz2FQU1MNjWq)E~XJln+XlZO}YH@FG zbx9g{c6NFkAAEUwfPQ|1fq{g9f`^5Mj2$74jEt3&lb4m2pPrtgprNU!sHVgc|vMEts1n?CyV$8{c-3JD80*Vm6p8_^0s<#P{ zMU&maT$%6)AX(WU^P7O5`T+P8W$q!)5u9GsKll;3lYczcZvcWwfH8n)0m6&*P5}0~ zs-UwgG&}7$oo9R=D2JrBC|%}qxpHqf>^;us%h!iSyIYjk04(LhAK4%hK3dfhF?ITh8GMBr3MTP zuM!v-tY$DUh!@P+6=(yLFbMDoah+%=Hqn87qMHEw|NsB>wulQ&jFM4jO!9Vj;g{IO za1*GEy~NYkmHj!Ju!x@e{%Gq3Kp|647sn8e>l15@i&h=!-3b&?Epd$~Nl7e8wMs5Z z1yT$~21drZ24=d3mLWz4RwgD^2IkrZ237_J`EokXQ8eV{r(~v8;?^MNJS7^afx*+& K&t;ucLK6VU6huJ) diff --git a/recipes/icons/today_online.png b/recipes/icons/today_online.png index efc7572d93eb77fa2b09787411bef52055f601f6..d843380c0ba9b5959e020dcdb4fd1f1f6fcc5786 100644 GIT binary patch delta 396 zcmV;70dxM+1j7W7Wg-9n|I9ox$u1+^eRlWZ-}vL=$uA_$J~YomInYQx_~qo0h8_dU zH!P8-Co9Z5G56x&$}%U-J2Cq0?ECTX{PXkt_VxYv`2G3${`~v?{{787GBT?RSCRQ5 ze-lYWK~#8Nh11!VgCGnB;Akx~0+uS+y|eH8zVGw??=+5)H( z$yo@?i9}yx`Xcf`5U1Kn8V7aIN%T^Z4uNjZ7ABWxD0*G|9_xb}bEiox&IsuID^mwL zsqV3k^$X(y9hd)y_dhj0NTUJ>%a4Bkf5G^=$gvdyAhi8C_%@g+b}R(8u=`)80cZxi z0^1*^5pa+dI5f>bf~vrCp6`}moEI4&P>>>mVk=uK9K@#{w`*?moD%vQr qMMFztPoc4IU!3;UYhT{h_5TCdP9?ECTX{PXkt_VxYv`2G3${`~v?{{8>||J_RP4UzdG ze{`>aWY+6|lI^wWZI(+&}Hl_(!7l&@mrK z8J>U320##?Dmea1QJF>H-vv(!g@p0f@0$>?;pmt|pTm%WBiC(FYoJVl~TZ_hStx+6@~TIQXmrIYDLQ z&fZ<1964)fx38b)Z@G?~p|!`}mKpo@#c5x?_T^o_{(s0LTS>rp5`zE$002ovPDHLk FV1fgC-2?yt diff --git a/recipes/icons/tomshardware_it.png b/recipes/icons/tomshardware_it.png index 320a95e93b90052d25c337b97656438fd99e958c..63559aafefb511c44bcb9a56fa650563330f83ba 100644 GIT binary patch delta 729 zcmdnOeUfv6ay^r0fKP}k0|UcNUA-Hs8aK7HZ))k>)YH2zqkL0C>!!BO4K>Xh>RLBc zHE(L`UYAz9WuSjuR{55((G3l)TZRU=&5dtcnBFomys4{u%gErWfXq!j{aXeGH#K!` zsA$~O(7vIpenUy^?YhY~6xD89n%xibeh}(+Lq+}dmgbw9+K;R=qp!=UJctguV{Lxh z()5`+ONU-0COOuCZ~FtJR&$27g3 zG1t?@F~p*G?WLnZ&4wbZ54Zv}-T^_}9*4sANAH5}+y#NBc9M-(`HjmD7nnuP$)9x6 zH)BhEPt(-3bN4ncp1j%c?$V>aZ_9O4@4kNc^6A^hr$2t?=iuSu6a076__NdDLl3R@ zbez!W=o9>P^ElfvM$?amLRaEERTU>rpHe?((yyz9my~`?n77dDwU0` zy5g;ySMNT)x3GsP%-&r=LPOlG@x}#%10KS%?8zw-Cm&DKNbvVz^6`Cay&?UCOxkmo zkIK))>lwtRw*)BcJHs9kd6Y3ZS2iIzSe8{P+NL45+*jt!1pSS>k2^J9v#WgD9A?I+ z#nEAPsPUU2qxy1<3m0vbAN*lrRa-WlB__5z@qo{#wual*g*-33?+G8MPe?d$;KRq0 t*MI+H-Ou{>>)X2z51(LTtv{u{JGn(;i9 delta 917 zcmX@fxrKXzay^4WvPY0F14ES>14Ba#1H&%{ApL@Yq11qZ;Z*_ygVhWM2JwP9y8>+( z7?^wld_r7-3T|7N-f^(Jsi}QaSMQdA!7T&*pZ~Ys)YH4ErF~ON=ccyqOb<4!?rnb&4BZC`inz!`z zZ>VeCP}RJtsdGa`N|NnoXNyn>!;c%!V$S-(u zBa`0bHB7VW85oni-CYiMW%M}#<=9I+eO=j~a`Es;NuA591qLyLe^p3CNl;?BLP1e} zT4qkFLP=#oszPQ#NiqXN#hk~dbTmAl1u-xybL%B@3o8qIPZnVoR&Z%BIh?|*yg5YS z^o=Vgj+{9nbAK#KYPOqJ?U;9X+%<=g5&p+QO zT=BR|Tdg~kYpI09%r%od&6o|lT6w0hF81;H*E(Coa`Go}kGiSFPbU=f_HO(U{WYQ+vOA^&u$5b*mM=;JZQGEMqU8R%$ z)=PecUbj~5T(@OK;NENV7X&Q-bzAJ`?5n3f$vx;wWsph^n!WGYf1l96u5`JgwDODA z@u4>g^wqW4cz+~Yl&*DbE@r;4{IopJ^Mq-9QeM|sW^? z!)}gxk$18&)rxzToo&{XGm~Nbt&|a9Zy?8JmD1447|!OkpHWQeQsRMqr(1c7-lZ#7iC#RRtqqN;)_>ps@wsPCfJt$WC^zV0lQzD%^mF;F}cD5T8(A z$wk<@)B#`|{+b|W^N&Bw0%nDqV8L3$KAK_)XUb3~ne1{4Z(`bOcd_-~HuDF~u?VM$ Swh{0E000096&4#8 z7#kZK92XcI7#STM9UdAR9vvMYA0Hqb9UveeAsrr(aTR|vGc!RwK3G#!W@cu0b98ug zb$oYtnVFfasHv=}s-FED@r zw0@XqXo7c4F@g!fD1SMC9#6#@6CG3*0O;cWzr;FwW}Jw$hO1D_6Yvj ZD1SU#D3-D1@Bsh-002ovPDHLkV1kqRiEjV^ diff --git a/recipes/icons/tri_city_herald.png b/recipes/icons/tri_city_herald.png index 8131fc14b5c02a4ca1d216a34600443be83bd76e..711911cc670d46b79ea24bcd9b2ac791f64b27e2 100644 GIT binary patch delta 236 zcmVZp1JMMNtmm`z7iB|7;u78QBwCY4>ATiVXDk zdST`yeFH+qE1ysPsmU~V_{`Cm-sfQi5z4H2_)Vp$o?cW$zVwi|T%#E&o;PdSAfCV) z1lO*)#|~5+m&-d40o#jrK_2__0@v8@~`%{5rLhzL7-$edtkxoVm~B5Nmn mqE-Eb1&y>R2=gI<{?#{X83fsHcG4370000g=qptgNc6Y-((5W@O~z`wPS< z2q{dCVKYM!@FpZAL74LPQ~^?U?@#MeiJyA`W48yM96oSp00000NkvXXu0mjfG@+Jg delta 307 zcmV-30nGl;0@ebMEPwp^{Pgnl?C$LB>g?p^);qRs43i@IN2SdNg zqeVU0#QSu>AL{-|YLMJ5L0)C>;bMH)4X$CM^MaDUr0da5HeiRX| zwS(IU`IHlq*(?<0WroaWO-%f_W!`ZBx{kl^#@h}*_X06p3DS=zY8C(h002ovPDHLk FV1nf&oQ41Z diff --git a/recipes/icons/tsn.png b/recipes/icons/tsn.png index bf7a3cbbebaa247e5e201f06960189c09656ac53..579936675f924f18f208c143b8fcf180410aaa67 100644 GIT binary patch delta 1798 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 JPDHLkV1gU+Rs#S4 diff --git a/recipes/icons/tst.png b/recipes/icons/tst.png index 42becd7397f48dfcb2cfbfaa1686809d9e3ea9aa..97b3d99503270fa1724c160e919250082f5edcfd 100644 GIT binary patch delta 1944 zcmV;J2WR-Y52X)~BYy{MNklz zs)Pubh!z2A1&+Z(MeZqBG>}MyQ6}LYkH1CWz-}6kvDv_g&RII57eao>vI$nhL>c=K z5)c3Yg5iblDSreBAY_k%oEXh~GIx7H@+QC1EF$3aXnp6^MJZc$EU8EO)Rx=d+Vmtq zHUP$e{ZIe}X2Q$?qI>`l8c#?^nl#pl~verjpKss`7&t;;`Ikz4(B zcN5SUkL(E0_Y4LPK;e67vY>IANrte&_{USLy8PGOeShsEjEg^7(@v%En$ITvu72HX zHLIT}e&H|k>YKZp`=tgRC+HAis+osi{1Hi9RK^?n&BmlLio9lCrZXH3qVuIRXOS9#d%LpOZ&}S<4!`zp`7CZU$?|Ssz<8e%Z9t)iX1G>9q4Nom_md;qB~^ zQ-3r~2M~uh8Ul=R{qpVIbrqs0PIb&)FkzL0AJsO{am9P6zU|Y6W1sBnb>#{(PIT-R zJ2EPZUW`VA?e?kr*>h*wcF43ZNyyY7Jgg5GPzF)a0+|VgRz5u_H%Sq7FeY>o>(MRGBBx&_JAog0%yIt;9zmy?30Zjon3cW3!4Ql z;!!#jKG3IljSLt@01t>Iav#skXha1tLQ;3&@|Vr~!&I#p|C#~Y#--)|q4rm;!SiL2 z$`a?CrA05lU$^a>_L|z}?dd5gvVSZYh($##PE-K)A}kQFL}J(SM$BE1zjAG1HBjMH z%Z^VPcmDeE>Z#a&Z#d9T@+(As@&va_g5`tFM>A12w%-9EPgfMN+SLVs}qh~6nG zYLwv-3b_A%e#1L;8>i)#PY{+@FWjp$IpCGcvMZ;j|8!09w$lTD-F5c$W4%@B)(Ns4 zMDRm8z<>ZS3F#0ONFL&KTcUol<-L|b%Ykc0o+^5wsGwXW3D>&Le|dcu1IEbB3?Wlz zvWAThng`r_>ECTS9ti{l0DmO}?%UB|>j2CjvEtd{>P7pe6lWkCW)5)@AS=^@h%pUE zDTFat7Xp>4gy)m8JYq;w;R2_x`^zeh*BCh zLR#Ec#gKRmDn&XM{7K2HR-@>T4N%I8C3m8+aEvpjd`f9S@=VM>#($$Ei-lagb=2d( z$ngp4Pzs@kb-=?NsYCxrL+jPri+gXh-17DNT085~4L>5L1U{1P?+-LJH=emvTX*^N z?NC3%76Jm%So}>^>Hp3W1{0Tf)qh}8X6cHOpC|KKnDDLYnTxLNQD{kZOj=g@i{gS= z&7sdC(8BRnjYa^(IDaETL&TvkMM@UQ1uT3wSl7|(s+jriKaRhlj~tVk{9|*J9=pCj z(J(4QEL8P?jwHaKK{Rd$cf$~4nrexixw);k&tq~LHMRZG33Ekfz7r>o8#}wH@8hrA zx5*F#5P%{cW55Uv6L!N=Fg2z+rd{aSGY}4b(Rk1&g`{xX6@TCU&Btd5#%zZrhcOc} zdKrL9Ll8GA?w3K=6ef$-ynuA+rvI>8x!@Uay9Tc&C8QB*Hej7XNYiaR6$w~@0*hm` z`wT%S17H|~0(KthF+HTvL6OTKdJKRm1b`yI3^0HO1S1Uq{U3n4S&V%@GXkIu(b1j4 eJ&W%H{0HDebrFosp09=g0000Ks%nU=DPmLP+inxk(IA7=@t~>{=@+ z@2?3-NN(=^-6SCI$V&+U5qXCJ@}jfD+Un_x*$s3(%8HS5et&-?IXTbgdHFssKnywe zKOsaEBv(Qs7==n8BB4AmhASgcoIx>)CxssIBtk$Wo{Y(46Zw6eqOTa&Awlha-}?MKikVf4fwYW+t53KGE~>%PZv z_*7U0;8y~n(SHXsvF5xarcH0_KyVQ_sb3|9&x=gI#vqDAbY%bmM8u4L4D}nihNrQf zJ6k7;)Ph8_pPXF#suC16y*EAI8LR!%fntzMb1}xB_2%J>2LK|7N=zQ<%(mSnpzUhj zmtT+D?cWF(^qo?0FxRDP=HjjSVQ9_1Yo9G`lfR+_7=P^}W&9UBLl^zSokf6348EzB z-|x!>H6x`d%(jV}){w%VBxqj$?%uV|QN!MXRe?$%QNQxVw=-AAI)jxUS_fpN%|f^a zH?R_LbqraSSTeey!RG$rp~?Ab+G7Br#jX->1u24P-_Sg=u@V_KB=Z`_c?Mn}*A0=C2$pL&Ma7FmS~Bjc04Z z`D|4z0k7qO!b>T3`>oRU^TSA$>zpJe2td@ey!0vo-xu5#> z-h8HM&#|h5B&v$iS|#lIfYonvtRu<7?M% z_kaCO^YrLs%NJkuXPDnkZ{SDjTweMJsH~~lD~VB+KnkB*9>7Kbxp7rqN6Nu5{m?ImZ`O?$=a-P(pKdMBG&CV$R<#Dp~u70=#j>%CT!Uj3@F;gHIfa(T{p zd8DzpBO|O5q*wy+Y93o&{NXZX2N^-KqXLncd4cDFq5Fe|{xW4Ne{i;XXtpt~(%Uvx z8*lU(AGdT)nd2)x?e?!;&IXu)E2Y;-3?9z=0Eb+R;$rM{BV8rbruIp4bd(lqH-F_{ zihlC~IA@8n-@D@~2UEA(&egnITz_UiyNwqEK>UDB_rsvbT!|9fw{+Ww;?UINJaLT+LGp)vH zCjbsk@gx+&Mj(R$e<=X}uePpFW(| z8neFuoS@yrWB|J@oTO+WORj7L66hz;xhO0+_z~1I0v*ylr)?bkr@U&5et$p^tMdM^ zE_!6nd{V`!>Jg-x-^i^EAHQiXZ%#X+21(}4Y}{g}0Ef;fj)>&2LB!~QNRNi*uQFUw z3{2MFoNv#n4Y&4?m7mF`Z!}N+yYC&%V^aN#5gL%dtS3k>{E+~Q4Cv;VOQbJGyGQGP z7N`3y*_)HokU0CFcDvnPW`E0Mx=!cS2@Y!jLAlAGyJNLcJlyHRc`#Rql;J8Eb}es= zXMTA^3$`nPg5EnmYxO+~!qktwt8t7r!n94o?q`Q}wn^RaM*%F9!BL5Ly%)d26I=jf#=wEGju$>;NB1AtA0tY@(PD5?w<>}56;WwlE4BPEIPt{6ya9nNNt4EkzT|GZY+Cq1eLar9G|iR%zI$#Vi& zGMB`Rx46=~#EOxF(kwEx?{f=L9$4BGk!uyzc7Bl6^w+|6>=f-US#aF3kSTNpfJH_; z^K4WJ$Y~1yq&>W6P6=(v`k_q9mU``$HChY!Y9OY4BqZxTM}KPKtW2hh857Z7GPs2` zly>p0^y=M(m$34#m=wz(d+w>gfJ+tI)a zTg1eEKnb{Vz$X75iqnYWvB)2OdC(lb%<1@F8rSQfWhITL00000NkvXXu0mjfXEMo> diff --git a/recipes/icons/tvsyd_dk.png b/recipes/icons/tvsyd_dk.png index e79b68bf554973d8a986da9b71f1706669e4464b..428789e5f985da656d4f5d752f6f2ae213a4b036 100644 GIT binary patch delta 612 zcmV-q0-OE439kvTB>@I`d3ktvc#|suFbQmIY`Z-@YinzhWC2SCvLho|Sy_{{0VGC8 zM@LIbORNO z0V|rQfF&A0gQh^RB|(b4B({60#qvKrxpRj#OfnC=xfa=Pd)DlSZ_bbjY0{i=IP6Ob z7-NC|FX&%tFy7+(B?2 zWtSi~NMjf5{mKCjj6qEB{rFZ$ERQ5E*o6Z;!SAn<4 zVSQ5S#P^FL*k-=Nx^bLRtV%C}BlHYGt9d}Tp)Eo-{bKCfTDr=CzdF|}*asaIat>(+ zQN;{TF!SJWVamYrMw?;?L4Os(y)T8>$UCO`$wIW8HlNe;^86xXz4 zK+$Qxw)W+ufP7*KC648-P^~%ka9R|NNx>m8MJ+NfEn?%vc?8etui^Jz6T=jx9s~_! zU=M<6`o!2rMPGE_T{lLEE5;yug7XP_lK3q7y1>R%+R4C*^SEJ*PHizT`zjKNypY+b z8f+JGH>xrGgM%xiU?Ir{60`^jtMZO7;^$_f;-`jS%O3`grV8HfBw(?-yn%2i;9W&3 yXL0h)3{|?Kf|tSIi~0000@I_czAhvd6O#vFbQjGYiw+6yFERVWC2SCSy@@KBO{Zw0VGC7 zMn*?RM@UFWOG`_v6%MQw53LpvKtMo2K|w-7LPSJFMn*8Z77eTx55&-q*^?gvrhnr}L_t(Y$75g^1)~5vaP#mmAW6gY>C=k_pN0}r zHOxs%2ZCg6>E!hEP$Ve>pr|zigRE0{QU(+xfHgot0c#(S)I?}#0&?|*Vi(Lq0>~O> z$Oiz)NVs|pAg_zPblyBn4HLm&lA|(+%>&aA0p$5vft4+q0tMY*4drk{%YT48ODLOb zGLWN`0aP9>f-_j1fxKKO+XNhb^*{|OxZ^=^4$vzy5OyX|#*U#9sKEzM#Ki---e8u* z%<0o72rvYLyijP!jZ1?bklO-gc>-B63=E1(!M M07*qoM6N<$f(cje)c^nh diff --git a/recipes/icons/tweakers.png b/recipes/icons/tweakers.png index 352f05364c4115d8e4bae4b6605fc3a6183c323e..7cd54eac9df4930ab804d40ab03da31afc1e3876 100644 GIT binary patch delta 1169 zcmV;C1aAAR37QFz8Gi!+002a!ipBr{0!C0wR7C&)0NUnC*W^jv?n|&LL8&4?n~L_N~9GzqZT>e?o6vBJ=*9>+v-Z+?n|vF zKiup~t|vdK8$8LOMXV)0svSM07CNsbK-Snvs~bP|`BtJ8Ie**dO2v*utR+3HCO)MZ zI@8HW*W*c~7dqSKO5E*ArW-n`9XzELIj9~xsT)13BR#4iJf;^rr5QQGh(f3xJI9PoINJ#rf=*5gQpjxE*VNY33x zzGy?Y!#=oOKv>KAV;3%(i%LX(skK7{Q4}kB0#tOeD@^Aq4S{PFB*AXC*WL7KzhDM#8hp3wNPofr zBKsak!kyIjk>C%qRYw8gedNUd;E%nUg8mxbr#{)NW#2Pz+`!N{o|m~inAAa@``VKb zU`bG2@cu)HyCM-64}JVpP@UYtqEp)SwY#OQt)=^CS1B1IFlcYC^>}KV+szx;04z3( j&8D7(PJ0uBD*geU(SuD(m!hWt015yANkvXXu0mjfpdcl? delta 1187 zcmV;U1YG-?39Jc_8Gi-<0047(dh`GQ1anD5K~z{rwU+&BTU8jxkF+2YCL&v>O}p5& zo3q^z8J#rmO`4`nIu$J{iVT&FDJZi4LC{U6f4~ISx3x{uc6MqzX$3=&PLQo*S=V%( z?QGp-ox()fPhX$sB*&hc-kY1&`M~$SopYYg_nxyPFlU%}3 zsTA!FV0dQ$$r?J7#`m;fY)=SZH-zw|f~-SiAV3%P8m=Q~RRCu_&G`1+5N1E`KqeW5 zdvgUkw>WXsQI9WnxiGOejISDkra8=HYk=Q2QRT(afcu2 z<6+D*_*N9zU!(ZxS}U&n(vELZzLMd3m~qxVq)Eo`^QT(nThA^>rKaa25`ObeC(6qo z*pLB^LY^4f?T4-eb$j|)*Z{wBqg}#*w@~GLXv1(7I)Be_ldSXOY_J*EHTdjO6qlFU z2!0qZzV7(Xa22Zto}`Ps1}c4+T_EVq99)k3d~V#klKbx)oI^21k`AoI4j;}P3>v!r zVt(9zUVi?*!c{O=BzO718m#o<(iE+8n(++3N?pJB#{t~^`!#IY`r!X#fVlFIsPkcD zpIVg`$$xASuOkDM9x@O%7+_sqY?B6BV~^>{ ztpIlAV)}XnbGKu810FCtXMk?fpBjgNmC5gbCwm+9N?a*?H+b1O0M5Ud)gB?HwS zOuT2Dge+c)Y~aSqb0{mb-T#Y~36+x>6CVU6li6ah)`P(s4~Cxc8kV6F_X01+#->Mg zfPX2(D)?KX@-bs*pI0)Ooijk{+vYZGM5_jJ^Wei1dpB%A<>K6zer%I0#T5fP=z`Q= z?Z(&}0mD|zVmRy8?=NBV!{zHX!Q-xSaqjVh0m)(}v!wy0o^39}$4H51FPoTOj{R%M zPSXn!Lk`ZFU5rYG&J+VRZYcF{cVooorGL+Hhp{n9t%y>^6IgH0InM-qlA$w2KZ7;( zR=AM*IJ9OcSZY-i(+sJw9p_4Y5|X(Kd20-?x+|J6)s_1nEKIguF?^~gEOVN%ME?_X zVIppjIu?>$SGY1t5x3#CspFv>+=Rg*1Jo@R>es4XtLs0UOneTRg<|jArd4v1b+3|;v z^OT$MiIV0fHu}=l^q{Bs!p8Tvy#M?A=Q2U_lbiOgv-;1||M&RkF+l(P{N*7r=s8F7 zkC*w#%;`c;>{@2*TW9G;RQI;K``g~{b$|7xtM7M#?P6`^CXwG6f2UBkH~;_uWl2Oq zRCr#k$TbcFF%&?-_f3loL1u>gpDvOsw~;dC@DIHV5XDXF7P548ib|$)h*t?)IdV$j zEEUSmw2#h!8jUAw4L-b$WR@`4?GIr@kQZT;T&{$1yJru8XD%-d@HQWQ@&z<}2AUj< SFoggB002ovPDHLk0$_qa?28%z delta 472 zcmdnX)X6eIrJg0-(btiIVPik{pF~y$1_p&>k04(LhAK4%hK3dfhF?ITh8GMBr3MTP zuM!v-tY$DUh!@P+6=(yLm>l2};`&5Z_w(f0|NsC0wr=w?b@TU?&EL20`TzU(`?C5^ z{nLM*IP>Gsv5)N&p33R{zIOBfpFb}w-CsKSzjO=vx^(sXa;wI-Nx5GZEdMld=2Jz( z*P)5ejT~P(2mCsJ`TzGH&kY^kM5VonPX9b*_V;bOf8W0MzO?>h=cH$9X1^|7ow!iC zKJuQbJJ2DFN#5=*OljOTDL@W;iKnkC`*Sv75q%EciRL?iLdBjgjv*44*Ph=k)MOyy zdhso*NNaYKB#B<`t?~8|Jl2((~b*^?+U-l%+uQ=_8_)y z+0q)u5B6*|a#%dA?G|L8mNQ8)78&qol`;+0Kjs_zyJUM diff --git a/recipes/icons/unian_net.png b/recipes/icons/unian_net.png index f9788060c937293828fc5956d6c3d3592d0eef9e..1e404bc4d8d4ac91666a323ceec58b7eaae448bd 100644 GIT binary patch delta 137 zcmV;40CxY31I_`EB#~egXaE2IEG#hA*4Il*PukkrNl8r7($mq=(*FMbnVFm9_1|Y$~ r$qEMSTtE&;fSVNxc%TBTk^37I3)ltL4T|gn00000NkvXXu0mjfM%Fwl delta 326 zcmX@d*uy+Q#gZl6(btiIVPik{pF~y$1_p&>k04(LhAK4%hK3dfhF?ITh8GMBr3MTP zuM!v-tY$DUh!@P+6=*Y2QM_J9$H3d$|JJQr|NsBLcI~>Sr_ZHJmoHwt^zYxlnKNfU zdGh4->o*G*F23U(Sqe0VwIs+d*z2+9BaixLZahE%#w2fd7ml`jQ>%a+_7YEDSN7*@ z!Xmonod+w}fkIB6E{-7*my;6`Qe2sOd{)$ig{84696WZ8k!?RdP`(kYX@0Ff!IPFw-@(3^6jWGBL3-FxNIP lure^nm(zKUq9HdwB{QuOw+1=qDbYX;44$rjF6*2UngE(pZ3X}U diff --git a/recipes/icons/uninohimitu.png b/recipes/icons/uninohimitu.png index 31fcafd2cbf9fb34b795d52ddadabe197280476e..d0728c47088a641869e56f6be77a4ffcd7f121b4 100644 GIT binary patch delta 603 zcmV-h0;K(_2c`#*=VFXJEsHxcjzBVxK`o0rEsQ)Zj6D1N`~Uy{XsmNDjXvS-<1vmv z{{H@WwSk4ajflRHiN2C+u6Av&cy6$HGLAr9rf6WOYhtNwFpfVijXf@mJTs3$Ig>~_ zlSn_7O+%PalSu(2B2ArIhk#W`uzG(pIrU@{ZgP_Tcu{F1r@uKj{#JF zu1Q2eRCr!>&_`F>Ko~}0VL%kqNlfjXz8dczUw+#Dimj(`pRsT09CUqdZ!Bv0pa>jP#*?{{tK2PwTe<3S^gG)77lCh z4UlpJKT>`d2q6`6ff=M`oS<6c;1Iii!vnNx8I+E|=`D7*r!Z0~fto06u5G_(a=Fa= z_Sz;yYb5Y<5t3V1Z|P(*{dTpLgvB2c=mYW(rm+s|hWU^O`Xn$6I|q@zJnOY9E5Gf4sB6T{c@duD|RDVi(5sMm)Z8ZCu2FehGBps!IZYZHVGW zM{z`L66gkCZ#9uhC06$U=$61J0I?_2>L~`m=oe7lqycbg7Pjq20Cf^T*WgTmGthK^NmVQum=5?C#@gZa{1cqNLi^Z)KQKLD zuTU$?yyK0nuzP=Ub8~XP3$2YtfCe3ad7p0{0KLHm{{WxvnaXDVEL8vi002ovPDHLkV1i~AA4vcJ delta 607 zcmV-l0-*h-2dW2<=VFUHEsQ)ejzBVxK`o0rEsQ)Zj65!kJT8qrFO5Dhjz2MuKr)U% zGmk+zlSn#~NI#ZMLzqxaomxhk#W`uzI){QLd={r>*`|NsA4#W+Kgj{#JF zvPnciR2Ug$!3SH~P#A#GPZS)uZQWZFZQToZOslUV?u~m-a3S~qmmBEQBt1v|Dy(ZN zmz?sZxKqiCAcc_!)Q@ z;%DMvir)Yp1L8M<$AtKlSZr>Qv$ctTRU#hwy>RnF&dUw-=8IeD6daze?wwy=p6{)m z9>P?qxP708*wN$HSuA$;^>`G6`ETOZ0UW=o)Eca%)ax}3B>PHPY2QH!Bgr!4s?otTMPeb z5w`}iqTAb1vKqv#6>a?_nNBB9*3nwUZ4fQ~t}@=^XoK=ku-Za

u&Qt2!t-dF?Z3 z(NE?0jH1o7=ZPcGP0ojMe2~)}5G^!HhHb&Ea@>MtlQ9|+E!Z{QXU+{Z!tR~QxZ8!s zp*gc}yelYAem+%{7R|1dtUt+_q$tC?H3R?v002ovPDHLkV1g?eE}Q@W diff --git a/recipes/icons/universe_today.png b/recipes/icons/universe_today.png index 0c26d3da5a7726ea87e789e2bdb7d2c4138a7670..12afccaa1369579f6b5956b84945f9d04b54734a 100644 GIT binary patch delta 2104 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 Dbta&< diff --git a/recipes/icons/usatoday.png b/recipes/icons/usatoday.png index 605bf45a9bc7c7ff6ba4d520e6dd7e974e3756a1..ef745c0b60efd36193532044ef9518aca759e8b9 100644 GIT binary patch delta 360 zcmV-u0hj)=1DgYoJb#-10Gj^*oBu+!|E=HujMD$z^8X{L|7OGg`ThU7!7-Nr000Pd zQchC<3RjxV?I(%y7WUWx009z7L_t(|US*QMOT$1E#~%p(0b+4>>*yfTRb0Auk&FfL z59sEs9=_NlbG68=0ST_L)xFSQ)>ENbRn*L+ferju$AZ=744k$NIK)c(Ccv2kF%4PA1?>_zS_Eg5flF$`$}-r38eY2LT@*wX zw4jdffiCa8u?5G_AW)={%q=*9Cg$DIS}6RC*b~+S$eDG zM?r(tOc&)VR6f00Y&o_*I}ke|J2JC@8^x`x%tm82`woBV_wH=}#ouB80000SU;qIj zldx20MFu!14od@qRC5?#A`J=}A$)c>m_SMaSfDfw4#L3-!ha9~sURgHX-L2kDA0y1 zkO9<@h5`y0m{A1`7(~-%WiH#ZHg7FNAdo>V?M>d#e|vLILj;l-%zy%a&##^T9wLy; z;Fk72``FFX>(5VvIF=zSE$9EsGe7^`S_W|%1CY3umW!|kC@_2XwpZJ3@4lA3?Ooop zG@zDyzh|F2b9jH(vHL&g&!3hGG&JpB&bImU=lfN#Pl47_b~zpGYByP86z4nBMKubVUe8% dk8G6W001P($yzbHM�r>*QkDu>>!={w1_*OkNph?qdt(3-6zJ7FpnYOUzu*Cz zPigi+AqgH*$p^;-s;Uq4@jM+H@T3*qfove{Ks8WrFomQUKv&Rl1IxGoE8s1#O}sz} kmKLQ7fVReOKFlAy0lf$XIWmX@yZ`_I07*qoM6N<$f`)Wy-2eap delta 273 zcmV+s0q*{W0;~d%B!3xnMObuGZ)S9NVRB^vL1b@YWgtmyVP|DhWnpA_ami&o0000p zP)t-s08MrfSbrd6hcIrCKzf)^g`r@Osc@UJfTp{TvBRId%&*7Pzti2%-sRuv?(g*W z|Ns9egHrzh0056kL_t(IPwmb@8p0qDgVDcsuuuxk{ZH#+K!06K^#Gb(^1VgSm+&*7 z07;URfU*N7pR;sf5&<1F405h{bfEJh+o&eyOAiXZcz?VEhwUM69tV-%$C-|H{@H_xB XhAh1IE?(-{jtt)k| zD|W9dcdssWt}=A40CcT8e6l@!vOs;ZLVvPIg0xJ5v`&JwP=vHlgtY*4t`%RU0AQz) z_%;I-a;=jw0VEgzU8UjY?f`D9=9CoiFW~d@^ts--+0A{Esbglqhq*rGUrvLx} zLPjM}16w3NfYYN; zz{YoE#xAG?U~X!E-*LM=p60suH_^!}1;Ctk?cYUiQ6iy3-)zs7sU;t9iJBlxK|Rvq z8C#X#kf=U^5&{~E1OQyJJQaddy+Ok+)#p%20EEL27y&qUBm4#$+-s=sSVbKY^jTFB z2pCNl{kd&6)2u=OnOD2qb&tV82}1;>yK?s<2HPfLL%{ZbY#7R&Vcp4*ZgD(3I3CR1 zVmZ!}fSwo}Ns@si0R*DZU&)w>AQl4v!BDTm&eFHIUg@;jSt7WcUzi>nXlyg$bqmz~O0to=?Q;M>-Hj+4=wg002ovPDHLkV1h>uNu&S( delta 688 zcmZo?+rTzKxt@Wuz$3Dlfq`2Hgc&d0t^32kz}OJr6XMF?vw$IH2}3LpEoF!YqGb#T zK(w48X&FP(a)#vP45`Z*(pE5}uVlzv$&j^*A#XK9-WrDdH4Ftnw3eZ0Ekp5Ih7urJ zCmO$0GwM@}k8Kcs5GNtQf%GN8Ttxzgjt5m#JscgM^$vSl) zS-xJoWUY4TI_~tKfQYU`Sm*>`tthympAXeym|lS!>8{bK7aoRL|=Y<`tsxR z*Pma${rvj<=eHlfzW)TG-#>o-{`LFsZy@^f_YV;L{r4A${z1V1fB!+i)ulBK7)W8B zE{-7{8te~AV zBP=S0iRI`|$&HHVVz`7>a6M`1Eq?v_RpJ6Qr#K&(t1O9&-~5tV5TYw^B_#a1dy)2H z?GueN1Go>BuB+*~Arc}kHmzAC^m_ZItR$@!YyC~lH}^Bn3JjJ%y}HA<-f__srp3}X zX0ZqwO6qV;x;mvr=ClLXrLC?TwJux~Dq6*$=@E1w%Sox7t6BX9Z{eS!8#NQ3K7BZy z`j5Rd$>Qx7;`>p<-JljQGFY@j2N{SE_ zX=!Euxhr~uw`ONza6q*D`6EgvkHzV(Sf|nE?6_&)u64_H$)wF{(QatFe?Y_F%$oHq zYzlg;MNVp!3q1S+k1llZ2{~_XprCNWT`K*a$kT+x#KdPuCRds|I>`1j$uKa)ygT}B Tp6HIX3_#%N>gTe~DWM4fvb0eo diff --git a/recipes/icons/vanloesebladet_dk.png b/recipes/icons/vanloesebladet_dk.png index 05f107580a440e778ae50dddd11075d6fd1379df..c531c3367664939381070bac54b85e49bfb4ebe8 100644 GIT binary patch delta 234 zcmVM�r>*QkDu>>!={w1_*OkNph?qdt(3-6zJ7FpnYOUzu*Cz zPigi+AqgH*$p^;-s;Uq4@jM+H@T3*qfove{Ks8WrFomQUKv&Rl1IxGoE8s1#O}sz} kmKLQ7fVReOKFlAy0lf$XIWmX@yZ`_I07*qoM6N<$f`)Wy-2eap delta 273 zcmV+s0q*{W0;~d%B!3xnMObuGZ)S9NVRB^vL1b@YWgtmyVP|DhWnpA_ami&o0000p zP)t-s08MrfSbrd6hcIrCKzf)^g`r@Osc@UJfTp{TvBRId%&*7Pzti2%-sRuv?(g*W z|Ns9egHrzh0056kL_t(IPwmb@8p0qDgVDcsuuuxk{ZH#+K!06K^#Gb(^1VgSm+&*7 z07;URfU*N7pR;sf5&<1F405h{bfEJh+o&eyOAiXZcz?VEhwUM69tV-%$C-|H{@H_xB XhAgMNFAhlGxZg6m4l|*Urk?(#>&gYIkyQcyn-hbaH!kb9;7le|mT0+ScUT*5}^Y$-%wK z!oJMJzy<{c(8t44PfaH#B`78(D=8`7)zRP9(&5=-xq?dbRI>H6^N`tj^eOG!{oOHoZs7Zw&05)o8UPyh7q8W|Y1t*W=M zth}_a|M~R)`}Xf?z8^C092<||BFqj{33l1CLWp%o;B}649?d zLAj#+e<0mkfH)O+(*aYa4y7i*u-mBZqP$xWk9Tri?PnUlvSd*;>tIFf`g``f177RFZbU?1`*MNBUSShw? zq$|gX8{i_}a9jtTbUp0Ew%+E z4g5x+`fqGNQ8p5g0)BsxX9UWBm9>q0j3p6O9tawN&DUQZd_HOlr+^?2%$@hND_75c zGJwCLHUjTs>-{a$EBGR7isTv1Kb_Gc`Da!a$+Mal??LiRzEOAR2^5e7zSULqgqL?! z(rSKK@$d2!wzj%39TppRWV=v*#h-U&*xoFvWZWz;YGeJd27F69p14pRRqQ|-7#2Br z+0YQh>MM`lytVX}MUI6QeRxlWU_?eNv@G_VXza}w$9(n7m)CUt{{etPh-4`e-LU`w N002ovPDHLkV1m)Er(6I4 delta 863 zcmV-l1EBox2a5=hBNG4#a7bBm000XT000XT0n*)m{E;DFR{#J20RaI60|N#H1qKEN z2nYxa3kwep4iXU&5)u&;5)u;=5)~8_7Zw&67Z(~C7#bNF92*)O8yg-S93LGWAs-(k zA|fOsA|)gvCnhB*CM7E=DKRiFldJ(4e@;tDP)$owO-xcwO;S%yR8db{SXW$GS6*6J zUt3vbVqj-uVQOY%ZE0t5ZEAONaCmcYd317nc5{1nbboqxfO~m?e0hOniovfvzwXLeRu&lhau)epm#l5-7f5E-V z!oJMJz|F+K&c?#e$HUOa!_dgY*Urk?(#_k_&fL?_-PF+D)zRP9(&5fqe#;oa=w-R|Y!@#y37=;QP1<@D?3_U-8R?dbRI>H6^N`tj`h@$CHa?fvrY z{qyer^X~ri@Bj4g|Mle?Y!rv#?(XjH?(XhxEnQ*Ae@ogVZ#2N$_m@j@pLe<3T>v2R_SNVELhSaC z0KLZ78_-gybb&S&%ND3pDdc`1Z4fI{kk;p?-`^O@QVKQtyZvet%W?t{&mqL5CVr*p zKUv*?+FW1T+P@Kh5Mol|`wej8oGMOoI2>{LB{>;o6BoQh7^FPBw9@8wC7FThxSEV$ z!fQ`oq(iM21XNaOE$pU>K>x>f5;R>X4XX+kK!?`+js*0*@1;XiaE{W@DS)5lFCgm( zWnu9H8rxq$;U$V-(*Qk70lR6?w2^x0fmsd=eJ#Z>OzsAMkB`kDSPnGJ*MuchF1?Tl z!N4vD^2!sVs;AGL1hCUU@BLyX6InFp2B<4WTzm^ODGLI*f=Y7uP3RVF)g4KadgjoZ z&?DOHV;(<~D%2p3fB{%2s?-BGLg_V!OHDPa|5fOOCT!G6nnl{}I!KdV@%dN-O~n~U z+KmT>+Zu~f6@j{$_Gl))`4FWj4GbDNezu{Z9;&gazIA;0;pfPqp=F<+uvp%Oi$sPN pjs0)A$KLbD@%;5Xf8PJU|2J53Jh}}J%R&GE002ovPDHLkV1nthuxtPT diff --git a/recipes/icons/veintitres.png b/recipes/icons/veintitres.png index 1bcd7acfe154d3b406bd686d71d6f7c6139fecfb..9afc903d94df8290cc793485da2f0d9bf1e78926 100644 GIT binary patch delta 7 OcmXR delta 25 ecmYc)pP(vO;1OBOz`!j8!i<;h*8Q2N=m-E@ zaTQi!NmncJKe!!_;Za(n5U3sFcwjua#GZ|0b1DE9S|uIPqt!1)+N zaAX1oaXvQSaev~KijiJ@f1>vVDkHk#SsTB+4v}x*pWO%##Ec4iqI9{gyUF#${Z`tI?dvf zj#ck#$qpj|zzSTBI|>Y94GPmC*B#i)U$>AGr=G&_PWB&Q$#Ui#2M7S{c!h`WKVZSo z)4p@d*)Cc0K`Mt$M}f<5U&I+W9nhIR$zqI7K7V&+*izt%hG`$Bvzjn>Pf= zf`0%ofO8{0g5X_AgccD&r*7xNDg_~C^?FSRp{v~S)F$Vw(IjKeoY|5DDj@ia-z~00|Zr81O#H ziF@vT-v4cSUb6`Z$h`F?GHchabmbS$F@G@g4O4kkl0;EP5D;M?dQXywn2C~iD2>?-QBhb@(*nfPtSYT5V|?Me{#*#IA^!ki9qL2%0U#hISs8 zjmqAVjd#?TdP5FlhKA+|*!wzTZ@0AtSpY!Ha@<VI7P?^Qwp`8z@MDE(K-7kK=-82GYW3)4imT>J2CpzylO9=ZVejaJoWvu4e3>WcYJ`{p{rti6lcl-y$1wy(2f#m3k0GaZSl;4x)OgOZX){r8%mXdoZ z4ie(9;=tFKTu(X=F&uSxgwzRFzezb~m&P(0r_fp`ygA(ch86p$Z-1lzIV!ss5`Tm> zk$P}s@_TO5S_6*=jay6&?J{w0CtNw3Tvw!^Qz(aMC7K86iNh)*H!}IB$XYBz#rva; z?Lyih_5M;hXIyh>CtPz8fFg%PNI*GPr^BiF^glBZD#LeS6V~8ToQi%&1}5-#JVE*u zt=F{Y=FXWnmvtrwB1R}uNC4!1TC<5Q@)nVdf8sXWi;v?xEXN}B0GfCW+wm0sjQynC j-I7cV(OTH40f_z$%E`^*e()8%00000NkvXXu0mjfcM>`s delta 2019 zcmV<92ORj^4e$?;BYyw{XF*Lt006O%3;baP0000WV@Og>004R>004l5008;`004mK z004C`008P>0026e000+ooVrmw00006VoOIv0RI600RN!9r;`8x2GdDIK~xwSrNL{A zon?Ul;OBk6_nbLBQ>Pb)f@MIoP@pWSR$Z)Out>Z$;7Syu#DCS`HHsP%f0*EIvf1^A zm!NL@W7UX>iZ?VUF$#e!Y*4yhswR-_C0m8DRP1GNW=>Dfob#RcwKLNesZt_5Pdf1M zqy-^UBBe=dH_N}r$WB3a%rdOUORyemu&RiTI)FX+4StE=VA^qWYnj#823^9u4(N-{5*7kV_N z7}-s0CymMHkZ;60G3x0_crp2lLignwoljt&`ZNoMD}TvtNokTEWpo>@ouq@$#TMdb zVkCR|GV&<-{Wwe7|6xn-dq_G!_Y(ROl9AmsCu!}ZF}WIBu$dU0kUc|@yjkc3jo$n5 zh~WxiSF-E}R2|a(Hu*;4W@5A;BYXNHA~NcT*AxF$n!3U0wyxZkw00r~>VLvJh|z+K zsFXG(B7a&?qF_-8*4BTID+UetjC-1 z`Ggg?2Fb}v%fWs8YC9!!(K;sgaNr>4UdZ_A76m8levt3na?n{5_xRYyU*W22o=0`W zNfNHcR}(hi1&(P}O<56RbpH7l*s|p{-ujmRaJa*#KlyoEZ~CSm-SNNP`<_>M^V=>X z^nVC_LZ&49_szQP)_eWuO*`E5$@{$YvS(YhIw*=mn`Vsnbvv!Fz4R!kE8P$$a@s z_xtgk_nVuuD4_7zA5pme`i`{X)AP9(#8cUv177+_kmP>73E`O9v z2^IxHq0=Qr9(Z7nH9vp0O{Xo>XfqUKL|G990Z~H7lL&=aJVB$;P$jN=*UP-^?XOXX z4xx`sX_`LBoj>}SFa6g~+;z_neQN7!S6;nAqft-`0tza`PQw3Vy(bg0$3=OFz)1Uu zCCd)cm_`?UB%{lgc=z>}YBad<#($r<=dOLOyz12&jlQ92%poEnJ)H1sT#ewVlxW^u zAF*dxW<}HJAS3AwrnMVmCMH%3*!O#8{xmeQBq)O5xLB09BjFdA#h89${Q|E)&F8E3F!`EpD-$t?amVLKkg%YXb0u?mJm z>YA8}Votx$q5ZWzI}Z~Fgn~dR4jtU<{`>Cm<(nT-HF?upF4bt&>fxMJMMR78gF@b( zFoj!j2`VQkrAQ4LO^TjjIjo9&Ve2km`^qj)5SW=^SUBfg-uHp$y8N~05)Q~_6-iVn z%8>eY@>D{>ZRG24G122{?SE(0-gxzxBi%Vm$7XH1VvXf1`Wk7}+@K&*A|WGNzI=&` z)}QI(7p?I8^Tw2T$e?t^s*=@+2s?uMTPP`(h&(COpTkzAxrAt5jyY`Z^2B zA6*dU(K@NIm|r-Uam5(LY6s8>>&QVu9B>IwRp zxkHS^N!8Q`>28Z$j|))w`wX#5$Xn(1*VW^*=GM|%$J{xNOC@ANwxC``XA^@nS^7Vn zP)&XTcj6kn5f@>pQ|sXWa69=M^dB-@RUF>P+y&GtEsEx)9DlMC%2N6(iG9j;k%Hgh zQ@9!bh|6&y&cTR9b9e~5a5sK|1LQ)cG#O0LKbz9@xaiN7jLM+5y$+TD001R)MObuX zVRU6WV{&C-bY%cCFfuYNFgGnSGE^`&IyEsmGB7JJFgh?WM>~!|0000bbVXQnWMOn= zI&E)cX=ZrDb5bYX3905UK#GA%GSEiy7xFg7|hF*-CdD=;uRFfcHK4~zf+002ovPDHLkV1nnh Byi))G diff --git a/recipes/icons/vesterbrobladet_dk.png b/recipes/icons/vesterbrobladet_dk.png index 05f107580a440e778ae50dddd11075d6fd1379df..c531c3367664939381070bac54b85e49bfb4ebe8 100644 GIT binary patch delta 234 zcmVM�r>*QkDu>>!={w1_*OkNph?qdt(3-6zJ7FpnYOUzu*Cz zPigi+AqgH*$p^;-s;Uq4@jM+H@T3*qfove{Ks8WrFomQUKv&Rl1IxGoE8s1#O}sz} kmKLQ7fVReOKFlAy0lf$XIWmX@yZ`_I07*qoM6N<$f`)Wy-2eap delta 273 zcmV+s0q*{W0;~d%B!3xnMObuGZ)S9NVRB^vL1b@YWgtmyVP|DhWnpA_ami&o0000p zP)t-s08MrfSbrd6hcIrCKzf)^g`r@Osc@UJfTp{TvBRId%&*7Pzti2%-sRuv?(g*W z|Ns9egHrzh0056kL_t(IPwmb@8p0qDgVDcsuuuxk{ZH#+K!06K^#Gb(^1VgSm+&*7 z07;URfU*N7pR;sf5&<1F405h{bfEJh+o&eyOAiXZcz?VEhwUM69tV-%$C-|H{@H_xB XhAK0ZE8OiVjFJ1QzFIyyQtGcyDP1V=_jM@L6XOG^a=1XEK} zR8&+21qE4HSO*6OTU%RQTwGsYUSMEgVPRonVPOdf2x@9-Y&C3bZ*FdMbaZufb#``k zcXxMqcXxbzdmE@ALEX85tQH8yorg`5he{BO@a?H#ageGV1E;?Ck6V0|Wm3{mRuIVE_OD2}wjj zR5(xF%0)uMPyj{Icayq14#mAV!QI{6eg7+9(9n{#wmI1qTNQ2?z)a3kwVk3=Rzq4-XF(78Vy5 z7a18D8yg!P9UUVhBPl5^6GBPtWGdMRlIyyQ#J3Br;K1W7IM@L6XOG`~mOjA=+ zR8&-0SXfzDSXxgw$5?C$RF@9*#P^Yirc^Y`}l`T6{{8*_{{H{}|9x*b0{{R45lKWr zR5(xF%I6|NVF1VBzmb_$Mj?`!k#TUO>~ZY9iBjoz|5xBcM>+2$^n8A#TH!l(oRB1K zI?lM_j^Mu6aSJPwd|xmk8$N$qq%kCgrrkQlmiKG&;`s&FA#Raoe|bxOPxi&TO}JlDD`_&fH(BR zE1c^uOqiiL2e4WRZCNAe3ShVrv}cA5py;oLh#6wQqpG3l-3UD;*!~xkGD6*SJl-ow j8nUbbNkPjRRn7PXEnR0>S;z0!00000NkvXXu0mjf1jgxM diff --git a/recipes/icons/vikna_ru.png b/recipes/icons/vikna_ru.png index a70191fd77932f5583a2d3aa6df0a580b06e59a3..f2a958bfc170f09cc8add84879bf8fb91ec6d7b8 100644 GIT binary patch delta 868 zcmV-q1DpJ!3H%6{!vFyP{r$%PC&vIT$tOL^6*a^F0RH~||NZ{R6*&L?|HuF* z$}&X900774_Tb~k02RUj0Lw^J-;|u+m7V6Yxaz#Z$Ra)KV!*}60368xHOVVL z$u2_204vEdLh8Q5%N01wJ51}r#^0Bo_u%Bn04v~^pveF@#Q+@Onxf&Kr^`T0!2kfq zA~?z-J<2OW$}U95CpE|b703W0#sDJ594E^{P|HSA$Q3S=Y5^)R!vGb;92~=(D=$w!G=MzQ`v#$R|C;A{@sRGRQSVlf(f(2FU;}#sC152?8)5 z#~d}uA~pZ}{r~*^$s#+)95~4c}0e?wtlYBiy5IYhM{ECk5&C(5eGKyv;B_!?Uh;$+FtKS@ds|!k?vB@p-0(`h>INcsw&kE@S$h}m$Ram~3uA?z|qcpRi$o{;$mETUi(J0;b_e07uv|N=9&SjyKomUSYhmNA)3Mck=urm3Yd$ zx_BL*b?da9uQ-v)Zclc3%Dl*aMY(>r%&;xaum+FArTPZSrN%aW6ouZYmA5)x$2NZx zz~ec8r>JaLd-nBQK+|!MjGODw4JQy&lG;d}%!=>p7AESHMJ|G-k;0=xi3-U>YaW~R zS!A*jf|A(3nK^wM`;if`55PFd;;jCXoU4iw%JpK%_b0;M)r*q8?_%}J68=1ZqM)9$ uymV-J14RHnC>UE(K_r@7I$<>U{s7!a)%)!a2brA!0000Z95~1vJIEq6$Raz)B0b0_JIE(J$SXa_D?rFKM9Bay$pAIU0657a zHOV47$tOL@D?Q08K*=sb$udI96*bBtJ<2OW$}U98GDOQ2HOmz^%R5ZVKupU*P|HSA z%Scp{RRJn6-;|u+m7U+0p5T|D;F_Z0pQquXsphh{=(D=$w!G=MzUsWf>b}G4zs2jo z#p}Vwldl0k2KM0N_u%A{^Z_s*{{8*_{{H{_{r~*^|NZ^{{r>;{{{Q~}|NsBnLOTnS zLjo>;0tQJ$K~y-6V_;-pmgv}`i*N>Zk+08<_uB+ zSAb^1%8g}eUZ@JzZ=7CGTvSwCSh*0WVBN+peRos^Yd1D4swycdsVn4d09v*gM&lD45on0n-h(xz|hyn$0y9%ZWc(vM0*!>D_WGaH8eD|G)jSPSh2Bx zQppEZ!G?|VY8&e78)|#kf}N0N?1ImmbF(!(oX{1lUkL#q1yve0E*J`60L<)9#k6AO z5^z*4TMG`!Rvol}TDP$~G6fjd$?^FMfGSpP$W`+|^Jcq-B`CRADVBhZXwvXTGoeG@ z81%~OWjn&W;C5}DOuCH-02c=0F1|clTe$kA^91RO+P_h?f;K7n{ z5(CvV-9cWEjOAot7Ko5S&w55qp+TUmClMjQ#K6EI6pN90U7(p)kb?o3zgf5-MWnH1 fe=$)$HU>rj7!;P5?EG~w00000NkvXXu0mjfi#d`| diff --git a/recipes/icons/vikna_ua.png b/recipes/icons/vikna_ua.png index a70191fd77932f5583a2d3aa6df0a580b06e59a3..f2a958bfc170f09cc8add84879bf8fb91ec6d7b8 100644 GIT binary patch delta 868 zcmV-q1DpJ!3H%6{!vFyP{r$%PC&vIT$tOL^6*a^F0RH~||NZ{R6*&L?|HuF* z$}&X900774_Tb~k02RUj0Lw^J-;|u+m7V6Yxaz#Z$Ra)KV!*}60368xHOVVL z$u2_204vEdLh8Q5%N01wJ51}r#^0Bo_u%Bn04v~^pveF@#Q+@Onxf&Kr^`T0!2kfq zA~?z-J<2OW$}U95CpE|b703W0#sDJ594E^{P|HSA$Q3S=Y5^)R!vGb;92~=(D=$w!G=MzQ`v#$R|C;A{@sRGRQSVlf(f(2FU;}#sC152?8)5 z#~d}uA~pZ}{r~*^$s#+)95~4c}0e?wtlYBiy5IYhM{ECk5&C(5eGKyv;B_!?Uh;$+FtKS@ds|!k?vB@p-0(`h>INcsw&kE@S$h}m$Ram~3uA?z|qcpRi$o{;$mETUi(J0;b_e07uv|N=9&SjyKomUSYhmNA)3Mck=urm3Yd$ zx_BL*b?da9uQ-v)Zclc3%Dl*aMY(>r%&;xaum+FArTPZSrN%aW6ouZYmA5)x$2NZx zz~ec8r>JaLd-nBQK+|!MjGODw4JQy&lG;d}%!=>p7AESHMJ|G-k;0=xi3-U>YaW~R zS!A*jf|A(3nK^wM`;if`55PFd;;jCXoU4iw%JpK%_b0;M)r*q8?_%}J68=1ZqM)9$ uymV-J14RHnC>UE(K_r@7I$<>U{s7!a)%)!a2brA!0000Z95~1vJIEq6$Raz)B0b0_JIE(J$SXa_D?rFKM9Bay$pAIU0657a zHOV47$tOL@D?Q08K*=sb$udI96*bBtJ<2OW$}U98GDOQ2HOmz^%R5ZVKupU*P|HSA z%Scp{RRJn6-;|u+m7U+0p5T|D;F_Z0pQquXsphh{=(D=$w!G=MzUsWf>b}G4zs2jo z#p}Vwldl0k2KM0N_u%A{^Z_s*{{8*_{{H{_{r~*^|NZ^{{r>;{{{Q~}|NsBnLOTnS zLjo>;0tQJ$K~y-6V_;-pmgv}`i*N>Zk+08<_uB+ zSAb^1%8g}eUZ@JzZ=7CGTvSwCSh*0WVBN+peRos^Yd1D4swycdsVn4d09v*gM&lD45on0n-h(xz|hyn$0y9%ZWc(vM0*!>D_WGaH8eD|G)jSPSh2Bx zQppEZ!G?|VY8&e78)|#kf}N0N?1ImmbF(!(oX{1lUkL#q1yve0E*J`60L<)9#k6AO z5^z*4TMG`!Rvol}TDP$~G6fjd$?^FMfGSpP$W`+|^Jcq-B`CRADVBhZXwvXTGoeG@ z81%~OWjn&W;C5}DOuCH-02c=0F1|clTe$kA^91RO+P_h?f;K7n{ z5(CvV-9cWEjOAot7Ko5S&w55qp+TUmClMjQ#K6EI6pN90U7(p)kb?o3zgf5-MWnH1 fe=$)$HU>rj7!;P5?EG~w00000NkvXXu0mjfi#d`| diff --git a/recipes/icons/villagevoice.png b/recipes/icons/villagevoice.png index 0717ac1e673151c553c0d205a31a6a28c35551d2..29f02e92705b9613bd527ddb365a225f39225a86 100644 GIT binary patch delta 807 zcmV+?1K9k@2fYT6BYy#;P)t-s0A9EM|NjAAw1J?Jau5{r>)EpVVNQ(&X;p426E!3<@(yw&WU!sI=K$fw8Vc&gd8(CT-o z*&1)Y9dN(d=YRAveZ@_W&G-8K;Oq7zb-@Z`y0_Bng0S3;x8I?|G=Bn z(&F;Q+wV`1&7;NT)8q53%jl1|-${(j;q3Rx-SEuc@qZO-y@j&fVVlzX{QmRz{33I~ z-Rkz1yy1SW+Vc1N$K3Cr!{qh&{CTU{AacO!y9$&500C4l_? z+s3hN+qP}%+V=g^XH}iFmY*bI#Faw45mmoiaV8)jmO(js6p9}IV24>0USRRH# zs;r_;bp$MG)Y>fiTD^#tnA>I0hAe)l3 z#l?0ZGVJbA-@b1hOwm=~8%Ib_MABg=Ua_sX zw|~kCBIF`5g)4rhvpybMdb98;T)X}!HVqPRFZP-u{)a(u35dnb+Jqm6tQ#KS&HH8I z;_s92(X{IP=!%?DB$iBCng&cj9Gs804J|xWEszSfv?|*5AO*O)FG=6$J=2>vz&AWU z^uH{y0`V7jN1Y!3zblTtm;1?uV$sA32AF(-0n9<%5zWByzT6a&kAq@a+uwd70PIw5 zFPM<)S3N!=r(qme%{G_C?m2vY@Cc@avQ3*e_LRz?HNvqaVC6Pzd96Bl+bEp_(2@tR zC%T5sg3%?H$Ny#hC^!K>Hiy@SIlM1k*`dy0qHARFEm%&Wt#&ZV`i;wuA;TK94+_&FH*LCnS3C9KivFz(rV7UE zF`97@LD1;*6js(JBb)*{(pZa~kf{`up#5EOtfS=uBs3-qjiJbB^ziMS2%|0&VYqR; zh1`@BEG<3DHud12CEqjFxcOC8=uP%KJAxVg621Si>}V40H-1>+JdbqZ!1l-k-Bq=f ze5Pw#ndR!<+l?(ceUz4!R@_ZZBkPjyaelVpmAndJ+o8DD(h^MGL4S|?siJ0A_Y#x* zN0cVD2i;?goArlc1+h(WF2|&`}Spr&=C*xT=we1xMtn}23lx}t&|361p B9sB?Q diff --git a/recipes/icons/vitalia.png b/recipes/icons/vitalia.png index 9cfa1ef63d99c009ac1ea07fd93cc3e1a5ebddaa..44cb88795bc4df702763d8a7576ce71c6c5e8b20 100644 GIT binary patch delta 297 zcmV+^0oMMZ1C|4jEGOI7#^Bq_+}Ozf|Nq|E$>6@Rt2Hs<-Ocgr-16?-^Y7o&p_b#{ z&yiCzHmfx-)WNdVv!>X+uiCh)+sL}AH89-J!K^kh-O|IaK|8QQJ>bQ+vrR|gzOmuP zxU^13v{p}%ydHnJVOhCaQ@UeZ^zh)kWnRk^_Avke0Fg;VK~#8NHOW=712GH*(Y6O6 z%*+gr8TY@dNOp2Q`l?EVe-7R`q;O)qxFB~P@jh}g?||xL8DHMr7O2SqKc!FgS{fX6 z5%FuhG$K=InAm$11`=o}b@*;tKS*-MxkfuFdAQe@hxx-pJMY{n;))GZg vg|)N~Eg6C!=Pq<-Oxmr`YV_dvt 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 delta 396 zcmV;70dxM71DgYo8Gix*005AYXf^-<0b)r+K~#9!b&|hJ!$1_r?=MKYCL*|re}mFV zm$)ee+#JP4Hzy%mi?n}%gNuTVTN7v(K_h9vMUZAlH=Ctk!DMmh62C7m93f}JOY-jB z`@COw!qW{O(%NA0bd$wrMG@iM^q+_^*?XuN3vaZ1n>dd1aesEP48pp=F7iAVLU{zGXmguA zIA_vr0+YjI0%S-O4IRa}x`BR@ZhtK>d~^r8fu1D%FR<{(6~L`a1>SWa%d+nvTxB^` zyZXuS<4(99gnw57RtUhnD4uF+_g*sv128#>fCd_^S2s$dTgVKKDe3ee&*zuy q_PTc<$fMM5w|0C9C|6xt{s2^2u}*A{x55Ab002ovP6b4+LSTZ!X1G`Y diff --git a/recipes/icons/vrijnederland.png b/recipes/icons/vrijnederland.png index 41806323a6b4f81aa6e82b4188c901f3805afdc5..6fe2f9649f07a016e16baa80beb21837533fcef7 100644 GIT binary patch delta 3569 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 delta 3580 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 U%7H(g^2b zMr%^Q3w_nR#;)6pk`vV4mu$mX!LfP10{1D0Laju$Ihn-TC91|(`}2OtVb?MQ9KItw d_SK}-f`4cX3SAdKR#EPntMTL2zq9W!+hNrWgyj5kS#NMW8`W|>}Ys$6fnaeAeAfvb3h zxOR@hgOJ69jI)A@uaum>lbyt!ro*7A$)T^&rnJ$rz0juDXbk3OweJYtL7>f|W>vyz)dBLFeNi@sOa<&T*S=&cV@hK;>c>>Er jIbc2x@!vP))b#iTOD!5=SbYPW00000NkvXXu0mjff^N?H diff --git a/recipes/icons/winsupersite.png b/recipes/icons/winsupersite.png index 65b496a16321bc8370c033bc7d89be80cc2badb8..d4c5442770bd395dcfa63f451871e0e1ca582450 100644 GIT binary patch delta 308 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 delta 251 zcmVX}0>hZM?5CZUj#{c}IG^%lLTRU+K!<~b5&(UJ9}P7r!>~isP$@Mv0mczzMTUy1 z70QPN`-|Z$L(Nh%($re9WibB3DwaomUa+dCY2QH9L+p0(0d#O_C?_zEAw^097#6D(A^|4jQR3E9?48@p?Ni$_1p|I0r*qgILDWX2X{Ysg)_Mz|B-@glTV@h+i z+j^uHR4(?#oK1IMuXhVr|ML05Dd>Wru0^YFK*cw~DiM81Gr=nOCP+~TqP5T{{;FtwC|D4rihc4IR*H#2UhHHNi3wWUv?gXc zGxwac*OI&T!ZRTBt!vn`_C9Cd_5H5zT6e+aJ|4A|{JB4_G=Hn7S4OK3+{38}Z<5Vp z-#dEbfn&|*PtP&u*bRV{=Gcfm#|-;IgY6x*g*k)@f5{F**Qb&Z5wg8PTwakmz_FrA6s!SU?1} zxrSgU6cOuyt$!(R^*csE>BdU8Yi!FI6vF^8Qzk59C_<*?-}Gz)cx)#CM#GYmSm0Ka zLd$jBgz)A-u~6w5vxE`PQOpXNMU38KWSaoS#Fhcr;#CsaW4GKSGo=AM^F`hkc;bP_ zvPj295U{#L98Q9i@7-%Xi;-*u2v~LEe~pveW&3EkL4U(;dbUCZH;KcvW+gSFvE0I$ zwGXniZ^U$W=DVlP^W1kX0~Q#8f*}EV|3e4(%oBIAd81?FwVu@@zbDHWwL&MaBPML| znrB~23}nV$j}cQESqK1g|HG4)_~Nri0r=$Uqx|~&*D#G5O8^JBB;ois-p6Q?*n8`Q zZ-07?k$)^8HX@6LD#p~_g3_UER02YZl}V(KS`iV>Kexg93mY6}>#;!q*>MCx1zbm7|Fjl|m~wY5C7Upl9Y?t#lX# zq<9}_bo}*~XZ-d@uT;iaIPsH1fZ`^Kfj`TQ0Pwq~Ug5^eZBz!JxSEKPXgp!WfW(7U+p}icqYY{@QciuVW$o+c(Xt&hw3WkYRAOVm{0_bED7zkh} zF@H=v`$9`EE$Yg&epW3!{iVxd9rUVr0}r;h^g&|`<#X+Jo*Mv<0ajhSEwDl^PF zkFT;#v6e;_>1|dCK#WtdC_@^!3jQw32m{VO`w}l*d^h{=SYTmqLm2d}GODipfp|Tr#f}Kq5uMKu~e^-ju-OpE}1^e)9o#+7rkBf=TgA5r07~ z1~pR4>)=-ViQd=Hgjkh_mN>`4Jpv3*xb)}ite?G37#Gi7=fc@nBdA4FTO~vvPjsk1 z0_1@J)Fu%c3fm|%SHqY&sbZa<%Y4s#-}C%4R|(?}&s>Q+ZH(B?;}qMb_jua?ME3q| zP`aT6LUzs}OJ!#Q5=DrzV1|i+DSt%^VU$ulFh-1B++2D9%o{J>B4j6+7{?@}#VCHM z`r_V6`Ch{p&%St5DPc%s{>+zy7JA#RWwow0AWH8f081oBg93#O%oZ^z4wNKQ5X7RJ zhkbY$PYHv`LLxH6%rJoPh9TBO=6I=kwn8SU1hP$0>4KHgS9p@801dA|!hcCjmNH>9 z6TwVTV)|*2uxMRZK_Q~6>$FM3QYb)A@)sSLg5aQtRJq)npOe<-l8i89Wc1AZPJj*y z7*Rs7=o0`T?QBA7&8^OGajb-y>wq!Huadxo=~6=&3rizR6(eH(4_v`OAp)UrkV#d3zq^wFg@j_(*OO-nkv%vh zh-IX1yzaBj4uJ7A(_0t7#J9+XOf9=A$_z28D)a*ZFjpaVh!v1TvY70WTz;gl-h@X# z@iSBc)Pci;d$ovX&NpVk3`ha4I|;tLpc23d1F0b9%q8aiK!3m}0}0f$6+@yh%2XJ;#`1-IG_JpJV?Q;Y=*jPuqDg00000NkvXXu0mjfQwvJV delta 1779 zcmV7t7wx+&6F)Ww`a7~Mr75fx>I7G)Ww7eYc17D6pCAtBsu47xyt;zwd2k? z@ALeA-#(x1d%Ugt`n8YA_^9~Xx#uIF@b+5(00000000000DtgGZ#mme*4MWAfp)&; zWZe0x4LY`0op|1>s&C!ocpwiymBoLa<#~@y$KzSYXU4qs_`V-sc+|U|0RRAiS9Kg!ZS9vIe**$8xXKLcj~HUsOdD%_pZlY z>*^gR&Tm`&;afiCLx1ojN~j{J2<7!}xZBUZ^E*BF%#~gVm|cWhc&Uxr~yc{^a2&{Ne{+4&M0dFZba;`ijn}N-Ba-vc!YG{R$^%i`(xy z^ZP&Zn17R8mY!HMDN^TsfoV5gw?Qmo5v#>QqNXH1^`Yw?|M+#+zU!*9E2{^8=R3UT z&5xs41t9#yZ@$E(+g9+zC$9U{Up;5dsiGxWrLkDW0ybEkh)5O^ES4-1gt!0bXT9S; z?zgP*`UhX=1Hb%Vu7CZHzS`{oCK?nR)J-+%g%$1Pbv#c*h+nc>2-F8Y69gTpN| zhiO+sQVm7$^p}Q@{^^&!?u~a@)_CZB_xiOT`Zpj`yLjk*_gc4re|YbgJ@cgtNwhE| z!L)R0UAjtd!D03Y*14T(@uurAS_5I%+33M=}ABN8!rJ5{n>qf;r>U#L+`y$ z0{r=}JZax|N$8>(W)CC7(HoB3v~*QdEH;p7I@F0_w^&68MZ$FWn|FP|55M)rZoli) z3$LAf^^e_Y4qkNcIe7LfyT5+NQ?f88$$!u?GtKS@hzMYVnq;a&GgU#e2tWef{ietK z{QF-H-thK&0l*(W{FspoibNX~Gq<$6I?UWOdSS~IM{n7Hs-dZACyRhcsA0{C;*k%2 z(HB4WZC?1&%P!x(0r=l1Zut0L{*P%Ol4xnB*~2u<9CVS825eB%Gz_=EVvz(a0)Og| zMDUKEe$=miof2c8c;_vq6;`^Z;SP*p`UDOJ-15fQ97Y``?_R%eEr77Jn#Q|Ka@SX9K~pFi-F zYY#jh{L=@%V02ed#Wb{4)WmAIFn=8mH%vR&AXZaN2VkWkum~0lttNCDa{w4)m<~$O z0@IlRDg@rBrDZEj7(j2L)mQ8w54X?wTPbS+o(0iX_sQS!kSJ-2hy^ zdZKeGg0_lwQf8~rVHgyE4S%Lxx{!dS3#KL()kZ`TIz+4|0A!tpo7$*~P+DL(w39BT z;c!z6Y%mRrsXZ`_ER)kIYq}*xnGUc4HSM6L zbdbf;X&_moA%`?H-u6>}VP+RWXb?cm?w}=w8ohB3KZ$YYAEk&|PfW zp$eu6HkgJefI756K_DpQpnEJgYZ?N=Y3nSqQJ@qpafnq-RZOq}RR^l(g^d5+mhH)z z@$X4Kw=DT==lsmR?`%&!>i3=n004joyy?n%lDn5>y{~g#o9(x&mv*|R?TZ}e V3SRJ@kYfM<002ovPDHLkV1l{LW%&RA diff --git a/recipes/icons/yementimes.png b/recipes/icons/yementimes.png index 489a608dfd7da50a2330c9775e453e2550b443bd..879e90af64bb750588dd243bd07f1f9d9c7e433e 100644 GIT binary patch delta 264 zcmV+j0r&po1(OAk90dSGq>9FoHuVI?JVM2jF99Y5P%snelUV_3e*n)(L_t(|UhT@& zQbGX~M&X4R*xiMN9UL380|V*n8k=HqYFpDNS`t%YMW^{pPd zPt@DRHmDgp_HAX$moNo14nuQ)LsU_j5-03xOr$Mvq>4S z8vqq&nvDZXreS*EW-AyO2N+W4q4EAY0Z;wGfsXY;t5p=6E&fxK`tKcXOg`7VodvD{ O0000 delta 346 zcmV-g0j2(v1>^;g92x;LqqNkl&6NR5Rv^R&TI72Un`W?1xmTHDg5CO%3(zKn0Lud|@X{{$nIU5g zoyJTU3)H9pVh%#VIDj5(FJau0r*yfnrwl;trN*cd#($VW%sc(G=nN3&pgfH}oGQs` zhG9@1rT`V)8kM(Iwv-zw6OfzX_d<`Od*25bm9zw{cUP&nZftP$K1pjGk0%K#7-+tgbnr>|YQU4+ s2dk2yJ0l2!AP6?R diff --git a/recipes/icons/yomiuri.png b/recipes/icons/yomiuri.png index 0d720916bbc3259f24e1dfbf1ab736a9fd285f48..a747d7df3323e19369f5d800c931236e9f750b00 100644 GIT binary patch delta 178 zcmV;j08Rhz0qg;gLVuM>L_t(|Ud75GZp1(kMA6@)44mqxCBX7H z!2$WKo{E3!Fog305e91Q4UVK=hsOr=Vsh4YfG`E01Da0vS7JMx#tGe{Ct+=zg)uPS z{DQ|bZ-}C4mg|Pl*3)Bwweu6OxPj?9-f(aMp{Mz%8zf#f3oDwm0}DbD3kFDHK_qSM gui@0<{sx!BT+O$!CWZz(j{9FHH!@ zJ72JD?E>or&t3fj^AelV1o_GxkaL5qUU#SrzW@fk$L90|U1Z2s2)~TlZ(9Vj%|rbh8Lv diff --git a/recipes/icons/zaman.png b/recipes/icons/zaman.png index bebb3e29b0f7de82aac88df0abd33ae8f39bd626..067c184d3194a28f8bac4f8f062f3e68cb46dcb0 100644 GIT binary patch delta 400 zcmV;B0dM}e1F{2nH@fpDomRuNSQ1{ zl==4h9y^g-Y^y<4o<38b^6&Q7*yTuEqVJLbHB6grdbD$XwpeGV{Q3L!@%Npg$@lmA zBuAKhgt%jGtuRfS^6>VrvChmp1nIJxr^z-=C&*I9+-WNBHW^}RH+UhS*oiR?F?(g<7Ntje(rVuWPY~L~H z00023Nklktr>e*#feM1ayRr2|02 zvi)?8nleE9;eV|YMV)9)_NlcQkFpdi`hS|D!5;(z`RI|M#|>IA%D%i7nZ{gzH4dud z^%Y7c7#ak~u;`bH;>DTAQ+z&9?gQHJ*^u=9mFH>0(Nwh;KP{|TB^oV%@2B0QJIV8I uaP_uBkI^85wGC`;e@j1EzX53L5ouX(JH-G100{s|MNUMnLSTXxILVm+ delta 406 zcmV;H0crlS1Goc_EPws{{QUX*`S$wv_xkkn`1SPn_3`)e@b>cW_U`ZY>gx08=J4g^ z@Y&kx*VyIM&*I9+-mkIJoubKzk-qPe0DOeFbAGmMdbDPAv14zoTx_dYXQ)(Srbt|( zNmrghRh~XmpD|IMI8K~3Oq(%IoG?wBF-e#&P@OGKoGeY7EPqU!EK8a#N|`H6n<`A3 zDodIwN|`82nkh$^EJKthN}47}nIuP;AVZcOL6smrk{v#i9y^g6JdzhTju0-1_h`D% z00029Nkl5JaQcd+!Al(L`KAL`Ven|NqL}9PZmaJ3GTb3SI(oK4%iN zv1pS5Iy`r4%YSBdNYSHYRCS!H(_lT9JPtOViwtXfBPbtbX{NqYw#a!*eW3_%`I1cv zE`b~e^|3xkR47BrOHj$McF+PZ)_F0(=ab+OP(S}H*z~9K+{7GR=v}3=L{_t{L08vt zx8G{E+U@4{O@8tGvu}9>)Th!H0fgPZixTobwXh*k{SVs}dH?_b07*qoM6N<$f>K_~ AvH$=8 diff --git a/recipes/icons/zaobao.png b/recipes/icons/zaobao.png index af5e8ab9e122292150099124907d71905db8b714..43d0d853fee68370717b66392ce3ba214e6d7212 100644 GIT binary patch delta 874 zcmV-w1C{*H2*C)DP(0WgJEZ_L0Gi+cn&71XG}szDr2sSln&1GM;H3aG0Gi*W05jJa zI{=#Cr2sSln&71XG}szDr2sSln&6R)8-M@*|D^yl{{H^h8aw{{{7Y@7+9yW-{r$>n zh}a%J-8xp~X@2NV}u<#L(-Fo$JTa?2w@BlcUsXh4#$U_RrS#`~70U>;RhJ*fJeG z{PFYIAV3J6;ZV8h{{8;5C{*1#SIlva<+a4lbdk?vfa!mc+nKK1HdC)2OciXyE`ttSq^Y#4j@|>*8Mz!dt1~}D< zqSfyC{`&f-2RWz;JQt(lt{X|&B0=`1^Ba>T0y%%qNklG0UkF+t5wuqlBMYSwPZ4qgu^~~7y?E5)4s4vyu%l@yPF~+90bn_A3BPY}ues%sF zEMR}^BtLrQ_u~zhMhyV z#+kGguI`QOBCvx=aiYT3acLW}hL(;`+{Wm6RctM!kWl9S1n_>7*!dK=A*O{Ws^hx= z9{uYb1h7vRqg12g?Qo0^0Z7l*#TrWLcyE6iu7~pu3oS{VCfPGEbMa@OPl(C2Lg|zP z#yw!*#2_EuuLqLC=sIwV!?os+zCte9+kf-^vJYG~7ar+bt(R2Kd;&-M?SrdJIOYpW zRQ;%7zWjQBmvFqoDPajwL374&`glf}fiD@Oxqv9P%Hu4aTUm^Vf+^3y54myj0Fw-|{4S$yG? zx19?6$SW9YGVP{VkaFdfS5`H)w>MT*mgkZ7A8>%cp5(-w1poj507*qoM6N<$f{G2| AI{*Lx delta 889 zcmV-<1BU#;2+#&XOKql5x#?oT z>~XW)fx6#;&F_cL@Q%{)oUF^G05qotIHw0Ws0uu-7)P!fNv<49uO3XX1~sw`JhCQI zvnW)zHD0^q_Pj%Cy(vV}u<#L(-Fo$JTa?2w@BlcSTP0W(kb z%F_1C)b`KT_WS+#*4+5*@c8`x`P||8{Qmmp>iXyE`ttSq^Y#4j^8E4h{q^?!_V@k! z{Qmj+{`&g<{QUm?{r>&_{{H^||Nrv~8=8|P0y%%(Nklchp-e>O%w#xd(=GN}+*5=0gN+=hM zcc9b}4}zOFl1G<(#0w7o6qZT(L8{H5ZPoU@8&H#}1(QF6nq|E`h$}BoD-%jDYBGM% zc~gI=JHodSuPT?;Er0uO3u;nc&_qzTVuF=5xOEdE-sNG`ML-qIqi!2BpWFHSOAjvn z#m`We4+20O4ZB!3y%sk!A45YNpt`eU*v*=;J*Cx8d2(aP&{+*3G9CTP%s)=h@%PM+ z$P$7mS)McVn}dBL%zTg=y@_^AwinH!N)H@{8U&odgtQ6eljH> zN}6}9n)>$Y!F`2qkwX+hq#0K}J{;^P)n~p&4k4&&FRB*IFPe7nlA^7>3Lqw2*7J2j zVT%DkASaym@nRw60-y>DhZ$dak&wcm@g#+9HuF4TNqRu%OABe{_fNBgDeVJOOG P00000NkvXXu0mjfg_+MA diff --git a/recipes/icons/zeitde_sub.png b/recipes/icons/zeitde_sub.png index b887b639044fa23cebd9bb52e1023b1721016e52..a2d858d29bbb6fb494c9601ac8261aec42c23457 100644 GIT binary patch delta 552 zcmV+@0@wZ81i%E4BYy%1Nkl0v@6b9h;CJ_beGKi2>et?TYS0Z#FMO+F} zQY^U?Bq)fY#znQ2Vk;F~#G=${{i>T9&`SLRg)VegrBD%36auEL)ifXXHaESWbDYlH znA}APKASmb-ou#zwGcraAxMGn9~-_F+QP&bK%o%7w_<|`+1qbRF zy~DM~xK0fS=6}T_T`#-$FA>{n;2|E2VMgW>0{J(be1_u&ZS{nG0Z-3}A2xvfOhVhuL+zwn{MFdAhKjT&0za1`V93hdUe%%;4 z_`PL{0;W`>7Z@1B6?;Rp2IUq#QHGe{Me_aa@yTcc-hXE2Y%fghc1419LANr*(r z-e`uKJM_fUL;`(N#rz0$9LYagSoyI;D9T{#Y5knNw?|QK$C~E-1+9d<#E-N1fw(V@&fu){qTY;byz%frDg%AQq qARwZ^ry=~72ZE?k{%Qd$E&K&@@cjm?RaYAT0000sA;-#FZ%B8iJVopC#W;g z_mb6`KD_XHY^i^fI{6gaGY?`!&2!Ps3*leg0rnF2_lRb-ro3y*LoZ0*1f8&7di5W{ z_?yv-ZTB(ldw=Q7dN&ft+5=n{rIRh(e1cp5yeL|)9UFaheB}4R0C&R*9?fIjQSWQe z$v2#Nj#C2p-xA6dJUj2)q1kZozAAZ(=|#--KRmP^l@y14tA6B3M{_`@McMb2bkd1Zr=S5*&->zX*`@nn(TLTS)u_Xi+@O7Vrm}Om3^8DB%jl02$2$$ zUElDs&y!c$X4X>6iJ~l+6|^W`P9hmcs>2xxC#d-&Pc@EKz?+&<2FwDYWv;>Tf?V71 z28(Tume4dbxLOR0VPthtYB%!h6ToA!-lR}BRNHGf#K504B*H`R!1JB2)hSdBji1p_ z=xBJ{A3F#HM#e&sNN|a(R4u5ON^HUS*rKUm1Z=s|gA!GCDcMjM0PFyN03zRRZR?NW Q3IG5A07*qoM6N<$f(joRJOBUy diff --git a/recipes/icons/zn_ru.png b/recipes/icons/zn_ru.png index 0f8c8b74853e56913a88f5e456580c597f1475d4..2baa7800a318d9527bf45897897489854d5d8f25 100644 GIT binary patch delta 403 zcmV;E0c`&41Hl83B!AUVOjJc4u+hw(tJ`|2(_!*YW&P!ty4w@2cVX z&+hvmvF`c(|LpkwFShUe{{K(G@*lA7+4B56y6_{i?mN2h*zx@0_5CWe@Av%vF}Ckv z$n(VN`zf^Vu;ck5vF^j_`%1v^deHShyzwWq@9+8k;r0C{vjOgr&lrFD{r`~I_cXZh z@cI6c*!Lx}?&|pc+w%O)?fWIO@B9A$D75c~)%H%o@*=VBQNr>}!14I}{?zdNZ_M;$ z$@5#r^GuCu0RR91r%6OXRCr$P&^40701ySy`C>9NGcz+T-2X@xIFiSKs~oE17d3sk zpl;T!BQ3WnqMK0igO+~?r&&bCiRg-iAH~)F7ETZ_|KYdov1AlxE(-)v$;s5{u07(Z$PDHLkV1hG1#n delta 466 zcmV;@0WJQ)1MCBkB!2;OQb$4o*~u(_00009a7bBm000XU000XU0RWnu7ytkO)lf`S zMINy3CbRD1_5I=X{Vule@cI7R^86#R?o7b(iZ|N@BRM&|NsB`{r@kv@BaV) zht>8`!t&Yj{2#FHJi72ayYM@@@YwPE*YW%+wD0%){xP=iVSmW;Qo`~nwC}Lv`603H z!|MA=!0~#}^*_Av`27CV@cbsT@9+8ks^R(k{{K+I@|oNBliBz9{r@nw?iGT7?)%N{`z5pQ`~LqZwC``s^dPbBPQmgbvF=a7 z@?^>LTgLO>r+?w^00026NklH} zmfrORYlb5EMV6F4%rIX#U`x5OR)S!yVM8w1YAm2aS3E#F z2H1~rFbv}uwZTab0`JMQ0RBRB4FdD#c7Hrf>(}SY5B`1W4HkD3p_%OXWdHyG07*qo IM6N<$g1NQ~TmS$7 diff --git a/recipes/icons/zn_ua.png b/recipes/icons/zn_ua.png index 0f8c8b74853e56913a88f5e456580c597f1475d4..2baa7800a318d9527bf45897897489854d5d8f25 100644 GIT binary patch delta 403 zcmV;E0c`&41Hl83B!AUVOjJc4u+hw(tJ`|2(_!*YW&P!ty4w@2cVX z&+hvmvF`c(|LpkwFShUe{{K(G@*lA7+4B56y6_{i?mN2h*zx@0_5CWe@Av%vF}Ckv z$n(VN`zf^Vu;ck5vF^j_`%1v^deHShyzwWq@9+8k;r0C{vjOgr&lrFD{r`~I_cXZh z@cI6c*!Lx}?&|pc+w%O)?fWIO@B9A$D75c~)%H%o@*=VBQNr>}!14I}{?zdNZ_M;$ z$@5#r^GuCu0RR91r%6OXRCr$P&^40701ySy`C>9NGcz+T-2X@xIFiSKs~oE17d3sk zpl;T!BQ3WnqMK0igO+~?r&&bCiRg-iAH~)F7ETZ_|KYdov1AlxE(-)v$;s5{u07(Z$PDHLkV1hG1#n delta 466 zcmV;@0WJQ)1MCBkB!2;OQb$4o*~u(_00009a7bBm000XU000XU0RWnu7ytkO)lf`S zMINy3CbRD1_5I=X{Vule@cI7R^86#R?o7b(iZ|N@BRM&|NsB`{r@kv@BaV) zht>8`!t&Yj{2#FHJi72ayYM@@@YwPE*YW%+wD0%){xP=iVSmW;Qo`~nwC}Lv`603H z!|MA=!0~#}^*_Av`27CV@cbsT@9+8ks^R(k{{K+I@|oNBliBz9{r@nw?iGT7?)%N{`z5pQ`~LqZwC``s^dPbBPQmgbvF=a7 z@?^>LTgLO>r+?w^00026NklH} zmfrORYlb5EMV6F4%rIX#U`x5OR)S!yVM8w1YAm2aS3E#F z2H1~rFbv}uwZTab0`JMQ0RBRB4FdD#c7Hrf>(}SY5B`1W4HkD3p_%OXWdHyG07*qo IM6N<$g1NQ~TmS$7 diff --git a/recipes/icons/zougla.png b/recipes/icons/zougla.png index a33e3ab0c91efd143e2cc8432b554032e286720d..2f918ed8e7eb2ee7467c5323a4c9e545f96a3e46 100644 GIT binary patch delta 616 zcmV-u0+;=t2JHoqBYy#6P)t-s000000000000000^TAj0!B_IYR{#J2@xWH|!dLXd zSfqnH^1xQ!uut;AR`9=8@xNB2f;$HV0;z^P(x*%F!C2g{PxHc9^ubs3!dF{JAfA3X zT}mNJI~qzm8%sMIP(K|}Kpj*=9$H8s0s;YBNFiHEAp-&dVv)xz9tH#gpME)@fI6aq zJEMX-0RaFA1_PypJEw&_3J3&~5dj`23kd|&s!Y|YOxCMS*Q`z0tWDUhPT8$a+O1C8 zu1^jN1>LYv4-5s9RskL)5Dfg)0dinL<@00A~hL_t(|UWL=uj^jWWgi*&+e}gH^ z!_3Ug%*@OTyYD}dMo}bN9e+~Yo!ip161_+zn#O(70>wc<)AQiIXJXhi&2TtuMti^V zKreu-%ta(67--?Z1MY6_v!te?XAtFRR*LsCOsfm zLnJaV%CiiRt=t6){V>5;Y)lPi$bwPDm7onIX@o1mba0?Ks%>v5la@Ba%$>@rI_hGbg+00000RaF40s#X80R{vD2L%EM1_KHR1PciS4hsbj3VNFiHEAzex#k)bUfo_;x>emS6k zI--F)qk=o6f;*&xJEep>r-eMJhCGq%9s|;+OOp)&8wuR6Pu;Lk-mp)TF##Tb@V{2@ zz*h0URq?-8@xWH`z*q9XR`S4C^1)W}!B_LZSM$ME^TAj1!C3RcSM$PH^ubs3!dLXd zSoFhKOARYH00004bW%=J+v@A`2#U000003)NklR2|hG#`mX z!r^dad9p5b8~6n?A|lKp(BQ3NA1Hn1pFR--qu2k!Ci5GFfmiXwZTExf)r-@UH}F*1S{M?AewMiAOo@Z2Ud7*%n` zEEz%7y<(0Q89~V+VsT!s@9ymA?CR;M@qs=#FATVjQi|ca{pn!k)6szb065N`;4U4E zGjkI7NCFyPq7TwR;lSB0&9!}HU)r{Petx^(X!velfy}E%8L~31|2NlOVA^lo;~M|~ N002ovPDHLkV1iWD3gZ9( diff --git a/resources/images/apple-touch-icon.png b/resources/images/apple-touch-icon.png index 2ec345caa460276d2a18e71e88ee53945f1e5d11..57f89dd7ac68372d7889b273f019e4eae7b0b7d2 100644 GIT binary patch literal 14633 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 u++^jR=qkk}^2DS!}I0hPK%rmo1kv@lzGFi$h z!%-W1wmrJ7xcW6;sbGLRI|!8WYmWYd3b(q%ob3ibXP*w-eO$q>?^aOp)!(vz3xi;D z*2mw2p;Fj_t$(l7H9BCVumw?HjnDz70kS~zgEsyWbUz{wvcHMl0Cg<4f*gHRL9RZc zAZI^wLGFIW0s=o%0g0cXfXKHlK>pdtt$@(CdN`?X@pxiC6(ILh2nOGXu=oas$xnpX z`~-^8kHx6{MyqTrF!<$G!`<&MVAeDg82si87JXO0_kRfh@5kCJ?<)GrtNs7eUYw8K z!lPeTIrzH;+J{d3{A=H}ptl6DE{OHr3eGk@z`U7Ou=D-T|Bo(D&;err>w-idV2lp9 z1M32V4>#z5>eRNt@^) zFXz<0xqm<(jc;E-<=Ym}`E~`AzD)tGpI$)irxnoq<^m4iRKVjW7jXGu!DCzjpC4bq z>BklD`uhdkepsNtsp{{ab&p~C&w%^$n+mx6qyj!azJSw@E8z9_3%LE=0)AgFD8i2x z6yt9Tit;xF#ra{u1YlD43S6nvDS0xPOeT}bWF0b@OeT}boc0YAdWj;EX?m6b0000< KMNUMnLSTYVB^`4B diff --git a/resources/images/bookmarks.png b/resources/images/bookmarks.png index 6467e8714b83412a0a4f60ca4d1f7a1c413a3fbd..7090f0c631b6e08e94342c42276f4a8adea073f4 100644 GIT binary patch delta 1849 zcmV-92gdla56BLXB!3S{L_t(|UhUogZuro-M zWLsfEd)iahb6@%ci3thKc}jaudq3CfZ}^=1T+el1_k9a_vVW^hC&q(;gcEl&5f*50 zvCoZbOD5zJc_8H2o)cP}9=P&TL~4N&3_uU6&limw|MY?~NqHN9zqIsxN(%~J2kzur z29$SP!IcHyC@cNIy8tvb>9~(d`wxK3We`RW4g909Dw&jwR{{9b(R7_E8_9;LdoWSd zKn{${)vy18>wjNEw#=HQCT*;$k^wJPfL(8==cD>M*)z)h$vN@;UaSBvqtzmWW9|q1 z2!NndR0+pS@umiF$}NOrlv_LsfQ~0DQ{+tmR0+@EQ2>-Evj8|yAp|4pQ2-oa6JQj@ zY5?QSXCREE@_% zh%BoCLazcwh;%2=Xv8~##>T=uVnv{$LUh^5AV3MhsPzGiHvz~f2vKD#fSnAoBG7>d zAsAJ!ihn?3Q^AU{8X%f%9zY4rSS#2Q763vIg&Gl`8bOY*j9M$$SPfu2X$3nJA$vwp zk7@w0Mu43RQX|{_lMk8AT7^?w{bpnle6ab>l`T)4FDKMg5e+3x3 zBr5*|{tTe-0Fh&zz;l^#;~y{Nipva827HkbViQ55Zz%L*b-L)tFQ#4uDF4avhs1FR zD1VH`W}vl#pKSis%p+XY{PP)am;duJ?@<8|eM^Ku|Od|{qjl}f6NxoPpMOGiBl)D_+mkWm)9$w4BpS$4+uB*1WiD`j#~7kx>t>z2b9L*JA~bL#EC3+nmI}L0{dUd^+@O=s-u3*n)ve_MOMk~s z4Pb8ub@qL%H4GD%PSpm5@dU_MY$!B5oFjLXw8nyD=Z4vr87A!wRXx+b*PL`3duvGb zfNcloZZ}BK`i^Jk9A}uFdanG`)cTfHDr{zyCctU{<&Pa3?%Bc!XZ>>)4d6V$$yL*c2Q(nolAB$%)QJAJGrhbHM#!Hq;tVr3KPhN zoeWY&l0sc)!`yesk-x2<`|j;eG}nvrV*X12+YNl>y@v9w?Tj%U$-E2d)?t=CL^s3QhyIv07jq_ zC?d6IH&>n@&pJ0%w10m2;TnV;c1O^4IEI&?FQ0v(VjH8xKN~A{es0C#`M{$iUjy37 zp!NacPcX{33BgK9xfwtiAy_LooKz5K^8DsRFQd%Ao-6PBOwFOBKpRjx0tv8_K>{n! zZJyV`DE03xm1l0Nd1E;$uYU}8AIOG6MP54pV&!&5x$|4*z540L56&0lF*_M_m4hpy z+yHquh{fv}BZ@Mvt}hnM6qV@=l}OsBM$iRi$aF?wDi}UE9BU1`kYnc<&t-*e41< z*#uNFC~+z*y7Y(pc7NlMiOUWw6y;UWfI-O{|LOmrn%&8aUjYD0mhHJUa0PpS>4R(= zeCP6x>IOVCaml_~71;p9uJg-3?)nQB^z5GZAvq%C*lo*So6wyq{uxkv%_mZagZk8t z>V0^uba7J!0ndQm!~b0ZHkTHso<86BwO*p&riFiSe&a^Yr>3@zJ zb?HKKZ}sDelfw_To>s5avO z5@;<~wIhH!GY;TE?;z86#vOKc0L2Yl)s7I{V8{V6yTe z0|-?2zWAxCxZ-; zK&FAt5V1u933nLF0=TN(?gtj0~l)xC9`*aMU+Y)l|XU;asU%TkP7SjjtDF%f#C6Q_tkIhcsT!5=ci*WwS=)P zj8Dc8#oZLjw9c_DoOcBAjv!mbAjb9LeKY94On^umVHr69IY3xxAW_6Og|Z;z4jAJA zWPkPrR|pEP7$htPITwS3_XKhmU>3kEz|0OnR__K|O`$vge+e{`lsJ{5>Q$2Ws!U=|=iNAIim z?V0Y_Pwlw(O;NndEMU4?V90SlK7TH2vgW{v(Y31b6W8*qf_) z^zFL8SrrG!0W#_b$T@31n*yH9^;~AnE?X<2KjvP!G*u4H`HD$MAX$z3Q-5X!+wU6n zeThIZ#)~4;76NmAh+_TO#>$f+{bu-aSLF`@bOYZDaNqzr#KFPc6)gza2H&XIC%`-G z2S|$pma1sJ9N@R@50Co3L^MZyzz_+{ z0ZRtf{?fFx7kRe9-Bo=ex_>77^E38lPVa$qN-6!!Bsq9>9O(M{eRiYZpeBQyZB@WO(jk0L{ zP)pSr%rF~jsX8Ny`pmNczu$}lh{%x)V`1}9>x1o>p*Ga|V7sa|XT$+KJz~ZIhU1sJ zL~+iM37?)jHhUvxxPO&<`iv@0xZYhb9Q*BaGY){xNT85y4l5w)V`Aln&WiX;7WX`NflqF!T3estsdIHd;mslX#es7fwx-KlQic4#g+I_ zQ&?jQ%>mFE2`0Spm7;p`8thj@<>`?F%Q|7qMh-0NR0*CE)qmg`H-nv+Ncts(8y3-qA^OA5oy8eBM}9th4%Z zm}Bz8{t+%UuNaHA)ov~w4{lM>I*2?X9`25|)h-xqt33ka7Cl(=RkW?9VglZ+@T6MQ qTNs@`dULd`b{)&5<`to99e)GiI2=%2F^?7i0000d}3|Ln`9l-a48u z?I?5fW1$fHtAN8kxy@&Ijv5r-vMSuQo6p$!C;$4C=q{UinR*wcGdB@={)$hKi~1-g)>eSpXIfa3)UwtJsln|ZL|@qxefF%}MbyBNWifC*tK2X(Fs>ls59 zGVF{~O|X<>6*&5u>wwq12F8}pF delta 612 zcmey){+oS*7n^A4)vQ(jCkA}3=a0LTzVNGJ*#{uv1G5=GfRTZbk&%Uyk(G;)LztOc zj9Ey7SxA#v+=PXln^n=1Rnd!0T#rp$pWQH*L)My;UyMgHghwlsFZKe^5JB4-5qW(P z1p`S9TgB41K$BH0!c;87OoQvw=6?cOcl9x^JJ6DplAy_l7<22ld%8G=RNQ)d%hli6 zQKI#sW0zPt1ZceT+o<%teewi_y>k^+8;xgw*4y{?`;&VqX~s?l$7U2?(+C!{JMmRf zR=REH)MX4e>h?!Ehne#Pq@D0~WBeu3;3bj}!MNfwL&c`ty&0@~w_D+f6M|J#=uzRcWCo@P4)_PF_Bqb{C}(ueEqFk zuy!B!BK5cHbe9~;b3Q#K_F9q!*M#ip*Bh9wXfXWMa9GP^z``nXyWZ^lTy=?p&3p4_ zB{bB2zhJ|u(7?cSLH%K4W)q|O`{)0^cm8ADmG8{N0TKri4e^X@A`Mw03j!HWI5H-M zE&08?Hh^KGiT+J{VeJHlL|<*Asq-JGHZW{gf@okUpI`7tdclpe?mGY3IHIRA8v&K8 z*PghdmB1i)-T%#}DXWc7GjkN^L#?>Lw<3t4DzHI>b%6-$2Z<@~=5sqSw&efabo$rV kZwZWNgyb?$#e>2>pK){A^Ih{i7lYD_r>mdKI;Vst06og=0ssI2 diff --git a/resources/images/bullhorn.png b/resources/images/bullhorn.png index f94c5f361e4fa3b1103f3a8fd6ef3664a65b3fe4..db2a11ed33cd8d42270da5b68e19cd1ef7b47ed4 100644 GIT binary patch delta 2594 zcmV+-3f=X%6sr`FB!BWrL_t(|+U=crY!r1A$A?43TLdpsp?gq{?IK!(;#ug<76Ot; z1dNJAB`9d^%#~czS*eS}fW@A76yToy^PGlvE${~jR;OKp=IKRnI z%6m=Oa28q1Qi^qQN|9OM;A8DOzi}+1nZP2-66FiQp`WDa_>)6Y1hSs#r+j$`IP=#I zJ3le@WeXcGAb%^Gb;=i&VsK)8n$AxQLB5x4X)`y+#dhG1bHL@ zU=m^W4xInAs&l;{f8{KedpE*F0GL3fvCc5v-wlq~sp4A?@qgvnyeS(_``>Ir0GMKT zU!!stzYT&yH@3Q*5CF!MvqZiD48KaH<4=I&-^BPpoBIg@z+m5aF?uts^Q*N8-n8|t zCjbn%#(z7+$Sq*#wHi47C^-I=OQu(!ozjVd0LYxsNnqRu(7+l+>^8au<*E)HDG7i~ zMDOIma2$Ih#VA}#=X%K4P0uf_>e}I$0s+8Aj^gkoFf~5}#{5ph7(JGD&4*3iMQI0> z2mt-m|G{Vvml&CpjM^R3G(jWCE1hE0t2(Mk0DovHtiXwS!C@)m?u z*MnjH(b3@zQ~l2lQMpfR$7cip_h%!|^HdZL^M-tPc!T~aMi{w4-&AjipM@hg$Ul=C z@_#*-8{pr8-#^I>`qz1ad@cNbRChU5VCHy9 z{vPxE7#w0TyXgwRfb!vez<8_8>U4$x{Ef?st0bDodU?|g0B@jRC>U;+6`Zdh07^A( z?AlA#m=XY34{+cO3L?5tn5n)A~D07|x$uOVRbWJi-TrUqb8`N%9V*1t9c;2?&3T|(qB%;#r` zzny887>A|+z`9>#MdvpG;1DEqe!#nOa9k_J%393qapPiL%o^m<7y$lrivqAcQGZEm zy=Dn10UOsbEujc7sg#fTD00;1>=M*EaL+#`BZB+y|aG2KnD@dVeqA2*9uo z%EO;`Nb(r$4q`h1P>Czow!at>OSW(S5%T1ZLw}$_wgG@EE$y7RTTfTRnpe|e|Gs#{ z%s}Pkx?B#$Mi9^wpqc<^bNq&P1)Wkf$C3b``c;fM7j6nICjd@4HsA>(Q`gCKZ4iOI zbzml#0S4Sp0Gx9CiS68T#D5y6WdLZ9r>yWoFk&%W8~*}+9wq?VueFCGX3!~yueJ;T zt?d!Knv_hw$JE!nX4y^jzcfDbJ>BxMd5<};mp12z;}K>K*bXMZaI;M+HovGYI0 zr7Eo%E#5;=xeuo94^_S5b?WhL<1HM^&j3uXn)?>whE=Q2j@?6HhIlmD82Y#lt>l8v zhn2oo2VgHF09I=0{N}=xDKv!=H0~0fXhS2&FZP~Y)7^3acq>PBXXO07Im`2VGCOj? zdw9=00Kl%ccKa-afuDGe0QwJU0Ky3X;e(DW3x)(B89me&066CxDJzF9SCSjYJzC@% zT6FI))e;c$&(#|MPoQ9g5!17YhoqEZm9K<0EDL3>>D3pTZ+`{|@$>ZtU_g1{WkxR4 zzzQR$6q$v0?o@)XCw_O|WB@GI8-Sr@Lru}l5tjz6GE)CIMP#va*4FdQ0YK2dOm6_P z-_7sFnMl~D5F8Oudxt+;Qm!=t0DQUbdw_~UH)leD!-nH)B5Gb1#V)4}-A!!^0EBB)jqJPR|VHHLxjS2vR`vtxwoXLfe#bib@?F}MSFu!WRO&~nnq<7PR4H`J* z0AmR1AZ0w7tZ$gjp#S|0G>$q7HHy>7`bGeNL5)DGdxD_vA+o*^_5o$%PG{bAG~oLZ z8-HMv^$j9#!1oeE0IIMsg{*EQB`StXFov(ALL!k!Boc{4B9TZW5*vv`BI%|70L^dd-?x-f_W%F@07*qoM6N<$ Ef-#M>5{X12kw_#G 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 R10w(c002ovPDHLkV1ho)=Mn$_ diff --git a/resources/images/code.png b/resources/images/code.png index ba6940ab2edd98053e7998a694917035b1ac68d2..1121cc39ac78cd4e0b86fb1dba11232cba447fb7 100644 GIT binary patch delta 905 zcmZ3<60YXKAuTPAiY|o?BBJghf+G4WRw#5SuTW4^ z(rR{VTk(Lm%j;Kubn4ge*f0yv7qYA3tM<;1T3vMR^1r4<4?kAe$n_t8YrA#kY!l?P;4-hd z^smHn&W8BRd53xbdmdoT;Z}T-==Iz9>&^a;F6qXd21S88*D*g!;7~U*4*am7&{5Mg zzFB5Y;4DUt6?N;UcpdF}d_Mex2UFC)kK#)ecIoWTGiCD3{GX+H#D4kYX^XeSOSlT0 z^)hCAI8$gz!?)G0EEm-`@zry9cj*i4FA#V1$-e5Q+mh%ie&W0O5oeACucn*rZEg6( znd)(L+tvPJrCog8O!xZLA6aq$>G)5s(fg(ySk&sNeE=vG1bxgm427HXct0X~6?~nwET9-`WvA;jf^YlSa=96MvC+^8?zRsx@fs z&|-{jnXl>L;lTV*o$HL9?~j>H!D;i={^fa+soNt1z!>mofkRL_G@kZln=tJ>`Okp4Ev+`@iXgRp@KT?e-aL2 zDeo_vq8K$3XcW)}h3xs;J_NIZEcI!;xt!BJ7-;69zke=fNjR;2bNMpIqj_5YZsu&< zP;2%7W}kgbAM5+ehawitx4%29)~c0($#46W=@#;h1xMaoJ{U6dv1xt$g1?8>+Wg)k z(lKAnYI0}0`OW3mwI_BL#QZyOK=@Y4{CTJTOE7Nq-~8dX3IBfo&rj|ze}9-WZN43+ z`HWgAJ%it0w0A5F&a8USR>zsqBrO%5GQZ4qNwv+|DZ5zOr)>Qu=f3d+Bv*X=!)T*< WohMW$_cj9%c)I$ztaD0WYytqd0i`ei delta 989 zcmV<310wv@3#kmSTLOQ%NkltBpP6vt0jq_!Kii(C>R*GP&egc7+HN_Hi5 zA*n?XLOcJq7v{`v*?FEh^PK(6n>jCMf3x53=h!)koe9hfA%qaijgI&UA*augKsO3>I1)&rK!Y3pmLGqT|IX0`Xu}I|(~-b1 z3LJ7I@ImrdOWpAI;01W<=mK0pfwP5q{_?QEq~vGnN!tu3K9fns0uvQ$QeZY`F_y|q z$2s_o3qpvwnxM!@d!mC@Q52a;hrC9N2<!D5ih7q_*sDVbQWRZByH!$97D|0bz@ zS6BX)k=NO^((#bJ~U(WzAiyDAo8IfvnPPXfXWB_$K%&_;4mQb>oYR^GALziK<2|aOkV~D z13Dj`VSG14H?ZKBe3S8ou%v;31s@J$ei$Mf*zn;NZfJw323CA{FE{+$3K0$L_&f1U zm^6w97}$UD;R0^C2+<5I`S2X~nEs{L7}Vu_j}a4P=C#3hxo6&ZOW8yYYOzkP4%qRb z6*monoxwmvfkE8V8d1RZC6N8J_+B3ICD?cqrc9qMjyuN9`MEdYQbO}KFg3V@8#*KN z?F>@Xema0}V?gew1NjyP$BF%P2%m0nkJe9z^2vV&w0_!vPc)$Q(?)!n0jZxh4^7Yf4{7{3u{WK>(%piY1&B+fo(Dl=t{6GV3Kh4PxGSK(a zocw8IB!0=LDIOg~_N zO~1uZ{q!Vh8{ZiA(}f$;e!5U&-cJ{3R6kXH*O7qgr;j-jAobG)7}Za|aC8AW@D=EG zB%r>LBpeAeE>rty?y2q?TNOf_2ZvMA(sKm-`0?Y%j~_pN{BX@*JSo=<1C+4c00000 LNkvXXu0mjfh6d;- diff --git a/resources/images/column.png b/resources/images/column.png index 475ba430acb5b74b2181129b532b220e9082a68c..1c1aef7303f190a20ee9e880368fd35c69a3e0af 100644 GIT binary patch literal 6250 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 U3y|Y~umAu607*qoM6N<$f)ThR9RL6T literal 6855 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 diff --git a/resources/images/connect_share.png b/resources/images/connect_share.png index 6efc01940e1c8791899c202cebc77a5502bfb918..8bc77518e08ff46bf417547e911c20ffec22e9b6 100644 GIT binary patch literal 5654 zcmXANc|6qL_y2j#n6V8}#u~*jC@y9v**x=qv4H$6C%e14N0Vdg!Agb97v+& z^n)k)qAUu0N-6hm-oD^VkSHxD=*!%88kBERy&Us5+%@G!$Y_WsWiaT|M#LvwwOEzu zi0R1gtDi1^Iz|G87JBKK`0QMDO{F78_-4n;a&qQ|b8<{t`)tHb%?(~I2jvuG#aJt= zobt8fJ|26gs4!i5`lEs4kt0&!I}xgfHF$nZ{TR7ZTs)RpTzr^2*|5CDw)pyzO93%c z=rGsl2@V!cAOaxJ#|TV7)vDZJtHCAIZ%j$da-P7${9h3j1P5wRAN~GlAK%OHx zvyw`^S6S(e3hr8!2zC&+cie^1!;J~$)@zlFbODq41njx`@g8EmB2)$ zccUqGYUmj$Y3Ube1AnRCuMC~{QgHCXfg}FaBMfLVm*kE8QRH+FQ?ToV=R{@~fjsUpLI00VIR|GK$8jT8Q_$*8^Z345th zNLH463@7J4fR_+dy`Gq;f)W)KLO~od<1kLvJiOlMPy^r&M;#&@Ycy#Dfr0EN2np!Xi9;6R4k`q~Ad-I7VN0|W3 zpF;8k@%SnNw+ECN#zB0Hma*?xTTt0NP zRbU_qRk+)52WdL&(n98#*Y-y;Gf6BkU&RW;yYVg4O-!_6dnES`{5E9LP7FWs9ylbf zzh^_B{XULjYJ*)dN>?8^*I!ZRWPGqdJ$1z=>BxoXpJ@w`5}qZ(b$LP$n}aqB+AdUJ z{&1gUA1zc#cKoQ$T>ZG{VEvrs8HAV31|1_IXSaHf|K%C;B%D?S1Hi?j6n@W@XkbUd!SwkvrTEc`OSX1uU6&z|zz1u32M< zXfJ_TnYr?*`^1{-y@s)HvjdTjuV2kYf_M~e9pmR%Xt{sC3W#{fWct!80mK_OvP8_@ zzPkZncJx$ZqeRcf89bO$|0X--W)~N0xQQz#>Q;=JKWB^{Gk?sirl(%ey*Bi!Fw|Qx z9Ou{>kpM6&2Ftn85-8~ES{pfGQ0y-$8FrDAALGZjT#X8qVHLZ-sP?SvXvrD<|17_+ zJN<8Fj$*`NBS z2E`3W1<+Rg%L+9|KFHTBiU6?*THGSgaLqC={qWGMXxEd0m`bs0dLz-<>@;DFwimy{ zqudD=@|%InTiynLr~g7#~fGOuP2!Nq1p%1%&}@#UuQ&CU}*@dhmqa=V=J zWZ!OfXq#Zgq4WuEa^u#mI$jEb&QqkSFlR={M6wRFHVeEBZBDOV3^=d*8R0$Oj!JKG ze(s8A3PMr5?5czzbgy%GV2tX(O9sTAUz_vae-am`L_2v7xIddjyp47ig3`QA+u7Ue z_M?X~kk?x|-$U9xTTKR~fNY2b)i0idEI4X&`}S2g=Wx%*!;Fx$A9g>SGG4m9tFlbNAT7 zPrcHb-Mdjw#^wQ%a5R;> z8=7S|wRhi`wlAvLk8kkuj1Tfl$YG@~tJNRlSYGjdqxR(P!EgW6R(XA%mB#I-Ro@Um zTWl6dR`|iIgf9#+l$%mq{d&h868BrbtW|KRr%IX2MV*<~70UeX`TJIQO)QF7VLxP~ zQru)7JJKs)RO`dDBN-x3;isX2jD`X{s|<_{lyfK*1PL z!Xq7Uq7$%h`Ji2H2B}(|fA;B=Ei4FPK((t?(^>$?ar*xpbar!6Zsf~Wff63KW`KSH{8Uo(7qqs_AJVhg~ph+D81U5mAhLyT~oZ_4A zazK0tq2(IIEARYDEZT$V#R?)wsNrumD|wiZu?lRRmZvu5&R#Jb76zNQ7{x|Kml2@H zWuE6p^(-+^^2e2tbB}BkPd|^0pEuWJ;TIQ&G3g=7KL#tar47CtSDad}3FeD$y z0PUwxd5&Zkv^{(j-jA^dW;RMQeq+BPo7S2*e2>d!{ECpSefzmFEvVr>Gl(h8XZr#}QE}Tk@!ZD`US<1cW>5Lg_X#G{{#6!|c4dfE*%gk^0$8B% zdQ_Ah`_7IhyG8kJ+52Jf3CC@LT)dRmQdueL=~wM2Wt47Vv0a6tOb!7xeUEOOK3}KE z(*8*z1Cj+U<+;ujuP4HMH1CR)+?3Xkyw2!b)<9%OkS--fjw2Y1$n;=i7Hf90OKfb` zR>6@jF>!{g`YU&i@mx(z5G}4(WPqO2(Kd2vNYo7t)|p6gtGlUsm1{AC5$pn>X`l!Z ztTYJ1(`=>TZ|x2}`?BNjn}af|U4ES5Wg4j?Nq2_CgU#9q!DbuBf6HmHKGMU%(J(O8 za`fVfw)Q(R!V8(XjX->E8sOd$9`vS;t&Lb)NRLPnFbD zV;&O0g2yD~cteBRz0lGzbtRxYHNt`TIl$J=Jw`)A%p`xq^GTUSDeNixB$EO8#LdUN z)-O9ds&kVNnlz5sugU?-9^4Bm((Ltm@`2F3^u!um)Gchu{0{LIF_%z!MUJ;5rJ1O2 z5YE-bE5enM`yE8Y(Y(`7?M{wvB`6x!u|0C>OF-)_>Ax7fF8+_eso`@ zk32OFN9jW4ZX8OO5jLpdjwY&LHOsRBwRADzEiZ%X!n*)I*=d&g3H%qy zDY2ysI9~kWIVHRD4%LO)VITSj5A8>8NXsyZObep zoCr6+7uWgiY1MuT-|(C}ZlQ${n%OSuW|4MVpHK30TcE#lrJ~zvc#LmdtbISAF?t;oiq?%&~p=wL4RbC^kJbD`;33| zMJ*iL-jtWpO^V)Wmi1eHAYkQdIb#V zm7xZ(Q20P2fs32lxF?EJ4vvN%EvvqrNPz z)Gh<}pVhGeDuayWPR0?MU*xv49|*yK zp1}_sbb{!$F_uW!Etorv9~3%KmU=`Y`o}&9&Q`gm%0xYzHn-`7p1zWdP5ZICA#bVs(Kza?ARO|95X}Oo;aB z!^eLNb53JBMkz#1F?wqPY#;sfDD5o$Nic6`5yhT_BNzE{Xuk3|b%2w<-wya?i%P3H zfOzbO9HFg&IRKq=>i}>VAoM+|Wrpq1lOPCmerO>gB!Q865$&y;-+y@`26C^2{`psV zt!7I7DNt7!QSFhqw?S zlJkC)*1Yqs@Y>ANEdKpo`15NdZdz74BiVejG$n$SGFy}8Lk}%K$K-;{XMu~CmCI{) zj;?G@#uoJ+i`q$IDO{e)a}8rbOnwnQ7wDs=2)R{;4{0TZhe-LHz}Ee&<3Kz zv-)^xZCds+JAbxMJgkltO>~xjblOfpd&{KL^Rd&OJ_*Vqn!LE(O=sE=N>q6HoD#Nl zs-gdL@zrHUN+A95xFXg&dOyvuE+u@@rFkcEUDYM)bxXCgwCsM(m%;a~j~Pp{r>=JS zu4G+tP~F4i!O)88XKoks*W-CQ<>+_#rs;E$m<2oH51}PFP`<_;vb!5t`y*_RtPZJ2 z_j&%ZKHr@F=^)R(Q$%%`$-Lsyif)c_UkhA(%=}1=tCyA@iI8U9@Cs^d%Y$Lxdn$qy zmF#ZQpC`-jwyfhXECw$AGs+z0LV-|MI4RCw|4fqn^i&WmBr#BXZ$yeWX~5{UOvSxx zy#-ub^QAZ8HV$cP1ItfZA5pSaAN-0aMEBsA%`RNp`>4NjW~F?kr2<{lh%gglG=;bs zfyM2{_Z$=Ot+I$0)l4x+(Ngxz`S$>4`#AB0j_SK38?8#9IhOH=Vxr>6_$8@~?I>j5 zel{$^5mY670cl?0yU=Po!hLjWK=XahkUfdNr6D?1uXs-uR+zz11@_Mnl12LzC5IJ^ zi7j3J`kDRcF5T2CDpmzr$|Uw(_tPNDU8YxJQ>f9rr6R9)dwag7ANGH1GR7L{!E<9!iCEhWRjj0{GQ zBuM9YO*~#?vy8UK5l@H+iI^PZ_7{u{Vgqjm5R3#e8~CX#)uf3DiBwpQowNmlE*Wgi=p5U8xuS59Jh7?bqWC(6}z zCnBp$zwEVA*7hK}aTWh}=HKZLj*NLK9lDkpXa75#C$zKp@Rvxqc}09gOPJF_fRY@e zm!-R1;~_>nXI{*2{<4p>n`{JQf?_%ki>~#h6Lbk8N77*vCw5SdH6X~YYv4UG?W`QG zuiMJwf#e3}f?Ftpd@H}m*SJn4iVlkOH#8e)e|*35*|-#>g`Y?TT7Uh?zE-vB$nlr) zvE)BK2*p$nX_Quv4J(Z6gprDvpH&r9oNC&*Phy*4Xxdudi;PEFZ!$li2b`I@vI zs+3=uoa_NTDTvkxye!_l>k;@X{C@_QiD457j=G>6an=uV z16>tM?TF9YR2uZ-A=W4G2}UwOADR*dR{LPKXsAPl{psVktVcsB$fFo8Q(lxRSu2C(^f4m^8_Lx?(D=qpv(dNlrRjVU}a-tgZ1yyk_EYHLf>&m Z7(Je68jMZc-1B?E%;16{{wJEzBB*; literal 5682 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 delta 2644 zcmV-a3aj;$6_yo{Ie$n=L_t(|+U=cxj2vYd$1AOfR9b{cwUpk??0T*2_8P6I6n`Av z?p|ABH8zLhucDZ1u=I9j_Mi;`y!ay;FiMnSOpK+85IpVeUNJ_Zq|%@?QmxmWceYR} zL>lQ2KwGFS*z5YdyK{B#I`8bv?Ynz3yWc;a+#feHJM%uD_kVex=Xu}vd1n-h#Tk&+ z6*K|>KqCME2mpWp00;np000O8fB*mp0Dx3)ycutbXHz%Dvx&{|ZHemuK#NH{n@p1w zZ8<|AbOC^t>ix<66#x{MWRrISfEJM@!`)X`D!&Xs>QexqWz2u-{z(A568+7}w*Y_^ zkl0Y~`K0ip6Mq26C4USjuvN@Infl4*Kb}i1uXf*}k<}N`wJEyx3IGsp?|<7w08-C# zr_k-5&h05BmQ6lqTZ@yi;qDaxAcWF8)H{=ucxn;=gKqaM8%oTj>saMIv20=|00^rO zkWJ+(lsOg~>4}Q2?b^{bi>~?YIYUM^xg7w6c^DW;zJH&TcyBzLxY^ZT$R+P{Q2uM3 z!>Kp`Xm&`3Q~eIg=NZH6;RH5N)Qtz`cIbOmRWB@wWD3jNL@~^z{MyCq3zyb3Oym+h zr0mn@B#VZVcOeX{qb$(bC8}YrQFU{(sk3Lie`PWLx1{m@4Y|y z__?8Ca(}2=v;Y96@)5nonW~omG%0I4$s08&yqcU*4feRATQ|)c7@b{Z>|a+l_h;t` z{@VFyYBmx<5~XW!$1JW4>Y>lpQRyN%qUh}QMYrv{NE|IB){yd#ksOFUl30jLphV&0 zwW?cBO@rbm%ReKA^(E1J^7(-ogM;UJ`KIP56vKArj_3ZAFIi7yy zSBSN(UE6c<)Tfpc*PqQ4mWPa8jqn%3)$0dFFQdkOB3O!_Ab;mkgs*5}TlZDu96sR? zE*4|C?kfSnxOvnH?bN`}_)x|{MKiZX47M&}nAb*c%}11Ue%``Od*4p~d(G({?&h%IC?V*ig?U0D#Bw zX||9W_8%S^|Gy*;as|+r3C*V+-kp0tMG~$6YV7ND-C+;!ancO}zJ8D^SfF?=hn8eh zE8zo>@Ptf6XO@>SnD=r|KurOFahcz@`+q{Zc9Y18R9XVjaA5a*C&j-W&n50A$8ip# z0RD1=#q&t9k5*~uql&>+`?4rC05Hia?X2={&+{QgFpn@cDM|F}rNiAF2m>pG$JteS z{bMrsdy54fwjKVQE-+a(}8} zmn;CzMkl&;hbyS$Oge>rog*t2@#$hDf}EA~m`kIYJm+7-v!w!1f}k8N2$Mz84g2RH z6X-SMOPf7z6EsSC-=Z4fk`90ef8{O!;9WsG#irj}E8+R$0GuZIj|>1xK1(`3kDl|g z;N4$#n>PReVokl#JC@bGFi8>x2%W zX08VS)qAJVtBz>qHKCZA7H2GX0RXp`q25i6^rE2gb{0dBwX9|Zc$ygih{w=QVedEC zjk14BG1wPWjeUijw*(Cd(zbPRkAH%1F>x5{0|4Tl(=W{NZ#*zpj^eG4(tq>zIQ+&d zipB=GH!mrPm2>t3*uwxoJpTVol=PAG{Lk4FMsDfbU&?^M1?B<(5Gu&}qhqfz*bi$q z%Q}nEvrc)L(&Z`Wntx9#TrGJ18_W83zZn2H_t1j^h1^nekJQ;O{HzA9HFH;5oh$-O zLb%zi02~)sfZm$c=nt~<>3=yVe68*=%rz3e$8jzr(r?89fOCVLc8>p9O^YM!sawCx zyEaL11YdHFX!6SefHMGsF*%>AX>K92cd=gp6vO(4gzq`z_-u+q06+r);FCuHpoIZY zjY00ia~8B+p3t5xzMD0LTHrw^S|}=ehS<0T4_fPe}kkg8`5+1OR{#10cf* z1OOohKt>n{0749aj6@Ism|g%(yGVm)soaDiBNfc6gYk8PK4lC5NC1FO=VCGl0AM;8 z0AN7~0BF>bFvHvk0Bf2A0KN>{LrouPTg3rDzyK)u0UCSQ2LQ)qY~=<30Rq4-Idig6 zD}n()zyPpo`FyroHF0h!WbF?G009G_1c7E=V<(Fy?>>4-)vdIQ0u%rszyM4b5;XRC zdj6NATE43u`}F`ofB|UG_md$9B9kx&3V+5M>CZ2%mrdGb?4LJGjqpdWk@v<5pY zs1^K?kOFYZ*#hJ{0f2LZJyVZb!P6nULP;1-4G9_aovq1>F|rD10bTa zA35*Ru^tMY1<=_i1ppih(t8HW0vOiKf<}8GYyc3<-wwm!SHWtuek(5Ct5@g<6Mq0S zZ2hTTr=K(}0QRwQM+BkMouL{3Z)NWko=qfS&tcO7U{~V!x#+MZnVUB1>&%+E;2Xvk zuPZv*$fL3<&4EBBGGo*v528WQh2w7v=eJni3O z->T@gY8fW zGlQigSZh5;KuQDl7H3lEnss^4LqW>@hYA25Qc(jw=F+&w)C~LZbiH5K;D3%;ie_$g zt2n}|k_5VqgC160aofK6E@5HGyH+*px<^L^#u<{GKFPfYR}LCP+n|Ns`q!oN9E5w6pMeiJ#F^1Y1X@33kJ zR0dYE{En>72b!kcn&m?(L5LXUwTf;%K+1YmlF|>7d?$=fUdsVcDf9bwUr1y0N($q? zM^V62J|c%Bc2B>ZM_C1Y!lzl=sHp*P940bW8!ry67;H5!7EN&UMGWgR^mj#jntd>6 zi#7oO1OPw)00aO)000C4KmY&)06+i$1OPy4m;V7fAf9gKLMJ@{0000 CV&2gJ diff --git a/resources/images/copy-to-library.png b/resources/images/copy-to-library.png index 4919166cf13da64729214c87d95f8a94e2038e51..17e874e8e40968cef235f426a02ab92ae75b448b 100644 GIT binary patch literal 8144 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 diff --git a/resources/images/debug.png b/resources/images/debug.png index f308a8d5ebd935c2f30a6509d60d845a37b70b18..8eb68add579a92c3bef8b6f8334516b95d722a55 100644 GIT binary patch literal 6821 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 T$k|mH00000NkvXXu0mjfL`VsF literal 6831 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 diff --git a/resources/images/devices/bambook.png b/resources/images/devices/bambook.png index 838107494609ba6db892c3677f8b0ef7cbf0fce7..8fa5aa0e082ca40d1f411b3d850a01487f8bed20 100644 GIT binary patch literal 16805 zcmV)cK&ZcoP)!(;^LfchlH9zm7w(IMP&$Vl}@UMxoMt~WUhG80@A5`1~@h%{+5w?QK z@i{)CPm^dG7dI|FAQQ@Xls9Y9fD*CFV|6V?$ppnOFp;9@385m7*; z==;OWaZ}Q}pbR`p=yW>c@mO9|Lyx}~i-k%7Rr`VhC{eyZquJ_)r+WSS|96RZ-Bj2eHm!@L{_;Bib0BoMx&ws0{GyA03QGd^?PayL5W>3 z%zy7z80I{FIzO})38y4i`274D3pik`YSrdf&Y?p__rty7djsSuF!Sglo*RPo74|{hO>{n6meHhpzAee*%2m}Hm zgjsnJf`FicIN>;y^K3a#-sqKgR(WrgmR@+F+QGSe)ZK?AA0Dam>!oaTy@n|VBWA{ z!{LV??u-<;41f8{UyeEE81ny)JMK98=%eTN2&!E0L;$>jUmxv%3j9I%`}rJ>O`A5k zAUTUG5SR(c5l|vOwK@3UTu3}LOm}4 z@Sd%=MNF*>Dk2qqD zmUrKM&ypnv?6N2i$W}1s7;AcYyyRl`4oojKgI<^gqku;xn8Bk4{0b<5iv8HliZx^VrnG?JiE*95jV6T4geb#%%yuzF|O4DE<)XAN9-surTBUuNek390ozy0lR zFm{)AxaF2xivPH)6C|rlDmWt!5Bfutn=HL4G<0a4`}Zn)w1zyCduVDGCT z1x6P5qY_N!k!nJn-VGSwO$rF=L?Jv<$qfhcT2@Gr!)8t*P)`78xpCu0B&b_tK@oT{ zlK-~bZu3fJO}!6j;jK5(A@u6qnhrkp*kh3kn)cwSr=GfU<;nqnBsC8aBo7vYiwJ|x zU3M0O>C=NMhuhaV;e-=btvc+QYp%Ka>T93)#3$lpOtX6Rk>C2(w;ulRhhzJzuKMW< zUho2+7^2RQ(8Fxxl#$K|T@xNtQ+E@~C!TmB*mmyR>RHetpcF(xWlkbkyLRmdKJbB} zysvr9Yry}Gcf8})TW@{i8{eqIJMQ@BX{Vij<&{^G2Qa?uvd6;v)KgCf`44{ZgWvq- zH_XH5KmYmXo_p?Hcm3N_>T?PlQ9qa`VZZn#L6O(!01>3`aTQ+u?QehU3*st{ILwYa z?zro(zg`>6^s}G+?2(UrUylSu~V%{Og$*y(3{|NGy+=ITHF=!aLGbIv&zUhtS}uet8}>;A0I zd$#ZR%Z)c3v3dhe!~@;T3b?zXMlfBT!?|NLjaT)ylOvdyJ|w%xtS+akME zUc)8H=J{@mToPmzW|z6dC3qAa>_2rN-H-WT20wv0c;n_mZ(J;j0B2`cd%QsGY&t+6 zqkrY3!pPkNJZ^5*JC2-WU}{6hGb~>H>Q^&hfBDN_f{--wpi2#Iy6Gluno#)o$3On( zKmXZFo^i$*JVs-AJ;IDVao46h88KvobHYFm%}mbEKa1Hr07h|=l(A1kVZ->I?K{)x z?soXrC?*|`8VJ}fhrpa92?C}dzd{rfK~$4dFkHvkhQ&B*@Tf;UiuFfw6a<7NXx&1N zIT1+|$^!X%$qO#HpeS;GCXL&#Hv3dYq>oftxmyU<#?bVV%afTDU0 zn)TQ=qm0?Mw zFI&Dmx6M-y&4?44MH@OrZ!i$g`TO7ho<7b(RNox2!hZib=C(1Rg|lfTEmKL<0U`sr z@PIc=#bmbzuyQW0bD=P|AnZJH1{*Tq_uL7j+0>n#h-Gsb-ucdVzV)qdec8)i#wUEw zd)|Y&F|Lr2NbMj0_(!@KKS37&pZw$}RsQ5BKY8E#-Ulb&9RU1`4}IuEs6pkQ{`9Bs zeCIoyj&FYRo9}wpyF3U~kfaL|=9AP4MA@VaL!|JHZ+ye~iYu-#`j39}qfdS6Q#vsg zv%nj20r-Rh9*FFF-}|1h#~=Ri2hH>r>8MW^48Neujy*J~WOjfyAy0(9``z#64L3t*ra@G}~I_q*RI3-S{lBgm*doJ4*C06_@l%?Uu3 zrpOQP0EB1}>R6l?0B>rgNnvf^js8%;b=O^Y=9yAnO zeo=i=fw(3o&=d1uP8U%pw9F?y@d-nClhaN+jk5QKAV;h(ed$Y$iw+nZI`oD@2*Sf^ zysGyR(m3zD^YpJT@K4Z*dnyr(sRXAkJFpaH!pb<9HTU8dzZlcwEy#fi9t!9KHmLw) zAp(Okh~IVV*6Hx^kAFNVl4tm=XFUrxdO!yvyV)E#m|y@ZE(hnB!kIJe47ngRuc3bw zpz{3l&u?DQ?8cfkYX}CtJ?vo*Yt^ORyj!zG>dh-g+-28Q9$AV_b1zOJh`9^(Om;(d zB+o~}L1}k*RQ;RY^d`3Vd*AzBXt50NE76Uo8HVqF|NEc*^rw>$Fpe@F^O(n!?Av$O z%-tp4Ze12%O1u}zDUp2L2`BuYGtNM8UV{YEd71?+ zBFd9bI?1!kmo3w+Q8sPfeBp%`G9b`_W+p!JX4#)3YMFr;1&{@EW>q1TC}z>Ri1Skq zNy`X-+S8s!?0@WIAG`M2Yr&cLC2!qA7St{OFs^b7JHiYw>~l!Fi^Z|NmV`5_KAC}8 z*%N>_H%$w8uoM;ZmW2?M6Cq{06q9BFXiT>)CQu60I&1E@gJDCY~7jt{cA?3;g}|DQijD-pZZio z@L8Vw+~*2Nls%Sc5v6$SV;>tOA+^74*x>y=>(Xo!y;kbVBnp_HCKk0Mn9LUC>k01w z2IS1H=RNOvrf8Y{n3&UrL@3%^L=0MBl^8$@nTiU|`~nD(#?Cq<#sN%t7!ir2O0-l@ z-Z0=(n?D+N4o|sxY-NrKJcT>BA-B- zZHEsR5jF&@X<4ifV7!Ypg^2_ZKmYm9g9Iti#B4wflFVRenm|iAULZ#n#~yQx4q5L_ zD45bE3808sllH6!Qh~}R3{U_&kz66(Q6Ax;4Z<=m2pNE=%~+&@S7I3I#JFCDNYIbo zpq|-y*QQNg!zF=Kz^bH`o%cVhxCRv#wj|g&;g~d43fHDz3+doU-q%My@)1H9mf!vE zcLBx?EAE>x`0$56{Dm)kK`2*&-Jp#G5O>9Haj?kaSHJpIy?yY5A0%Bo4mkv*O*vs- z{_>ZlH<|@TdE&m>Lzlk)`Okk&=IfSVXIs!692S9RmwMI}LeYj?VRKM7o+5q#hD?nM zcutK6Q4ix_Hj8(j|GO>fMAOM0W7E5rTyhDUz?WUWe*Kf4^dy`I6#?Lufr5cXc*ZlH zK{)gKX>NS13_rY13}C@iPCl7FCNPBkfkY8#hHIN$ggSH$WVWffY|;g&5Jh%GQJbxh zF^0q0g4P6v#)X9S?-4Z!l#EfYe=p#v6^9(66W9|KMxkHSqsZN~2^L{y(E~7f%DrT! zVM+l5Q9sO-gcmPT!0sdgv#!9=f8TyPtBsPz)6ga1kwsI_%|KER$f!AGCIuOTM?I($ z$N{p#nEfmUUc!r!1(sJzpWX@y6NVd zk;vKW*P|fDn7R6GI$;(tzIN=u`m0v1Y&vK8+O>p7slZ^)`wwiBxPpw7!o$xv1I+S6 zh;+JNhi>sTgfEUBZG|N7U))w^bi5DJg< zpLhOS;kbvM(2UekN3Gqk;no!^R-Sd%;rcW%1bK`R+7&0M7cD!ScPzU26 zX(2=s;GR}_f(kDd=P4Zsw90t|Se$kzVzqvL%N2N5({EI+_AxfcytM za?yEnk_8z-n^JJ50=y*EgJgBZqGT%|vQkS>)%Lr$?*6W3d+1d1I7SBG zp2^**j-mqFwh{Q{{ecG!nL8+@2XSZs7}%>9Ghhn*^Ugcb1Yx6b*^8-vbO2X0p_k$H(aFA$zOm@^VE6aKxD08Y>y zksr{G<(t!&@wP97GB*9pXFdZYjL1>Me6{DzvnV0TW7M7#FAhjz%SogTcox(II}vA# z?-B;QPyj#q$xr4E7^TH6%_FD9!+tDCLZ)7|_bgXMnLE&uI%LsAC{oZs&4UhHI(vhJ zz55f)m#3;Qg86OnQXT+=lr!`k#R({8XSYaT;+H*0XKRyXpncJTmNhEFsi{BmpgBhT zTJD-)KuDtueY)GYkZZ;nQR4N?x)RjfWrP|96)LyDNMoHhE$$Jd5d4lwOHF26?$Jar zGEIQzD80X6aI>^TxB9{_$qX(c7RU4~HD&=Wf5j_a;X&@ZWIWsLdXs~4o@AidbNUN{xQd2YN1DpAueCBQisd-omzUp0ss2<#%Sxl zo%`Om*{q|Dqxqu$TD1zmTdjn%ZXM*5Y;RVrLws}J$O9JG~pDP>Z-NXSs3}WYWxYAjrRkYz1~5VxtNTYF%**!IQtQ2 zo+)kv0J^`#eymUH7xRIN873I07xNJV;1qBjJKtjTE@L(KM0uPD@?V1RN85v6& ziy)O;idoz1#B>qVkq&c@#O9-;9#SOB+*4U|aCNubuTaeO$m??sfXY!!D30YvVC%(@ z%PUzh zR8X3^28D*EZyKnkN$Pf%%h71Wh{KT;!e%VO)Ak8`(y_WZ`C*inF13g;i_(EI(AMp@ z|3k|jQ8NNuGq^w9Ut#94R}v5a6gk%)PZQ~Q9V%qLSt8OEHIxnn_0|9p5)2570>lO| zU$eJ_0nHo$;2+3QE3F~4j(}V#-voYbFI&p8T~R=2P|0e$rVt-&pg^s?vJt%`Y1DKj z#Sd@YdhO$mJHA!-tQpj1L@%S~9rtd(A6&6FVhASzAMrcZpEEXM93XnqXwKr)LX<=kRZm@ zCe;f(*yY2y}eu-xCG4y{E;9B%1N1c}&Cle#HZO{put>Q%4G>=1t}!yrwc z4d{}zpmdm@oDt&6T`-dz$cua;t2Fki8h9Uqtu^!iOSBf)Zy;faw(j(OckR=(ebVm> zILBDjGvSG?;kR?w{)R0pD;$)|AoN-miEi<$6t4f6joa^qeQgTsmL%@(oCz$(8kN&; zjomO>dRCaPsO5}Y$P5A&VaDT_O^8OfY}uwOM_EZ~{=@4#cc5KVB`;bhD7n|BvgQe% zW+wM37G6@x2=;|RApOs~b0->UMrcD1^k+^Jq32RF80YdN`)ck>>P3B0QX6NZFG#$}qU+-unt9H=z(gG?_am z5#;ob`q|UFty&xcJ5EOZ^!*cSIY&Q zUN#_~jdw7_d$WlkQOxi*DNLS=`3V0B?E&DH4I9M%O#i^XtqX(^!EoY9r5{2AVVJ zuV>5LbxAys0ayy>QOWKmoIT|$TY8ogQ5*@|X+Hz|F6aNz@)`V>ZD>$7O6%bnA{+s4 zLh^&3%Flo(THO4Qq1yo68SNsJb~PKg?!KsB!0QgOjZ5frsF;pNU^^!5joun^7%M;& z_ANB?7&i>eqbqS@qQbY97bKaJ3}CxKB704PIUxtzoRVP7@q}eDV=(JEJ0Hv{Tfa=; z3t5sDM&~8!Q(~D*sSJ`I#xGtV0&k8)W^Z46mIU!o9+ay=%f(w93X^o6NBVHi zwJFC75%|g~lk<@9mGK!xz!YGAInvN^-V6$x#POp%zIegrCJQn-GxgzW^fH|g2Tu=~ zCL~fo+ISx^>rx}#buJZ3dlAD4NH50pvKR;+&bS9(;!)sX8VCMP9#FyPLlmJbLhd&# zyp;q_c9j^hf1GgJ6q(w$#i0pi^Ks6?`u-{F!rA|PZUJSz9el9gojF9$Fv9RFRxIPq zLrd^RgM$t_Py<1xBad9&UTOZx1n`Ik3hwsLvnoBS+0Og!eXyE+cS;hJF?_(%{g>>& zpBe)vGZfNX*$}y@fG@b&{%5Jh9#OM9s|mCmQgeY2!Q>USrHEy&=X}qv?eg)2Z1E7D zV*?^1=AnX*%NT4X5CH8NfceCX#d=8#Y5$sW<@pQ1_~}o7+5%*9g%-CLxt8q5uYBbz zaXA>&OGHripp6iiavYN)!TR^OdZs`6cMsK)JB9BlURCYwgydb8O$eHRLX(O_=1Fu4*89I1V%dun?V4o z_8OxJ3lXg<0i7fX_2bp{!#6sJw{h! zy%Dzd0z6NMN@3N27Hd~alMWj5Ma@!>5c(k=pq^Jr>M#tW_1x3}>ePOdW{)P~y?_nF z8PMjl`M6_8^7$iTz9XaBuLcyWfx1Vaa@b+3+yzN7V}#|FdF0Z)OA;xeFVAflYfb3o zSmyofqy^;Y30c6KDaZRSr-)k*JS_`Qp0j?v_ve^2J0K*52)yBW=bvvZH9?-%&o#{& zFm$t4#XYB%?mt@+%x@UFyHE|o84e+Y+IA2mYG%VQNKwrFa87@u1tW z+f>rH-AS5jMjRnjKlvT%A?HvCp0RPcf*}$`^`Is#_V%~Got6>5VlI#eQs4B}E=gvJ zX&<*FC@4@F6p*tGZPPC;Qk$+0b~98kZY}Jvaqz&5-h~-uzdLbI+zCGbl($LUmg`}8 zNI3yTAk-@qoL65^!DXo*&TTf6`!{_mC>J#9%n@zeq*M`K!zDA?6|0>JwHLr(E>IKb z&zeRJnz^PCw2VgmCQdV%&SURPm$Svd9+6ZM@vlr%|Z=yu+QX!{v!-*VU-1P2|VtKx|$ zBVrwE=_m2}VqTCsA_TCZ3uxK61Um*mls-?TR_;(o8HR z7BCPYa0wxn8iYiO9QZH%EInYIsbR1211WXE282b(yCwX<*Eb5QP3TDjEe$O&U^u-DY!FIfAuSZ)zD!SB6Ir&$}s5z7r+@uYQc4uR+1t|#} ziGgMifZCe9L~hZ;Nze8hqBbV-#XaKFJDVqDetVWR-tSuo0YS**G8q6ocRBIkRKu=h ziYmYut-#4s`3wC{jg3W6n1Bxyp$j`-%;R2yKGdi8@UuDvSkLDfk-9HYD-QYhU}?b|y1b zPs}pSB$azg+{W}>)5;+9(*t6|W|IYiAS4nX=@gKRxOOnt-j)OQA0*Ws1+T;j^jwGu z81Bj#(5yXb+sew?m5h8@0ucuVfq?X^G1=x4>eQCJWVBWQ>HuQCZyR$y~K1iM=_h&CFx7beKtm5!XCH8_Xb zz@F|9OF#kMbN%{9x`j0DpF%|0_Mim_lWYP+Gym+-0-it`?hwxN3EK53+E)P2BtCFA zkIu6p2WiZzFjjz!oy75CVSwrtz@}bh=18lIvr$q>rEia4s?JhLfV<_XJd}_dp(Jcg zO%&~LHcG^Y0w@sy?0&LMo{RVIhjZy8L_-V|pO}H=9uX{x6#Bl?%h1Yk|NpCf*#|k< zOHqyjNfCjH4enY@!nwDcbd!XDW^q)4HpkB~BOOlz=Tbt(ieQ5d)IkfU7M>=VdL*~L zSVAjS+aPT$bg})N%XZA(wFdGS&$4Y~kz9J|Q>hi*7VR$BWj~Z=AG~cFS4~eQaI1od zz~V6sC#Ckzh<;SgFr4$jB7JzV-@uD#c<}8L)3&y)^2v8sAJ_#78?mlBGD!1F6_V}6 zr*+$20`P7)57u5yi3{yrjwT9YH)0bR?L?uT=^ChoB9ca$>2N5-J!^l0*60+%3;00| zallBcN^8uBiv_33bo_u^gA$V>2$f0o)|o&e-Cqx}JMMIMEFxgal{gBg#Diw-YoyN> z5gV~M0V05R_H3E1#UYXwE|GC7-qT-&hnqfb*@<^E${?D3Cu;r0NCd3|cR<29&UTEH z;>y&)oCE}20UXY~0tw6um%A-a@X&`o1O}XYx>m%7s|HIf2*rLM2Sa-hp>PT!uQn;w z10v8~Vn}ycQVjSP`_lBl9uzP=-3T5T1aoFt zW=S!>!H?l^l`}kC>YyLR732}s*UVYVEVzCti(?iBF2QDj3gkSZv9lPFb5SFB8;+bn zsrVZul?z2Il1t*$z9>5V%F>%019HMEb#ApJs+t zZJGoYtYFW=Jqhe2aYNX(MwCQRj2bo0;D91F&M1$m`!!92_@jR2pOZT894Ia=sk)rJ z+?-o=ZdJYdzVpq4z^-#27RwIG6htXOM1fIia4f$5gvFSA_)3ldP#?B9TG{cOikTs$ zjXYX2@SBfvenX4+gR?P4yD%IuoG+~ZE~(nFP_Aa;D_BO zwWX*HYTq@EDAlB;IuQ7g|2kBs73EMHGyJR%io|KH%>-RQ)`J+K02PTiFD!&pIUm+vy73&M-?t^W|tinOJ0#}N%aN@@TAbCg1#P|+9AK>{~)eX5>>J*7D}bD2>n z1djnC%B13pQo7_Z6okD3>|mmYFpn|w`UEXVXF+WzIC%*6CD;pdA>hQ419t$%sC>9+ zan2G)1Q?hQ%mXCOJo7A_NfEeO+&#)mhaS{l6-}f8iHlJgVIB`*s^vDuQFU{ntjP8}r3 z=tsfF6On3o%ymhtWR^{kg9^HubR~#1kT>lFHHL9y;W9xZZn~GCk3|+k-X)h@!i&Re z09CN^AX>P&h5PpHV;PhH+JJVxYu9efe<=e1gw!Wn5&)-KNKhYC-W(NP5gVe3c*F&M zO%%K?DD*=RqH)LNdJ*fl9}}`8i9tzKM$lFvM7_p^gH47Xl36U{Cz2CV%TGx6f>eMX zcOE!k!{S?Sx#cVS??;Bv=ED)K$dg&fmtuhbR*#I+v`2!*I&FFTm3Wf-YuG7QhgmBCTEsIQSs&=-Akyd@aoj3GqW zO7;K@FdJqFOj7T|*r8b8SfJ&=lGhroX?>^ZSK(YjEl#wGS(c|VZ*i6pJI`DNHFAT> zi2WP0xL>etK{CN~Be`pg4M7IPwSxhUl7-e2Ic?W29D`V75fusb#I1*qAKHK+0j&XF zFpKGmL0e>q0g(jnKIa@H2{p7NG^)hckW-uQu!H(1Eggg#DJ6Kpv_#@i2mJNH{$Dgux2 z*HoykK^uc(4SUv4vMWUN@$L)_O^3I(`edL(Am{2km`dmZ72`bZNCyDe&6W_Lh&coR zGQb38f!x5>hgyIWV2KF=^t3ZjjNl@&+hgucy)+Gro$4wz832BLZK5_VrG2M}rA_Jh z^1jc=1RYC}wm&F55nXI$Hnhbo4KGH_##L|4DQG@0rVs=!h6^sZK-4kvpPdIzaCLqz zBQShaX}pDzkqPqI!PL$vcfVvz@erQH!(-!IJDFgTNhD_t3Aa-Pz_#o-S^)2n6g?Kpiz<2vUkzz4OFVWCQ?SH=SX6tb*A?`GqUQz^EQ# zhfxH;gBP88DrrNOtRkL?HEQZ01a}9YOgF>6EXfHV{n}Dl`_|>ItiaHw#2sNswtqw@ zQ61IigwE2iRxPG*m4&Euzh;YDPo#zE+UH3@N6x3B7$u{xx#pTnFTIrhhRFPw3vayf zMnHT1`R8AE-E~~;z2}}wf*7%W`0qJIEd)T0;y{2JE-1?1Awq&rhJFBb;4!idIB%(Z z{Ud!OD0u{TbH0uVsiLz{U(9VZR>xij80LxEWx#wit4di;5=Fo=Sf>4&?r9();sQ`N z#Qs2{e4F>G{V^sosc zMV_9Fl}Y&2kCp!wk<->46V zlht`aP01Na#i2XLlAB)i2OBXt=cWiUWDPYNHnIpk&>(-n$;3o|UWqD0x*UmBK$?I9 zlm(PSmT}xTFa0_;ntx;NC{YnbO*o} z$T8wNr|sTN1{q3$E$_Jzi~x`mZ~*ebbhsMsX^?|cdhI4vldDI6eF~BVeuhV*1WelK z9AA!LNRGa_x<>DdB6A1pq8N?b5 zC1^onf$RWMY$aJ^a{&oE6+^GaT^;;`+RKZ?>amsTpe{l%2oDGI$Tx+S@D<7dHQL;~cY`TB|f@)%aR{JQAG|; zZ^dHFIR@nccvdD@4a$-S4OSEY7<`mJ!#u~9fIK{tjP%^h&vhTon z9$pr}1E_vgT25nn=BC;((i_7%)#fx>jh@NDgIhosQQxYTSOvg?VfUYC)mkyhfDWUR z4L(*Q)uAvXvTYw|3OpuhiI6MZQPw_^-$C~5tZN-4qoS?Z4?8eZh3ZXZ!Y==JUodpc zvJsY0RLWx?TmRXRZR<|~FIk2>6c9Rin=o>PYDt9$O*ivWoCT{4Bq32XTUiRkB;hAM z2J^5XuG8{fPGSeL0Li;4O28ZMCCC9DSTNVRDhVkN9hE&;4!U;$Ja6AYa9nm^vW{L| zx2Z*6*cA!(QsakH{CmjjWQ)6rZB}Z_YHa>?oK)aQEo-bYd`2J-o~7QM@4E2qZZ81x zt4Vqwn2KxL8aX_#Y+TCNn$Q;{Nf5fMcNQAA@=%g8gyBp{Aj ze^q9rlZ;R0Ax_y?0d~e>gahmN9XfPSDkaK-nah+30GlK6zUqgmZLDKZBAMuHiw!`~ zIbsqL3v?9@8}0^03(C6B(U`cfN{vRihvu) z9W1=jB&ivLM>tA$6z;+EbVQqD!h(~^E>0w7#3JM3grF5U+^op*t<62jBwzzP)d9>8 z1UR9ck`IH;8{!WQ;2`oTOaVIwD1P6)oA^OcU-hknL$U)cm`~3*cB}{ni40GypOs2l zVe?7fWSBJo??k$`f^E5Mlmt>c06R)bXHB?3WP(Won&_JhXuw%IjdF4EUN0J&DzG|aoh(48xNT&T1x0#_8^PvRO+hm2Ox>5MrGM06sg!>UY{Yas}V5(VHI3>(zG2&m21FsQqocaq-wxrGX$`PvVP?^oq7p+0K*SqH9J zfQPN0JhePVs|ctUSuzs9O;s0TTTDvY6HjmxIVM@%L$PzVk4!Zk?mHb?GK5O9NT18r zj4!CUz#UZ~4QKlA*`_g+EM&J&1QdcchcU2*((J`O7Z`Lf9 z5@^gxxREM;%t!UM(6dle?+)eYw_;r#jYiN)j%f>BRC8?M7J`7%N5xQRpb6|_iw zoJ^4r(+juXXt;CD9Fb>FVh2k2fpp}!R^8rs)c7>?9$SK@txm4vj4g(;c3)Esm9m&a z7KGZDT3jr_%F>RQfjt{V!}Yl=BlGf7VK;&>)@9?JSfH~b{aJMa;RP_$jd=(dle&7Z zwl7=WSVt!bkXoAI0GQJb+(T?sdK0HC)<;dnlBg_`613#IyjcY((ITw{ z^&IMkW&%jWHenptYmw|L1>p;>zk6_80RZ#ZbyFqg9nx)YBuqD&w>d6WCC$xp2qeYg zOVnQ=8nIZ*sZua#fW1h>#pQVN&Qq{1Gv~4dPY^DPfpuN%1bsu0;0%$_b}V{md2NVr>r4stdGqaj@ZPL z3yQeIzp&HBJ+K2}qhJ2g9X682CrQjS*uxs`e&nHt=i2#Xb%aR4_?VKitwjsABQ|-! zKsKr!hEkBGtS(t&RV(XHgky`F+6EK}Kmrv!uE)W8RoU*g+T>vPf@}CWm6Z4i6NZn? zdgh5e7ahe60;PaD1c17YppLbAl@=FH3HnUI*IM#g7sTw!YvRRObsiGhlI=$w79y;S zz#l0Di?wLPm9^&DN5!56-WrDpt_>5UymCS*138i-N;?>5U0?}yx!>fiVe*0ycMlste5aoRSFKjTNk zlTnm!$JuXthq<|2S%bvpJ@+9EJaqJt*rUYMC0h(fUnR>b*~-=Kyut8tvIk>V0VKnw zIc^l+%mQ0ba3lZ=gBbaBJf-! zTcr!p9%`H=3azw}C=xAPMeO(qJt+H>NLXe7s2IbKRmYWXah)+S7P*o)YwK%UCkWna$!4pz51v7>g1zGU1yb3BY)CjDS=Qavj!4;;fu2`ok66Bw+_Dcj5Dsk{`$)@1qyUvb41 z?|c6T6{h5@QeZ8$33V4>b>u-QK%n1)c3=ScLx7erCs|cmthmNU0>H8cR)(EPX&@D> z0sJ5e6P7{D5`UJPiV8t5=E7dhNLILz)b#!sMRuqWXcpw%0SwgfWFPpB(B?0^@Iqj~ zrlfVbl??7529qe07D9j?u>Xuqmrv^*$lXi74&`m6-WYAPQY+b+P-c9X?e%SU7c+oE ztxtUcsFU=_Mbu@CnNBG@@m0JO&!SLB7@UL>ESVjM`*lzTt;7H=6qVp(HWRS)m64F} zkH7xyjz8SK=R+U<)9ru!?XADwxAzM#eBSd-OpE!JOF_&@LggnI7R_i&Y)_bRjR#P2 zZl{94go{W^Oo!#Tb^?oCFjwFYyRikJQO?Xg2o{=&CLI#qr3aO(na*bO35TjuuA*|wq6A1 z_)4mR&%hg4a*Sao3M2Il>@mwkDGbQzJp#0Q_UvJ!u=@ohrH)@PR5`@;BF3mn;0WNi zmdMsw2WAL{?Ecb==#gj>{>o%GaY81Is{%NgR%OOPA_>YM7nRINCk)k@09A=nAB08m zHCZZSGLcTqIPU=B34#L#eJZ~0I=VNh1kT-9ypBln49R00wOkEf3)_)G)~u&=0VZVK zHqwA7{VErN#4E&zTeoaZn1T*vCR!K3o8&#Jc_v;|%#gu*O4o+L1mIi{B6h}TI;t~C zy((i8BcLc5r~#4QgrA9(i?tZmX5F&Wh$Eh5$U8e8zY)bN+~~Ob0_La~5hHT(+E6%B z5p^X)gpvQ#3z1s{FU=YQzp-Ix(b92{j4O^9-(VA64+?c3W+a6$A}5x|%Ydczav;T7 zAT0z@7rbALNR))J?k%+hx-X=TX~N+qgH$x|S(EC@VO+DjCd0%M{-(x-BS zMs>1Lu+ZPy+8CGtdr-j*3L{|0Cq16~s!oU%NA)zN@Qi-Ptea*B{2ZTYWYwRtx;;?N{GSVEp#;^ue#1thd%b?^j^mS>AEW2y4Cd#TB zZ0v?U!ze|yQ7B{n1+G_5?JGIDf|Zn+D19Y3oVLJ>d6N1fVF--6xlx(^mmzcEf&d<% zatoC}6j95LSS!!MoQV2~0Mou}K!A^d%4!e4@HK`~-c*@-UE&G)ND$FIRMf!+HTctF zFeP2BGZp>X?`7cLz2Sx%FarR1LIE&E$l3vaI`UY4hQUGUP@D*M^0@7`+it%3W-x#W z@(MRan+1)GE@@Ey#+ZkScX+7kQlvEzbK{4@ZcKias-nFeHJbob#3-)6i8OGBLzHYH38awU#O?&rQ##PVo+NLmi3!v&`&R4xNQ)yQfu_J@3Y!fA z8OII*2)&DTOmj%AWvaxd!&4st6Jy{6IU{}Z4MMDg9G&!ufnzOXg1!ou&|Z};*@3}4 ziTKukIPw%bcLZGmfKif@CFxX5FiS9DKyRS==%N|~T?0f_Y9LG4o0DE{;-aFWZiUol zdlKyo)|V25$B*=Q32&@{n)fx!)b65e_ifoka3Y0IQx{pWKVJXDp%{^1jEUYwB}W%H zNzpl0TH-v_yi1=*UnZz2!7SqmV4Q?z<9&gx>e|`1lw7|?Q2kyy_J(5@GS5PsrCKmuTKF^0kQto znsE6tiCNWgHM0WO62KI&Z}leb10$?@#T{AScPc%YgiM07Q!%Ngh$*BJR8UG)v;_Rf zA~TZ-jMrwwj9=;f69mKmPpP^|G@yT0aMmm9uI%;tQoGS^tr^P2vtb89i->Q6LiPA< ztM>GFY7LAPkYhD=Fnx&keRa1yn?T6uI8lHBKQ=4cx+#awtn-QOp{&Ogi|HgX9?DV9 zS0C=Y)_qsU13j7ji`0dzRU(uIQmD&H4D2Q4rO>AZis(tkdetmW-8DCv6^!Vz6 zq0iZp{&H}H1o@$|lq*S7hYaYDuP({3T{TTbU^G0dhv*1%o`$(|Yi Y1t>0__lz%iW&i*H07*qoM6N<$f`WZZ&j0`b literal 18788 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

DI%SuXc4jR_A2|_xE0@wn2l}v?DhU!$t2^-X1{Z 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 Rp&lVH(l@(Uq3d+-{{ZK!!Gr(+ literal 61808 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 literal 14373 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 diff --git a/resources/images/dialog_error.png b/resources/images/dialog_error.png index 4e8250cee0f8bad2734d9060b9a3508614193cad..f28a9aefa2c284ac4ef839b3492d9f6bdfdcc675 100644 GIT binary patch literal 4325 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 KMNUMnLSTX>nt3Du diff --git a/resources/images/dictionary.png b/resources/images/dictionary.png index abbb5001dda0bc81d73a379055181fd37929c072..e0c0553ed605a26ebaa186ff527e0c058388a0ac 100644 GIT binary patch delta 2790 zcmValW)o1GIs1 z(F?vpQ7*UwM7cm*QSm1Ls?-aL1QJN}7Op^wstO6Ip+cn~fheNs0XJ40T01_(@pv*G z+jBnHb9hnKMeXdpC!TH3*n9R~KWUU(PiF1)K5MOKy&HCgVSgBg5oj~7yz+{2!Tr%y zG~~A-Z~;I+PLCb{PMCu*j?W$-=cMBJ!HYk7bJlbL@cUO^7aKan4w8ShI)p9Nu(0BBUoKLzjwoCr)q2=fHM8^5?3gTxPU zVlWDnFxr32!JV{P%x}U|$SJhgrG#4&Hz3HRxdq4Im|igm?xjMrNLE!;jBQKJ``fkIX=g z6p*F}fNQT_9S6_{6`_GKv9=QkjGfzJH#6voYT8wf#-+Z!}*07#+V;w+>SXdNcL@E$(B@kjjs z_kNCGco+u}ieh3B2IoYq?9eSUo9H|jN~KW(QpP3vSHZ_z^DR1*h>R2tyFlDH2`Cq z0ALNsM*!2Iaj+96VD3&5^DCQ>5?zfDz~hrcxHKJYZwe3=KLS#j5~Q?*c&G2uR=5o0 zK7e2iz}Pte7$L0P2N>-0LkLU!PV`;Oh54%hQh&_{lrtmc;e7x$gfKP<0G$CRh(Q4-97jwuLLw24x__B9IdRi$F%`j)U#% z2;#i}&H#+Gun1%%XMjUsOPIpbK*st3z_2ZxX94<1OD~2nwl;(?HohN#VG+o1AAsRI zf`3>69GbdqpaB^618@#t4ZsL41Gx&|48XGhZUwg&Rsb9TJPUBbB-HB-)Kyu{<#gzO zch&%82(eTE{C##6$(n@76jtGexNR{LC;EA?$Soq%uY1O1n+9&_*hc>Cp(x$oa8; zJpF%XV1&(dN%zMe3I}jzpr?r?%)*;ND+iu_ha$S>7zhQuc>2PyVR1jZkbnju=zrAdMXDg zVH!rGy)eRFwT}6f6jV^wdwg;bI#~XuP^$eK6zPE1PJbm>~Pyc7l4yQw*k|?oVP#*)Q+J682&co7PCp@qu9F6jztX-G*g!&D=LptHX{>)~Cv7_wWfVPh+=s{QpD3J2kG zD!N@kvvX&C2iZaeYNkiSG()iMdiZK0`UGR7Z$gdJ z;lLA9QM=v}rd9v_AwRW1n_>R0pg!{}TPW|=bcRO*@Ohl}T@A|?Dpda;RR4!DGSCAd zpwksB!Gw@`<`_zexjaWJ}Hh65B=U;_0aWmX3kjs!3Xgx`I{85Ral-tK%QDcVU$0KKB^a>qeMe zOh8I$z~^TU5$HUjxxe}xr&6ianpz0)P;D$Fu)dYYIdgagsed$0Y!#aN{0t*4lmV)> z29<8=7U^+`Z|2Mq08(OhLHm7#pr@a&iF?rZ??m@5nJ${3D@fv&sv9*EvA&Luuci4{ z!?bgM2!R)_Op&L~2-#wV&T>sxaJxXh{AReFPCWn2*&{f9-7W7k&Mh_Z`JIcQT?lmP zVo1KUi>>Uw?SCuU=O=}2l%UTAkK2jey_%{V%s(;!gaUFJb&5~!Zt3m|&Zx|-owzDi znBNdWPya)DW<1t+5ydz73jOKfYt6#bNRXz!bxwd~pwpaicyzeWV06t1LGQSl&phI(k7I9dZs+SIrDw z!H*XckPS5R`5B&(R8+$w+2x)<5^HPJ`rT(J62x&C>aZAkt=xn@mZ>Pzzuz!U}?-+zQ@-1T2S7^oupTzOCE7S10 z9q0rL>pS$Lsh}%eGKC6#TJuHLk(Py0l@ePa=q&PFj74Uqw(P0$Ma{L*H#Wx3-5=3`x@Q<9l``4}BbEA2m%T8e9!L9Ns*ZbI6#(LV;9mfKCj>SD sqyf}m6s8U!Er36~`11AJI4KzBe;i4gMD;C7zyJUM07*qoM6N<$g7BdzM*si- delta 2882 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 bNjUOf@;EbjV{+5300000NkvXXu0mjf0h|7` delta 2670 zcmV-!3X%2p6!#R6B!50hL_t(|+U=cxY#hZE$1e#Dqzw>S+R)S?x4XusoG+wx6%iF7 zxA9$|K@or8wt`AgD-=XT?A^VPAk@+el?tU){iPN`t5PbeR0iMK{AwsdR$KZ5#4r1H z_8dausxCAUB|ivJLU3|@vv+or=A6vz?cVOpyeB8{o+a<6W=&*&Bz2j2@`3E7>t-1ZAmfrw1EN}tnA=V1`yU=zfi$zM*IuP#6} znrN*#av6Nve;=Xf|L5*sQtQclR3>cesm#%Bl82jqG(O@_`*X?s_&F$2NY-MJk7|}X z97Ewx^e0n!GJhA<3fS7L?Vp01OJXkmls`&mDpV~XqX8B?V@CuL-*dwvze(gPvr)YO zp7{by`}N%N4C&F^vyVutm) zRZa()rK8ckZkOBO^S6K>;<+ZxBpXpt&I})~@R~(l(SLK~%H{;7WEuRXxY3$tS9#!O7A2G^Nx^sz2&oupeTe{3^?QcZ9OfC8hR1d??u0*x# zeyhCwe1DdkxUX~FOCJ&x0P2UdKKCkA{M2c67vl~5Z444s#Rl^P`;A<=6IDMMfCFp^ z7H6{r08oFs)gmcW{(k^WvZYz0Us44G0QI-@UiT7IzX||r;^)f}0pP7@l4d!H0H_N9 z^&N{hjV(d|)CGY0nATTXhybVy0QJ`{zJ2rp1b-mr0B9!pU6_@|boa5%8r7TWhFw~W z0I*-{7UAm^>g-%A=+ zP1fipDM<*ykz}8<3ISjR0hVvHT8QTN&*Vwly~41_Isr=Fo4aWxG624;T3B*_%prIe z?tk}k1RyNSS7yVQ{uFZv_G-3sIRX$`Qi-b)Dk|TT%#B`(0E98_%{$K*BryCJO&B2n z;f$SSIr$!peP>Yavy;lM)(s;q2tXtNxC0^NGL}~l)NnyiHa3oY0s)W!fa%qUO&;Og zuNa|y8xQ~)005(HZs2$9=9~#$Xx*^mJbz^X=qP6M%ZgVgWdxvh4L|a60QQfI*1hy$ zH2^4N3LVAF+o_>dUyu=iYIf4s_VqI`6P&FI02S~LIttld`2cX%`duP|CW8&1)er~( z&r`+j2U7h#a}j`mdL)SkK+XT$F|=|a0x+eG`)l6^b_E51`b4^z?L+{k+vEIHHGcpq z9fO&}@aNYMfa$}#KTr#Rsvl1kGWR6%`Ps??P}S)U)c~0Cr#kz)&sQA)?jA`J0RY}T zwsh#~cGUr(<1%b=vw{G4{!V(ZyIXMp*ei`C1c3TvdN8wD1OR9cqev@_B?X|iO6Y!M zMgNLlbuWpu(zsawco{rjo_`tl4S?s_zoz=TE{ZDvwd;5~vP$D-0nnI;jd{A5 zc{5d9wLGo>EHT|r!tZ)Ll1gJ00I2zqR3W=IjsQ@rwdud7*0bqYND#bpN~N(v08C{M zf85jGGbe5U_olqTs zO1h94k_mvCmN&5Ki8Fv?Gk|B+6HmJl07YVe`m+FA=T}?>$P2�w7o(KnVco28|zw zDf@ZZGl5C~fO`Q50PY4N09X%z0E7yFcojhiKyUyESrdo=G!B5!Rlx{ABLGNnE&u`W z4}cUWgAf3J00@0HaOuD`Eq^e3-GKo306_H9!Ks1F=V2VX>9`O8J^+O84*-v>bufnyrbIq5Nm05E=Q3Yj}UJ@CaP;oPk70l2iV@?+^@=DAbi7z7{= z5p%czK%lx5#NJhR6@viC4M1uzdp!vK-i$d61VB;%Dv4QiClW+Hc7NIoCITQI0IB}2 z`OJG42!LDwqzAfn5Vpj;bBO@R13-ExdliVfhpTQOMgTCKLo;mmD{{$SnZ?*H{!xbH z&Qb2Fr5FKlkg+xCdYR;J-LT{1Y~VY;sQdFzUvoAH`AgsRiZKG3IJbV8F#B$UNJ3xA^TMj;84`3oPwPJOmaPY|2gMjDa5x+*D{Xzu-BKr zl9)x`TR_Xt;&8q(PypyFNZ>EtXKg*E<=l+O;(OyY$s4Tg*niuH)|^X^lE>+EbD#iJ zzY(O!^Fl+T*Xy=M07rn_3KW#32(fc_$L_idBB znaq!$qr?nQn+0Cfd7BQw%t4n^aTcDwY7MS+3wyp}diu|uxWOZB3^4tJIdUaqW~|=L z+DTtppjpm$bbs5~9JL#!^DW(Seg@a}Jou!KG3}s_i&NJBGOhwn`nFi^gMz$HHB3Kl z*v^dQXVT>XPBgmYfq!(A27n>s8AK+K_C4_1Ga)npykP0Jk^%qW-%5%PF%9dxccj@1 zsGBCWoy%!MLB#-goJRe;H(dDLz!^WN+Qs^HMW+3S8h?5Jp3wCGXV@}552ER<5{rKb z{Odv7cwsAom_F2WSIapYI9r3f`9jtNvRYSu_$--|w&AMJNQ`yPbx*|dy}VeFG~&j2QQ zh^>yn9hMM&mORZq*=KSncX6hSjmu1|wH;1u|`zfgb zNZRfd%mCQ#U9yZDp8xkUj{9z@0r2)MEb=bn&3`zk^_3QiKIX-n#un9fHyL7|QZ^yG z!9XA5DF$J!1d65gROYa!zsZ4m1+UVXW|K8smCZK!iI5P0_;CIAbBy&@2^EY!257lD zXL!UVh3GY>qko2EN_rSdQp+xhZQiV5jQjvCfKe*L7gl^gZz63VSt}`f4WRf>jB*IHd&}NhhclY~y*t_GtWsiQ c2Q3`?FY-7uc{0*L5&!@I07*qoM6N<$f~I)<761SM diff --git a/resources/images/document-encrypt.png b/resources/images/document-encrypt.png index bc5a87d66ec812e3f6dfcd6e1931649bd082fb55..da05a8dfcac4e8f57d43f9f73c05037653024e70 100644 GIT binary patch literal 3790 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 diff --git a/resources/images/document-import.png b/resources/images/document-import.png index 004628b825f8c5e24f3ec85d6208a6ec3bc6ff21..3465e4a2854971530f07c7d520432705a909e244 100644 GIT binary patch delta 982 zcmV;{11bE34etz)IZD4kK!Cq9X3R60Gb@1AB(_N;ZQCH9Ksl;88fMHhvrR6!Mi+F^ zD{9p%sz4{PP9$yHB(_O2szf4p-7v2}9(d9%v`Z_hM;vk0k%J)^C$U8xcGxCy*(7bz zF0Dx{utqDhL?Un1vAih(1}v{dB5~D|>j4`xEU!f(an&Mm)hw?@EU!f@uSF}bMIv$4 zEU!f(an&rZMJ%sHEU!f(an&uaMJ%vJB5~D|IRZ-rl`-3KlZXPx_h-3JU%d40hEbvGUi zQ1|#{U;#w7ekt{{Kn}nsLk1841OVx|)jJb`fH~i^){h6m0JCfRYg3jA0?hiVf925t z2w>(H{W}km0e~4b+DKuXIAD5>_D(@>VZgM{x^D7gMFCSk>H5j-EC`tLQU94Wa)8P2 zHIphaz@%!;f2CLoFyWnMGfxOGwu_-_VRi z07IYf{hNbH1TdKI*IR-~1dv?H_it-KB7me4zJITsL;#7!eE@%;xwNdypc zi|;=iMk0V-H~9XO5hMcWah>l!6+t3^Zn=E_nYJVXf9QCD??2m~L;#WJ`TomMBm#&$ z$M;{2A`w9N3BEt4D~SNYkMsR^Vn_tg`Y7L@A4?*Dup@kbK^%zy?7R8?!Z;EE*!S`M zMF}JWSj_jAB#{t+UjUb-k`$l<4Oj&D3IIPsz~2z?AObv!0WTxO>lnWv!7s`1i&Ffu z9KSKae{aq3n^XMu96zAIk7)2iD*TuZKd8iyYVpHr__!W^V1YienU6lSDuo~0p${%s z!;fy!hu7%id-Mec^c4>DB^LBGp8xhD6H$5&;wqPgQ?dXc0C1)GEiNAb7UZ7+1OP9s z1H5PuAk#X)k_G`Bc~${#I;cT$W>#7Os9f5G7#d-I0RCrj^E2wFPXGV_07*qoM6N<$ Ef=VB>`Tzg` delta 1003 zcmVj4`xB5~C$uSFtp)hw?@EU!f@uSFtp)hn+> zEU!f(an&rZMJ%sHEU!f(an&uaMIv$4EU-qCIRZ-r_wsdVlZXPUq^vw)saMqjm z&c?N7f1kB~YvR6JLsSD3zxss=B;Rh&3k{?{rc4E#tR?{fmM9By$YCB(W(7dyG&c=U z*5r6gFhHG+1_RW&e`qj3U5E(?kUq*p_In5}bq)lOX6D7~Jl})s-N68r-vbO#x8lJ7 zb*C2?pzft009jdXf_?x%W~04^;PDUufEsKy1kRxV0EK0`35Edx6t=|>JRJ@IQ0OK@ z@M<&wK(*Hxg4bh20Xtj&O2_Rc-UF)#qQ7yC0|S(602rXwf1?2|`vJ?VpQPCjn1u$^ z%>e_*vG^Sp@Z%BCGsQb|MFC9Gf`A}E29TOpxh)Y0nE6#}{je_#Fym*x)(`rC0Mozd z-?`r#0+{w$|H<8C0AT7TZKNl@1Q=Pt_b;s_0~qm=?_U-o^D`X$gzsM!A~O&T zD(Cyx){z;C2R`EaH`F5$Kz|?o8>{x)0!S|9`#0Ao5kOK2-@hxIL;#7!eE*(?Bmzh% z;`{gU{gwk_ukrl{B1i<#NMYfz8#4GqE7Ms7pv{J4iI^S@6T;dB7n%leE*GD5&<+m$oG3ML+k=XWb^$6aU=o= z-@*46{=452K=^LHzbJuZ01Np3k|YuW@DpHmL&hFJIT|n@{Sm;wgz%psejvgR#rTI2 z{&9?-e~{p(WcW!bep-%SnBbRY_{AxHd5+(pz;DsuH>vR3boh-*{8lY~vl_o$k3X>R zePlC73gGL|s#FXBKeqF6aJfPd06)6LA70~+@9`HH@K-qSmss%Ec<>jQh*ER0SGlT5 z5D)~I1DN`=8=rJj06P){{2zel_VqvW3y@(SEnuNv07t%E{a5|`J9c!2(~f%QqPA26 Z^Bu9Vau??-&w2m=002ovPDHLkV1f@X$GZRk diff --git a/resources/images/document-new.png b/resources/images/document-new.png index 6a8131502fbf7d062d2738a7d24fb02200e961a0..b1c5ff46d52761ddd93bebba4e8858e42c215ece 100644 GIT binary patch delta 593 zcmcb?evW-Y2EX=Z7p45Gnrof(Ho3`{+?ZJIp`x?SRjKf*_Ig*{^{$EqSCopbDi>bW z1qsuP~Z? z#`@%Gsf`a_>6Tym{Ik4Y*kQo|+39Z+KAkOOef#qXBkQ_FG0ROFR%h<-mN>xw=LkdH z3w5>+iyM?3l+-TzI~Lrjf6O8<{o?xKh4FiVlDFJ>9;7=aG5oi6;dxN6B+`(x&C9@t z;l!5z8(60=W?{ItEtk71nT6rm*$~cs0t_?CPm9IWsxlayU#7c5TAg9TG}Gu01`o9o zzF*G1Ykf#^2UErFPb!8D)7?85Ynr73uJK1adcREmL$B0@S;}k%_us@b)i+EiR|vX2 zMa3cV<1!Wo2yifo-S{umaM3$vHs94+#!svgjtWJU$W9Pv6ko^erLieet!2E5W5PBSV-ZHJO%3j@4Bl2nAoUk?_$MlIdkFF`4vG+L zXcbF2_OZr3zs_X|+^_{d!_B&t67H2coDj)daKDs+Te|=M|Nkou%8pyX6iw*R)K+8+ zxP6?7p6_Ig*{^{$EqSCopbDi>Y_ z3hJ(N)miVRRCrBiz3arsA#5OZ>s=??GRg@+m=JQ`t)C8)vl+|lA9}hthE&{od;6x} zAqSC$hk*fwjN$?Pm=ZO#Sswe}Q3JkP6%K%a#u3>hC_Qnc00# z<*YzZ`_1d~SI)Cr_uRMY+3iaqBj)27dMw)G79r%%tmWxC@HJKKdP3T=3S-p|W?;CqX(89WWvxu# zcJF_EH#vg&p8TSqostO*XL}i#Ux=9~q}wEXeeXQ$V?$(yLpGCygO2z64fPG{brUWG z-<%WRFm(@y0|S#l0|SeRszaC9l=)nOo;C+mwNp>}J!~*_4GyqSSju{bgFR(ygAQ1k z0s|w5L&HLbaE-4~k%8PA|Ed&3i#{=VY&tYWr;y1-^qA5d2G%f#dBP5fd_a{Bn;8z+ zy9kCkNK_b~+Etl*{G8~tx0MzT4hRdvjmyYm_@O_0ftY{*UqP1Ns>t>NhC=sGOF!4& z@IAu(PZ*|-mn&h{n=o5>;R7847bF_gA3w9d#_~a$Jz@oCf~S<}|3$}M-w3Ne*70%X dQMsV@TBb{~TxyF|9cMBCfv2mV%Q~loCIHy_?yCR* diff --git a/resources/images/document-split.png b/resources/images/document-split.png index c51cce45cdb6961a3a5352dfc06c4a3e76d87c7d..4a189a9fa19d017ed2bfb5169157c63caa957725 100644 GIT binary patch delta 1173 zcmYj{2~ZOU6o%jK8je5`fdx-4i%||K42fdp2$GZnGk`@AwIFKK(ufxnN`bIzD2F4q zL^=?TLIjbkU_1adLbrm|B2|=Q1fr~X6a_g(1qu=x>rBTt|GfGBf4)&p11IM@J;w{e zRfl@yQY&M}dOzFPZijP1yVxFiXxfUpAneWU+A+nG4f^qvCwVer8g!s*L5qe*Ii3EK3vvlR?)bu z|BS`Xynb1<`ao6sWs!+S%koG(a*JKphS#8h=VPRD{x~B+>o*Z8V@FwB@2mvnPiE;zjmws$$}q0ZtFhZd&N}Tt zL;9XDd$~r?tI^O*FKb&+7QKk@nKVEkV*3cQCBPgy8~F9~$gM@dtM4~YNxlqZ{iIrU zUXH>QiJEX+Yt2`X`<^a@)oR*pRK{5$q8b;$(-r#J#K>Qh7>x2+HxI@YH^#)4i%k$| z1~Od|>x`$6nrF3H1q@BskbC6s3QCEn#?3Wj)^;#udozLIpIdtC&D2o_^3uTDMjo<|%)dXrAL^ZPK|-<$Q@ z!XOq-{K=u#dxCiKp@~@Dz`AnPP6=L8iFab4C z$w8ud1|-=KJdUaE@JMc5xU0OZ2`9PL-rPgj;vDoUn?mdhy`{yoSCCfnb=dc zzzako5n6%MV)!u-u|N-h&Qt*zW={n|aUR-%(b+i70PRV%gQ4_g<`@a`ae4>b4nV%b zChm}Mboo9849im^h@j=2j;!I2f-Do@?TWkHWRlhPJZyiaa%BBpzO>X*@R+xEeawrM fT^5BN*_*$$`qZV5t#*I>6m;h=3KG_Dj*|TgVZ8l^ delta 1228 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 diff --git a/resources/images/donate.png b/resources/images/donate.png index 9183f1bcadf10ae5517daa8d889c208b82808f83..85875997a2f4803b090d68a77ee173834dc831ad 100644 GIT binary patch delta 3538 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 GEAD@FD{E{3 literal 3591 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 literal 6299 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 diff --git a/resources/images/drm-unlocked.png b/resources/images/drm-unlocked.png index 2b911cbffd602083be32183677c491452db3f2f6..9cc82b966c01f3193862713468c31daf9f920215 100644 GIT binary patch literal 5653 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 vkO{q?n( literal 5842 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 B;5GmN diff --git a/resources/images/edit-clear.png b/resources/images/edit-clear.png index 533dd416bb2341e5adac22ccffc918dd5b5a760f..449312b95f2a8f862cccccded2c8f210fd6ba9b8 100644 GIT binary patch delta 2855 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 FV1mjhb9(>) delta 2855 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 FV1i!5WIO-> diff --git a/resources/images/edit-copy.png b/resources/images/edit-copy.png index a3f27a1b3025169c03f9329f7e86c467a0611e4f..85676640105939a8ca3fbe6114f757783b8cb06d 100644 GIT binary patch delta 3551 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 Z(0`-#(!~qFeF>a2w4h~gd{#QY2(IJwzwD( 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 diff --git a/resources/images/edit-paste.png b/resources/images/edit-paste.png index 9085e3df3eedd726a29ce433d561d9eae06bbaf4..8d87d7ccdeb16ef0fba74967035e85832750975f 100644 GIT binary patch delta 1225 zcmV;)1UCEJ3fu{hBYy;=NklA6?rena0Zge4*fJFpfs0M7K$wp`}A!#!+ z_i_Gzh>I{anA^DkKocl7$H<(gna&odF>3^6u-~96O@~wS+eJjej zNGVaNR5~ObR%`4kl}e8)JqSP)MaLu+)goqgT%izv@c$5Ze{y_$yo~^)Wc<7PVYSw- zVzGFL0HkC5_ed&*nmPm^yiohNH}D!D0F6C-lcdKL#;$U?{4fCssIiBekN@z~ha%{i zdpjpg>QZt)et$SM_xyFrpEHtr70#7LDYU0qDfH}tJuk`a1tlk|S9?x<`{$ferm)U< z*eB^OC1kF%|1$oMD6HM3Qt2T{pKFb6zU@M+_#Gvuc*piOrBI<2MR9GLeXJ%L`}YZV zFDbm^%>wZL)F0=cJJ5Ma(q4rSegc?W$+-00N8il;tbZoV?1H4n6j&U`i)zkI{}1mg zO3anX31CXb`QxBfD&I?8=5)7U!1_o7rA9#?4dOf9^t9yi$b`&H z01yBZw(bT{BrpLse+|Gh8Gy*NwUK~AYxV=2Zvn__4*+@q6f+qBGu z?eFR7xu7)$1_oYUSy_2+baZrq39vr7wiYux*w)teRDXZ}k<}0cp8!SxTJ3)!_-r)< z!GC*zEgS(tz*WJ7ppfr#L4+VuRuF;?dVp0-0(t;K5c~-t;g#itAfE?;OoE}Ip`*jY z!++)TAP@(u)#|xM2zGK1NC*;5g2H<9!F($hfrOxtgNwomL4qnclcxd*0ZkwWhFlN8 zBnaLEgbx5AUY?3Rb zzyl)MxhZ#z+*QG4Ws#ZPBWX$@gzo_u0q*|l#Kgpz-rnB%e2yaA5k=AWl}cr#Ua$YU z`a$EpHt>GE&ld*=2hXLi3PzW^zhP$o+%ve5_i-FI{k*}WC{p9|UnHHQ2LM$t{(tKP zpa|dr5qZW601t|E<9dKJVgi730WP$l3O3~wD*#M@ydnUc3}yv@6%Z*UC<0gkU{Af0PwJATx`84H1ONm81ONm81ONm81ONm81ONm81ONm8 n1ONm81ONm81ONm81OUL_{53Fmuo5Bh00000NkvXXu0mjfxKb{< delta 1354 zcmV-Q1-1Iz3ET>hBYy=WNkll-YPB%};E4UJ z>U7S-PL@igM+tx%_Fn-|_T6;|z}6bpLL3Y|3=AEBTCH{ifG37D{A3hG4-|A2*WkUVl5EzkAP$08=T9^W#UW8!vX-(P%V20dR1LbMR-g+1%Z&DfGfUyI+CI zOQ}qB+^!t`_KywM02oQIhrIyq9Qe&EKJ8)uPY!twc9qNJhX8ze?T&4_ZOy{Io5mn^ z`>2z-hDr2?6t>QY9Pf7cD2kpQvim02XJWcQM7mfKn90bpvSh_H#0MH3cxA1vm|p&U=P7lWnKkH zto0&K(1#~~Uxi@9R{%!(E)lL%m&W7M1)n1o)w6)?8RQ`4LOMVS$%Gs z+X<1qzJDfxChH;q28XK;Ox;|>#b0>pj_s|{%kK#=?&;nW20jq$(A;{(b1d;%E zAxLllw|^pr115o?<^&513pu^GgbX9%>#BiokxJ~Pi;#C zfXY{ki;K$umh&95_uawb`n{o4rU1b4a%J%jzc zw$^st4?vAEu-5(ppiclufcIi6m}OuO2Vf&0Z~!s{S0w@AN5kmlq08j*g zA^;Qtpa=j(04M@L5dew+Py~P?02BeB2mnO@C;~tc0Ez%m6y>=72H$t?2rz?ekN^Mx M07*qoM6N<$f_|V>S^xk5 diff --git a/resources/images/edit_book.png b/resources/images/edit_book.png index c375d9d0b6c808283c50b4388bf96807f5af5222..f558d5a14d96634e0bc9d1c6d98590be79474a68 100644 GIT binary patch delta 2715 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 Te`cq}00000NkvXXu0mjf^`ioM diff --git a/resources/images/embed-fonts.png b/resources/images/embed-fonts.png index 3ac249350676edc99f5fc3dc6c15adb70cda4395..b1a868b6c60a1ae56978c0e6059a3781e43adf1d 100644 GIT binary patch delta 692 zcmX@axt4Q6h{85cmE7A(<#%k}15Zk$qibowHi;O~X|nsfi`4wRASPDi>Z= z0-s^(Ku4=D$Q!cowyUrCX2$BMc7hIj}$>_%hVisPVJeN^d7{Y{;7i>QNo_v>a zZoQnRi(^Pd+}oSs(ZYcO#~(5#H>F-oV2O6n{C0zBg$b{MD|5zyNh?+a9^lF_(1=J{ zI{)t}hX*@2ZPKRSoy(J2SN?zZ_nEgFzs}hHxqHK4@LueU>e3_9ce# z^3fLAukRT*b5={;@a7TFw>`q}@su-2fA$|?O=|E65ny)VH&tlle<2`w zm_24GTTcCJb+(2}zuLJJip8d{dn2Xiuz!{>V*&%S!~q62g9gS|vz%BB9z5?3`0y<0 zbhVac)53k7YZ#7YZTG%>N9e)!JL`VQ|7e(4)NqyI=F7mG17B26urU1RFN4d5CjDh(hjdm2I9%<#**OF6nOZmMOfdk$qibowHi;O_ki+hO0a$maNuNF1)I< z!Bwg7s#4)Ko%OCtMOU@gyD1l3)m;Y>1d3l(D!2-in(WEw#|Gqqm~$C*g&|Ck9~)y`3I>B|xO%p>r&Y7)#hJg{~5o%&rC#(U4t1OBQ>xvx-E_ zI&f)8)z8x+oRJEfIZw`$Iq69McNldNt~0* zVff%!v7l~s-GMFY3`rU;<_;Ch9{xVUV3?%L))6Re=+NUTRnHWX_VUGR)`Jy2f({$! zO}3BU!)7lh#C*g>#?Zm%m@xB^^T&@ceAJ%El(2m_$2=hhE2iqygE>-+3{1OH8kOfV zF#MC%Yw}@Y_)$NR_49cSh6lH`xTkIAWO#5_gF9^AY_1oa*1D^rkDGq@()+?DozKw0 zVsa>?(`H-1bd7!1>nuMj>G$*aUd>|P?#rk%^@nG0{r|H$Udi4srR);aJm>A=VK_14 zFx#7_jn)iL&r}hCnczWMLH(6SD`+>K2qB1fvnN zAVEx`MhHtd@BaX)@}@87VS4J&4OPs43svW|_hDZK)6e>(31xj&sxLb*SkzlL&u3jPrh`vid4 zX8`0r4zaL0Em6*BLKv{6cT>`VqY2w0JATZ41nL40v@1$ z|Lr?{^OY`u-&YGhXae|swctvJ01*2?!Jb>Tl0sKA>;P-jJfy?iYX+ZNjwa)|Ia{D}Bk=y41 z54n9Fu+4w%^MLMM<$eXW064!x?ehRMazDiK0~R~v4l?oWPU2CnC0Hyw?&EwJ1t9p>=7GjB zAL)EARL#Lw{ghBvhNX%^UEJ2e=A>|F9adTqPF-pG3JZM?3CE(RFy6_m7*fLg3l4EH zwAgPL?JO-^idJE;Z?nR+&{`LU>aK)@PVrJ72I?1MLbr6Tg%Q`vrG%FP^V$5TQN8T8 jlh0-XVnXxt^Yi-;mCzr=w`f6`00000NkvXXu0mjfG38rR delta 818 zcmV-21I_%(35p7^<^g{L4oO5oRCwC$oBLN1Q4q%$lCYQd02Hw-dx!S)6-o#}k=TPs zu~HM_5%d550hQ-;RJgl4!+~f&>Y2N)w)$&y{Q2I}F*b zm2-19!X$iZwFS&+txlow$r6a9IDx`PR=OaR?(&E~|8flkQyqWzc`voD%#eB%(y7f_68jHj$AU>bj9vOdZ+rzv0>W>X#KOBn>hKlS*= zfkw@d&X>kMXG)FVbD8TcY6;|BTFtoDV6*E%hh-gs1=n92XFh1~KY1Lp7~hlU?~XYh zNyr~?h&!H%eV66$`yBE3^C?s%`5(s|-;6AYy^Wm(#4(@0^`Oubsek#HgH1Bi#A7dqs{jB107*qoM6N<$g77zo%K!iX diff --git a/resources/images/external-link.png b/resources/images/external-link.png index 4bd53578af619ede933fec7c98371b79e41a75a2..47badb2b32a78046d7af4a9d5891877f602195bf 100644 GIT binary patch delta 753 zcmV&r}hCnczWMLH(6SD`+>K2qB1fvnN zAVEx`MhHtd@BaX)@}@87VS4J&4OPs43svW|_hDZK)6e>(31xj&sxLb*SkzlL&u3jPrh`vid4 zX8`0r4zaL0Em6*BLKv{6cT>`VqY2w0JATZ41nL40v@1$ z|Lr?{^OY`u-&YGhXae|swctvJ01*2?!Jb>Tl0sKA>;P-jJfy?iYX+ZNjwa)|Ia{D}Bk=y41 z54n9Fu+4w%^MLMM<$eXW064!x?ehRMazDiK0~R~v4l?oWPU2CnC0Hyw?&EwJ1t9p>=7GjB zAL)EARL#Lw{ghBvhNX%^UEJ2e=A>|F9adTqPF-pG3JZM?3CE(RFy6_m7*fLg3l4EH zwAgPL?JO-^idJE;Z?nR+&{`LU>aK)@PVrJ72I?1MLbr6Tg%Q`vrG%FP^V$5TQN8T8 jlh0-XVnXxt^Yi-;mCzr=w`f6`00000NkvXXu0mjfG38rR delta 818 zcmV-21I_%(35p7^<^g{L4oO5oRCwC$oBLN1Q4q%$lCYQd02Hw-dx!S)6-o#}k=TPs zu~HM_5%d550hQ-;RJgl4!+~f&>Y2N)w)$&y{Q2I}F*b zm2-19!X$iZwFS&+txlow$r6a9IDx`PR=OaR?(&E~|8flkQyqWzc`voD%#eB%(y7f_68jHj$AU>bj9vOdZ+rzv0>W>X#KOBn>hKlS*= zfkw@d&X>kMXG)FVbD8TcY6;|BTFtoDV6*E%hh-gs1=n92XFh1~KY1Lp7~hlU?~XYh zNyr~?h&!H%eV66$`yBE3^C?s%`5(s|-;6AYy^Wm(#4(@0^`Oubsek#HgH1Bi#A7dqs{jB107*qoM6N<$g77zo%K!iX diff --git a/resources/images/filter.png b/resources/images/filter.png index fb08834bcb74534c3949541ff96e177a7ab1edb5..afbc86eceae268bccb371cdc3e5bfee31ec7f2d0 100644 GIT binary patch literal 5430 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 literal 5857 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 diff --git a/resources/images/folder_saved_search.png b/resources/images/folder_saved_search.png index 9792bea808aab4cbe3865baf1da19f8b94f2ffa4..babec1d23748dcfa29ece8ea243a7f3280636bfd 100644 GIT binary patch delta 2783 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 literal 2793 zcmZuzc{~)}7QbW67-Nsd`jUMpYmzlYF(e}t*`|yTp&@ISY$c+s$@-8aBtP`5GfK)J z)FYE^C|hFCU~Dsw|KI(5&OP^Y&iQ=r=X}ro+;ft#mn`^@5=a05AI8%B@_{BE3Icwx ze>i)+7XV}(#@y5){Ks-3Pmsf~IMGx2Yt_cYt(RzgCM2m z8r&v?d3I0mfch8 zme`#H3F{2a2!Y;!lLNa7jEx}OZOuCWoT1H><>F1{onR+lZ*lE#)KDsl+lK4JOL23( z(rUVNRFbp;+b_F;id)VaYH49tUB6mf4fN)gk_R|m=fgvH{ZzV`a)L~+5FV$Z>fP6& z9*>R^WG7mbts--6zv|}+IeX-K4n{is(dpm_B$f7K%1Vl=@ar6?;vRMJt@#D+w4I~- zbCk(-#6yNM2o4&L50?#dk#b%z-th1LO^0(jC+=yhfAR#!PFtWxfFqsCt$D|zP zIRV)R(G|cr4@wA-N<#zyE_yRpQ`G^;<{=;FRLx*T9D0tgr_uA64u?m}NA-_X9X-F0M6&h8I z!-A?b0xwLp4IKU3{M;*KKgju+oZv~f_*zbYhQHMLvTB}C+$^_edJ+@0wz6)%JzT{1 zzM<4GKMpnHy-nr@du}^-feC-vrK97n2~!1=3r&Qa6a?wljmM2?bc9HmknzC%xG?C1 zfg%)8U8B2}f-Sb~#d-JLbJ)gnc8O7i9{f#izcL%6_UhLXY&EaoQSD>3XcYK->t0Or zW78>uMS?{SfxMyPrdw~w&~fki$V_CizmEb<;*0ijeQ^~fEs z4y<>59D@9T+f3TX2Ws$Q2w_}lu~!oz{Dlh?O-+QtsHF6^8p-x|dR{<{VjQr}q2zZ) z^%+?T*tEC*H2?T8hc?oi5dM3-{hr+jAP_%(jx+;8hOnny*0UrXg^hjHtyDz zQ9z@;@pP!0`$fJlU9|Q2^ZJolQoj@!MT}X8Z!Pzk)z2xb06ThS#XCmC%g4ld^9t2z zPhO6aVwGi>rKi1JE(mU@UB4^@n4g+~J@Z%!$YPJY9v(a(BrgYoadSCit4M-PhTLidy2xqCC?C{@^eM(4gn{x((Tsat$kGnggemX z<_rPmum0-hfMyix(nbsyK-b=TrVi|P=G>Rl!qcBywaGe82Td-ICe8iaO0`1VX0!m} zWy`choIj%}%a!$Q8^3BvvsmZODa}B$PXudn1ME%kJZVAmAIcI^7IBhh{%zUi?QJV*d&mO6fAq+uiGMk+ae(?812jf zEsT7G>|VTU!*z$hbBlJ<6(_ggC%xuh1p_e4X%d4e*+u6jc#1tg56EH=a>lJMHgMf+ z54+DYA^>{tfO z#O5PF3<`MRiDtRBaJ0-Q4@eaPNlVb85>A>R&`1Kuhm1spN}D-MIz)k(HK3Vtv`YQu zqN9Eg-~jzi?fPAHfUVA-C zGq72lGuEeG z&H9p$NKUf4q_Mz*Oy8w3KLduuRYBEI*r0%0XBYCx-xI^83@00nJ6m129D%2lUS>vZ z#i0>(wz-@k1pe z?nho0RnX+!bqK91!#m7*rM~%Gw-r{!<+8BJTSHK{_VeXpC^}n@YQabJ7>Pq| zNiVRq(3$=Jf}935$eHRD=|t1KiZzU8!R!{7l$oXBoYHSQ?WcN|%zUmJ` zohC?XMA~+ns#&(xXpH|nhlZPDuQ^AidCS`7jq~IHY{M(0c;6=|D#5DXHCG-6TpJN#ZE`UTOrKt)Okq~KA>auo1^;Q*lRAwQFLY3f)xh1; aW(nwZ1&V&66(t>h6`1pv%xll#;{FHA%LsS? diff --git a/resources/images/font_size_larger.png b/resources/images/font_size_larger.png index 2d84d58dcc1a3f61f47a42590f248cdb57a38fe0..f5d6eb347395c5af24033e4fef3e05e5c447360b 100644 GIT binary patch delta 3155 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 tRu&VB66><$Z5FtI|c1QRtk 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 diff --git a/resources/images/format-fill-color.png b/resources/images/format-fill-color.png index 528e840cf201bcfdf1ceccaf8fca28cecc1992b9..62305f327232d257ff8ad5b573076bc24c419edb 100644 GIT binary patch literal 5795 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 literal 5813 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 literal 1668 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 delta 969 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 E0G?MDLjV8( diff --git a/resources/images/format-justify-right.png b/resources/images/format-justify-right.png index af8d48ef69887150bebc8e69dfa3ee7d7eff721f..b41e92648f15992cb407639b666150416055af0e 100644 GIT binary patch literal 1020 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU_R{W;uum9_xA4o?37TMV-LUo zoj1d0V#cybTLrqe=t-w|8lG0V;m6jh>%vlBQl=ypt+BdkYklK{Q*PSVt6YTyHYMFy z`*y3*qUemIXBx%tP9xoZK?Mkt7oEqnXyZ=KD= z?T7b?cnWJSnXu$!`j&Otf@f`)R@q1{tBTtjU{ah^G&k+}_4!|S%!}7w9&&$v?CR3> zaVnI7wSs5=ESYlLUVr_UJbwM(8$L7c_;;{h z*|IF>>)~m-H^U-|Z26+6t$O|Q{G8hOx-&ofjlbL5)YrYY+ww_TfB%Kuu}7CRudA)d z*FGKZ_3HJWh5El&ujlVuS7yc_HNQqDGqSts>6<^E>C6+JHi~iIY}R79yJeN@;V99D zqDBUVE366(2*|%Bwg%qZc#T}3uCups4USUU5EzstkF757O`;w#;k z@ORam{2sIE@52uBRexOETspTx;-qxYyH@9EOJ>~q8x(*3e(&RNeUhtZihnxer*wYi z{ww!dA6LztaOY0`ytU8H-tHAv^yXT@BG!z{C`|ow`9ffPjCLeeHQz6Yufc` zpXT%ZowDfrS?$^XeA5}hfgf^sUTymJ`l2;&r<7!_YEs#%6{$G)pOMm5ZU2=&YggrO zd+zUMX#38=v?uF^aelpd&=J3gzs9d3`{TA>x_Q2Qfs~u+o0r?mwr}cQn;pozx%gqJ z?y5xQ*n*J5D}a3Fy$k{<@KsdE;cp=Zs}l8gt#dVfGyVMf_tAQLLCl@z`(yuCYw{}Z zQFPDGQukkd>4~N2$`4;6N?lJCKhrco)4kR-Fb&9?q?UH#K1>rZwjw1ytG zFwObAcek?o+Z*r1!+0NG*>L*({|!q6R*D+?@fOQXk2M4e*#w-lFg>$ci}9+#1#SjL zWO#x<#K4bt^LG$wU3e0tYxnn?S8KJW$ANTezn5Ec{8Yt`DaWNJRRew2rYi<^#)hbQ zRk_>WTin|gRl4?qi`Vk~b0+9rSP69HwYdGpMc4dp{B>U`6}uo9sKxf@tGT+p;82p@ z{b85bRRjJIlU0eUnXYd5q!1#2984_gO@X|VLrY@2vu4coUIz@v-+#NOf1BF99>|QY z|9rbtKKsQIUhlmf!J!)dZdv!AUjc?_EDJO@F)%!kPe^*+m9>3l8c4{~)z4*}Q$iB} Df%nt9 delta 1100 zcmeyv{(xhGay^T>r;B4q#jUq@H|F0-kvaD9`AAPa!>B*Pc~Hl ztD6=hvwz$BKPt^%?J89n;Gp|t?!F!3>+j1SNzT-+e{=Q3r`$*nMNh@nMc=H%19MMz zy;s%TR1_2TCcKZ+b>sUlKQzjCJhhIAw+Dyi**}c9x^2Ze<2JCI^HY{y^gpe=clK=Yz4w1H#Qc2FzEI}&Hnzmm({i-C)^8Kp zrW3kee#`m8+fJv~%hYcxKELJv&pFliz4G^cs(Js+qJ8b}v;RNbbDOk(iphiiFFU1A z{Co9!Z}aK3ybW&d^4?L;;?ABeD*Cl#E2GCJ?mLSs>reblzjJOs!==Y}7Vg}A;Tgk& zdM5@BWaz;C=iu6zyYENKFx-Fm?@aWr%eNSMmPk+H+fnaXT%7RJEt~nn-R0BEg1c5v z`nEIt`I%p?nz6F$^zWbDpSy9o-L2QJPt2ZJ>Fc$(;EA}*W#i1QnKtTYmTk;3PK!DF ziu30UAEo&Qbt^3#MIN7yw3o|YfA&u2tEg3vpXh%&DgE?gY|rZ0=#clvgEvn)ws}g; zpW5AZ>waxq)~CDFUbg<1VfQ`r_3M6z*9n7T=*7I5@+`KW&z*blA|N_)S?J1d7o1mx zZd&wBK>2<|*zQm5A~%0LkKDM?u5OV~s9noKscSW+Z(eTSFRPVgz2@NltDFB7-}N$n z=Wn`ZX4Z}1{I@wO!Plc0malHR*}3c5Ba2Yeo?X|JEL}lV(n2Y=dL{>k9~?-5b$~hW zdX$cF_~8}Vx>F)oCoWsJ)gf(t+uuJwr^cSXvQ@4$eQld<_1m)l@0aEnAIdKJqL%jQ zO4{WoQ`ek~)O`5zREVMCla`O(NBe{~$E;`znHd!rYFJnp;4tBykm%;JjEt)Wo0TH0O}1v;n7DF=>H3|ahu_tfRbKZB zIWz0hw9vzPdv{;&pL+3GkknewcY(Z%rPx3&t7mwY{mAjcb9@z@4ovTF#Ft=w3XrO#dhAbP0l+XkKS-Tm& diff --git a/resources/images/format-list-unordered.png b/resources/images/format-list-unordered.png index 183d92e8ccd2fbc69d66b279782b10422c98226b..2d9d0f2d77ab7b24975fad634a2db14ad5c69cee 100644 GIT binary patch delta 2200 zcmZvd=1L>YLf~0I?zyV%*BOF~SDJ4^4loEoHN`s>YNFy<6P(nmd zrbkpjHtB8<

@C8Cs+mOi#nM_Di*Up+O)oCF>1tuM1AkIL2J)6%= zTWcZYw1bl(O*5oI!AhQG`J0(AkAHYHH;c0QK$l|d=OGXL?UDsdaCrRPi?rqNf%f(b-Z%HbEt2=v4`qOgWTZX?0uF%FuPD^Q2y zD=0z~mM0eTp#&j{k_JI04xE+10{6&@2cJy5z&xrCJb@R+_(qt$1a%BPRe@W1hbYYJ zdEtS+%){_kC=%SFOA|huvgI+3G%5UJm&3)R+1J}BF5CsKN~clEn$3r+tCy9oCsRhk z>B-^F?oB9Gm3r@^AKm%vv*1FRNmw$mVT!5RHUuJ*taF5Lz*!iVy*Qu4@tnT*-cRSB ztAG3a$@}jV+v~eoQdG0k?VXKI$kTcE!=HchP-YK5yI07}<321{ON`C3%o9M`6Q08+ ztTx9dchj=)Oqiw%gsG%BJiWY})q$llDG23BmS3KpIp-y8$ny|;##Hp9?f`{^sZ)Rt zP_{5IVN!C5C%p4E$9&^#KFY^EgZT@2w274zu$iCtN8N7>NX6FnLt}%}O`4+PSI*6AFs7N4z=GJreZt^JY!UNm`;3_^Mhx1E>7-^R{7+z zHegtn+0IUx~v8xs*Ujm;rwWFaWtPX`0V^jxz=kTXKnSfpL}|HczF2i4(Dop zb2|_lH=sZL^hbSlb@$#e3FYjvnomwC(Y;FX(bMC>Xmot|G&s{+H5SF7X@J*kJOAyY zN1fG^S$B9;ot{nm`(>W>7O!9Z`JMw=rF2!Z6wJrC{-u9+1mU7f7G~IKsWMP08Y>t| zr;%&GIZ%j%2ujKCvc`tO!xA4V7~tEoy{fYCHyWavEbaeckHT`ssu{yfbYorKHkU*Rmz&HZQWN zn+?i=_uzY?r1FL&Sz1*X1Vm-sv~^uG(IrVjsCz@pplA&6JjoZA-oFx;vHb08<5a_| z6k|NPuL)sgKD;WJ_i(5ZEQ+-QznK)$l;|9xB8q2oe+yrZxFLHtw%&!P{zae+!s1tTDM zw*S4}(BWzjLSK#X?{EHkPzj|kSS`MR!(tb^+};UI+f{YfwtTp(6yP915?f>w%pMCysw{PFJZ%I|i)s5>c9{VK8DZt@DmJmC| zLTFkTAPNggsT8e^xsaJ|ySl2J12G2=5921$|{-8rWTaCku& zQVds#?Mzo&qooikk3>cXNsf?e-J$h8JTaFF+BgOT8i5j(rD?8=X$1Gi)NrJ}r{T1v zftXI?3PjQ98Oj&jgt^%KMb>I)Yp`SqwZ73>?rb*GMkyPK%=+}QyjWG% zv4nF432GKmTIBirfBm;bDy(Y1^WJZF}b1kNK>0jL|+JH#>dvDnAGxQF6i5MW%O3?_sge;~NuH21CIezyb&kPDnMj)5TI z@MLwMu&(@u>*? zMT0v=83C4L27E|pZC}wajS&8x_v??yG{jer{E`R1%z63p9EpD5E0nP3-(BNJ(byrU zTzEZ0!C*FEd=lJBn@Y-58|r~QRHhOkOX8=-T2%&wi*ys_&GrSDh(gOKXMDf}>k$^E zoc4x5oJ#Jz!ohLAiRG5nO{*2bG#}o!-YP>0v_`1R282*=tZFJsNtPwHng!5^N+uK6 ztWeO=l=RoVry*SPIKTQG|8=F$OXB?hPX=*;0$xs0#zmS_=X%FAtSxx^GB(U6qN%{D z8pXFyowT4h<_gKU6I<4omSY(i)~mRa6k2hK;h5gf6F z5Iicp0s~(k@O_DCr!kPlQ68Bq7Dcj4L^XI^gluft;I+dI2~p)_z^Io8ie$=xH3X(= z1fm;V%S6g4?ajD36FBCznrJHMtmO$q*0)W~I8P)As)hZ@H#Tr0lDaWQDQ#`s`Vz(@ zir7QA@=jq;E<-`EPOu6-GNW-{o<{nPy_zlpjd7kPW;mdQ|BP6*TpT@n`p&!WTFmzMZ!bITU$r8f;ou?L$H515`}V!VgQwaC z5DR%!R<#T0-fM4VdBJ6hDAC4oj8e(V*@g1UdVcP7OF5H>YG0$Ei(y6t==w;LLD`Sd zbT&JjFR{$GH@B9{OTuhgNPX_9bMUD*<;JM%0B=5i@+qU}TMyo@tJMb|Kg@^Y(Inl! zz4!2wCx7vy|NhR~Zw!YMXY{B@JB(Gwm9MjuIC|0jca};#9!g5Wbh?n~Ad%b|mnTX0 zj4~gbUz)mUD7X~KQa3SP9t%mE#uD8726bIja_nq;^y{b9p!o?CJv@j1f za8H5hm+{z5M$`lNik1ua;$R76O#%!ehk|N*TWOxkfpf|lq`~2cV3e(vpjC5_ZVyOd z)6|&Y!EjhrtN$m9&i{>Y7LTsSwL#*`0duW*P>)k0H-;N`O~_mjGT-%NFE8#PJPBx64DVCEFSC2r z4YPdTxk1yW!^wRiceN?9;)eCq!a0%WgFPEy`3b)X_38ie4+x_T;qP0bhHJB>S8H$= zflp5Y+!5;#L}Q0IWg0w{#G&RSW3F^eoR1}4A&J?28} z=A70`UoA350N=rpmusyCe5MV^Cy9)9=oA5aiMGlptsSISvi@?=N|QKx#W?MqN&|%W zQVfezGy_;O6#**%t5=4YKrxM9S?JEz%2usb1|mk=-n3rps%o^cjKii5qS3)mB*B>W zwc0l|99Sio*wV6)RT=Sn99%5*0$vZ&M?{f0`tSE=_;s*PpL)S*Ujl{h;EaKy9*E%} zr`HakI&D=k9NgI7hs`|{TvS8h=*s16Ilm+rRv;4J;;Jgo&(Cp0gp5)+5;OMpZpt*n zlp#WsV))?O-`Tx!`|M)c_wY_m&$o7Oh`g{NXzc}orkwzl)uOGBv0k}V#cH29nrLsU zx_eR~=*j7cr{ugWKRGeeG{NI0)&6fA?=<>eykH+-HY>Q3Fq_R=qhEjH?(hHb zyZ7(kq}WMHWiBgS-+lca=Cmt}PNoZ)XMe0hfXH&_?$4B$O`qAIb=ajSUrXk*g)EtOlksZ-P zKG9T@hILE|rUVW~xz*_DlatG-+unL7xSX>Ln)e30q`easXsQf4<079;SK6Q~A0Qe| z&yKT9K*lXd5mhu*wTMD&JL7OU3!pp-pl>7K!_l~wa>h9m-EEXZuHDL`Syc?`Vtp1K ziXD&m+sB&7d#FZq8@!{8MtU73QVJntEINpDYekNAu5FbuZdH~EmoR54g^(GJMN)Vo zK($I~pHky{O*D(`q>b?=a=nrVRcKK<&-2^!3HNuBtn3Dy}}9earmp5lHlK%?isFF)%44k=+D4FJ=u_a zNo$S37ay+OljE)$%A>^kB%s6-jZntrBu%EvvM$TI7~E8vOeVK=d!$v(NFD+Qvk-`m zq(!maET13}^|pLqL!k`Twm}HR0+!2ci^|P_3xF+ih4P94kg8S zd2;RpR*`dpP_SMa$bY2TF&{GR34=oX*WtkH1jm1fZ)RUS{>IYk`mR@k;qVGI#YlYB zaeET&Jpu}Stfl1=16TyacyMn@1X9yuefV@2*#YN_3LSMF3E?tH8>$=<15Skp&T~1d z26CXvItQO2fk{HL1bUMsw$>q=6Cx_0oRT@_EXe{1U})`-8D*p8X+7L91Gc)emp5c| z(w#P=^bJgoQ8oPNEngA!*5TR^sa17Yl)g>3be|e!(0?FvLtQ+ z;w8l}PQg|idV)fk7O0AngGMcw*o*Nb5^j`3jqxD6ijMzTmShtinV=Sdd5wyU(C7sc z_j%uFuqtfMo%O(k@Uf4nTS9o`OZ3|Hwda<*vXJiA>7K^hlW_Y4L#GvuY&zcOK}A1~ z$Z?@OMt&M=E~g!D>(aC;_8veF->ISn@m47UQyk%t!w-RgC&$O*&8=sTAFURP?VEcJ zql@$NxYkf>;S=#dpFjXLIiGfb%Fea}uDie_`gW&!`*^Q ztD4=EbtTEkbSA7>PUnh(OHGnwT?AEgd3n;*PN1#H(BraPE+tD;qZai_QvdCDzb%I8 z(Z%V_Tl)a53OpVTUb}nu;QWL#`PR4IYL`u2HAm0S=F`<=V=|pRdHm#g-86~p@Scs` zy^VJsyiqOBj9zVR4sY&u5wTm(o*fE7+qUk+Uq|On48uT4nrdA-=lY~DLg@AM-Pb_z zw$JtKeU-pSiE-K5mPDT$@4;~~puyLPSnm!u5A?aS;o{=O&eqs8D^>ZQ{^ieq?+^dc zu(&s!KS~oMS)kDq5C>TA2^6@NkjO^FmLp58$Hoi3on}eb3OGER-rCOv*U0NkumGJU z4wbHlT}VMU1tPK*}Nr(VuPDV}NX&R|Ju^)=!^2xw&(%TDg>QT`r4cm?s-> z&4>h1r%WS2G8wGFrgf{;LSa&*69c_<)!C&<-bm&M0p8e^3d&$a9k4)nOt4}cK7Qb& zK(mg9h+-ka=Ul!?3Sxy;M;JQLBA(~P=zdYU??&0+!j2&2t0(X@V1J9zNr-qQc zoUI0ft!8mVF)OP@mY}A!l$n6qx@oeUw{Rvc1_N2wE%mLpCBmGtOvv1N4SHUd$|MiA z`t@n1gpl9R)e4`B`Crfde?K%RtiQfu6loXLIwU4_+vr*&xFFbDVC?X|)TP7`WO<{P zXU7v7g!3%=B^eS#BB1|~ve76F3~R4pCWF?4##vI<6(uf}G6VHN-)JX&Qgg&qh;u;c zY&sNLDwkUWE zH`m}HOVU)!(ZGSWpV)+J@0|CAglTY>;wQ87B8|3dAvR?YhQ?J~RF=!d#$+p?(Em!1 z3*wzY39gtEDid5e~z}YW)L-*@0_8xf6Ns<`Thc8lNH1p9a61Z-Ziv(=eMyxNh z5U^@2^w;>BvEiJ9q8*eZ0^SE_q22e*l|h8YFoKxd1!|rca={`;HT0Uts~V_ZYx_K} z>&3VyJNw}JYl;%mZx{&1H}>{AlR!jpC$*c5hn<@xW3+9JHCAAmrxF;x7P^S4tPnj~!J{{7qgyVHyF`E15{5)jhCP4axv z)=aSB#xR&=sg3h?cXu-=yE!f{mrDkwKEO=#IGHY*x;4|;JmHd2{^*w%+)v48u9GNEqj?cLqMc;m&{qO7JUB5iHc z^wBGX!hYSrn;iP@^e}!eYXyHx)T06pMP8AVvU@Bg>unxqTyT!Qt})s`!;zTHk!tPP z+4=2TcOHK9==|*X&;Pvqy&wK|aKmaf=R=H9i-M(O?L*~Z)so&o2C;T+WUQfPRU4l) zN=Z35J3q;iY-4liZR8OV;iBf=I6qsg5CQ&7houQBQz@IWl{7;RRK=5xJj-R8GlW`< zLBAO%XbtyuQOy(Qsx}x^%~V22d45V< zgOQN=FafA`|o zE92|ysEupn2JU%nCiJq~(&x&>sBj2T4*WTnXcF<3m%Nu=l}gC>P&D$rRHt_k?rl3l zh|8O9F>w-`#o^O24kJ#2bua}a92XfYa$u@bL?{@6b)%|Enh&&7o`&IMWOY4XEg5Ct z@sEJa!{97$Aa2*B-|asI*OM^TFi&VoD`(KGOCWoosH$(9LlF&9_o2 zr_Y|BfBFnNmoZKqv8$%mro3Ea8)MD|B_tw4*kdX=847ee8DEqo%{PtfBR@fNNI_o8 zVq}<$XTR6rLj#dmK&%!V;b3A%4-pncvG7Y@ognMLBZP>QG8)x5L6g375!JcGXqQL{ zE+o{}WeKn~XBLM%$q5GHG!#ULn3y2RBoEf;wxm%C^bMjxj0xOTL{e!6A0zd);+jO0EOu2Y*{6a|4(PmChPpjoLOOvK5gH^>~*tE^r z`T1}(JG?10g(;++ak%4K`6OH#>`0W-|OYA1QxDw9$gbh|lB z!5%9TVuR8nL|)t4z3?@U+-yUu5JU=s5SH)%?5QOZU$l!LJ3dcj{{H(Pc#O7ocYO3rf9A{(q4{piWz z$=THUMKN0J?%!|JBOF_{oXNH=uY5Ofw1npIQVsnr#@Ix|sWv8dlCqr>~@MP!a?fY*$P>^UG>bgBWKWbIEy)ni(n)Fi+4rC@w zXG@Hea=8kD42l8;Ct}`eA{pUa7G5OQBMoS8Y!Je!%DmNvVi7#IuEN-00D%JbGB^q0 z8lfSqrdSi4BPb(uz>&B^X?)MIcl{Coel1-Gs$4lj@ZGy_tjbm<(t8u#*ewY#h3@U_ z8DnKK>8haNcxNyujMBj=5_nseoMS>{6ssD9ahA)-jrX9H21T02#9%1*Wp_BsoeA(G zh3X|bI$WI{mnY|qQ9|-u+a|;?n_g7_n6p)YKljo+V+2qFP#(v8KJPDNr7ipg<5!d- z{RI(HzjS>u7R<+c^xyn->uwKI$A?@?yKrw#_!*U1|MPJD#JHEx6Wn-xQt1o$E0776 z^XU--L7{_xn>nnijxv}!t+T6YxxzS7;#=o}jV9E6Fmj`{(h6ty#No4 zYPkdiQ-$77tDHV20V#vwp9nY0RsSo&6Zy8B%@KB#0Zh4^UIH~6yI%DBvTiFxkNN&} ztd;O^6V<% z>VRd;p~3)Xec+&YYoRwMcyP8gaJmfAyjr!}`{Px8ytOmL1R7tAG6ZKBlov@}?7!Xd zUh_Ol&mTT*E6o8Y4nY}f&9tmN*gT3rDJuqYcMGw=2peF2x|}y|6}Vum_wy$GKHFHM zjS__F$Qz_l&*X7{IRbNMLB(jJwN|tdp~1s-LP4hFaqkfru2Lj%kRt7c%dx%Ay?4=^ z3v($gY~dX=-AT;x_nrgP1l<4|o06v07-+okMty^sVjwwzMwp55=0F4L!@WbChWpdE z=MSv`p*Z>mS?E2T8FALpNCNXQBRwm!4r+(6IHv{04C3FPw{y2-1@9fSO%flB(NhWXK zxivdGP9=4m1P6&omlwy&vfUV^ZF8=g*)T84r4E>-snB}mu%j>w(58jt!g;w&GvZ8x zNsx(btkbr&l@(N~MS1x2KqjPZs?oSWn4Y~j1(Ulhdlm_E2ZyIzGHaU6je_vycs%~# z<4=#DpD>nK94;2~!7#7tWpv-zqLj2%_gm$UKK-STMV1#@x0lQ6@xk#YpB^A09=!G7 z$@3#^TxWnT)4Zu0>!MuDLO8nCQ2X_nB-dcJHKylyViZwHt$Oyf(Pp)(ypI%?c;K(m zqnyiXvX#qczN~BSU8D8I#l>51z4gIIk1x(D?@sUD9e14?n(0 zByFqn_uhSQbo>IdTbo;NdMC0>YGpX*J6oF_SM1@#PXu0Eof3= z)EzaYarTIGj^Hr|t+eZ%fKeb|(qWOaa8gyZk}}=i-p--Q^WCj(8cz80gBJ*K<-)va z^U)X+r`mFBlHS-GQr>7&I!gz65gABe;@anmP{MoT%&68UfB-pe6*q0DOS7C=(+E=z zo4Ms!21bU1jgyP#z4M(ENgq!_X!JV;tp`{wXsw}=i$f{JA|FWbBr@^oE8GcsR z9)MY|B5U8HU_vnQcLu%JTFQ-k6aUCja4yPhY5&CiR+|uz-~xZ;S9Mvu@q}R!bHN>n z+x>X5`Z#5i&#-eogOgn)pCbQNM4;cKw~t_4=#5prK%vGxjz_LtvR{Q^t*10Zl7XX? z*gp6$APfMkPdO#dlxL?qshn`8%F1e+jf%l!LI+tS7BX1AoyD;};Dl7Oc}Lw41lOx& zA_S#GSk#rUx@`xeG4}#|Nu!y+gCHL24G{LCtpN}*Dz{M0ily1n(Rni7ZxpFkZL~q4 z-bX+J2Nx5|5heL#xV(_*MxpEssfdDe20Y%jr4C`EybD@|Dob!WuoTai)nc*AcgKhU zLn)T@)Ww3v1y`2kMlv*3kyy>9S*EO_*cscuzz_kw{=8!jTtDSM`Hql}#gl^dIdqWjC|Fy~C>X0)Q@7Tb_h8^n6vVYBwGbIT>(J_oMBt-Q$C^Cr>+DqSLN?anTAbK)q-$j;9zl z+(^Q*m1&PWsb9N!yIqu{k(gc9%jsfrXG?X@l4axJ_Q~<0lP+6ZKW*!iqXqJ8K5w2r zdph3On@%gvQioA%loT;Q+ySae0X8K%s_J|sn|r+Wzra?liR>|d4J3_B!~z{Suytcs zt6D4b^;OlxX9c}^d$cHz5yFl3Cr9V+{^R$$P?4jP>2g^;dDLu8WHnpt?e3;I^Th4m zxNlupPNyx`b=Cam|M-*fINR7NKK#YOciww|Qy@WWT?gbxqr&QTFdR&$vqib$iC9%S zAfZ*YG1=&-QE4Wmq==YeET299{(QD`n0+K4MW?x+cg*yI@LUKgESsnYvcaGNo{m4!fkqrq-el1E$y;u*~+x zbs?+Xvri8Xx+aL@E(xeqiXk{<*n z#&@I`j9%$7=!+-?zG{no^*zHE*Y7XA|FvrBb2{)Zl6>*nE!snkbme{@r688XmPc&5 zfKV$EZ(Uz2#NH%85V5=0w?F#|0wRa{8;j`47>jq-nJH1pA;g??{j^nE9_%eQmzSNBg_my2d$Teh#L;Nnj;$Xch z459c+V?I&TMaRfY8k=|?+-B=}dwQ}X#gL0E%}!^_tfOh{?a(}N6o|eoPI4FX*?2fK z$~VT5L_GfGFK^#{W4V|!F#je+R@h1}F6RQWEqyT_(TKr#L_l&4zVh*@K|w`}&52Z; z5b=vgkJ^X6$UA50n|);o6>LnWch)s+LzzgjB+!63@CLo{#Dg$tb88b#XVv9`M!AyH zt!>(6E6?iH>2yAvY+Gar7}gn1WONE*0NZ%uLGa~mTulij>v$}$T=e6h(Y+qiDET*t zb7`&Vo6<(BD7;~q;9lZ#)rKa0dx#4Woqt8#yW^;u2c(2V^iePYBuA^5l*r<$cHSA% zH&?%*!5Ivzb->t14fa|Z=htS?#wgqeSNRw@5J$X*yw)v>7huyir3I7?=vG>x_$fvf zi=)_G`?5ki0=i%xWA0%D*z-h?BlM2ZK>CShdVL5h#o7nPVdIWt`gf8EkDbf}IC@)O zHD$)}az67da6Iq7`F5%OVyQeHaEfAm=9_w1uBuU%GBT>FiUwR(mCVzwno+IyyV4V; zqg)LW=^UzSrL>n22`3rFmC>uq(l+}3t=(KE!P__Xw~VzorqdV$v~CFKrVRFkZ1R&2 ze!0EB^ZK0|+Z&VCO~3cO_x|)x{w$Rb?%mma^ymXT9XxpOouB{g=UdxD@9^pIoU?4Q zu?;Q4$?Z=bJ~1X>hCy~jNdFe~Qz9^>Fb`opj|n0BpG8y>>PVogdt_R}KvQW#=%su? zwFZBUs#SskEZMtR9G#(ylgfK8=1`+!K8!X9IdTuc*m+TEoZcCaa+4jPaeMi zlMml`^VWC&>3146Qq?6-TEZGkjmI(kEK0zf4~tnBJYCiUncC#U)h!T{{(X9+cuN%;sll4hd@#dfdbTo`j7xG^+6W>kH#Gv;Nxu zopbK2i#WwAmnrs}ZE3C*tQT;4geq_&Qds zr&GKt%6WKS*1l{mT&L~BN^Pi9)$)9^5Sua^rN#N>Woyh}Fex@Rkf5GJMgi+{6pZbT za<#2tE1uBQ801T4q_bECmxCvm-ckc6v@YA15j75GepV{wj_a)TDxDg8{~t zVAjRU-~ZOz+dG?x7qL+Fz*5wjz`_Ct2Q6uM^TFGXj5%JEzIB3f1LqYghQqeeW}TnO z9LCR&PHVxpZ{G(R5Q#IAqY%O<6CX&YcHYc|F<>U&gOnNTmz>~0?5;dD2=#148l9^x zj;gm1fk;N7!)095_%hlX#4-l&WDM5!K1N1JA!7~Utfb|HCv}5RwrHRxQn28DJ zNC67^4TjEXqrm$ljtI~Cm>VB2!&snTLL!xkW4soKMVJgns#iSE36@fjL@+*zu@9!N zqoRV$gIiIgM6N!|a^;DK+QizVG(4B09BD=UaGu9Tv(bn!>mO~b4 z-PFUu;KkvKYE|96duMO|rYFb%Mo7A*+juZ!);xRsvB>EtNs7rPG5Eb(4?us$(Rd)O zTKtb6|3xfoE%+FDi27}{TdC!;RE-JKrk!ND)eAw{s;;yLo0egb?M((pmoG#<3f6Zq zsKJ;?p2K?LfO)|Znn(fm-#~DL4hi%#1Ya4;klo%)_im2rd9`z6w;Xv?0J2r)e$`vy=ysqEzHA)X@Np zSmV2Hzp{^FbTUXm8#bgZjsLt*vQmoAS-cV0U9+;C%Cy zaScefAbx>SbYpi5j6D`-A{QhDRb`T~H{ZOy4$ShNVXC2yk!^!kGkXM2wORV;1>D*2p)m~MSPW4wbdxR?Zw-f#4>?FwQ0?NJ5}%F=CwPdVp-gg?iWmz{^g?gt z8{PC&RW%TSZHrWu3lW9aJW#n@siz0WO8d5jOw9P{MmLNYu*EPlMwRu7y1-@Xi5HYi zr*-QS&|}~jmEhsJG{B;PjW5Gc4mqBP?OjvkI8Pu2p14)HawZ_K#TU+_lWO&Fe(BR< z`{r$t7eRnU52rlKb0VPQp&e~o-}f>ar5v2Qy`!sP;P)&o_`FuwD2^Bh^_&g3UxRE6 z0R>%^6bU9c(o#+Qk?{c>lDNc4%0pzJN7vZckmng90u!lC(8{Y;S*5`|r*2f;#u9=M z9IupbXwZndg~<4cJa8JfF>B*=aH}Kn8^N>dL1}lBq$y>*yA1(#u)mPfI1sOkG$o90 z?d%MOV;FI#;2(X`UTE z<4Ll2>vl2T9Byu>!%@eXbyzwi14+AMINn7pqaBF2|JuCqv)aBmU-4qoUpgDjhIWI*-CdkVtaCjXD_jk0?UiZ|ZW)V} zdOSaI9KD{ALCh`E+I@XhwTq$(>T^kUwufAx0H~(aD~82bN<7{u z0*N-S5D*w=F=)XH$&x<54fZkGgT^fo#JI!sRV;&|dTWhBf03=?f1@c@T)gA4e)vBW zIk?U*i(lg(0_4ts4csE*v5VgcJrSmlxrikT3n7*Ufn1jzeYSiA+xt%@-l4BZ;{Pq` z-PT9I&f{46#KU9O9esN(9=(G?Jg^A_qF4eko@n?x9On8(V2rMhIQlRDI|8n7HLl;$ z$WC7$_UiDlOBRPm*WC73VR)okty+V^RcY!hQf0fm+#2MgJWIJ?0k>s?JWgaX91bZ3 z1SsxKVr3ffjB2(lFBZa*tzvAerd_Rw^BEWO%lWf|rya`yRurSRwztwWZQItEnA1%n zszM_Qnt5!=F(*s`ajlx<0Ux0GfmkZspaqI3Knh1*#;Q~nerc%i8 z&5_?6KQC9wcs$zPLWEj4&)`uJEd*BpYkZy$9kBOt)wFE zkb*!C|Ju~2Uz31&=8;xfFirrpid;!(b#WSHWdT5J%$Zmh%#l|ajrpMgXSIr<)E=VJ zwMEvoO=FDtX4Qlwb|5K(`O5g%caCYeQKN*TGf?Q0Vt03UBgHkYG0K# zB2aS%%p9T)k*cw+0vNbao{$6+DZ4^uvXf?GnT?9kR#J>do4e6XmkXIq=L{DR_xb2Hn||8Du^1{S$(zfh*Qa!3gq#&{Rr_S@plD`_3=R zuB^V3&rL5??x+rGX$A%Xv&^!+SeVcJ#r^=xfMFRJ%rK0kk<_iwv8pS)c=L&_z3;1{ zmBxcVNbS?zRn_&XUbyexbN2b|Ux3Sjz)v|R0GNajk3eWywM}Ot8UiP2KKRY{)^bJ5 zdhzV}r@#8}qs6Knk8l0$-~6w(S?|rJtMfJW{h$5mpFDba|LkHD9nfDP(AlL>grH1UP!I>%18WTRn;IB9YvUaWM zI{U-TF>K%^SfR=7I|pe-vK-RQluTEP&2&5k0ZV}FVMHtXCr_SSvYrnQk9;JH`D#3# zdhY@Vxtd6+Npo9Q1Pe$Ult6;4el1AXfN5{m$670o`7tfwOyK!`*>+RqC^)LSFzyEW+ zEE0aNuP4+`DTnq2W@SR_X-DXA<&8|IyGC5RSS40g=PTl)iKGj>cH9zmHeWASo6UNi zC30M3S~72-d9At1lcMkJVsYVv0he43v1!hO3K$?E?g+D75~%=lmzqiyrHU#cZ(~!8 zQHksI8>I}&j=$k4J=7rIBJ#$7-lO<~TTTsqldBuA*t=Mn4F8SW&KHBn1JDMew*zM| zwHa(x3mTjthGG0WOb#?K0`CBe+yeb709+76U;~0O6iFUQ&~H>l*xtIrX2`YkHKAc> zdErxO1=JT3tm|N>#Utp!91Ds82<;-r6D%-^K#>oUijJ}lyC8zlaD^*fHLwtZ8X&Rj zUKPc^-*!Q~u9>cL``^r4!=GW-oiTQ$li!X(e1{{Tu<|V_gV5Mlkw6;L3n@_w?%eA1 zoymBTY2Vg@@p4_83V6zSF;0?%GSJ-x0(wj2dGXoK>|)dQb$k59tMUHyXtEE}nRTCk z_Oz@@Az>m;v*hU3VNqmhnov%Mh9nb6N5ub0CQJw**y1wLYLurJ7fY$wwG#W#;phhJL6v0vtoAP2Z z175x}x)P@A#> zO1$Q2qSni$pgcITm@o1}r6BSoN=^eJ(8ycDEntA4Q%u8R&ibykd73@EduJiz=4auv zuU?a1{CaV5_UHfYkL&7;IREpX|K7U~@4h}e`|^tyQh?d-tJlXL{QM7p{o#{M-M~{} z9+Vqdxv?NhioKzTT7{>kc*3|>%r9>l7Y0O zwBxWGYVRBDh;ai@p6>UH8wvSeA>A-?_K*lb;;acAXX^ zP|=&dwV|r{a^+~qqvf;7?kPn49&w&ziFSQIBo%qeE*5LfxE9pJc5iP+c>@9a@ODGU zeLl+4Ob2QNgXC%7su)J=RqxuEhT@Zd`e4+wPBw>ebIGZGU!DoikPla zU_2x_m*uKbB5k`?2q-_Bu8Sx%5nK`mKDe9@yFbvFFfME$+kQoS8>3bE@Vu^?G66p0 zKXN_L?D>26&-4x3n{V7quRr2#{*A1Mr`KfiGZ*&eG^W?hJh? zME+m|3?3MsmAF#bZzU#g2+ptRGS{z#(XFFTxB{|<@YI?yPsoTy#b_6)4N=m-(Qu0f z3hkX?=YiP4K%3?yC^SBD!##bCFHXNvEq)&z>dowtYtl3&|1Q91S8cU8+;)94jsfDj zCYVR^b_@X1;98GE`llo@^vyx44#ruuoe(T~+`{-sxg1ZYlru!{kfDg#QI6PTceEF7 zoIO9Snri*D%JMubigi^Q=aO_Rlmu95log|qO7xb=4`&Qw^&IKa*m04J_YsU{v{b+< z5m8G9O!EaofVkLQ&%_g`#B2ClkaZ0rK@X4p?t-G_`nT;OY8lX*Kv7_LS z-9qhv4n=MqJuA!oY1+0OWE2>y6p-sA;2#!U7$S(by9T6Sf=ZoUODQQ{4iOHc-BSL& z1=}TjMffOv#IkrB%{T=45L68SL_uMuvR*KjD(!7M(8>a0ia*{!%YPI$VgwS4xG(02 z1bS!FBmrwO0IvMDtfB1sYQ<&9E3Gh#N00y&rxZy*tz1@Q%gu^%5eatyaxl1j!B~8_ zz|lq+vrR@Lu9F0bT>i;tpD+rf;NAU0&ICxwumO%isn{5@VaWXP(eGUfsmJ#|{qv|@58pXFxbfoYnkmA;q$tsxC?x_Tsoo^l2(Z;WeY3R+GUnWqDK=o)7z&7S?{i8brpef_@J zk1pK#$%A&in2oY}1&epv(67EYRWe;{%6wGp@85j>?DVpI*7t7EOGi9?q?W$7)RQZ} zN;)ushLDZnvO+=7AdF?mHee(GVAY2n#EG4e5J{z7_~E9Y-aAe>0o=t&&EL5{Td!U= zwc{}X3Zm_csh&-Xvg*6ma0%4%w(W@{sgTeYXxb1Wg2kROf9K9!A!TdKrmXVGwCPQ2 z{d`qcO`8>vEZOw+Xqc=t|KNjrWzB@lBFz?yQmWZcA3S0# zDL2dU&Ws7ZSS?bz7u0TWJQ5Jzp8@VI9o)(-MLE1oVpjOicc0|-O*1&I}sUX%p;tk6o zjvQ)x^`PMzpBPw2+fol-8rBoahs-e88-bsn*HN^DTHy-{LNkL;j?)MWvw@^R#c2;G zQPiO42UMxMI%EJreODSh{QWH^lfS$C>ic?{cvB#6&7f#20^KDKG^>Qfu3FuBaFbet zLCUmk8fUCCHk%cr>4*vzh{Gg**vUjLpx$L6*~xFJ+s~iA>RQ_w*R3{;iK583f=$qP zTIf`Afzs>YI3Q9MtOM$?1l|TgU%BXDfdiu%9;ArKrA;^o5aP`Pi-Z+MhNvBYq6JG7 zOO@I5-iCGUUM!a)FJ`+trtc{Ob_#NH1A49mj5y`Cw;ZNi4V@gS`rh}g@zxb-K_CPa zY0=ny132ATi3x`!(7vsKiJ+9Vmcw#xV7KZ$=KJv8bae2{hB5+?SMLo@QGlz&K&(Zo zD&-_9u}vW|km6xH=1vD6B7!kLS`oycDTU%e>H-RhIm6cohEhEoMT?uoAe0D*PYi8g z)E}Y1gtLL65;1pH3TZJcmJ3MpQc7WFx1fk-55CXW^3=igMhOX9TP_dw_b<-Rs=DS( zOm=qNl1NgbYziWZ*M{U{e?C(9eal0v3r>|d)GW~n+KRn#u`IrCw%a4Bj z_}PnvPKy^WU%&U$Kl<$P(}yFOO;dqfs$IhpS|;UYObKV7ZnoJFNm zU`I(3NGGIw_};A~<6yRoM)`vRxVPi2P|jvv&Fqf50WfB z1382~KVN%KWG;Hkn%1R~NaA)zX>oEMXY5=R0Gw(Zm|JUT$mvGmmw(4(K%$qEm_u{;bG@WD-2N zz|mf5mZXsQtd#CLUsX*oZ>v&_(0HwJ!V#~q4SEf$#29}2{ruLiv7-3q&$BBS2`Cso zrpA)>#%-$6+}&K(#fY`L={mB;({6v96{9?csvj9mcIs_3R=3bh3hgzxiGfF(JV=akq zYT&P55zgNKkyQ9=tH~{(v^AUlz8}V0hkf-h9SyRMw`MRNOlN6?S^n&M#H%_0gh_}zOqFQ;-sMT``-&sr)i(p+;vuF`oU z7BCdI@1EitJDgJiYxjWFP~TZuFoYYbloueBz%k56!`b1{6bhL|aBifN5ldxPS6{q7 zHffe#MoH})P|hUOTDF5u6CM2JU@nl7LQWxwje7%<2*p%YZ^})TCLCBnriIRe0Fg3K z5F#wI&h<*@w&@~+=gMsl+ybez*03@;7q51W=%LJj@P<*&okJ*lQ^Hx_YQAi00kg(o3brN*=UrXo}4Y_MvsfJkOH0P)+Z3!<;#$Y9ry$7bgz@N#rfL12Z!E$44xoINUXmJ*dqBIeb0rM72h_!$^ zD@j2b`Rv)Nr_aydfA`j3{KcPMn&{j-IEY4CUw!!a z{JaG56#CjRx;1Re^L$g*P?chnS4k+uwbo73P>R0i10~V9;h5t_66e zqg6e?sT|*57lNg+yK`sf{CwU!n@o0$Sqe$prn-6K*6ZU}9CVoYTOk%{Zo3Z7D=a~z zO1u~6r)5>Or}O8VO>=r$LULbUjm9Yz0k8`S6VbC*pWnT=H_CGY^kR7NQYr#_8P63R z=St}qToxRfKD(ZA;Shnbsn%jLE&2@*nJJF8ZMD__kJ8k%Z5QjqgW1{HMQ>=| z`L3Fe$N4#3{pbJkU;ptRfARN!{a2g${3n0#vmzh=>o0%x-cR4XB>SJeetrAKt>ELj z-F*J#=VjHTQ!NpLWayq6C_}5z#9w#FZ4*8kOW_2R0>jT>}7RK$^cMAefDoicV)D%|-7**Le>BYRWtgJkz`*fAsT=#}ckSR*{&{ptm&^(#(;t`nBUj>G0`0>%}*I_!|+l_~p0hKG!Sv z8(u_LrrvMOXv;ODFi9!N6yQ4X#_le^9KZo$pl^1La9FWqs?fb<@IFA{UIeUwZ`n}Z z1;-`7d;b;~*`aDqr&>x*S==5yWFc;`J_bDlT>OyuF9gIXQ>usnraKzU6lv=Ej^uF| z^lA*=n`n5kWw{Ytu$yS>XlRk{-nhk7ZX>udBd-m}(B8o#J7a+HDp){zS`4fz%iw)6 zDa@u>t=2XbM+du7s@@LT9F~YAab#7u&UMUbP^>uPJRAtDRD2gKB?MWVq3=2=)euk3 z7?4|I1Qd$nF*MeIWtsPq3rVjsI6Aa!GR_(2Na(!>M$wPQ4chiAp?N2UgACu_ zijO=VRgNBli0WWHgtSk_BO>`BZZot83OTwQ0A~-(sc}uR1`FDV6F()+2dz{VN!Y)Ab*G@c#Y#`%;_rYEJz0#q&j) zrKj`LyUD$r>ygzjk6$mAP1|~4gdiNw1Xm2PJq{P{#l-tixjc?R`ZK=BI&1v_z8aCPtD(dQqvjJk{~6M8~9?7Q=Khov%A*N#iPK`tfx1@rNIM@cTdg#XtYk0@+H5xHLjhZ`t~YyoJ7??qvgrl_LqVMDx#S_*BzHIOj1P`xC#SFao}y9pcvGz@ zjY-OM5~S*N8g(Mj*^6X`K^gS6`UZW?>zUGT`yrqhP5ojUeN3>5wjb|J@h>F*~@is*U($Fl1G*CoMK{n{1fIvMSTAX{gS+0^S0n~>miedGf zqE;M|fTgq0xEo3$kh2k@w7PZcR<~Z+uG^W-l-3?@f>!u)xh^-W>13BNVH{bN)y>%< zj~3o}i%rws-)}0F8i+YgXO6ADNh52$661H5RGn| zT#D!{5S$f(a9}BITcct>8pqCFum1k8|4z_?Np|zjonL?aSvoo>TmIFHv(6Hnn}$Q} zhKVM`sI_jp&44&2sn$wj%>zxwtAN$uEM9szGa;zP;3Jl?3;{FmJkbAt$g|5+STrz* zl=z_FKV?3fWHVzoy=%N6DJ9Yor&D!wWA8G27p(~#pd)BT>Ja)ZH|5{`?f;z3c1GjT z!SvS4$O2Mo>?86@yhl*|QW+4;c}l_8rwShO6-FHV;?4<^3j zYNWby6)7JTQy(f$!GwM?D|F6nFB~Kp00fK%!IIHV@u&%_SF6#^P9MYqwo;^baas0_ zS*6+NVE=GEzj*%mqg1$}$g8IP>XT0&JbIL9S=DQ6{c1i>6tTWa_p%yLK`I62FBp%V z!!&qFXiiBcsT_~!vl9rNwwxHPrmelh16=7Ikk!%u*G(DjR!#deK|Q zIJ3b7W`u})JL%0kJBK%B)^!Ig3FuoUM40r24-S__CnV@YIj7b(jDha*UmV{(y0KonNJmA=Qd-G4 z4?F}}dP(Fc!N}c|MipUOqJ}%bCKrod3=h1OkA-a$nNoi3NQ6_dAcXsX+!cDbs>6O- zxG*MGM_vZ8CL!@YHR15$d~$xedG9AvpRje!50ic4p0bcLF9>Z&$Vt#R!`m2p#sa>! zfLn_ACO{Uk`1)N?v!S^*Xt3jV+eL`U28f@{v`>SBIy_u;oIdp49E+f?l#&LRldxpUb3ZA}hgtJND= zX>sEfu1zzI?QlxdXf)-a&XZc^87zkY=yWO?7Tod82>koho%R$DdzrNn_!OnQi@{}D^)Fc6{{ z!^<+Fq-!e{L##-6VB-nmBhZ+TVStzGb_5*57ZI>7ac#?vKp^@j32BYvTx_or3d{z; z?J4kwASabEZ? zTI+x}I~OFO)9JKpR@3S1l_{Cs03M(&LNO>qQkDLwi6d8PhBQvW9h-&K4OC&ZRL1CV$4-woTJ?INH{_ z04oBP4$cZ}7Y^o@WGDr&#krllez1W-bi)qBxhMsc*Id|l?(cu}i|1PAoQl48Ah-ZZ zJ-c~u^Yr|jauV=T#f1oEhn!b+*|fDhS5oU@G+nMQAauL!C8Q(wOhQ2Y;_tF8GrocSHJ)M!@4SqBwx%|VYxKXjb`I7zkGcE;r*tm zF0GIHt{#o0>0O=|N~V;XvZ)iG_6Qzju&iD-2h*{V!WkbmU000@uCr`B-FG0vt%XXY z7lLc8=zM-^TnpCr!ALHipS&>6fpvH^MaE<*-+6FwaARV^lB=2t3;k`%JOoDfn#s0x zQej>PM=v?_FyYvceH+Ul%oGT5+gb{AOPvW32Hg5JY0|d`l8W%(o9@zKkT4Aez%?L( zf3I~_ct%k(D69i@#VCF8;;9mfiD}n%D)@;CBKDC7%j3GY!5vJLdcM4KZuHS~UB`63 z5VIs(FBbW9BC~9>S;OX>jd!O}s?GVy4wvp=zh0biSX#t-cg$mV`)pY|xuMMGMN+jv z5i(Bqj?2aI(mptxC_Q%}N;Y0P_vuAeb>X%xHJP^cGCkazuV?k?gesN^F{RF+d=LZyTmZ!_mJkC@~-j z<@zYnVUg8!)%U$|y+*ei=z>LNOyBiZNgZ(kuvd-|Ya83Wl)Sz8-4$_BKSo2s^chcTrTFTp$}5X8W&^Tvd{&{+cS(xz^q1Tkim zXv8msgBBrk4rBz*(#84u}jHaHTU4%yYuA4X4~pDosv zCjug)(u7MUlz^40ZX;tZ`aCZL?>l3upb6L-(cw@NLXGLMBjyA4`kl2Xmj!oVsEh~{ zc>}g5g!q9CJCOCupn&uQE<*(OzLlgOf#d6v9ErjHg zZRS$As;PV*jPreOncz*`O?FZcS1C_Kr*bKh7y;@<^G|Fq3`^vYZO7ojlitBax>#N8 z&kpLfuY0;T-a*%#DiW%djGO} zbNks>$3wN&v?j~suP@_MX4B24_3OI#9hEflShX9eAr|`iv!~XXJj+pEX_$bxu_Vi^ zjoyI!UM{oyH*cAB9Xveys_7$DkqDXObw|3kEXxbV)a^Szef;?Ne7>m4+E@=YO`_|j zVS?ug6M}H5Ml*T;;bA@|O}Eg}!uiSrcHSHX(2yyv0#3-5`%7$kGzLgXo_XA_!bnh@0!+jdR~mL@IGyh!R| zF<(PTdzHgf`y*qyac-8HDvtNC*dU$(>$@Y^^Ls}F+ zpYDG6`6(e}dN3VL*k_NIQQnvyz5knk`}mK3eyfM|3#Oa%ReJuk*_rUYlmp&sT?~^& znUD8!A{x^ceIOxWC!5i;FFSR4Psy#%KL;=|XW3UT|8?(BNE%tBsICv+I(qawO~k)> zc<8oH+=4>zY50823c4;d-^AM@d7Fj zSeo_JHbQ2wG!kS*;DS0#mP1C8QnIyelBlMsLiEmogEJN|fvfVtLs`O#p_ou4}SW4 z-T`gB?>ZOb>(?(1c4sOkt?ByM5a%y;FAJQ{!Un6mVu4 z7Mp{c1?LA}e16hEPrW4;EaII5PzWN@h_Ab5=z$E%gROiL?LA_IzN#@@Rjm(ijUuh2 z_DLcbb3#z``Q%U{2QnmyVpkz>AwbORU`84jIU5#0PRn|p#8`|8cDMClXLfXBI-bnC zHIFfgzULBL%A|-=kyPnuZ81)y=54#Vd3bPgegV&mhORSO@yl$lghPsS53n~`TrBsG z_FlhwT{ca*DTU+^A#6LXa!L(_ejUdUOg0_w<*L}sSG)OG>hxl{F~oWzb(U9sJUhRr zHm8}U4dNlIz>nGWHf^iv7QJ&AwL;Pb2-NfAp|W^Wo>eUIwvmE!kiNhsj(3 z40^xLl{8T<-Co`1T60xgScp9s)3~$QB8Z`!|l8&Af#NyHOle?#V^>{Knn`7jh{wA=zSXWXc0CwY*QM zr0F?u1EP=23cv44z5hs4nJvx=l_V|gReI2g&J?3B=I-f9`{WiPef1u<`9t{r? zX5asaKTM2htv_RXsX#0R4iA zI5e5A&J$=dND&a_?m@}{!NuMNn7vW~ks`={XW$g&k<*EO$|0a)6We4I28a$(Qw{F} z2XqN|)=Uwob%60B0|h3>Xg0uV1kY9cWfrfRwj6)4u1h5ZiAZ%FOpr_r4*3!hK`3(N zu?qrrsPZtIGqznD!L^yyZJ!`A+*?~9Sr%4MA61eJ2E7buKvv7N>HFb*^r&nf03k+( z_9$2ja!!#$1Ew6%YKL4ykARu?2IlDCiG<#OPZCI#Pcz7YRwNBnDLyJ%t(7oaUl)mvQ7@6R)z>|dzdHfC~bBr>iAmH~nAYg-L&?9C7 z1qHCDMX*kDw%IKEYMn-)8G`^1M9-LVYkQs8)x||Roe2n7L8Jpg5#ud^o})ZHIax0j zzxaoL00Isnm+dDLJ&^#@E*2M)om9%StvW4=-U8SuveE0a#liHx^*xwKb$+!hY2-pH z!FAa-EEbV?4$s6Bt`I_k+QK+1BwXMMPJTlo*cvgKqM;((PC3WI8ji-%(NQT>-rE$go=Ytxcc!np&TrOm=K%JA31>tb$rxjzHRT*u zvcBh(U~@St&JpO}Hf=S4VXA4GG)8U+`-L2;v0K1|h9uRd07m@JM;n{`qRR@3;I?q`TB_@+P_}B@Vavjr@ zDHgaOlB2{u3<>ba8{s@6#bRIzF-jE^9sD|K=Imj8b4n5XXtm{1ARl&^bK|p zc*$|J=yH*EW)%Bru3M@4;0X_~9JukVqytf`^a@-(?r+n zm2}PCB=wE-J;^bUF2sa@1HCsfz473^#b%QP_vmLX89g!24~3%RBK`EM^{6Op_#A-= z7kS^6R@gRntFCWAZXNoAY5M%xMXG1kRTmdsk?-Bu6KtnT_!%P`1}=|sE0>R7uWTay zj>AZQ%MrdNWChehg>NSF;}~EA_|DrjtPKp-6VRq=oe)4WtP;YsrrK1H@I?iS4%PtV zS4t@c0w!iV#=b4BGjb^GBnMP3;&xM~b=BF%fCkES0LXwv4^t4X5Oks_g^Q9!7@V-7 z)zbGpcmcx#N#TKFNICDjhI3?Q`>Vy3#DVa*#V{k}UG!~xes)$A`)%8&Y1Y&oM{;EV zw?7w=Fd-!$m@Xkw(|b1riFpUf0$^kFIR2WSuwu6q(^&uOqFnRQb=gv;6;%r zH8^wjJ#0CYRFE=_p)S6+OafV3DP=5RkXxH|{KYMA+QGV~flj04V4(lQ4p3%CiEJ)l z8e1UR6G5Z{AzKAqAtj~<>16lNwEOe(_Os7U(`3vzpjnAhT?;nZXJ==9Z@3iRMd*Ta z&joC77!NzUQ!af`$Sh@qIyfwy(K@98)c06$akw54gM^L`H5B-Rs-8C~NwP5&95_1g z$<;_aRFQcC`Wtii{&c-)&W<-(IFwRss$FcFy;(kAHQ1GB z20bm|F_})39F4}4;>h%`W1ypaygS?5R43Lre>rd?YTDkkLD@2wnU*}u$IAxNQi77+ z)3eigUxlt|G>fkFXD6E%FBWHuRqgt8q;%q@ySlNP*-e>_c|L(`Ap)0<5gZF(A`t6_ zgTtsbVAuw2FTBJl+Lkb|^XNS1dYEIaaWTG?6zOXe=iA@J+2w0k#kI-G6+aOf8ZZL= z_f`Kp;?Rfhe3wt-5Z}sa;EX6nG>e6;{||N7)f~xAg_Wfa6CBo?Sm2U#EaC(Fa~?=V zK~Y79OT5cQ$4Q-(@i`hBlX>DUD%%0ZM$<61`d=(u7& z@wBd?fj*?6UoQGq9f(8^n(=V4ahuiZ)#{@U<9iREpT1ZuNEG&Wv)Y`qLN%2vADz8& zX7RA8+SWKlFNuBe_;@*2gNR#*WXLNPlN7WM6G@x7+Er<+CAsoXKX54T8+PViWN}cX z=JArfy!!psshc%jRj&o}AjrSfgwel?;@?xz|5K#@-KODf!z=w=xsy`*J9;~LlSa|q z0M`7BL3R?c~C0+pjmB;5#8S zc8I`|NJCRC@l3bV$T`(kGPwAHQPPehc+Q}nwW({#0q;*S4bFCBPpkUD@({8V6fcYC zKF66OyE+x}CBzJEwi~dZWe}Ym1r$<4-{Y@fgN+FgW#QPHBXCslkdh_?zo?W7ZUCb^ zUD?qxCSZp|xbqhFfJ*K9j&kV&G$87_S}d2F-G(!U{1{HO%-ECTHx@}yeBDGJr|2~V zx*E`9*Lpr{C1Z=(tf`GrQUGownD3*Fn#g3uSZFTq(kL(B*5)8%D*@KeL^-U@364Cc z3DCYkP%M=J&Z8y!sr)hLyp*lCCcK#>bdcivwAP$4BVC3V&}1?py(d(Xcm0;QrkgIi;b7K`Aq%Bdv)HURjS<)4ZBB?yp#Doz1q8#6X7iWN z*OC)!L(e>osO1311K{5Pav?E`EoK0~99%)GP5=DWRU<(%a=DJrUOnGzhtVbv%n#!x z;Iu`Okt+uHK+`l3^c{QvPfp}&5I9yP2dH=_YP7x)lB1L?0rbkL^|81qmW);0=Xj+> z*Y_bZDB&ow+5XoP5x`?`I2fKPAwVZO!2%7rb#Ra@<;uCq`tLMo|iA3bB@ zsG2|f^PfTfz@k=W90v(4X=uk+wP|O{7+zOQa!=rZFl?|>UOW%?w8OYhcgYbt9W)qK z_nWkw-vjfiJP0Kwn>0~OSYRwJj%P=Y4%@TKP0lO?m=2L` zQt5nL!wn$M#N0z+1eGvtQs;wBIpCxpu!RSg6(hhK8^=h9%mH9Cms~_X>pha z>D?^#9DzrOmOOWJHPXLW@995l^1|P8fDL6#YO|TP=f8XF(aNaJaCJ6cWR~sR#4mqJ zoB6i&;TvD3##|hbi5`=q<)&p$<`3>l97 zoxhO-Nv|06u0)`jrI2HA5VaSC5)2hydNI$nA(TwcD=9O>^^mb~nm{1N8IX|!xm<5w zzP?D@+p20g9h}mt(jM-cv~3NpGmv~FmJ)=_G={5d0rAx(wF82= zvQKpJo(~+q=Lyj|Q%Mn*xaCK~zLJTUQ8GZ?6uN6SHbS(sj&tF>g*ga~Iw=z+ilAh7 zloBP=G!7HM!XV)mzHp>i^Z~q%=Xy5oI@8o#a1SM)3En|OCF^{4Lg*OjZnrx;I%H6+ zWKKaXrj*!CU1h2ivv&@y6G=iG#Rw`e@Um%JzG z08e-~i{qm2$#~2-wg^CWbYtIL0%JHAN_l628xC$jd+Y@L{Wym8LdP=%2gs3<78tLv zImM(+igkkuU{5EoL+B+u1bO-Nsa;(fPWlARONzC$gRD6L^iN9s&|SQEzC3x*TgR&= zWr_^t-0gP1y1FdwJ3>W>NKHph)AK*SV8~FaOkJ6URLXTb5N`{?p@I;7h%&^Tn;yOI z1EbBm?P_`dc$lVs7+!3LhVirW^LjQ@Qg*AWR2u~~Ep*g) z#58Hi0EKj(JbiJ&fB@-!Cfz+@N+O`D(ocCOC7t`(hBXE??CLutRN}OxX^Z(X9Q7Drm3`PP+ zkW?~-V3ru1O^D}`EbTyl|LYf14?d%&sxx(5lEZ_=AAj?78pj-ik^;U8V4%gs4v!Y| zh2e5+8euea&p1bZjX>!lOX%wX%>Dv;qT9)t3!4xJ9Vv^mAW)|IdH&ha9%|j+==Xb0ji-i_7J33^C2er~Xrin42BnSrP{>liDFh^jM z6*w%W1gK^Ns5@d=9f6G?txMj9l!bTTt^|s+7)n&j|4|wAkF@{2M(J)YZtTB0a|0={wWsKK-S%+;*2?{L@Cp*ysz9 zC!VP^XM{~Efa|#fGA32>mF8gf>JzQ&ETNG+s!GMUqD-gk^_w*|5O9r9?2V``OP$RiwqS(@BZH2u#sTI=z#=nv=T_1P9zAHq=Z;Rt&(oI zjTuaFVo;otkkGEQ?nb}aO;@YkwgYCvTOa;(yIgXqj5gCaX+S!v1SX7*b3TSBlv*Ax zXY-chjFky3j6|OEW1e-k6k?huejJatWXo7(C z%gDK;oG321GA?GRl(o}sGHdVwpa|z+wFcUX=(=$}*AhskjxK44$xT*h&`E*nnrfi! z&t|*Pqw!iQTXUr}TosBXbi_{1i@E{zyUQV9xL zUPC59DTsX~oI}e4_wnx{hYZ~l|E7jVeh%3~M2y19%>)wyXjLdEL$$UCL=E@)$a!WA zF(1k)LPU_;1vl-r&obp$B8RnBPz7dy?Ew7mcajkb6|_tt3Y?$nrnb1ZR*b?;V}J4b zRWwy2Bm>wWF(r=wqUk+Pp43$xusc(_u6T%(sjJ;IjlmKv zpPjz+9^TCDX7#a;eE95g_2-K#?nB))v*p2db$-7V{cb0j%!G#M`q2dq_78P!B&q?B zWlwwrz+5s891~Bh=icVk>LL*5EC6O&OQ9o!UBMyl3T!`g-hudDP{EM#&2g0YYaamF z!d92y@F^0;HTl}V$pemSVT$;`_KS4 zKwkmR1Rn_W%Y5E#d~h6akt~P4OI;G0p1ydVJ<#%nVhAXbs%e~`PVV1tTIIs#_^6sK zWK5mnlp&UOJ&L3QCILKM2`M_Yz_=i(XvL#-{%U(MKbDjf??UzJ>G{F@Aw>ERBnjgg zj8^-E$^5x5eC+h)<#v50#bGnAV~E^TB+3-Xt{Zoqhl-9!LRE6(ysmZ|hs6Xz?IfZ2 zloSAvcIeNvmfd*q{trFMP|qD*T%FEMtt%4=lhurfDpK?6^`@=oF0$p}aRwe${PEkr zdhq0aoP~zF`;QNGn}w3#H*b5ii=wDtpbRx&6i1=H+^$Pt|AxRTO{go!bVu08cp`;H zHeedZiE}nhBc<%kczIH#0LGRQFF>YS#44-7p?=(VdbA%#HS{_&d7# zy_4UqR1CWX@aI^D-hqV}DH%U|N_BculwuKbRkc*k+7~Zg%x0~Y68aR7dlkl^myE9a z?Ze}R21h>zP;;@-VUj4mp057<{MBrJ@c6Cw>uRoa4W*BUi3bG1rcsbiz&Px=MInet zB|*`HVtU|~L+gZ;bp8xdl9aWH8DOe}@6%l)gQDC{*lR&bF8JN`yWw|l|0xD2O012D zZ~2s|l$vnvFkw@_?pn9jiKZAyM`b#A-O~wLx!hx$|y|5D*|UvB}E}AX2~%}Jr%~--kTVig+Pcd zDG(7t-1dAaOyiF@n}98XwMsyHlsMb4eD(6F>sN$$0azc0{i!jL5h)>;C2XdO3dp@| zD*m>+`_qj0!lwj zB{Xfkt7r4gdYxJBhOUMdDC?))br$_<-Dk$4PdZX}e);6V@fe~F0XcFkc&M>F+>kX z<3hVMLiuCCc`}B;NN_$gqCD`t_3FxBoO;d|<2Xqu0~rICpmjp4p8^~f3kg%{`LfoA zQ0|Tnn&qL&kRTRu@j~>6(GptBA=u8do75u(>FN8F??|$&*!{-`L`5bc$(1JtCcRNa zLM0b;AJD>Zp@@vb75?yjv*ocqe>Gg4T{;_%mk*{XOar*@kAe9pyvqSPaopLdtt#)S z2SX{4M^LnUa@Iqgf<)z7gX;lY4kWZQvs>+E2k#j@S@J zYI^eCg<2$*$D>B;YL`hss*@CG?1)L{`)n#+tSexNLP0CK3?t`}72k9GfD2^oJ;}m@ zSRY`c&1NU1jt>4rYxzc^LAewpgj$U{zFhY{`@Xzk^eTe9)G^il(@C( z&9@JV%`=B*XOogbo)G22F=A;VNDK}n-`o*Shk*x_NNSAKn{vKl9LPKvVHYWfU{nxm zBcY#V#c~Pl5E!}?9z9Sg(wqflf$0sK;d(}1=Jn)QE zSQNjrWGEobi4yTwf)$?Sc%c5VZK+k|5Z=SgnuH+AtS0-XXD?zP z@B7dvP1VA+FH|HHCE}0J4(cYBB!zM*3aH~cKYf{2yKx{5knkp!2K5u5%N7UOU6_&M6Ig?18Y`v zMve(3kW6m7k;$s>hSum;XRG%-xM%$^^rNUuipjuYmZhMJS>?v7^=_q`MGjt28hnX- z0JpkwcGOxzF{l*|Z4|F7$sr#TUNmyB0KXJaOQI7WJXO4O6DFpk8PDFQ@m(8P+*%K_ zvZ2g-r*FX^=MwFj5_0DS)B(11hN&Rf9aGpHgS|9IVs*KWu)78~(}V>Qp;9Wyt_4RN zy?Xv^+FaI(M8&d;1YkHCNY-^-L%U(h8PH~4X(pxH^=$O*(XpJ(&aW<&X%huz443Ns z)pa$$*lc#Vli?|)rkyS4v##sgrQ=csH%TEmbY>J`d>pzE!gW&>^0Yw_1`t%{8M$pj zN-)-)0HX{PoS!BpQ5q@`v4i!8hmeuz8l%h+c1A!Ev({;$8AhD>=sDq9sd>|6#)`TS z8+pFO(?|igmN_WJgn7VlaKL9_-@wX3NY5cpgLCkNEc!%+D;d?`uHxWFes7>a*WIi>`xHT8@

pZ)3aYp=b^ z6K&gup+p3dYVG^%=X27g;inYBiQU)I4m?>s6@BfOvY(zjapuz2*-IaP`k801of|Zq zr5;a~CS#9BgIrx2G~;167!Deqx$x9`Pj7N_JQ=+8-S7PCjoIw@;qvnGVMLi;zy9XY z-rioa5D}!!6JkLMskBY@(+cEdZA%wF_JvdHThCkz)h~^Q!xv7U`Ot~=watFmm1$d4 zC*wgk9Cov%@nC;E?3$%c=L}JhgHL*bCR! zK6UNd(rh~W;nLF5qja6W{np#;?d_?k?K-kn7P_U)&6Cgm=I2jbdhw-hvi{}TAHUf5 z^Nm{Pk(n&)MpJ&Mh#e7|D3+s?U86TM{hw)$N{O?fGDPKGY7k9ZuD0Fi(;vQaw&~@2$ z0}s1S?z#u1&)cTXGFeaKC^1V@`|9H~J6)R(9=~_U@$s~5Z@)WPTOKZNomgJIeC5io zt*C)FrDZ9X|vE9dLZ8Ey>!nM(b7rxwgOP@d3+d0=zDUOVcQ12A|BjAC8 zX|1*I`?6bWjulL6%`<&dE@F3WDLclsW^<~hyQY~R4-fizH*&78@gd-M1A2*X0y~9f zOmpJmnc>+_{z1EO;iZ0dc(FfvEUX&QI3O&~Ebb$22HM7q(_Az}gx&^P;IlU82yPkf z0%J)_B}zZ*`{~}^-n_b5AB{&BufKKU@WzcBukP&bJS>B&zfW1e@DYtx{wSTFa-GXC zLkdJ=AWhod2X0Gx$7a*XXN)_+zZrI%ey(P&)mq=FnePZqF@sE>tmzX{beUe8&g#l& zG(LX*BUgX)TVMR`x0}hg-q4viobAOj?Timo&cA-|DNV~JW1saC>d)YX$xHVoE z+Hm@w2Yx-BX>uj;GZE1y)M`)rnnOYB++#YOvvq21{S()&U7n2F?NZLY_#3YEs_hqq zPC{Vggpvm1iXE61*f#!q<9)-m%sgN1r8GY&q3=uFHC#+{&GwmEYhSCkYvyCyAbh+CM(dm7=YWe!5RAMNa}lsNa|XF*$%u1H$v36tPp!GKMGu{}z&1?-3X z4M}f43)RQmIHH6ez`l z5Ye+BYQj>&r>Pu>L0X`uMC><-H?_;oDuaY;JFZULCB2Ko~1eL z(~-#xXE&eER6;7c@VU!GS;fABa|48=1OhBD3o-~Wq5>n^XKioTc^7djBIZb|Hm7W# z5KL^#P;FPi$j;N5xuw$7rW?H*&G6mA#m^WF)}!9 zWU8?u6fzPEi4g`Po)}0v4_w4OHRGTrx4I^FF(sYYt~I@4djkd~O2f{h2lM-PuD{Yx zkN&yw^?xwSPu=0v)f)^~e?4o3I*kQ`lFZ^jAe_O1sw=k1FbS$Qwy`}FoHOy1K_)CP zxZqlvZb(bZ_N`YRz43#uzkL7JYcDs<_ThNxcfNM>`+xhR+pm1HKic0x3N?h1Bz3bMAVcHYtv5El>J$M{J4L3>w4Y2^N&Z} z@NbSD-G1lMy*HR1A2dK!iC!SUEQr#X!7A7V&sSMFbCR>4InV0(i^~2A{n2xZ@*JVH zkPI=^LWnj(3TZ#YjWS$%v$TWP>Z2QPP2c>+joi6;uQY=PCg#)q2V_4LU?jx*1Q9F< zC}d^94A&}KABCmUOUingI(;7LGNDx|49o(t&`LpwL&>+J8@&-td%Zrsdw+WC-=}%- zP=FMTBn-|4un|8a{+oGUG|(+U=>j~!IR;ID7GWTTSO`8)g^!4Gh;&3htvTI?Iu~Fh ze#Yhr@jnP6fC!*KicEtONWlE+gjA$bP*_lL)^r7%pZYIu5qt&ARnJBM00008~{Dlh_Zrr$W`}XYz4<0;v^yu;9$4{R={rvg!moHzw ze*OCQ@85s_{{8>|-}HxRDbO%E(rUCcZjr>!pxJ4gTe~DWM4fIbR1x literal 0 HcmV?d00001 diff --git a/recipes/roger_ebert_blog.png b/recipes/roger_ebert_blog.png new file mode 100644 index 0000000000000000000000000000000000000000..b4c7a64903fd2b4feea61b0f564eebf924267f55 GIT binary patch literal 617 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyoCO|{#S9F5M?jcysy3fAQ1DEE zPlzj!CKWI;GBPnSv9q&tb93|Z^78ZZ3kV3v$jB%tD5$EcYH4Zd=;(NOc=-AGg@lAe zL_|bKN2jEuq@|^0W@hH*<`xtb)YR0pwYBy3_V)Gl&7M7b&YU@O=gwWUXwilZ8@6oO zvVHsZ!-o$aJ9g~k$&+W!oH>8~{Dlh_Zrr$W`}XYz4<0;v^yu;9$4{R={rvg!moHzw ze*OCQ@85s_{{8>|-}HxRDbO%E(rUCcZjr>!pxJ4gTe~DWM4fIbR1x literal 0 HcmV?d00001 diff --git a/recipes/rt.png b/recipes/rt.png new file mode 100644 index 0000000000000000000000000000000000000000..e2f191b54601e3e3cba705c374398079c19e10f0 GIT binary patch literal 746 zcmVKls{_}K^%sk+1tBIJeSkxqR}l(RDwxRunHuKq)L$@ z3Vs3wQPIv%VsC3$UM@sc5HZ@0oGmX+{iE7Tzf>?4JEQY*@*@+Z$1HD2LQdtHU0H13Qz0>1`<_@1;RH`cgxZrbn!M$+Z0WerO))s54 zZ~V@xi8=SuoTF8e-VTk#AS@z7J`dnJ6!TPlyelQ|Y6*igJofzVhBrn~2alCk{>;7M zqpqzYas{@(hpXTX5rFSAG*TR$KGxZIJJMhfAT{*}z^~yJ0Rj~TN*YbjjDTt#s!6_j zLs3UX*bzaCL=6C1L9pA9Mz#|uqc8}AN*$vg0BkPTc7KGj#pl;mXuJDNKlTPrAf-Ye zLB+YETEjU<|neledQeDo_EBkI(#Xi@%Z(hUB)aLoJy}mmM=TSQZy%y^$%% zfj~DIJf1fO6;VEG&p#dA`5Ib9Fd&F_Y5kT1j*Y>+=uAHx3 zbpaqS3LSTw!O9~l-z&ZpR c0{#QQAI>B#?^-6x1^@s607*qoM6N<$f?7{ZlK=n! literal 0 HcmV?d00001 diff --git a/recipes/rubikon_de.png b/recipes/rubikon_de.png new file mode 100644 index 0000000000000000000000000000000000000000..930094bdffdbd4336720573915a42c4f518fc922 GIT binary patch literal 463 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaNb0(?ST`C`^ReE4wb(xoR)p42bg{^ZTq7cXA?|NkE-FDEDW<;$1E#Ki9I z?j1XJJbC)ez`%f)m)FzN^U9SgWo2da=g&7R+#$G{p$Mp&v%n*=n1Lb24TKp*rZ@xb zW!UWL;uvCa`s`$Dv1S7SX5QHw9`EOU@-N(1aKXW{$!u3YuTfu{vt{}B(-O%&l^hQ) zm1yl$Q@A(pl-;hjR*{5m!;}O1eGc zs4|>hdd}hPw+&$n-mO*R=hmFy`n#d(z$u0aOd;kwTK8AIVwiY(39rR@$t~sFrvtCn z8rZ+iU~J0v(L3)MTFPY_z1#EG9rtRzGSACX z3oE!Zm>f=FR^A+|nAe9^=QP4L0Q2NR=VUBA0d1)Q)s$%Y7qk;TLh21vqIS(}xXU6-|6 zzkC1xbB+&-5!fbk?GpcQ_vQTVIp_azHaHFD9-T2D89^n2O5iFDI*wB1)&^&-(W%u3 zwR)_ofhsjr3F{8Pk+;_wIJ))>kcblzEPf!tvL)(rtSveaF+|2nMR{gnYAQE7TSBNL zpfLEOx7WXQi~nJJ$ZQEVw2Z!fpke?L&5r))(&$5iJ*zD2jyH(Z4Rn;!F=-g=ks`ZIq-+ez%L<$t9r(La&2d%bH zo!wt+a_MUQk||_u?6WqxZOy+sj;si2X$zUP-!g?88hf zmY3zcEawu5g@abnxLGp^miBtUw z=S9LIN5+8=i3skEz6hM%)#($p18wcA0&6Ez z*zlo)K0)WwX+36J{L&?W@&E)}?5BbNfSeI9W#a3TK9jYtS{UefnaXZ5ATZ)U8~}^R zSP3~JPbbLB>^Tt)T&tY%FDFB-ZfqqElJ zsnPYS#eiDuexzmo+W69g_TA17k6Jvly^U4_-1*`B$rn0Kv~CXBnu2CqP;c>DY@x?m zM-Do&lM}S`pm9EW_^`Km)99OBciSuPqQzG#voL=;Ex2R(kkd=kfh7t_{wjKSw3z00000NkvXXu0mjfvxOXt literal 0 HcmV?d00001 diff --git a/recipes/saechsische.png b/recipes/saechsische.png new file mode 100644 index 0000000000000000000000000000000000000000..a6982cf3da5f8d2b7fcf8ddedf580a848bf99349 GIT binary patch literal 467 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 literal 0 HcmV?d00001 diff --git a/recipes/sardinia_post.png b/recipes/sardinia_post.png new file mode 100644 index 0000000000000000000000000000000000000000..0c980c1f54c373d8394c4ab1f222189dcbf989d0 GIT binary patch literal 1209 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 literal 0 HcmV?d00001 diff --git a/recipes/science_advances.png b/recipes/science_advances.png new file mode 100644 index 0000000000000000000000000000000000000000..9aefdc57234b2c5db40ca7f841af5a5a5fb5852f GIT binary patch literal 720 zcmV;>0x$iEP)*cvX_952}&G1(q5*&s97AvD?}HrgdO+9f#JCOO(CI@&2a z+bTTUEI!*UKin`u+%Q7iGDF-mMBFq*+%!hrHb>n#O5Qw8-aSs3)gne~RgV zi|U1s>V=T%hLP)xmg|j|?311Cm7eXEpY52T?wh0TpQrDls_&z#@1(5osvAFWLz4N%f_RrJz(A4+R*7(`n`P<(4-{Se;;`!j?`sC*O=jr?C>iqBU{PFVq z^7H-l_5Jns{r30$_xJtx`2P9%{`va;`uhI+`~Lj={`~#^{r&#_{g33z6ACMmiy-=l9ceowv@n?i*F6vrpNy0BXBb zsKDxvCnUxLlkl+x<(8%Bf*R({N8bLh9c5-xF$=Pq?ns&;s)_IzdG#UE0+E~CK!Gd6 z0f@ZhHj1ukhvoQ8RtV|P1^OU! z;-^+rruQ>1|9RJlUYYBn5H;{~ zPvZT23z`?hX%EV1xdYQ~IKw0pfl(+jxZI99RM#&dDo`GYj5z850000~H990%}Ek)P3>?8>!H=W%DBptqG1Yq+eTu=xW_!fhJQV>)Mf=fY2 z83-*0p>z;N2N&od9C`{OAO?tpnBXE4L@_}$6U0=2ScnDUSRfu^13VifKpa4TxPXM9 zA{eNP3{(^o6yXcE^2 z&vqejT!;|Y1%640OCo|HaWNzW0|zqVmWa5)B=hzsBKyh6!9?U>5^^w^=dq#E;~LK+ zrOGR%$~%eYo%-x(a^+E&*Q>Y zxUOSZ+&R+FHQLnku37rNRi^BCqv{%%=pIt{$|t3Y$^Ox)!FQU$_Zo$AMxmNfjL(jY z&yG%Nl~Y>Pe_GY_!Z@T^n9wY$rx(;PXFg2LEltfYY37$_w97LK%d?BRxeY7xA6B$W zkbZGl|3SC9tT*WNM%}7$W!0#M3~Q^9(PS_f4Qr+~$h2-+-*_i43pe)7?3CZJ5U(Hm zrRq=_Zl}dOd))O4gu013_K8;TwKU6>U`?_0oUPZ5+_2565PI4xwl~+~xMrB{A7Y5x z<)qa@we0AezWBy$?VR$-I)yn7`fH*yiys9H7d|xaEy+|;XuIg2e@9i_9dQ5SQT~X0 zi->e#JddM(v+Z2+Z@=nItit8pc*(2mVr-c8M<~yjM$lsLD3tF{Q(M{YJ%Sy(z|W3Ce3PMICh#s^zAIBw$)nl^i; zd)oJ?0x9R;P1`?vj6NO_XQ6VUu8g_gpD^qw7Tv02;6)V)Gx_0)w_lz4c>Pb?ygPh_ zkAE}9ntrm6N70;4vGFLaE9hR6?;SoI_4_%GF85@x%hgR+Sz|SCx!`PzeL&kY3~v%R Mg$?%O9lx0IKlIs-(f|Me literal 0 HcmV?d00001 diff --git a/recipes/scientific_american.png b/recipes/scientific_american.png new file mode 100644 index 0000000000000000000000000000000000000000..d2b3ef71c0097a505a133ecd6775071a9d0e522c GIT binary patch literal 445 zcmV;u0Yd(XP)Px$cS%G+R5;6H_)h>Z3>^h0o2N{lxZ~g74^Ywdy(kL)bw+&!l1E$D9e@b_^HBMM ztYE$52{8MD$~uVPO)1HBNDBVBNuGglYUV=d4oS%vBn4j;B+LInbV98D<0vU9`vysY zqNG&aC$QZMAx@qxB`G-{$qHvlNlD$dTmN9D_WuNm_#ruRkt7I62keC?`99(QL?HJe zLcyN`NeIaK4iegN_WxaJNy&*w0rhvO0YpVM$c*;C|Nps5N;>>Q4(cBpVq`%o&i()Y zeln0hO;S?oGEBkCD`3{sDj7-1zW@K1PTaI<({!NXW|)Gz{ZI#Qm65Fc|F8ZL2zW_K zn*N3<@PK;oU!-K$|Hm`I>{&n~j=>a29fKNCBzffjv?pNZGoU|8UdMtRgO>pZ_I-#m;|(AHAKPF!~eg{?wvBZW!l%vQ!+B9?E?zm nm{pOIl9E0B@*qmp1FHZ43@LO?>ha2+00000NkvXXu0mjf-}cF# literal 0 HcmV?d00001 diff --git a/recipes/scprint.png b/recipes/scprint.png new file mode 100644 index 0000000000000000000000000000000000000000..25890e091ab05b3628ec8ce79ee57e5f42bc9f73 GIT binary patch literal 1777 zcmZWoX;hO}8on%|f*usT3VLnrXX_WhW(t%_K`3CvAQq8DWO40Kp$-&5DVrdOpn{Mp z%OEHqpp{k@3k7vVWGYxt1cpVZ0cu(cl6*)LhAB)A)*Hn!-yEw^9a(FdLZ!v5nM#PL?i|e zk-s4lhZBhS6_Jl2fx~G;&LHv?B3~mC=P_{j29Y8}&gx4vA|;3v>v>L}!9I2{@W?Dc zxNVlO<&#Xltv&Yn5b-hVLq5|HkK<097vLlSxP%=6VJje+0)16C^549z{c8YbG4Kdh zV>;?e0P+FiL#R`R1Rr62%p{+{uoVC(fKY%4z3iXLw)O%hhNHmVi9#ghmZ*tDPDd6CxNjP6i zI2R>FC6eM2$=^#Q6%}|JbV(^)T1HM*kTMg3s z2I=kF(%a3_@0+E~Ez;&6WNmG-wmY)6yRtiXW$h2-ot^ToZh7}(#gnIsr+te40mabJ z{Lt|H=%|v*RZdPSr>2xVp6caG)$h}4K3`2yYLQ4S5^E$94NYt4InCUhMk>`vWg3}G zBbRFw3awnO#XLW+RjIUUwN|asYBXA{b_3VY1wY?}qes6=&&tZm%e#=DUvR0kyu7-)p|QQIySIPj z`NYJ;z#1$UPvNvaZXw2R^Sm^&n zo6959N8I!Gvi_O5lChH6SXE{0LUk?=-%+`@@!%Qv{wZo@>(uz+qFCi=?#7x0^O~_F zN_ll`tjr3;Ex+06X?MtG`D62KXH~lx=DSs7?)hGiCYzS((ANCAfLcq=YAufS&!T0mDpXj?%Q*w z$Sr4YNX_Z!j>KY~Q}MJsZ{1&K{aDu0ksHO1txN5XE&J@CRsMSh4$nuzuS)~hEc)W( zmcM1D@hI1yOP=s7mXyh;7mcy)BOzu!HNdYvC&qAR(v^2ApEL*14_iYz8));@-M+%z zYZ!4BL#^gHVGoGBQ++W#M8?|M!3}~rwy7OseAwyh_MHB2dE0k|`=n*|E6uj|ah(Ba zd2iLDIPi^g=w{gN9=Y1pC1bzX>W*yS9Ot7C#CHdRnvmh^@s^F#p3^p0+9F@28Qjmv z{dCJSyS$P8t9u_-HeGl*6IdunSQqxO-~-QRl|M-yFv3=BTIe&rm$9loDaV#|pnjjn zwB4`wO?=ia{)qkaW$WzWhGdRckMe|Ed!EnN$3)dyW?$Lb;7nNMtQC6l8ACNE(2x%fTZ zw+#ztYIBDhe8(;}r?!|yn8narl0pXa-#z(VeX#YW+@S?!AzmHT3~Pnkp!vi1+4DCIiN>>uv|JIVe3Nt{HP=71TJmnr za9)%1#uN4L1l4sMm>RwqbabdGtip}!>{z!^d88?>cmsQ}yFpZ9TQdMX>yNq3zHhoH i=u*JCHJl^y=|)ijDRrVH-w*1(r}s8rR=wxJ%zpzt>_dqF literal 0 HcmV?d00001 diff --git a/recipes/seanhannity.png b/recipes/seanhannity.png new file mode 100644 index 0000000000000000000000000000000000000000..27ac037d6f4a4e54df66eab2401d56f93060e318 GIT binary patch literal 1143 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+081CvRBPlzi}fsJ*ZjZNMr zWz$W{CMVqDj=RQfR5sbDYzh(wBWvq?FN^dgYSx>TO@L}32qZN9yB6A(ciqhh*A#q?0$tZTCtUY)rB zg05e>@%F`wH_x5}k(Fi6I*+LRUJ?7iC_cU;KCT@^#e6r4n@Ji^>(%ezAi=QntRGh zXLhtNv$V*WH+Q?gUyX%%c2nb`n(BFT=WcU%D+>&$y>b2S`*&~OzIo%~T;kzgo{`=k z7TT1RIUy%|Vo+e6txdjZ1Y=MmLw;2$HlhW+ZA+mt%!_h0R^&k zp0{;YRpp$X?v=hi)up8~3k#-pcdvAHDGl(e28K*bbX!_#Z&Bg2g!oQJ$0C1^GGGt^ z{XTKRT2qtE}%WA|ZM9pka1!pvKj%B%3Xsd_{G^jt&7CS(ddHo=v95?%YWZ;yBrQ=UlDaT!servj$5g zUogJ*ab2jTiLzPbF7)$Dbae=7NcF5G$iymQOMyCK(j+qYgQxEI~I z-6i(_x_vM6*NeTs&h|BK!=!}Hm>tt=&l>F){qFpNO}lcM+AjG+3s;v~{X2Clv;Nd_ zRsS>R{`~sz{al!JUj4PJ*XyQ>m~5XEb*o~fnd_y#rhk`JctVr-ma~3{=02wU?%0Z~ z2|eFFCBNy_k$S|MWaL-e(q8j(<9_+AGS-$m+DjHq mYmU~at^X;`|0&l0F?*CT zz5h9DdpU%hIfTwRjNUn%?>U_JIj#RW&i6Ui|3Xq)LcRY&#{WXr|4K?qN?K!5LOD`e zT3R_NS~)RVNk$~b0OZ05bul# z?~D-toB;o&0CnU%kpKV!S4l)cR5(v%ld*DKMG!?#_w?*qdRot{g#?z1Qs97?Q2c|x znG7-_2%P{{VO(1~W9coD_f0o}ag~8{KH%OPT)P?b^yb9_Yq-6_2|x%!MC6glI=$65 zg~j#x`ydt@A!(3C%57~z7I(hdD-o1SN z^7RFhA2z!+lx{#j+$2{*JOx~HEkh-=|B{7bbogeKB>0RB5@X1Puk3sWS%@)ETpjg~ zx<0HITWml<;NkmABm_bT0ffgam`yZ(dL1zQm+cW3iC@?MxZmkVdaqR_16b;#E4{5bkfCgBgoepInA%SlQg7(jG z&g}rDQya96th_w6ZAI$Zw^+=Dyr4pR8+ P00000NkvXXu0mjfCghtd literal 0 HcmV?d00001 diff --git a/recipes/serverside.png b/recipes/serverside.png new file mode 100644 index 0000000000000000000000000000000000000000..b770c5bd6471e3bae2f9b11f6a18e9fbcfdafe7e GIT binary patch literal 1398 zcmV-+1&R8JP)cX- z`@U}%yMU>vWq0wvc`nX*p7TE+&p8Kr(To2(!~<(eDTqYSjsk$L-!2cGk(jh&q3hPe z)00vlr3z^O;)~iU826B0+07s+~)#RRflYZoG|VdmA?}Rg;7PsMa1n4YLg{&;eYgsd+IME}qAkm_`5CEF6wGXto#(qn^5JD=8|@AunGbr2-fN&~Q3~ytN>O z^ilhXu#KUm^%%9kZ>FO1C_kNUr>fdy)2FZEOgw-PZQN{H$LTZMxlnlnRfY7?iDW#P zjoqG&W%;T6{Ds)D5Ko{I=gmXLf17Fm&;`F+wYN< zT0onxf{KeT^XLh*CAuQ~8uFmDd6*TW)d7Y%HJaNA=iFl26pQDxW|BN&CoQc73Z~IQUVaLzKPjfPR7FY! zDNS@eh}UZ{#RZP&nJ5A(tM(Hg53Y=@m_`OZe<5xDlL%!tGp4WS^jWy#*@vP)%%fQh zb3(NjbUlcaCQ>Stl&Y*+TS#7h3YOWfv3ct<-0oZdYIiTtb(6l)ps9l~tPU>MTw-); z9IBc{N1%+|d!HjA!NJ_w)u`%hrnsQCwu+Qw(6ph%!~k*+}9tvC=f(W9c9klvx0)HZKGHz@Vc6)zJQzFDgIt=Jzw}Sx7Y9koDv)6PJ z5xCu|(o=^DE2`99g^u=e1dx`J$v5BmX>Q(5ge{&)6E2dLdJ>yW(9pPzvu9!GkW8?^ z?=QpczLP-r`xaoRcNKO*N`)Ga#bj52n0^cB8$E)E~h*ymlAeDoz! zZt3!7`H}&+GWH-ChTp$~Vkxk literal 0 HcmV?d00001 diff --git a/recipes/sfbg.png b/recipes/sfbg.png new file mode 100644 index 0000000000000000000000000000000000000000..e52d33f3ca39711bdd8b84b4b8667555760fa302 GIT binary patch literal 2132 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 literal 0 HcmV?d00001 diff --git a/recipes/shortlist.png b/recipes/shortlist.png new file mode 100644 index 0000000000000000000000000000000000000000..5ff4e901b260d3c8b8e4062a3fc04a006877ce32 GIT binary patch literal 654 zcmV;90&)F`P)goHvukUqVz3=O}uKS{-l1lo=D*J_fCYE3> zCZZ2d(S@%gyc;^Qs75wg2GFayZt!;i*#fpI?86=`#S~;>(AT(zqi9d;sm6KCkLynC z#LKY)Ohyw9qCEAj0yPnA753s*B2G;LpufQWg36@U_1K@-{2pz%geS;Hdo2z{;G&cU z*pLW1fJSV@_V{c|_^!d?Vga(L_j!qRKkmgRJV%R==6tjloq;4k9-kBIDk06M@dz*C zdy5}`(cAF`xQpEh+ZG}9J$Q^8xQT~CTNbT!4V=T3lx+eQV3W`xm(d#G())`B=n=Z0 z5qE?X52bu6u@=oj*NiK`FhrBk5gTv-C-DFu6MnPNfO2K!X~;iTvIZt$nQ-9{PT!9E z5ui$_hhvx#eQGgXcH)qMet)ARz$~1>LLoCZuvIv99`A4!-3frQ@EdKUj1Ay}P}#%1 zy)pt^z?W#>907;bi9We6Q&MrKqFzJC8zbq;gjT<O5vJ+-`21-Y9mfi6i_X)qu0WtqID(4>z73zW(4-|?V&8$& zPOpSXcpJNfyS_5Q^e?HTlKu~U10d#v<@7qjod5s;07*qoM6N<$f=U51bpQYW literal 0 HcmV?d00001 diff --git a/recipes/sigma_live.png b/recipes/sigma_live.png new file mode 100644 index 0000000000000000000000000000000000000000..410207e2f2bd9f636011d11bf4a78bcfd27b4ae6 GIT binary patch literal 1791 zcmV>p&vqqNZ%8yZ z&vkVz+`9E^Z|}mLJM-7CCtF(LZg=wh`Pq(+WJCQEj{As7=+-_Or2;zpS3tCyYIJt` z*cGe2L<<=*Z*#bSPSbcl)?xRtXcggt=pjvfx z;@r7dW#!zB8@a_rov~0TW-^6zy0E#4jbb+YHlNppGMVKceweDR*6QmMjg6?`*5fG6 z)h@g2JVyo9p~Rb-CLNAccehEJfAa<#sG-Ybv$|z%4UIb1*HLE8>y1`cPP^S&-SJ^U zWYLa}lDz^($0-T+Ul`Ns>QqIUxq7v*zMflIDQ<4&S5`J6ku|>`bFw}$p~sZX=3l?g zEidO*SF=k?*qyn2S+&_>Cr(72&c6d!62!LqhdL-9b0}(MiR( zh}9bA`7mQ)S&r7#rFwesuN06yl|mHL=|pGew9BPdR79()o>QveqS^^!D*{m&pxkt2 zWe5OCiyIr8)6=o$X6#1ncD1ZbEiczmRu)l|aR9Zt`pxJlY9JN|2ZMynT)Y@PaU!fZ zR9W_L!fp`6$bLC*@d>>CvBi?sH1tb#xpel!hr@~zw%d21C}Vc6%S;V5f?hzxOng#zi5>aq7vF#nsjL<;$Z06N(a&Wt2pO5VEWTyJ)mp z(KrS$M##9$76gc$J&U0S&?lSiM?#oj1!K`~37lu*guvtQ)P)P_$;kl61tlpY2tiRC z<#|8H`6%@fV&WC00mjB~)RdGY^d@@t?9$Iaze2YxmPZ6Ccp1$4VG8_(i6Nf%0cf9n zw$R`2Mb|_TWsnZrUVLw}`;4}31i-eRQdB}-?aGzcem@#fZr=*-+S2K0gBi|eqXZ)e<9@kz zZ3G=;3}?Rpz_+h7MSFqlI63smCvVf~_|2O*e~hs{NpoSjfcVg~i2RA3ydUK8*6u2zhSA^bFuJA?U|MM+YwJLMoLS z8X5o?Wb8i#+`NQN(~_MmIbBS`u>ndkgHMkf$!J=7e0)bN_y*rR62= z)$(%h@#E^%tBX&c=4NLz(dg#X)T7$keiWee8$zgFj`k_n$`{B^NI#`Gb;;|^CzI=A zW9djlR|6EeV`KVzbTl0dBGv8HV?NBaOCF!n7W_Xt!8 z+pF`pj!cZBfrVTG=tW+R z>jl7>cLC0Ge92+(|A)T$!)ti?ON;>Eo}tkV@RdPi)olKP2!c@IMkMVlbWJ9H?)gJd$GOGgv3Rzzx>Lqt$16&OV+VK&=+dFy)9}uRXXb$i><AKHS$!(aT80 zo#~})3nsv`&-#%ZV40ZgUA9q_=fq=ApA}HoXpb#+F$V+^^e8Q8jpfLuvfCglr4*PBtnFJz6?p31GIsdFlge#?Coi)Ld=CrlS`V6q@b~ za{=gi8H#GJ?}v19LVT5?I{^7iyW&2l^R>Y|*(af;yL@B7T-(FoZU+G8=|##dijqOU zN|Da1}{vGiv??G%s73{+Peki z2*;V^Giu`rJ~qM2>M zVs!=!LM;4_=DHYDv2Q9Q{%IWEf$4;deeeoM2d!j}1bZr&0qXl*&^TJ)XSPTp@$3Bk zCw~{hB8x0O%1rnKWquC&p$NGpO~@!{L{_m5$vJh1Q`hWAS`9)Xi{a&$#wLRo5fy^6 z;S;HW?8N^-R0(_{ikKZvLPQJA*XX(hnKJXOoF)?cr6M-17O5Fk@RA;fOc8~|loC{E zb*QZR0`}h0F^J^sItV_Ah)$`7q4XlgGD--VgOOQ+(bPgrl-DCFv5I-ur6A7{nP&pI zkO>45A`qQYgV=;(3XFi#vWp;r;@-j{Z1x#UP0z#bn1yp@0qkeB&p{lNgWKijFe4Jf zL1PXD{r@99XQLCKNJBh=q=0GLsmND7IMwPQJ72?)U^(vMqPy`1w~jJx8iYCEf$K(@IX_+v5>F9 zoGOhBdlLcKpC=HPSV9COsK3;Sjm=+p{O@?;zDfj!N9=Ikxrff)5nO5L0bDNZym^bw z$se&bHI3~lCpN4${Py|{KEB)_F9@(n{t5vVBjAmK;yTREJ%(v$f(+hwdthaq-mS0` zskWXG)aTX%J3Dx{vWjO-?O4_uIF_jWX?+8qwzmH~0k#ceI03c`6gUx;17rUfdJMx9 z2Dk8@FRwx0J%nM>!BzcDTu3_u{QDg~Zf;@4Fo3n85v-YRVbwT<*YgW_F)%~|6=3(0 ze~&;GWjHm$V=Iu8cb> zK?(4YpM8Nz!32E6a(Izp z@C`e~;dj&x#+tncgy%uRi;kZ78CqJ)Zs&^Oz8RZz% zTtHP(17yeYAq&c2b`BvBlm(HuYPVv+i01f}Ikmmw`M5&~NU1FMnI@rYGarhddQ3SV;QEbmgi^V7I2Vvc;KLNHf_o`1}W9tywzT84#c@t8oR6ko@$GG(l3d^qI>CzgJ&!zvj@k2rdm1Fg{B4i)f ziKn{$I%m4FwuK2QxwG?6psnpfXO|hT-n@rl(8dX*(JonA-^OWe3j|pb#H5Mkl}%`> zT45x@*ZVA-kkMqr(odyd=t#fGa8HahyiRz2pxYA+9fZ2f-{RsMpr`-qF>M*cmC`ip#1zSRKt!0`ga)2X+tx`VUUob5nfc?(V^ zSM#_QR<iVFk_b$UY&kdcuWAx34J_^c=(uuc_0Fz9BoxYdWc~g>TVr;{;A7@jHO$ z7WHM3MC5nR#iUiBbN)XBQQVHF05KBGJ$qpBqHX4iR8_i2q^|IoG8enXQPyNaaw(4& zxQ<1a@^++3>+(OA=c1dFD>O>u@~C|um~qe5>@8i*r2KM97sV(_9G_XCkiHwgDO3IP)RoO<|Vt1b)61B&J*x2 z;k+|}3obXtRD*5v5I9*v`4`p*6o?da2V(2Nd=G#zB|*JOkPIJ^6;-Y4U$qDk+l1$Yos1SlDr%_`Axh>{yqWT z1AEbS?KV99ZHmC^>MABD=P))t1A}oEhDUBdYuuixsX6YH=Lk&xW`dtIgVv%6X@nFv z;MSrL5V%hi1DzZJ@24bim#xbZfV0grcwy!!6B_qp2N^R-bB>|oUVLN3) zuM9jRO+jK|mp55^B(X zLyK>&{)kU6UdGFsw}|-;LY)1r`y*AM5fSJcH1IiqF&SS?8IwDJiv`Lf@keZix{U#@ z8p7;?fM-)%Wh0ytsM(2#Ir$41O=%mudgF8Fdx2t zaVRQzO{88oWJHKe0jYm7?aL9I3k}JEcR(Ix!RjX@B0di4_!tNdRwE$5GX#Rjm@UEi z@Zthc*p!a0w`AM`6$-yVHSTo{z(Q^lwOr)d$Fs{YTYq4(WpZMenlmmQn90}-!Z8@b$Nkk=*GfqH7rFFCI_!_da zUxy;A3BFt!ju5xy1;T-();2_VWFT1sHMA14Jemvg8qr`P9?d*Cl0C&}_P-cT|FiM@ YPr`P`(&!$M2mk;807*qoM6N<$f*}J}od5s; literal 0 HcmV?d00001 diff --git a/recipes/sisainlive.png b/recipes/sisainlive.png new file mode 100644 index 0000000000000000000000000000000000000000..be5f64cbef45e9b4b645b7b37d42a2ced071b085 GIT binary patch literal 1389 zcmV-z1(N!SP)W-6@)ff0vB4T%Wl{nU^J0)%)hU+3Qa_wnz8-9YcmH!S~sobR41Y4R+ zF^%9%*hV-;xJYOt^owA>%LtzkUL`p3Ia6*L!=8i#gg*#F1XuwZQ%Oi6ES@|OnPM&> zp3r22QA=nfC<$}^ukj^>0>ao7jB7DU_@1!PR^!VEmn<&$NbqEd{6_G#W#<-+-@70b zj?1HA=el#UB+4y&LKE`>i<6z@2;7x{f^(NK`_WzGiAjef&wNg35>Bmivx^k7Ji}0Q zp&FlmU99ndQNP*HFp{QYSYE{%8}}45mM`w~WDMM&iG%A?v2cA_348C|c*rLLDM$0+ z_u_s`ciRb zTNf&;TM!a^9Mkk3O41s45k*E4Z)HjjSYvLVwIoa*b28w&Za;2y^})DhW&eyB5-$Y9 z)jt+BwH-K^S%~S<skNh8ztz%;%A?f4lKt}}(@2n@@Rt|L?2CIkqgw!@e}Q1mgr=+Wap;{HW%=siGv(eHi9 z``XC$#S|*T_EXVS*gqDoOG=g(C%-AdMj~xQfFVN=FBY=|XoDCU8ph@~)Af_-@7}ar zZmg!R1MB41e#%i@#Ymh$h{iNL&NQJCu$j$ccw zB%#R8E#b;($HrSG&|?#U!gE(J(>;Xe!F`kuzlg?I2p9o2e(knHQ1$fd2n^i*7h729 z$P3m8SPj9&%e9zIO32Q(BZ&#yIctB_wiAI!?j3f-4uATC#0%wD>fl75X-0u38;Do;Il3Pg5wT6 zw>1r2DvbzLUu(m|z7hI+fD$w)A`63baGuA5(n^? z7u22thsUF&Hz9e!hwg;NmQFo^5wiq-C(PI02dNSQw9}}BQ<7bTzmpAN-ga!=ok{Ym zIrH7U16a8++0?a^`FoO1bK(;aNHng58u)EU$89^skx6X-z!3WS2XXW7UIb7v!aNWZ znT3D)2Kje}vn0K1=m<)@K);zboIPL32{1K%NlKv3v@((@;#jd@N0*1!+I{>AUhzsI zR%}Ru=jy$<$0L+4{NU2Ju$+At;lC*bZtR2@Co-Li#m~kgFg%m%fkCKPks4#JBC;2& zSB;K48^A>_ElQn@`z;iz>^s{vgWf}D2oG4Q3m1viEAA55#phwIGWNSmB$#n;Ti*uE z6ZvAi?!?37JL@|)B{4^QRy9n)cs)-zi!rzDampT;$%LyWifJB$V!fBhkJ=r4B|GvO vPNv8tl!}kmUJ*^ax?v?2lUJs2HX8m9P;YSK%ag?^00000NkvXXu0mjfY8Hut literal 0 HcmV?d00001 diff --git a/recipes/sizinti_derigisi.png b/recipes/sizinti_derigisi.png new file mode 100644 index 0000000000000000000000000000000000000000..3db4f3fc8a5d238a2c0ce2764ff69784aee957de GIT binary patch literal 1896 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 SGMx&vg2B_(&t;ucLK6U@YsK6E literal 0 HcmV?d00001 diff --git a/recipes/skeptical_enquirer.png b/recipes/skeptical_enquirer.png new file mode 100644 index 0000000000000000000000000000000000000000..301441a1bf2fc417a667aa9407d32a18020372df GIT binary patch literal 2794 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 literal 0 HcmV?d00001 diff --git a/recipes/slate.png b/recipes/slate.png new file mode 100644 index 0000000000000000000000000000000000000000..8723e24e194a5832974821cc0a18dfa271ff87cf GIT binary patch literal 666 zcmV;L0%iS)P)@at9y~=KJVqftN+m%{CqqmqL`^A0PAW!ED@RZ) zM^Y|HQZGtVFiTZ4O!ddySZblbM8+nT3^}ikhR4pQe+cr&tFxrAwWhMVthTzWx4p2s!L+``xx&S{!^OJ8#=67CyTix4#L2$K%D>0X#LUpf z&DYP>*U#42(bw70*V@zA+SA$G)!NKX^7Hlc^!D}k_x1Mo_V@Vr`1$zx`uY0%`uh9(`~3U-{QUg= z{r&#_{{R2~qw?=w0003INklq#|*@uY3Gb&TFALrjkHW!$cUm43%kAa!2K;2^E~MK*;N;8)YrgBrj{R z7L*DJ|K#SqD6v7Sn*em4U0S^kK|67I-$o(3UjP)^%eN@zGmd5)nhg$21r zR4_~BqZKy2F_=cFym?@=BXJ*!97!s-Rj>+?d|K#lZ}zXyunDW06crJgBm%2pp&~R0 z(=J#z)6``Bu<5&CfzTkl4?t5NJ>;+VOV07*qoM6N<$f|4Fs A)c^nh literal 0 HcmV?d00001 diff --git a/recipes/slate_star_codex.png b/recipes/slate_star_codex.png new file mode 100644 index 0000000000000000000000000000000000000000..d477e329342fcecaabd8a745cae705295616e6ea GIT binary patch literal 2116 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 IM6N<$f)IBv^#A|> literal 0 HcmV?d00001 diff --git a/recipes/smith.png b/recipes/smith.png new file mode 100644 index 0000000000000000000000000000000000000000..a125f69f685aabd2cc88612448421f8edcd5a940 GIT binary patch literal 573 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 qx;dw_zFD!#M~1h~wyX_!FIu@}$3FqX=#fPL literal 0 HcmV?d00001 diff --git a/recipes/sol_haber.png b/recipes/sol_haber.png new file mode 100644 index 0000000000000000000000000000000000000000..6312166217b38955b53042bebfbb69831080f2e6 GIT binary patch literal 965 zcmV;$13LVPP)%-cjHw&HwWj__SfKT+}UVB*?A|P zLO(j@;Jg+wHl|U$x{?5H!NU#a=7QyTB8}p%+QH7+mm=Exac+gNF(Kj)32}50E><0& z>v6S^C9?@&39hd&lH*x?j=zhs9^8RvgiL9KZrs?6Kp%Q52yDleB+n*m#zwIzjp7Ns zu6gXNNTaxIIwQ&idT@FLf#pI_9jr6=74~6~pa*4iV!ZhMw;q9Z72jhzgEc5DIPgI z#VfH1Yv+jsj9@cfO7iSzQvwBvC3pYPtl4mC{h`}_9-^ckv*7OTXSS9C+g^D{|E5-dnBQnuo zQ#%HPs+Ug>7OHm`pQKT|1Fs1^WHWRY&kW5zj`Iq3^`0K#nEM)8P4a9M-&Qy$oa9)U zz`*oi6LjO=A_ium=yqBWupvRZXH>&|f|g%ivH3xCrBS?9h=Y;B&QjcuyIUZz2Y)fU z7QBL|TQERqLWb~@#Nf18?DWT-5@Bg;>@i*;;&vFQ#8+G!99g z4GMRG!>3@l73{(bNuC|6OQ68VLJjzR9vx5NOFWe1*+A_?qiWbD+zFo)YQR}@aDEV5 zusO-ItUOs&j27ZL+$HFLpU|!~+cz$x*nVsi8j_z&U36;%YKU|QmycCgEeKMCvx_C8 n_(MqJpM)#={v^--tg`(d-aYrrUMYLH00000NkvXXu0mjfWX`ux literal 0 HcmV?d00001 diff --git a/recipes/southernstar.png b/recipes/southernstar.png new file mode 100644 index 0000000000000000000000000000000000000000..1972c3d21e6e9e0e499f55b5f24701c3620773d8 GIT binary patch literal 1704 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 literal 0 HcmV?d00001 diff --git a/recipes/spin_magazine.png b/recipes/spin_magazine.png new file mode 100644 index 0000000000000000000000000000000000000000..703456057b6a921f0ba5de838e2e39a26d305732 GIT binary patch literal 317 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk@BpAX3RW*PVXMsm#F#`kNVGw3Kp1&dmDEQvf z#WBRf|LLW`!WIXfV;}c7A7y;aAhUt*oy-@R0QQ57r41~v6iPP;P1qo`&rYYnICYu& zgHK<4Kh5@(Yl;)eXR7(N-(bP+yHN=eZF~!4Us!!>IjH1mTsnLEjDoEUY6d>R!s5Ld zp5NDRVine_Z<@nsqs%2F!qF}~v95Z!T-Z^DwV$}$OIMi0-0 z9pZbje`%7!cGc{KO(DW;7i`QVC!IRqEc@V97`U;jMT%RjR{p#Mr>Z_%$63>!e< N;_2$=vd$@?2>?9pd&&R+ literal 0 HcmV?d00001 diff --git a/recipes/sports_illustrated.png b/recipes/sports_illustrated.png new file mode 100644 index 0000000000000000000000000000000000000000..0baa5411d91940e9469eeef1018c8ccd2fc805a5 GIT binary patch literal 483 zcmV<90UZ8`P)47eijRwpk&KOy zjE<3ykd%^?n3tEDo1C4SoS>khrK6;$qok;$rKqH)sHv*0s;sZ8t*@=GvbVUpzQ4l2 z!o|YE#KpzO#m2|V%gxKo&dtrv($dt|*xK3J+uYsX-rwNi;o{`w=H}+-=I7|==jiI| z>+9?6@bK~Q@$vBS^6~NV_4W1k_xJbr`1ttw`T6<#`}_X>{{R2~0Zpuk0001@NklxNRI+6fLTi0&_rKLoQZ)|5eT@^<>efGAi$nq07S{7$@91a1-&i2fuu23 z1=1iv4-PLNX@gaP7}$6#YduvZS*!|J9iUEd(PhD^fPvG<6{^Hk5Ss!n1qBrYGdqx! zlL$5i5+FfyNf7CbRROOjP|(vBBxNat)eY)yuz(WA?nnkU88uBE4P{YA96>z_2mn3; Z(g0#gEzathnqL3_002ovPDHLkV1hy>=63)9 literal 0 HcmV?d00001 diff --git a/recipes/sportstar.png b/recipes/sportstar.png new file mode 100644 index 0000000000000000000000000000000000000000..977ac089c4806eee4c34805682a5bc042f65a6da GIT binary patch literal 1088 zcmV-G1i$-Lwg3RO6&1Fjqqh|mx4F5u zySumm0Jy-wxc~sV006rH0K5PIyZ{xv6&1V{9K0ecy%ik293s6UC%qyoz5oEe035z6 zF1{`@zBM(!006)M0KkKW!7eqyH9NsLJHa?T!Y3=jE;YhCJ;GdH!pO+OI6cEmP{W*^ z#FCZ8I6cKeO2$-I$3{%YMoz~_Ovgx0$Wm3vR9VPXS;$vf$Xs8@eSpZw$jMY$$#i$h z%FD}KU(0ZI%XN3nU1HBwS>BGh8?(XjK@$c{N z@bK{R^Yilb^z-TI^Y!%e_4V`i_Vo1h_TuCA_V)Mp_xJhv`1$$y?d|#P?)mQT`S0-g z@bLNd_4@Dc`||Sp_V)Y#|NQjy{Qmy^`1t+&{Qdp?{`&g<{r&#_{{H^|{{R2~{r>;{ z{{R2~+askd0005&Nkl1H}>93 zP!Wy2mo;PD_hQOBILN?uJ6uh`5r#0G79_Vn;^Ic=Pzo0~9bM#1#oi zSqcR#?^ibOobbY9qNK_sB#u;>{K@yd@ui3EV9lQb*(!0wl}LVTxi))o>;8LLUyq2m z5|${1CqA9(SlH5b{)wknRKh`k<^JX^3+8ofx{LY%{|kUer#H@TTQ@QIMW9Hqe7n&a z-!=@pKFO(-Wd^maW>V@TtV#fx&A|Q34 zS(cAWnx-_(Z9Xx6aPEPm*UenJ`FddMzCGKIj_r3H4Yu1i%;8}TV}|x0bQA4i7~gWd z?tI#n&Y(q>^Gur~qo$W>ecP)s+L!H14^HXPD?Pmb8~=Y_);QH&;BDdn0000U162M7EA^}hdk`~Uax@$oV;GKz|d)&Ho0{{VY?dlVED z1qB7f#Kg6=wWg+~KR-YI{{EoD-rmp8&kzt0z`(!(0RgeG zu}VrxqN1Yu`uZ#^EbHs*xc_a0goO3~>$v}J9UUFq+}zF0&1Pn1zP`S%udn_8`h))f z`1traIXNmSD(UIz8yg$i+S&{Z47|L&RaI4s{}CuCDA?H885tRq{~>X4aS93w{{Q?n zH8u3~^o@;;*#ED7ets7h7YGOljQGSZEbA^1_rDDQnt3XPft((|Nrpt z@ZsU%6B84|!^1v4KK=dumX?+;FE8%y?$y-Q5fKs4(9i+`0>l4<`}_MmJUr~|?D7BM z@&Dp>c6ON4(Q5zz0XRuSK~y-)W2htm7>1UDdfDpO>Z*EN3Ovn9c=S{Bg@PHYuqwzB zHD-W-d~TO|EDEwq8Q_4q2vdQ92vEY6vnH;Mfx*}uixuUP3=E|l60NFi8u_`FSQHex zGB7yTf>?HL-q;+e26BQuUwN{uCU$Rv-N3*gnG%^6FO1cj4RJ6(#Bmj4Q{d?#2luE@ zAl9Jf4drlWcXkD-P{O7l0vLR((Sp7LKm{6Em(GwuNKFIXgvNlpn5YVCaUj>oFg4ZR4=u@cbUE?| l^0GoWR^2@|Lo(yw005%llN5{79^3!`002ovPDHLkV1iqlj;sIx literal 0 HcmV?d00001 diff --git a/recipes/star_gazetesi.png b/recipes/star_gazetesi.png new file mode 100644 index 0000000000000000000000000000000000000000..c85e686f1a8bd08db1bd4849c5f2befc1336c8da GIT binary patch literal 1363 zcmX|H;bNfX0cA zNW^l{`?DZmj;Jr~0svpljoliFkm>(32*3~k!vMSmVB|fxQ2<6UU#T2U8(pt=7iCGIE03d*a03HHF2#_E^h5!WumJpyqcq(M?0J(ku z10oD8U|m91$`DAv+-yIzn+oC=7(cL?}*( z1rxD%MjV_GCJR~SiQ?^1JRK!sIHDv5N@Aj9CzQfKDNK}t6&7mCLhYT=#mmqo%hBZ? zsE0f1;f?zFkGZ&wtz12}%5Thfm1McM#K%|S<1g`FBUuw52@jWg`bd5KrNM0J`gPK< zFll6zG%i85c7rTBT9&*`9v&s<#K_|kRzMQWNdWtS53 z;X~v5_l##`PGlaM$jX|?!N{4+$(`coOywV+DmXrMs!)YdSga~7Rh5;g%F9(1XH`|_ z)u&6;XUfzUFKQ|(HC2_Gi$7?pFKcS5H8nMwy4va5+G))7*R;d#HI zug@^hZ~R+i9C$tVW^hgnn_$RlF3bSxmm6-E0ku%xLK(%E0t#D zxLK{9$I$8Kbu;sN{rs$9-e8zF8sEKpx1oo<7#k}wH!(g*Iz4ATX5Jl%t#&sjB5Z3= zS#rPbSbr#sqRNb!DsQfDyFn^$-SMrkSSYLO7>Z`Ql4G*+UO|(SlPiU9U-7)WddxmM z>p4cVo9MiLgJ^CzWrV-eckh#y0O+9ijJkI`tgvC?OUM4~q3=tQg5p!Uc)Y&6&0fW9 zVoM;yg3?$$mrmXRSH9kO2+n`%IXzoJu&mhB3I|p%;t-DVL%gcOLY^DH`90{aJE(j) zY31cwD7-DGj4yZjf?E*Kv?Afk2>C&2ulfQlchiMr6>)EL)@r=#&$hAW-pB-ZCm8v! zJ|aARxZC)24}fz*UOcE>6KYYETR}?Q{zB!v(3I{xkaKOt^-r4Z;Y+&)+gos7U5O#r zh3E%Jg1XIBpPr7Ec--asrRKdP2+}Vd+^DP{E$oPFUn*;Os|)MTM?#HpWGEmbVL+g` zbvB)_#7dB0rxj-RdS~LAUlcdcN$YW)JS#DuJ**AfayjzE8~n#|OZG6wu_IJKOAAf_ zF@m8#@0VQ7EI1K-3deHN74UU_H9>{hN9;0aHD`J83nmEr2CV3v2N1Wx?rc$K$GIa( z93DHuWu*Hr{Fc6gw$p;5u;X5dSDe@)=d>TWb%nv&)lbieN~?S{PPw;6@VQja5-V4x z%hvsAmqKItL}F|~j(Eip^Yl4?E7Lv?Pxr@>-O2o9S#eyb5Bt}pO~N>aca)n;-2nry zYn^Ah@NhGK;oi^I*2Nr6;JOzT`Of|{-r9MOm58e$XWcyU3!$-hY&tNid}4e|Z(UC# zb}v28zH{|?^pZs9fvlN=Y literal 0 HcmV?d00001 diff --git a/recipes/stars_and_stripes.png b/recipes/stars_and_stripes.png new file mode 100644 index 0000000000000000000000000000000000000000..d5fee9b56532e2c0084d48baad6ccfbdebba8cce GIT binary patch literal 1242 zcmV<01SR{4P)iP>f_ zLEYHg5*A!EusD!_*a1^6<5p<7luIeF(ks1v{o2xUX}i+dBD6ret(4x|ua~t2+A^Tf zky1a>7MwFE!~K|^2Id)+S*!ZD!)_wq(!6^@ z1Z^?6{(Y&*?oF=mJN9NGN=^~F$Y*Q^Pu9kwEBl9tMiX+LDkfL<$r2jOm>j7& zgcIN3_PhCHt~7UvfD?7yobd#JbV>&R7X=dan_slWt+r`C#mFw|#`TGTl=5qp%me^m zt~p&yy5254lHF6&#@`PIp>+CGKIwRN-!DC~+)7pgK&;ds_^jnhC3LMxSS`?n1dMtA z!c*uGp!I81bLY@QHLaq9Uv`w^M-l*5o8xF!du}8DTp_s`yuaCYQv(gIMAqXu@eiZx zrZo?K{?V?Se)U2>l_`JibW0C6sX@RA13=y_jsi$)5Z!ggSVT)dO)rsb=iT2%_xlzM zGV4Q{g}pOnY>V_FDYfbezqNi6vR7VMIh>v?@boj-W zVZLbhy@!J#O@8sj$uj}i=_BBSGWlCq!J-Q0OBp!btZkssc{W4#0tQ z%;yz?_e&wwlrh34TgeOqy2(N$yCq^nSGSb|J*h_|5S+ub+UX3BXvo-sHxg+<8u{UG z$_#=!?*B(H7cSE}A#F@Cs`~oMeN?$?L;`alm}M z(Kq895YNHo!yM2l9G*OTX|a!B`c-x42SI%eZLqL9S+3&(Dy1=qVxSZ?)uc*3~P+EUomh=X~2`vA)&7=i~_$UZDIh5?0InVj$;>Bpr+ znB>O*)1K+{M!L|5aZf7Xpo5+} z3I_+Ykf;a)mdvqZ9~A*WK(F^mr0(HS2MdHbT$s+Z5~=#CdIP#8WH9g6 z(bm0w-|ag#BGrh)+ekDAgKg?2n9$~QzyYI zldpKZduy5D2R^qz7?@Nm3JQH=a!qEIqNoH}2h&pSLJi~jD4)d?oYsaN&b{b;b8y956hzI$u>e{fKaD@n&b6z6@1{L|k(%+4=ZkDF|4 z>|RLr@TyRAL8$D;R)fN}E{9j2>z7?L`iueoUytA40c_v6cYxnqO#lD@07*qoM6N<$ Eg7!^Or~m)} literal 0 HcmV?d00001 diff --git a/recipes/stnn.png b/recipes/stnn.png new file mode 100644 index 0000000000000000000000000000000000000000..31c465029a2b01d4d8563949b7a350daee3fdbfa GIT binary patch literal 2306 zcmV+d3H|noP)2NuQ~)opi=YXVOeH=_HK~iJImQXX0qo$)nXaRZ~&4iW*`SU6F?u z#8r?3Ec;^j+{Zcnz5oCK4OEGmxPjs_T*#sge5S>9G3!gxDn-l$Cc2!>`LM>@d)fUg zZ~mB8iVOe%0096XMIWJwos?_2a4~;A!?u_PhNpOky3i>|Dl4(}Y;lcxa ze+%yp5t@_$0001B2#B}iC)AH{=K)UdqddZkd$@ilF>FKw01aG$wOCE1K@0!@0001n zfaq52XXrhCy^nG~XW!zl@20v12q_Wb2JsNl5K)5&F(Lo}00008kRl_#neOMfd>ucX zVpWY}U5!8c0ngq~d@<1!#Rz~LwE`s~ATOx_0F;aX000dj+=~5NbeQ3{DBtDeJJM^v z3Pcwp1poj500002kOBaJq)I^f8TzZ-KgFdL6x{g(cHOu7Pa8wR;DL@_i3AXkQ_fI5 zgAZvfC)6pMgd8R4C?zGszXAXNNt5sZ>fd1w_V3lQ9L24juXZS#|fpV_k2+= zac`4o34ti=BWx3eC4_y1HAICXydggY0Rg2A-wB2`N68}2Z?hF*jPcYlKuc&5o>R0F z)2AZ6^S?~7obnN)2Y)S2{?F*)y#|JE1uVk}%I^}E<2-zea+m~y#)XABm(A9{^qZXf zX>t{tTdaTz(}#HJM;Jnr#aN4d7$;nSb4K*UpA&^;meB7?S%8ugA?c~oE7;x zfo%F^(yj|`s;e*BsWZ7<|H@-gi>m`5Fy6ya09-&>5hYFag#&a4af&c$VC7r`t8xRY zE|OGlrTsMJTFMGx0xLw`CiEhc&Sm4B^YqStP-k*GF`f3+NnjmB$QgZ_333wO9oX+I}LCR=@TtiEicBA-ukCX3<{GdP7A%7|#8&eWSG-}?edPlLzkl+9U- z8@+kL#-+wYW@UvZ2saFVpDJwrabi>M{v(ruE%AYLkhDV%PFTDOq00vak1W%^0^Gl|HMJJ75btsz&~;;J>e7*z@88DdyKEhz2(?wJ=%7Gp!FnD zz=ApKsB=-t7ontq~{!ICR0+JMvg3z zT$A(j%JA!d7`U{gJYG-_V&4{57%$lykJebu+^3{bo+sLr!&oWWcxyrAp-!M>FWz4SytZhdw1Ww1Li zS@KLEMuB$OEncryYsK+n2isiR$rol2z!#WcE%00;m806-%!Qrh_K73xdB zrqeniKQrvq-VY@8NuBASzcJgKOO`3x>qR|V@d;6Jg39BjPv2+k&;k4I4K$FKWB?!l z0000fGKM}^F?Zcs11mQvir*0>OE8B}r`xA!SBXwj>=h;dV|4#6=RO=U_8ucIMl_HY cWB>sEACLf~3x)YaumAu607*qoM6N<$f@ytAZU6uP literal 0 HcmV?d00001 diff --git a/recipes/strange_horizons.png b/recipes/strange_horizons.png new file mode 100644 index 0000000000000000000000000000000000000000..b7d6550a65b772e17e9e06402a1b4f66932174f8 GIT binary patch literal 1741 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 B`iTGl literal 0 HcmV?d00001 diff --git a/recipes/sunday_times_magazine.png b/recipes/sunday_times_magazine.png new file mode 100644 index 0000000000000000000000000000000000000000..1ecd570cd9863d5bb6bc441f0d16f5f6cdf6e59d GIT binary patch literal 324 zcmeAS@N?(olHy`uVBq!ia0vp^3Lwk@BpAX3RW*PVXMsm#F#`j)5C}6~x?A@LDEQ6O z#WBR9_vz)mdbIU;2s_ASYnjj6bXhGjr15U){3mRB)2&_^u8$6E$bM(`ljFrzwf78vjSJ$ZiE{MC zG4nGty}Gb?2CL~CcgdC~*Tq)p{NPr|h*`CbXJ*>YXFpWs|3=>NzMy%}tcLxZ!}n_8 TQbP0l+XkKOo4yK literal 0 HcmV?d00001 diff --git a/recipes/superesportes.png b/recipes/superesportes.png new file mode 100644 index 0000000000000000000000000000000000000000..2ee02edd44a417d6bc144f177ae3c3a2da7d7774 GIT binary patch literal 1755 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 T@{ZD literal 0 HcmV?d00001 diff --git a/recipes/swarajya.png b/recipes/swarajya.png new file mode 100644 index 0000000000000000000000000000000000000000..74debb2b0059f6c9ce3fe6173aa7047e1e3c620c GIT binary patch literal 2295 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 R1_^Zl9Bm0U6?pHMe*=eW?4bYv literal 0 HcmV?d00001 diff --git a/recipes/t3n_de.png b/recipes/t3n_de.png new file mode 100644 index 0000000000000000000000000000000000000000..b731e268df20f38e070911b000ec7a9b6e55ab53 GIT binary patch literal 569 zcmV-90>=G`P)Px#`A|$$MfpNH`=Ffu@bLSWlK$%I{_*kqm5~4c|N3fU z`AtRnL_GfY_x|(q`BqQ+l8*a^f%!^9`9?nab#M7iM)^cN{m#t&*Vg{*>-u(Z`gn8s zPe=JnME%gt{@~yJ*w+2o*Z%MB`e|bOl8yO8JN?Va{n5|+kBa=Stp4uq`-6S^kc<4g zxcN>;`-On~uB-n3{{HCa{^{uc_V)Q)Rs5-?{HCM*$i@0}Zv4Hv`UOkx%zi;`<$5iWM27ORQ}}S`;mM#Idm{!waSb2K~4mZ`g6kBVW0ibcP7k~rRB+#d}1EuvE z=&pkxI8mR2Ab5GW0v4OWmrrUAdRjqNy@nh06!NWLLA`{+g|4){Frl8OpqkwSCR@SO zyn0p}IF92u1q{V;Tz}h9D|Bcuax|t>{bS1=_fY;}|Hu0TlJ*k5g1@T`00000NkvXX Hu0mjfT=YFd literal 0 HcmV?d00001 diff --git a/recipes/t_online.png b/recipes/t_online.png new file mode 100644 index 0000000000000000000000000000000000000000..45b794c5e2c7345f73ed1f0f777fe28f799f01f1 GIT binary patch literal 629 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 literal 0 HcmV?d00001 diff --git a/recipes/technology_review_de.png b/recipes/technology_review_de.png new file mode 100644 index 0000000000000000000000000000000000000000..c209657d60050d25ed31c560aa3006fcc504a6f7 GIT binary patch literal 1496 zcmb``_d6SS9Ki7?ZIv3W=PA`5*SqJg<*XpckTgb#UEz$D8fOzR;;gGxTB_6;Ez(1Y zM=6@ptAtkUp1oqEw%}r>f~4ks`#;?Kd3`_M=llETn`&caA|WO(27y2%aHdAK;Qs#G z5fuj0s4T4$0y$M+V_|0uo+tlDDyrX7Q&Uq{SJzP2070QpO+ZsqOG`^zTN?(0>A+yR zy1IIRo*oFevG(P#_?gT-PE3=9kn4ULS9jE#*=OiWBoO>sD!84hP| zZf;>=VQFb;Wo2azSXhpDNl zX=!PXA3sh9($h0CGBPtWv$C>sb93|Z@}A}8ji)?_+UScnSmY0`T zR#rG1&MJqqwz|5uwzdweuWxK@Y;JCD0b5(#EiUK_m%F{a&ExTQc6N4mclY-8_yC_T z5D2~szJm7m_YZ)BgG1o(@aXUebbNdaR1~!?AGs4>GJxR`9yVvU{_pqp3$-e-Xoj;Vm0>%GnFWDBD(Shi> z_{$0jT}vuwt2CN>$ig=|-}}=Wxo|mp7iz?lwn4@vU85{0%;BOO_wzVo?8+ zX1AN>ofdzc%P)WEC>$c`M7+C@QC&r?+uU_k^bz7RXDTaRpD4a*=OFi9&>?-Y;=Eg4 z(JARJcMlyP^zdkA*Utg)$1)-e7Q0PHCed%i7pm$ z!QhdcIP3VgCG=UwVs3uN&AFcX&8j=pcF_{8)-8o4l~ITQ0r$g6b8DUocehl?45j*B zp~$-m-CWe>pdgH3VYcj3Io0zz1WA18AHLYhv&y_+NNgBgRl-9{R3@jqA^I1GZ26kGF>(1`|ThH@`AKL`Hgre9@u4g0CNRb|qL4GFyWwI(oD+ruwpL zn51yiwvT==cee*<1!ZsLl_t!)zv-Mtj+0l=r{v=5$s7Vdu+fG$I(|2XLnIU_H4jS$ ziN>7JXqWe9CKcl};Yj@`wu2PHL-A7A!AOl$DM}b~wRu$#v_T4CF?pB<``b+E*gGHi eJqLuNd#Bo=ogt1`>KE_{AUI zF=*9S6bA60kF*2{K^&s(5G16fP#i1{3R2RPn$|(OrCv$k7Fuv9qT*tPAR?U#RFD6kEaInjg=tJ(hvMp4BuP7Cot_I2Pe4rc%R zLY&FIR_w+;^k4>2h}Rbcl*M39IEi5)URlza4$S7t`-C`E^C_!Byj{a{9yjr}5dT@i zvlT~kcRmy1*cxT=B|gSJAx>KkWpM<@aYBd(ltm}TrHz2!&@aRX%3?h8?GfTw9lloXC&Yo(6Oa;Y#P4`hI>{sWL+avo3`&3G5(qtz9VM68!)J?P)#tKu6B zEqC)*N5F!RPJDXyT>DQ(j`MBetkxVgLXD07*qoM6N<$f=ebp At^fc4 literal 0 HcmV?d00001 diff --git a/recipes/tedneward.png b/recipes/tedneward.png new file mode 100644 index 0000000000000000000000000000000000000000..e322942578d2889ea0a4f1dcd44553c27710638f GIT binary patch literal 1790 zcmV;#3nyjfuCdw8hDD`+YJ(=S8tUY^KO=o0y$s!6e7*#VyRrhcI zbN>1lh`#vxJFTk4_1~5C{P_>sy|}!-U0ipT+h=L=narPS^#qd>HiwY4gcE{xDwAV5 z{7h$`qa6v5B19FWL?9&qn4Dyrz#4h_b*kGK|Ex*t`RBep+>Fj{?A-dCrSGkjYGO}M zPAC1h$}9pvw4PRnC(U%qqyY#(5dsikSO94>I{Wg4KutrKpUj^$qfyk5LKZj)l^x1 z_{5ISnit>JU;IuGC>=Bn1qZ-S0RTX;Z&M%?fD8Zt0{|ne8LP-g?epTTZ&p^1rp>{8 zJU>bmHtTM?{6KO?Q--pr$i6xZh67MdP$dDzj)Vw60qn_XYDSa!Q9GU8H)BiiWCm+i zU3vHKzumn4XOx^$1y!htDxi=wgohV;5fMoyDUrblFv1Jq2)Cww_M0D{|M2HzwWvk7 zDT!d;=exVB)w}=R|MZXKeciNFgVmx27&HMoKq47PdWL6-0u)4vvV|9VW;@EWdD;Bv z1VGSDBn4C;rkFQ6J+O&tJ044_X)RI(B&b1+*6P8r9-b8UesRv#$BJ8n!*i4l-bdcT z>ghODNjnH2NyH!^2r_A!j9Qx=9M)*ZQ>2>KN@^gw1E>$=^d+8slXJd%Qz7{jn<^2d zXX#LS=8bDrO-3*sdPGId9U4dwhy+mrCE!7TA&{XhNsD1hPJfW^Kg32}&1`alW*pby(2)F3=0LThLjy3UN=Csi~_!%LGdoyd@CcBA1SED{zngG|gN}zMADz3311dDthXk?9 zAb?KU>dfS#1XLxZG#m_!g76)c*k}A`G(!kTf&q5c0(BZ)-LpI#|4ub?~yVxKA85DnT4V26fC#9X~{L3*=ji%O&y7hW} zw~D*ld7HJepfj?t>?^j*fWiQ^l<*)id@05tXM=km`95!MyUl$8s`1R45rDGYtS&F^ zt}Z-pG(ja0nNb*pbaK}wBuJWvgP{NzESY`hDTk>tgsOnAK5l<`g+9{JbF>HB_4?-Z zzp#8a;eF-8ZHl~Q-biHREeJ#&`T{{lm`4c7p@r#)EXefUL9sfCF|Ri7U)_8-zp+p=ylbO*Y5~2XXA%UPpI3vJ8{RJY>K^y`Qs)!fs?$Y*} z`Bz_Ugm#;j@;f|3=OeEEBR@tuf3 zAp2u%r%wRD{hIP940sS95D`Rb0Eib$QZ*yXs|#pIT1iAfR7i>KR84GDRTMt=zWaXW4MXYBY5Ma-DK-HChr|-9Hc@Jo zw)|~KYTOCdoyqJ7tXPuBLSsy0z{KDRXo-uFVvzy@sicqqr9deyMPZofOy}>-^v!#7 z-#sp-&>CYjCbKY^FK2Q8a=v@ccfU&z5y}Z#*6bC63c>%4;GYfmuN}EG6)C%u1q$wR z?z-u0j=SP-F9kf8vuvk`fIm$fJmZfqoT&_aAIo{c3HgbcR4wMXp_0Wi(0g zxOL{osa<8`Gl+DHKi00@L9(@w)n)hLnO-NU!WW=BRdSFb*b zB@0Q@I)5>iPCB`~TW9#vSqKkKo;Z2^=D1no_jGoz1;OeYeLkN)aB^JHFcEWp=#OSH zg$0pIua6J}kW^K&Eb;AkZ-hQ;n;6gS*u73TaBgtS;RW4Gp$LpozhApB9GRTRT)vii z7SDZt_?H!aR#&4voVKp~n%UmfeBi+Q04k;A1wpA60N_P|@w8iC>y;%6fZ_0DV5QXC z+YtzOayf3!bIxfZQP>o$+V^Q|eA@bLCfk21`cco8ZQIt4KH#r4d+y(z%Uccr0U@9y zeW^uEqMbXI51pNQ7I7#eNHAdNfq*AI$3vkLQ;!^@!Lwn#Z}|MZxukfzt#R|_)kc%@ zW~=d4UrS%#g(pwvdOFnJ-r(7vqu(DNY46sNA^;E|&y9wd$nP{49nigY}bF5nKG4r-t6hMWhXu2-LBEf$KmRj(a z@e3y}o?I~H(&=I>U0EU<09oAq)A%{c{}+`tdxfAvP$2*SfIk4Eyv0Dzs#)Uz0000< KMNUMnLSTXgP_~Z% literal 0 HcmV?d00001 diff --git a/recipes/thairath.png b/recipes/thairath.png new file mode 100644 index 0000000000000000000000000000000000000000..c774f86dfed1fbc45d27f2e2ae25a1cd9c197e7a GIT binary patch literal 1333 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+m^Cs(B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxOgGuk#8koDQqR!L z*u>ION5ROz&_dt9Lf_C>*U-$$#K6kPNC66zfVLH-q*(>IxIyg#@@$ndN=gc>^!3Zj z%k|2Q_413-^$jg8E%gnI^o@*kfhu&1EAvVcD|GXUm0>2hq!uR^WfqiV=I1GZOiWD5 zFD$Tv3bSNU;+l1ennz|zM-B0$V)JVzP|XC=H|jx7ncO3BHWAB;NpiyW)Z+ZoqGVvir744~DzI`cN=+=uFAB-e&w+(vKt_H^esM;Afr7I$DAX(!G<*}2 zGxI=#nqXbNzE+-j#U+V($*G<$wn{*A^fEJ3tXxe@O-$X4EGz*Q}aq-dQ%X39dYUfC5YStpv^9+MVV!(DQ-pixe8#9TV-N# zi?gAjfs3iJ5l-`wR zJzHSLco^Ul;>xgvfpH5X^Hye-tt{-@*g3az^6uaj-X$!uOGIL~gw!4>`MvT=`;?UT zDXZ*PQQfbqaX>@wkeo-~q{A3py6`04kjAAf)R{QL9wzu$lU{rUUv@Bjb*mrm?X2YPm! zr;B5VgyhwOufrG#tX{stY3i~WM;Go|T+#Bb`H|2ngTe~ HDWM4fGI;wP literal 0 HcmV?d00001 diff --git a/recipes/the_athletic.png b/recipes/the_athletic.png new file mode 100644 index 0000000000000000000000000000000000000000..e5c22f2f0ac8cf0af71615fd52cb8693107a4461 GIT binary patch literal 377 zcmV-<0fzpGP)Px#AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy_en%SR5;6H5F`K?2A2YrJz%f`rvjb-5MWD4!9-jNH16C5D|jr7 z(~<5FBfN1cnEn6%>zDuk&&Q=e^!5M$b7uVi|4s~tf>5CG9xgy45{H77|NlP_5(2t( zEp`Rcpa1{w6BO+F|NpBjRt3pG!>t4b&45%oRt4Mt|Nk}x2)_LPzZ;tZ<)8oI;I|qU z1%>~SKq(dlN0Akr#8jaB_y7MAF)%3j|Nox>hJyP4|9>e%m%A_P)#6bjWUs3asxqVot6uS6r+ zc(2$Mk9)6T?v?M({rKiS75tNF&YYR^KQnXwGsC}*QlhOJ)Ki>Bb8H8(2h|>ef5Uy; z!b5!NWbhe*eXp&CcbLK^9jxr>J`u>am)M;bEN=^9)bX{EQ4va5fO2GCixaqqSDh00 zjH7sxGjSe)RoIUG7|--=z&@NU3K(y}du&V~nnf9hg~OMf>#`{6qKvvl4Oj3qvaiOX zVGw{>VfC_@6P5QM2~@B=vOQ{$7&w7>IDqvT+bnLxjww|zf^`D(kw5s=G1dsW6MKb{ zZ1!Ek&29)(Fp2L8zVjRYO`sBgrWQN?4ZQmxqryC)EDi)IoVt{6koQqE?N@ z)r7sj<~LfBs)Wb!T=Ah@iWS&`9at!wi(Qz&Av`V$*bgNf!!hAn&dxO{I>>`6!#P~g zu7umLau@_)MyP-+CImSi3V~7K3}jIfJxm5kV6#>wgb#R~HKbHQS-9iZNFzLstvI6f zJpd0d-6??z&I_-ily_YPOSH>&Qv2aOfqwl92s&_AxCgEZidslu zTBzSP=MI9tz%$&zZSCdw3whZt7L=@8RPjkjAnAAXAAc1;0Uu1D?29gT5&!@I07*qo IM6N<$g1CeHO8@`> literal 0 HcmV?d00001 diff --git a/recipes/the_budget_fashionista.png b/recipes/the_budget_fashionista.png new file mode 100644 index 0000000000000000000000000000000000000000..8c0266c06b25a66c4cfe3c90846b0f9f6b4698d6 GIT binary patch literal 279 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfv=>VS)*QBH*A0Ho6Q&Slk8K9Vv zk&!4E02zn=bvXisI7@>3f*AzV(#}m^+vC2pnWGydSl|&^%)r1c1j3A$?$-SQ3if)s zIEF|_UOi~Y*JL2l@NmCwYTbI3Giw`TJMZ#(#q?G&B$v4NKR9`HqxpsRkJ}>`CYW_P z={7ofx3p+EhO+kQY>alf>fyn(Sv0HLaFHYH4AHFP(}nHi78=^QtX-2mn{me9C!byw z?ECun%3Fu@`TgJDRaLz^KIi(xFROkn{OsIycTthuA=MzMsgu0_w3<&}&%e;_!*&j! P!x%hW{an^LB{Ts5+sI|n literal 0 HcmV?d00001 diff --git a/recipes/the_clinic_online.png b/recipes/the_clinic_online.png new file mode 100644 index 0000000000000000000000000000000000000000..b21f3b82b5f864783ba7f6604fd7304afe2de240 GIT binary patch literal 624 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyoCO|{#S9GGLLkg|>2BR01_nls z0G|+7pd@}EDk>@?Bcr6Gq^_>6r>AFRWMpe=>)_xJ85xC>mr zo;`d1{P~L)FJ8WU`TF(iH*em&d-v}9_wPS`{P_9v=dWMCe*gac=g*(NfB*jf|G(gr z?G|7_aFqo41v4>M*3>pUd;a>(>|16&K+zwbE{-7ZrT4A2T0U>}`w-2krzXy*iCQU=WTd6u+?3?;PoU(G z0ncHh14Zu(%g!}a+bSye z9Tb3!tJ>N(tkrLutK2s*xN53>%~$<7VO*75wrrI}5wXU0J-7wR-VXAo(h)lGvn}7s? zT%h6`WoLrn&AfhL2s zLv%r{fLH`J2P~ouvI1ls96=ld=fW9aaj;!*#U8t#e+LFxN=cAkFaskKGb=j>7Y{F= zfP|#9jI4sPil&yXzJaBUldFGFTtZ@cMrKxaPF{X_bzOZ^Yg>0uZ(sk!NmCXrTeW)4 z+I8zUY}&kK_ud1CjvPI8`po$Ym#$p9`S8(`r_Wx#dHe4DhmW7Xe*gDIv0DNdRZ5;N zjv*3~XAh=ZHzhK(J>0FK?Pat%-R!38w^MvKJ!Aj>H?N3`E4fnq<5BnLEB24x&hcG( zKIUcbPQUeDp~ub^gI%9lxfc(mTne*BA2lM#}Ve?&Q4wrcBP=OyO{G%f}1h zYlXf}X6@@e^BiSocn~hk+-su8k Ohr!d;&t;ucLK6U@-%4!& literal 0 HcmV?d00001 diff --git a/recipes/the_daily_news_egypt.png b/recipes/the_daily_news_egypt.png new file mode 100644 index 0000000000000000000000000000000000000000..91fe5c1326b075b41d826a6b473936e9ed6a85e2 GIT binary patch literal 1596 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 IM6N<$g4Zv9BLDyZ literal 0 HcmV?d00001 diff --git a/recipes/the_friday_times.png b/recipes/the_friday_times.png new file mode 100644 index 0000000000000000000000000000000000000000..5e6a875d9da0f1d8868351f0f96f43d662385258 GIT binary patch literal 15463 zcmeI3Ply{;9LHb(f~&Q9v5Q`Y6s4k*`IE_?8IyF|&DLFL*07t>?Wuj4dC3kWc^Fo}2>!99UT{t>HW8fA;Rd-Kg?E2{F zhb90o95JgK-bVSPrrWI~H0&Bm_F8S+8-V=LUK{G?k;m0g!?X&C->-j};7p^CIGrww z<@O@lG?%v>w7$Js)wj>h4Q%mv_%I&&vvS|-7JPxRvFBiTU_+3D$5-? zZCZxib)U@(q)W8kQ8ZM79x7r_l@}FW&Qv8u6LXrJdrnMiq8JMj_9SAIaqSz>gR?{# z=}X434YR&AQf^+veFDW1?6lG@|m0{^OA&AzE%@cJj&(_ zMXw_@tw@7Jv0!6~UUE!)F2QE3k3Sa!CsPb5g|e#7r%)=(D=Ed`bM>^ri)pp4W(+m0 z8*q?fLYT2s%chH~X)9J`xYmM`XC1vf+By)LW^jVEq2nTdV-^yFojE*mh9`>e6Krq| z>i*eN)cx{BMqU_cjfOS!?7Nxc%Q+i@Fn-K^dXDZUvS{TWh6;L358~Q&uL~V?tbr?J zmYo?!9d#qa=LP?)ZkqqWLb&k4zgvdccGdr48KNrIH=)%)Mo}0P@StI~l?&H%bY~5A z>-bL0M<+`a^}LgC3pws+DFi$9IBblz`r{1?=b!D=C|{6k6}C{ZdZL;aCJE$>{iH-c zf7BBL1OgdganY3h zigG_4(H!&gpFXsJ&^mrlM#W4zol55gvYGU#m_5|oD4F;_()G{!6wVa*kitaAGt(Sn zLfneqhGW>ca40eQak8|6v~Fk7z~@GAa>^B_V*&O@@okhfoSMWVq;j2$kU?fY42bi_V8o87=|{-DJ4vdSM zWVq;j2$kU?fY42bi_V8o87=|{-DJ4vdSMq~e;3UVlXve&@A|Uv7Pkd*(8J zF_zPp*UA8FKMlae*8#Y9AK!lkpd$kC+c^NV_W(F#zkBxU6Syy5Iayu0e;;?5^}4BD zyNdfh>aDG=gU1u8Q}ynf?|*!K=g!@4Z~s)oN`0ktta@Z!KR3V1S>Ju|=~G{v?cVtD zM#uVd{px~rEA_^{cW%$`d_Moj{LZD_yBBsn_Jpx_PQ08h?D=`$BMZCse|GcxYX>X) zu1L*=LzRmUgBH-QUIZK5FLz4Zm9K7c_wLT$s_X|pynXl;>D8T{Kkk*K)zT-&&t3it Dgy*nq literal 0 HcmV?d00001 diff --git a/recipes/the_journal.png b/recipes/the_journal.png new file mode 100644 index 0000000000000000000000000000000000000000..27d4811ec5406f02683258af0ab55cb1769de501 GIT binary patch literal 726 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10Vnu*Yi0l9V|G)qE9T>ZI z!i>}BFTecz_utoVzt|W?lQC4BLxl}!gMTo<0Z^nypWCnbNIsq=!F z3(mLoAA9iVgNVw6o%` zr>`sfGahk4amMWFYvX}JeV#6kAr_~T6C_$1CU(wj6`jvyR#sNV=cnhkhpVBXI^CG$`t8u!y7ywP^_mx=t5w&JanTz;Jnz1e=GWh==ishRjGF)*=B`r8S=v zycjR{b_uY0@tjidV!h12DocS=v6PKhVQHuC)vV0Tn_s^$da-fyYv}}jMQ7z?VK%v7 z<6^V6gKP{gx4A6@(`_q&E>kUWjVMV;EJ?LWE=mPb3`Pbh*rdD+Fui3O>8 z`9xlq$-qD7Nja<7L+72FjUNW{E3I7Fib<^l>g~7 zo=<}qn3cKplDUPIg}o<>FbgZVG?*MtVOHK8qHy}gl@mwK9FaM~e!9V9ftMb`D{;Y+ UPfn&&fmSehy85}Sb4q9e0Kd5swEzGB literal 0 HcmV?d00001 diff --git a/recipes/the_manila_bulletin.png b/recipes/the_manila_bulletin.png new file mode 100644 index 0000000000000000000000000000000000000000..733643dc2bcfd7bd8e1004581e12e286a280b702 GIT binary patch literal 691 zcmV;k0!;mhP)`}_R)`uq6!`uF(y^!51i^Y`!Y z_3rQV=<4z3>G0&|@8RU_-{R}r-s#!g=-J%o)Ys(C)8Wn0;LOnA%g^7)&E3Sw+rGrt zxW3f3ywbJ2(zChHvbfN(xX`h<&#|}9u(!{!w$7`v%%`u)psB^3ro)<|!I`4Ln4!Ry zpud%#zLJ}~kD0rTm${6Vxr>#!iIlg7k+p-4vw@7VfQzwxh_H5ot8{>>bAPIGeyMSO zscw3vZF#0^c%@-(o>^y_R%Mt^UX)HMcJi9Ah+ zI!lK)Nrg5@g*HcoF++haK!7VfekwhEDLj2BI(#NMd)^`lhX4Qo2Xs5Jk}nk&_VI-GVd^cXtWy5*+UTe*}Yu1Xz$a;ZC}` zdyi^WnOP(nOX!y*V$q11SxH=fCSGC|De^DHBNkQBL@ibv#4J%9BupXs5AZPTb1&`q zYRNh021lniZzP$n@!61?ebw{vLfM{Vx~uWGZk_ldIq(T+lT2Fhm(=Z~FFM{Df!%SS zMlxyngReCBGovox_yTD925R{Vruu=IJSg*j6$JpK#$NKEz7?Q6^9@kCRsahCnUMlG zTnDP3KSAA964?3zjxPbuWzr&0_1X@Vk9`20Y5}ry0M|0*ssZL!q4y{akQ~W3s}EI_I2oi$M?s!O-aZ*+0!g7ziEO(6J9)oY2(^UEZ+u Ze+PD+1G}~7|Ahbm002ovPDHLkV1oL{V?F=? literal 0 HcmV?d00001 diff --git a/recipes/the_manila_times.png b/recipes/the_manila_times.png new file mode 100644 index 0000000000000000000000000000000000000000..c229ed56834450a572d2e08282e26afd015d57cd GIT binary patch literal 2339 zcmV+;3EcLHP)6l7<+6FzUH3kyDCq2BO*mgN~2Nr zTkn0--Q8W;KdP!S#tg%#*Xx|~SS%I}hjFniOV@QmNHh`^MCg_wOEO+nrnrI|{xMAx zn3|@=<8dJbH~>EqiFiB}MZpK4j^p4WNfL;GjW+;^e<0Yy0T$tsBvV3M$I)HyIIc@6 z1ry<;feC0q2tENh4TR$#*sudyghC zD0O`;ZQuw2>j8vFYWt_zY(g4z2QTtJ4n#s&V2y6H8oEI^4~0YVNGw2r!nJzca%_b$ zS(2bPjHA_R6#}4IG#X7L62Pm~YJkI=up_%VR{_wPS=@Zjj^=*Y-OXJ;p}<&l5#`TXU}m%F;UKmyD= z6L%`r3UJPyI~M?}<+5el-MMa6QyDz<9|N0BSku$f)7{;@xw#2z@Ch0O2Eq`b<>lq; z*RSIf_e+;9!4B9uH$OKuH8nRkx3jZz@!~~@mn9|yPKJLp{AB9M z%a<<~78ic{^Pe3%b}XcZUOazsjgK1U3cap-?yx zOGrWW28jeakQMI>czb&r7A!6<4h{|?h8{>FsZ=`Om+v1K$Y#Nv2ABhbgFW3n@o20w z*AdNZ? zvrs6MDkW$ZiALV#ORL#vwG7)v43I^Lfem3uR)oQ;wMy;b!-rp7`=VB@!S(I!9RPm& z&9`5E_2ta78I(`0URzjL;DYa!O2`GU6OKUJ2s)eBf{27hWtk?6Mm4BoScam6+hQ>$ z%f;fxt($-T?e9N-HvKG_NPcqmED`)~6MwyS?b@A*3D@LIV&&cPlgCfIJf<=<$aUn} zQpr}MF6?(n4>vfL<(f7cONdHbA+oAO5vQhUtQ88gbFC?mFkCM!l-n zGo6_qpC6r>ohes$7MGSr&zv6{|MVB1eLDNy>~B8*_1(#PnRI6Cmtz;lFCIBGL&L(giCD0Y$)i#VVOBbO%@vQ8oF#XO=38iMCy~XqbH9apZxoSTQ~otSF3up z66ke!_^|UkZZyr_5A!F7PX*j@)>k(WQ$#qM%y{`t6cbq_yON;Ha`(6)+ngzN)pb~u zMftv9@7;C{#5OaNpe8efeq?PH9i| zbadq3bz2^4>B2HC*4_~of=isKLP0GPjUm~I--eHl44s@Eezh15hnQs+A%O4#*SGNW zc4eWkG+i3VrTYfDqsqQ?G)#n5-rRe=Kt_gv(4L7A+k{i3aJVB9$%SHBDc(k+jIejy z{i>p!sbo)YrZ=C9rP~2XX{q7CE+ldht>V#ywX^Ku#o9hQ_k+PS*{U}m-1=&5`YsXP zOeP8=3C=c&SH$>fK4+w{Zi#YfueiN2H8b_}*$Xuu6XEnGO?Sf^N_+dr`PA%EVP$Qy zSoyeYj{B;_j{QU%I>o>F?)K{2150nlk9^b-L1D@jC&d0(AWTL^iDZN-#IBUKo>q!)Cle}>Rzh=`XzgtOYv$RbrP;@1YcFurj+H2+mY$U3 zLWhmP(072z(o zYn0a{Qf<|@s*N?9@C2cn6nEuVG})W${ebYuZp9Famg(z@#ACK)*Q%99qiI+sdZnf* zAw@;?N($3k`rhsyb|qC&&_vmpL{o6g22)rTDugI;C3_&7Om^k-ZHc6=J1xU)aaO4{ z%3PXl$&nH=9OVIyunUpX*8!L3g(+tIblwZAAPrKsUd>CJ2R> zWQvACW$vniE3qieWQeSgW}Q^4HQUw&tfh+JF~f+2G^_{&n^Co4RBH}*wMZzGN@2)z0qQ^=$`kwbDd;i{I>DpL$1-Cf3s+z1t6qR|pI<~L~XN|A?m6!@jh@{luj)g5z zqOO7-339?r4*$8r7l2J3rQzwrJy$`5?6|h88wyim@rb6X{|5EM*+L&8H)Q|-002ov JPDHLkV1g_FZ?^yd literal 0 HcmV?d00001 diff --git a/recipes/the_marker.png b/recipes/the_marker.png new file mode 100644 index 0000000000000000000000000000000000000000..f6476bd3cc5f7bb1a85a3fe449463511a899d6ce GIT binary patch literal 1587 zcmV-32F&@1P)hA99@9ydD?da|7=Ira_>gnX`>Er6? z;_2t%=;q<+=ile$-sj}q=HuPwA@n;Mn8e*W%vR;oa8a-PGaR)#2Q} z)yTWl#k$kPyVJ$G)5N#Y!L!i5vd_J-&%ChCyRgr@rOL6T%CMx$u%pSZqRFnE#;Tmg zshh^9n#QJ?#ip3WrIW;%7n~A}ecfEyiynk@Jes8;dZ@hkQyM1lDd~LgX zY`c1Cx_4>2cWAnHXS#N1yLD!|b!WPDWV&)=xp85-Z(zA@TDfOgxo23pW>>joRJmhR zw_;SdVpF$aQ@CMJxLr`WT~4=LPPbi7xm-=TTTZuJO}Sc3xLQiLSVp!~Mz>T$woya3 zQ9`#+LAFpqw@*N}PCvFyKetUiwo5&?OFXtpJ+@0ew@g5{O+mO%Lby*uxKKp7QAD{? zMY&Q%xl>2FR7<;8O}tr7yjo7ZT2sDXRli_c!Dd^*XI;T(UcqQy!f9W_YGcH0X2o%5 z#c^%Nc5cXbZ^(FY$$E6ket67*dCY=(%z}T;hl0?GgV2nG(2Rx9jEvKij?|Wq)Rm9a zmXXz$lGT`#)tHsnoR-#{m)4z^*PfWyotW64p4p?H*`%S`r=i=YquZ#Y+^MGAs;1nl zr`@cm-mR(KuBqOxt>Ck>;<&Wpxw+%KyXC*T<-oz`#KY*v%(I^Y(9Z1A(CpFD z?bOrm*3|CS)b7{S?$_7v*x2ye+3?%k@ZH?;-Qo1(GtaC_v`HU z?CtmM?fCBR`0w%h^7Hxg^ZN7j`t z{r>&_{{H^|{{R2~ktvZ10001dbW%=J009C71qKHQ3JeSl4i69#6B85`93CDYAtNRx zCn+i_EiNxGF)=bVHaIysJ3CojUS3~gXlZI}Y41el9QL1pS8WdzrVo2 z!o$bM$jQmd%FWKv(%ReH-QeNj;^OA!=;-L_>FVq5^Yixh_xSkw`}_X>4D|LN0007h zNklAUK1*i{0&WG5<;766?|Rs%q3`XdJcYM$(l zF#saT(H0~l9mz=mEIiH4E&@&Ye3QgX7*4l7i1|=~?mxT5l)z+?><<7jWDS3VrBt&) zqdHX%^B>cWq)8M26elGuqVe z9J>y%HUOZO+1b|`7(FakMAi`epRg)jMh$FMGO%v-Nxq7XX?^Dgfk8)F0iYDuxlY(P z>!j-}+W|l~8uDJ`N=alfATw#`-p{GWzP%CI1c)FF9n#!)*F7S80r^QoYqVecJR*kz z*-1mT7hko+BQhFDIhKZOSM-)1k>vo?p&{EA-K9s2w*s&X2n?1`5ddKAXI|9w?_#Mj znj~WiCaoKiH6m9Sg0X~V$(gr6fxJ1hg#Pp2eWWN86+6M~p3uF1=@kFO85_hgh`6Zm zq_3+E364o}a8-XxhI$i8Lz29EW|E7qN|GGp#JcGLAXTM!l7{r*YPS%M^y?1CYC?DJ(ns zRX12xNNtqPeh6UZ&)@mO>p{u{sQvLf-`tlo1g#y002ovPDHLkV1jIFgkAsu literal 0 HcmV?d00001 diff --git a/recipes/the_new_republic.png b/recipes/the_new_republic.png new file mode 100644 index 0000000000000000000000000000000000000000..dc756592a1e0210ae56aa528faf1af4afab27c47 GIT binary patch literal 370 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 IM6N<$g4Zv9BLDyZ literal 0 HcmV?d00001 diff --git a/recipes/the_register.png b/recipes/the_register.png new file mode 100644 index 0000000000000000000000000000000000000000..1a7a085c90bba670ae25cb899949d9b0975a647e GIT binary patch literal 481 zcmV<70UrK|P)kdg0004~NklGQ}ZLW8jWe zYEl-^$Gu|sovxGw%-OE@g)CPA9gb<~aS@P9$WgH9CYUh7OQqn(O|Yp4Tg)g0J8pt2 z1mS=@oW=lN0f9_Tlt6@c%fHWrSFlXE zo&$mk_=7ckmXmlk;e#AgrpNdb&^LA%rBV>MF(080G_%Wf92a24G-;23`NDE!Q3vQ| z9pM_UX%72(bW2-Go#D!i3$UQKta3$>G%4`XggMk92;<7urg4!_2_=e3WE)|~rnxo{ zGkz|T@h?FmF@m;jd)UB>ZM!o4JWyb(m`6BQF$0`BzVOt9Tpg*6>wif_G|M#}Y9QI** z0RUUEtAzmF(jT$_kkP!MS4o*r3Z~1_CAC%hhNS{P2orNH1bB~y0zl9d>Pj_rXeNr2 zzh59|67O1IzKDs{lVX53c+diXX*??fh~i`!H}ScN<}0`z#mUDqfS?JCHxZz${WSm~ z$&TVh6U^svt@!QB00P7;E=yxnqx1<^qBxl-y-{)Y3juOOIy5({d0fQZN;j)czac=L zmPYFzbr`SUAS;P!(VZ5}@Hw;h&(j`qY$HQ@NfXU#y zbb6h}BRsEVykUS6n0D!U-VxeZ!ouqG`d{e}@n)53BnDzs00000NkvXXu0mjfuy_%8 literal 0 HcmV?d00001 diff --git a/recipes/the_sun.png b/recipes/the_sun.png new file mode 100644 index 0000000000000000000000000000000000000000..50f213c3d32defdb605e224bdae7febebbc37ec9 GIT binary patch literal 1082 zcmV-A1jYM_P)i_`j6&36N0PFx2>=hO46&&m$BJ3w8>_9;6008X( z73~!j?G+sD931T&BJCU}?II%WA}8%9C+#OI?JFzoD=zIWF6}Nd?J_m(H8|}!JMB9? z?LtEBLPYIEMD9C1?ma;6Ktk?DM(#>X?oLqdP*UzvRPI$*?pa#yT3qj3UGHCC?_*`} zW@qnbY42)k@M&uBYHRRqZt!kz@NaPNaB}c+bMbq7@q2vneSYzRgYkrg@r8!+hKKTt zi}H?-@{f@6kdg9{lJk_6^OctKmY4IHne&;N^q`^ip`!GprSzqy^rolur>FF%sP(9+ z^{T7&tE}~`to5y}^{uY;uCMj4u=TL9^|7+{vavh6=KSdB{ORfZ>gxRK>;3QV{qXSp@$vog^8NGk z{`2$x^Ys4o^#1nt{`dF(`T76)`v3a-|NHy@{QUp@{r~>`|Nj2}|NrFcfT;ig0w+mC zK~y-)g_CJR6EP5m6NmMJvhEr~p!EPRJn;gpRuJ(7yeTSGi+C$mR6NmYsfd8KZho<| zlRXN4U_R{bWZs=gp3E?N8Dq7ME2JWm!u?+$l&A;bWB~Q(E)oa{9NM~0^$+E8+5X<6 zZRko73#0`dBd=#X9hf)!Cf5O$3g3Tk0UuK3F#1uc^nBVKPcE zvxeKv%H{`tPES0zcx)ShzlTIIuJEcmg#?7Xs}+1+u3VAEH$x=F%BG$Ip-A%_Wnn0W5eJ=BVmRoDJlkPJUnT-8`(oOt6|=(SO4dCx8{S7ar{MaChgv zgPF14(H6eAjcnsb{l|-2p%T+TsD04HV0VPkoG<|9zixnZn1D@BsC#M{8EN@6m$t(Y z5@aUAzoL%SrMp`Jo7(X&3mF}{St!NT=cgX^Cb^Aq5@6Q~MAW=Lb73%hD?4x|z0=~* zsA&2e7S5d{oM!I8RWcY(ht*E5Tspaa!#K;>KNUSP_tK4SjsO4v07*qoM6N<$f)d(R A2LJ#7 literal 0 HcmV?d00001 diff --git a/recipes/the_verge.png b/recipes/the_verge.png new file mode 100644 index 0000000000000000000000000000000000000000..d0f1599b346dfa431244d29247232db9de100d57 GIT binary patch literal 800 zcmV+*1K<3KP)Ay$8l4@&*SwTX7Jf6Hy?>c-lBY+2*)wgYo(vDZ!HAi9^6&2wXJlf*G=BVcNND) z<$TX|V-A0GV~JmpD~+7cU{etDN4!HxA~>lW#fyRS_t=A9x_MR-Hh1GJ{tDLJfJZAo z>L5-m3uvVajXaGPuw}^kYAm$U;YzR?9>ey4pTl%3EmE$+5{}}NV8I4F-^l&I7NU_i zE1y3F=A$^WG+7qVN^^KqIrCvWgj*&Rpd{Yi0iW?!EB&@YK!=0)Dp+_8o|KmTInb3uf%?)k*}OU!*=ETBOGX@Q&j=WIye*XcPJHe93D|_z%XV7^(GSV zISvKX{y!r@3Q zw&Cd!0;ZII$wnSt#o=~0UZwD+kn3vI|Ke< zaoatuezQgj_y+F<{EB33jM8n$`SlG0j-oVjb8)vF5EUS-+_Arzj-D37Ha#9 ztMA9~wsQXmOLS7o?Wf&*3R^4dM>esM0$S-@Bfo@=L#Cv>12porc%l+wUzgCU6VQn` ztMvRD9>mQ7e@c1ijKruU9E(Q2Og;H#@b#JsQ2N7t0slMp*8VH}p8|H{ihw_&JUqr? z)dCuM8=eeIi^|oTTT=n*jp>B)u$c%Gzw-vs$m{*{LYf~i^_05*!gbPBK6S z2qcOCX+RpmEM`!|Vl*s(e_$4D*s=fwVliTaP!fq;1cOVPi8jnEM#_pZf&&Fn01OC#YOS%x6A~~WB?1XVFrWb&REU<^0ssWiAZh|q zser}=2}UJ~fS{^q6pSKJ!3+XqkbwmO08j`ZAYcFhB2*9|6ESK6ia48D91gXh0HVQ) zdUaqM3(2V^Y{Ik%v)YOwlriQZcpzdlX_;ifNoricR8r5I=}cD7tU*?GMO=&#@^wmcce!L?cvlkv;4yO@bY@LcDk{=;L3_8n|yv> zpYFvPK@^R~Mhz&gMlBe@sIkW3LkPjTgmbIBbXjT`<_yQNnfSyqo>Ap?xoKa$>fgQ< zR=U2b%(Hz{M9LsyEYTa$C>j+2TtRS$q@el1}^DOU-L9`{77$Z`MEjOn?4RtFg7DDF#zl9!MQxaRLTZ16a@q0{(au_IhHh zPAi$!q)nHluG2NEXH5HucE_~4Mq9Sm50Cos?(SHrn=uRl7Ct6nHcuMw}27@4@3t1Qnuq z)SDoXfE6V|>18qtMJbD!G#x7~Sy1#cK~TbE?yWH!7ZNRPTkRM3eH}C+C!9siz*Ib4e;z zqsi+OjE5RL2)L*g1Q3TRsg+Js7TcsI`Z|tB>>TKmU5-!4oOBx08g`HU!(FxyC@KX7 zk1vDwQH^y5=f>t)Z zqs?MRrxm7POXrFZtfI z{NKBL|D(_}**CsoUioF&OF4U)-}_bn$j<-4O~BoS^OK{lo2M&{=9yDsyQ}L<3FNPA zl(Cw&$}A~PCyi=W4<0-n{oDWezxnH>>#OqhKj-FI`Ob&4zx<%w|CG01%f9t)^VUt* ze|J}%l7J8701=la~({`LtW(6V7F$! z*x4SZS>0SUGt&L%739yJ7sq+voP6z&>0P&a)~XoOPyMgxRAf2eg>8-L>6#`V_XlJ1QM|MF2-OS|vQ4Lkh#DtC3Qo3CVJu_%sX`rmD1u n%|^gCi|Bl2)s(4(B#QZepF9mZ0h-1&00000NkvXXu0mjfQb&k+ literal 0 HcmV?d00001 diff --git a/recipes/thecodelesscode.png b/recipes/thecodelesscode.png new file mode 100644 index 0000000000000000000000000000000000000000..470ac285f3139068928667048c348a626b1a0d54 GIT binary patch literal 1085 zcmV-D1j74?P)tIXOByIy*Z%JUl!-Jv}}?K0iM{KtMo2K|w-7LPSJFMMXtMMn*?R zM@mXcOG`^kOiWEpO-@cuPft%!P*71(QBqP;Q&Uq^R8&`2S6EnBSy@?HT3TCMTV7sX zUteEfU|?cmVq|1wWo2b%W@cw+XJ}|>X=!O{YHDk1Yiw+6ZEbCCZflarK`l$Dj0mX?;7n3$QFnVOoKo12@QoSdDVot~ba zpP!$hp`oIpqNAguq@<*!rKP5(rl+T;sHmu_s;aB2tE{Z7t*x!Fu&}YQv9hwVv$M0c zwY9suyS%)-zrVl0z`(-7!o!FB*Wc0bc^Zxdoeb}xqrIchx2ttLd z*PHreYIN*zxuQQ=`T%1Y&cl!?5R`(~UsMa$$G#D_XnKE05Eaf>H5N1Y|WV9e><`Gdl5=0@h>1Yr>z!_?M{|n z1VmeOTgEk_V6#O&n>o$oemD`Z*`iu~-q^qAev5$3;kMT)?HoN_ES5pw%{Y$%*Dd6< zvX*k(`F!Xj4gmWT$%;xb2*b&Z+4p8KFc|C=4-OflWD$IrAr1f@kd$=|Qm>UB8Fmr~ zNxJhM@|AY*a1{Ropk`GBo~kM!lq7;&8vZPuW)dV3On!o$zTM3nc|+4p0@H4~_*f@o zzOc<{61Zsr`#DO+jy3j@fNcZ$Jf$PMH$)QH&VRvguOi^2GiDH_00000NkvXXu0mjf D@|V9j literal 0 HcmV?d00001 diff --git a/recipes/thecultofghoul.png b/recipes/thecultofghoul.png new file mode 100644 index 0000000000000000000000000000000000000000..dc616b95cc4f6ba02809a4478fb8c36bda1faac8 GIT binary patch literal 274 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvtpJ}8*MDhD|28%KdvxyKyr_S- z_Wk?z?q8MCzhm?M6$$;@*Z1$;y??8U{{8z0RDRUmuo+14l?3?(|3?N4t_SX%0LpZF zx;TbtOibQ!x`FAPQH+D=gVY6L2|8BAYANiK4$LujDDYx9_uPh=EwO-6vg1JE4hLSB z2R_U`4$sd>CMPdoxHG{xozKAF!vYS65Ed>47KH~QyADVOvGnvFJf;-Qy-ng~qJn7q zg*klRdwF~wFs%B1e$LDWA&2yHb6i*=1v}QCb1-cXy!qlJBZJD?s&M^ literal 0 HcmV?d00001 diff --git a/recipes/thedgesingapore.png b/recipes/thedgesingapore.png new file mode 100644 index 0000000000000000000000000000000000000000..6fc596c2ee419b5ed82a977ffa49b12e80f1c3a2 GIT binary patch literal 1057 zcmV++1m63JP)TQ|RxnL(0Cxuk;r_U1J2`2GCC-}f=U%@c-2B|O z(K`FD(WSnuT5EQ9 zcd1k=cpQ&_47YUZLQvO+n>ViW<-@NT9UCP{l6EJpbr-QQ1|bBe zr>Cr}tgyAUg;FZ8)A0^al)!bMKxckZv{$taByrPW2wN?pC_;uIG8~*?g%IR&xpX-B zyxVmJ;B5)K=?1_M`P$e6&KN@wq;Xd&6^@RM2yIZl0<~HVfRl8QI@RLMfFE00~qt zDu9`pnXYFyJ>%o4$oYIe+odW1r`2$&cL!bwm7Sd(3Wb7><5*bEHrYec147bQ1%b##L{cIo zh%oIcglOAYu(`rmjymua_8|lgfiFm_*@Ui&#crG9I`yYN)A;>4)!H%N@9Z!#GV+h~ z+cR__TwX{TjfS*bhj>K^qy@=7oeJinYXRqT-*TnG*hWaFT>91vs#{m3tROMd)6)Y- z?m<_pRhNVofbTrcHeP8_nhoNWl;znVZjs*ioP(EgF>$!4tJ%8s;rl!{UdrWkP~QLF bujhXQyYw70%`}tr00000NkvXXu0mjfqdnje literal 0 HcmV?d00001 diff --git a/recipes/theeconomictimes_india_print_edition.png b/recipes/theeconomictimes_india_print_edition.png new file mode 100644 index 0000000000000000000000000000000000000000..65f0be8ddf4b6eac4b76db54de323ddc767497cf GIT binary patch literal 1156 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 OOBt(EXg9&k%6|Yy!2Y5D literal 0 HcmV?d00001 diff --git a/recipes/thn.png b/recipes/thn.png new file mode 100644 index 0000000000000000000000000000000000000000..add1c19635d5c89ff837cf9ef4d4583bdd3e7a23 GIT binary patch literal 379 zcmV->0fhdEP)wqvw#9=Z}`;fr;UC zeBpC@+gM`TS7FylRMk94)H_GgE<4dJI?o_3PWFGo0000KbW%=J01zNBKu}YFvKxz&k4=s`bD1|zaRj8SGGzRXqPq>0a3TrkSEs|;^#F`G0(d!17%&?8%G+tg4 zGA6CRx~Y&;N`RjecalJ*HGnwfP^)YW>XSfv2*&a2ONEo%-C59oQ3rtQwuq=A0|@7H zT(lY;_|O~pizNBQ9VW?wRrh4&Ujb9jd3pt?iGMA*->n02uDUx&((o|vJwUe$wfMKc Z`3lAo69g~J*zf=V002ovPDHLkV1nKnq^bY_ literal 0 HcmV?d00001 diff --git a/recipes/tijolaco.png b/recipes/tijolaco.png new file mode 100644 index 0000000000000000000000000000000000000000..556555c622f7f2eff9b343d676fcee74c848c5dc GIT binary patch literal 1127 zcmV-t1ep7YP)S@U4kS6Jyf&V2o*an#N!xHcq5L zq8O!MtSE9*oT-Qg0YybYP&;r`T5{S6t;IN_bev?olo7Drz=1-!%1wdJLfejYny8t~ zn>#x@>#Y5)_3dx%bvF9=KM!<6a6@oIa2Y{1o4sIM1bmJ8H~vZrvX=oe*=*)>#`AWL zpk1cn59@l>2HvoaEmI3erU!KMU86G`xu#z?H~Vs_MWUhz26d)aGx%%vpxWaPK-k;u zJyyR;HTNPKPv~D*8h#k66-ZUsbL}%TwOdx-qX2L+b(|v@!*GD2>K0%Eo6M2iZs1|& zrMhle!GjKTzlGU1rg-uUjAA+;)jZmD{{>-^$ys!-=`K^mS5BiUCQT%lOk%faoruip zluxpy6R4DGT=?}x0SHtG1ozEMPGf}pvVOQ`X&60QyoxV5tn z1o)zrH>B$tnf`HHzh>Y5ZDR|R0ssmMXsK>~U~*QX!Qc)7?A&+3F`*a8of4@22!_w# z3qS_hIu+X1eicom_@_lTUdvxlYY~`44iyOy-hw z-@=@wfD3c%xx&%d1^$nx6v*dvzT$E>4F-?R_F-qoy}th5ZjT3p;lsnw!NKABddTJS zIULrZp*4d60({%sUb{W)cH3}#V|LbSvn3;ued~&;j_R~n0=v7O%}xKicQ%vBNTCD{ z4$vHnv=i=5q1oW+R<$5sBujsuubDU8{A9NPNy@8f$A8I2;3s^pu)AxS7YBDlPrF zqr=SMV3n1|nwp;~Dljq`graOZedPBi!{Jmknn|TjlF4)`1?7VkW+EJhH692gfys?$NCa`;&6I2KEULI6%39l->B$B6_~40hAWByI0P8-o8n{sv2(q0g5byZrzF002ovPDHLkV1o1(94`O> literal 0 HcmV?d00001 diff --git a/recipes/tillsonburg.png b/recipes/tillsonburg.png new file mode 100644 index 0000000000000000000000000000000000000000..c734bfe0ccd7754d853ce5fd05e553a735e81829 GIT binary patch literal 971 zcmV;+12p`JP){QLX+`uh6$`T6(v_xASo@bK{N?(XU7 z>E`C<=jZ3++kC2a!j*N?piiwGchlhlNgM)&Ce|>&|e}H~` zeSCX+dwP0&dV6tiaB^{RZEI_3XlQF{YH4R@Tv=LKS6En8R#Z|_P)$xqMMg$LL_|SC zLqS18KR`e|K0!V{JUBTxH8wIYFflGKGA=JME-x@GE-fl6D<~)|C@Cu^C@Uu^EGH=} zC@Cr?Cn+T+C?zE*B_=5)CMYB&CnF>#A|oXsA|@guBp@LrAtEFoAtN6ks^ivz0001R zbW%=J009C61O)~N2@eq#85tcQA}1*-D=aWKJwHG}LqkMHOioW%S6EnCSz23MUSDHu zZfes4L{#^wdr|d95D-+cppvYeX#sImwE(e0Cz=P5%(FZ+p-B`-U`W=0 z{i|FqXLd*(2o;5p_MdNu0e)xaZt;;Apz0m%CjU7+qLXd`B2uLh?n_Lcq_3o@-jA+H zXs9azSd18oG9Yt>O0B>yt-SQmW`Gc8k{PUYav%ug9JfsXwlK_r)2U4bhdIC$CLB2S z4!Qu$IqG$!MRAV-JMG@XfGT?ItKOV8g;jE+1u=abBJ@^8aWY+6|lI^wWZI(+&}Hl_(!7l&@mrK8J>U320##?Dmea1QJF>H-vv(!g@p0f@0$>?;pmt|pTm z%WBiC(FYoJVl~TZ_hStx+6@~TIQXmrL1pC5-d&&^IcsRQub<~{xsIHnwa4C;8Tk07*qoM6N<$g18YW(*OVf literal 0 HcmV?d00001 diff --git a/recipes/tomshardware_it.png b/recipes/tomshardware_it.png new file mode 100644 index 0000000000000000000000000000000000000000..320a95e93b90052d25c337b97656438fd99e958c GIT binary patch literal 1460 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7e6l0AZa85pY67#JE_7#My5g&JNk zFq9fFFuY1&V6d9Oz#v{QXIG#N0|S#!fKP}kP{C~r(>o59H#N0y>gwGxFt}x)|MUOW zn|gXTwX|<)>D<)Ty{W5vRY2yZp8g$c^Bbxfw=K=C%P3!$Rla3xbVEbymZ8CIbK~2V zrZ<6X6T_R@I=74rZm4PA($~MCu609I^QNZG4Hb=>8rnCM)o&=NyUTp${q>gSoBDb;b##G70WAV*2Qq*fL1yafgPdn%2$E9M1R4uc zpaMfcHpn9FJ2vJ}S)c<94S`01xo~A*jX+(h8X&Lf>48iD8U-{EEC@Ch!iJJJbs@e0 zX#pw&I_9>S2|_*4g<3iwU0`!=>FI;511SUf7cOpS0CXELtU!Sb4^?2W0JUpsg98oh zWngfD)!Z@$dlTp*pbLS5V8eml2dUT72ZaDk1Z;~2FnnNwAb)|h0QEu`V1I$403-ZX%HST^}!z!vL%*n=~W2DB&Wo>P$#HhKJ zP4DeKP7@x>&!_n9F9^B*xGL&>OEU1!J=uuIanIFLU+Wkae=@K7V%zxLx#Op2r?1P! zgG!8I$|8c7ZuZ(KQXS&` zZ@uJK=yhw=&UIT>1n#{ye?h?VU$@16&c1r;liY)@R0gT!pxOJL{r3qC>`Ip_N-Mu; z9Upq5Kwn*pjrT{gMd@0{=3?dx%TLSmJWrU$C*^gGHSW7(^G|03WsWB~9DDrP8>iSa z?B=K!c_$lFt+;2|*=9{SGa1I;N*Mw626AjxDGjZR;cQ;}8O5Y7B_7y!x|OHsUFy;; z3sUqFEUdrReKa(?ePsVL=EK5{PcPkCe*+k0swJ)wB`Jv|saDBFsX&Us$iT=@*U&)M x&?v;v$jZnJh_nq1tPBjkGghoX(U6;;l9^VCTSHsS0TG}E22WQ%mvv4FO#nOYBlQ3P literal 0 HcmV?d00001 diff --git a/recipes/toyokeizai.png b/recipes/toyokeizai.png new file mode 100644 index 0000000000000000000000000000000000000000..cf0e48125fd3d4c8b695116f6f4fcd2b1c76a0eb GIT binary patch literal 1560 zcmV+z2Iu*SP)7?5gZZJDE9iW`5_K-^`qg?fg%YWaV=`2LGuiQ>4*Ifmke*#roOL0yCu1H05~$ z#*f{P2FwGtAscBTr#w^KtzU-2)`3CFY8j%aJEVEW@81Q`s5Kad9XN0R+e6TlTCE1- z^L#$^4<0-StxBZ?O*|fF0FueZpRa*3D=RA_BLg~~NzX2TR;z_^US1xL$Agtw9DyMy zVG0biZCWrET(|)JoSYnJH7XToXqtw0{N6ZdvcaCk?$+x+rszu}P%#l)tf=5bphCO6 zdHv?-;Ul2Q%*=%LR7uIk#sCd=e*eP4!hrb>cWW4e5~jca%)q#+vN8pr zRN@FgOAshI2;lzseYE>-1gT>H?d;4f7XXH-DUd`WQ6)eC+0ZH!t)PTlGrqGag^pDE z%MRy6VQ4+91g@YDo`O*ZE6{-+aLo|Cl$GKd?~4O&1Q{Rtxw(fnH{bq8 zioO;gB`64^v46iLrvc>U=AvVYc+TPRmf8FG$vyZwfDfk#x$JhkebZj_oFGk4clSO? zu1ehlu~-b6Xf!Gk3R77E>_8x}=Tq?o5mbHt*iB5_M|nffVyjHX81ElIsZ=5*yd|hk zfct3S;^HEj`UaAPX%V1$@W}lLF@e}_x_*5R4tze?I-3>fz%UJ zUmvGw_PO3kq}*IudVwo&4WUsJ1diiNFJ5pXfDV)(xk3(AR(k7}%jIG{iz6oiUa!{! zDm>$DL>nv*g+jX##I`rrgZ}lPmyW*w^lM}%9*cn^ariU2PNsdT#Aq79$5@;6ZyChF z)~kT+MxI%WS?jUEhOCfpd_-c9Va3lTe3A97-hz6k%G66(Rad)WQD%$hORJ~OtBG#s z6{G1piFDY*6s1yJrLxddq4_$4l?9jh@yZM7$DYa6K zeS-5X`N7pdczx;B>o8=G0dSM*0Ra|$w;I`6vD9HB(lb?@jb73Bow(9daM>X<=oF+}tfQmMiChY<7nR|JeoWY9 zsO`J09d(uw?I)xPg>Cqzq55p|F@kDpCToA6vZnxuo>J`H#`5;oib;sMl$WR)`S4He z%kDYd5s?Zz=fsVA+$^utZEr<@xWMz(Q|Ew#F`>Tan#sKQa^<)TFObN9gPZ-;0=zgv zzl-nC@g#)H-sGTfxVselji?y!tm!(9EsXY9L~X{>ruh4qtw!C19kWY%bTaY`uSQ>ZgMjG)HQ8y zof>Lho^d4T*u29%H{u3oH=cza>ZriOIp5^Mh}q>B9Cwhe@v+GvhsWa$xcoC?>{Sn} ztdJ8A9zXl@k6EYPP1+uq2JiJe@QuY2F?7+Adwi0A>8Ja@%>4~ijK8xdTZ4cA0000< KMNUMnLSTZkrScO1 literal 0 HcmV?d00001 diff --git a/recipes/tri_city_herald.png b/recipes/tri_city_herald.png new file mode 100644 index 0000000000000000000000000000000000000000..8131fc14b5c02a4ca1d216a34600443be83bd76e GIT binary patch literal 332 zcmV-S0ki&zP)b;O_GE`ZQswL2Ivgmd44|=xU3=p|#iL>hw=`w1uF{yvyQ^rqA{$ zX3zit0MSWAK~#8NmD1aegD?yP(2VWeFR=grv%6SrMIaX4dBjI3W7&P5QHdz?FhDGL zCzPH3(s`PDT;tt2a-YXDNDv|^r{A>81M{0Q^N(ztmNmMP8px=rgR}!{5Pd)90TyW3 z4p%IY0PRH<5M7K763{+iTL1#+19$B)xSoItd9N1Q;0it40lE05;pbZAOtX7c)wIgBZbs(Au>#I^`|eYN`wNODFZ e6Kx#bzw!%e83frF&`#9=0000Z<2zO7fR> h@f@_ToARwlTJwb1gtC8ZPk_!~@O1TaS?83{1OOfMYzzPZ literal 0 HcmV?d00001 From 8874ff73aaf9f98daf8fd2029fae68f36f0e1bb1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 4 Jan 2023 19:57:18 +0530 Subject: [PATCH 0067/2055] Also wrap QImage::fromHBITMAP() --- .../gui2/progress_indicator/QProgressIndicator.cpp | 10 ++++++++++ .../gui2/progress_indicator/QProgressIndicator.h | 1 + .../gui2/progress_indicator/QProgressIndicator.sip | 1 + 3 files changed, 12 insertions(+) diff --git a/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp b/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp index 4066f807fb..20472129df 100644 --- a/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp +++ b/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp @@ -143,3 +143,13 @@ image_from_hicon(void* hicon) { return QImage(); #endif } + +QImage +image_from_hbitmap(void* hbitmap) { +#ifdef Q_OS_WIN + return QImage::fromHBITMAP((HBITMAP)hbitmap); +#else + (void)hibitmap; + return QImage(); +#endif +} diff --git a/src/calibre/gui2/progress_indicator/QProgressIndicator.h b/src/calibre/gui2/progress_indicator/QProgressIndicator.h index 9817d4242a..41a609c8ff 100644 --- a/src/calibre/gui2/progress_indicator/QProgressIndicator.h +++ b/src/calibre/gui2/progress_indicator/QProgressIndicator.h @@ -163,3 +163,4 @@ QMenu* menu_for_action(const QAction *ac); void set_image_allocation_limit(int megabytes); int get_image_allocation_limit(); QImage image_from_hicon(void* hicon); +QImage image_from_hbitmap(void* hbitmap); diff --git a/src/calibre/gui2/progress_indicator/QProgressIndicator.sip b/src/calibre/gui2/progress_indicator/QProgressIndicator.sip index 209a038009..ef9a52e6af 100644 --- a/src/calibre/gui2/progress_indicator/QProgressIndicator.sip +++ b/src/calibre/gui2/progress_indicator/QProgressIndicator.sip @@ -90,3 +90,4 @@ QMenu* menu_for_action(const QAction *ac); void set_image_allocation_limit(int megabytes); int get_image_allocation_limit(); QImage image_from_hicon(void*); +QImage image_from_hbitmap(void*); From 630c24407c41300d48f595ef1cba252433e32ee1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 4 Jan 2023 20:07:05 +0530 Subject: [PATCH 0068/2055] Wrap the newer icon extraction API --- setup/extensions.json | 2 +- .../progress_indicator/QProgressIndicator.cpp | 2 +- src/calibre/utils/windows/winutil.cpp | 39 ++++++++++++++++++- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/setup/extensions.json b/setup/extensions.json index de58f3c201..7fc30f2683 100644 --- a/setup/extensions.json +++ b/setup/extensions.json @@ -173,7 +173,7 @@ "only": "windows", "headers": "calibre/utils/cpp_binding.h calibre/utils/windows/common.h", "sources": "calibre/utils/windows/winutil.cpp", - "libraries": "shell32 wininet advapi32", + "libraries": "shell32 wininet advapi32 gdi32", "cflags": "/X" }, { diff --git a/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp b/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp index 20472129df..af41ec457a 100644 --- a/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp +++ b/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp @@ -149,7 +149,7 @@ image_from_hbitmap(void* hbitmap) { #ifdef Q_OS_WIN return QImage::fromHBITMAP((HBITMAP)hbitmap); #else - (void)hibitmap; + (void)hbitmap; return QImage(); #endif } diff --git a/src/calibre/utils/windows/winutil.cpp b/src/calibre/utils/windows/winutil.cpp index 6a64abfce0..75147eac71 100644 --- a/src/calibre/utils/windows/winutil.cpp +++ b/src/calibre/utils/windows/winutil.cpp @@ -75,7 +75,7 @@ static PyMethodDef PyGUID_methods[] = { // }}} // Handle {{{ -typedef enum { NormalHandle, ModuleHandle, IconHandle } WinHandleType; +typedef enum { NormalHandle, ModuleHandle, IconHandle, BitmapHandle } WinHandleType; typedef struct { PyObject_HEAD @@ -94,6 +94,8 @@ Handle_close_(Handle *self) { FreeLibrary((HMODULE)self->handle); break; case IconHandle: DestroyIcon((HICON)self->handle); break; + case BitmapHandle: + DeleteObject((HBITMAP)self->handle); break; } self->handle = NULL; } @@ -132,6 +134,8 @@ Handle_repr(Handle * self) { name = "HMODULE"; break; case IconHandle: name = "HICON"; break; + case BitmapHandle: + name = "HBITMAP"; break; } return PyUnicode_FromFormat("", name, self->handle, self->associated_name, ""); } @@ -162,6 +166,7 @@ Handle_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { case NormalHandle: case IconHandle: case ModuleHandle: + case BitmapHandle: break; default: PyErr_Format(PyExc_TypeError, "unknown handle type: %d", type); @@ -978,7 +983,7 @@ write_file(PyObject *self, PyObject *args) { DWORD written = 0; BOOL ok; Py_BEGIN_ALLOW_THREADS - ok = WriteFile(handle, data + offset, size - offset, &written, NULL); + ok = WriteFile(handle, data + offset, (DWORD)(size - offset), &written, NULL); Py_END_ALLOW_THREADS if (!ok) return set_error_from_handle(args); return PyLong_FromUnsignedLong(written); @@ -1134,6 +1139,34 @@ get_icon_for_file(PyObject *self, PyObject *args) { return PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, ERROR_RESOURCE_TYPE_NOT_FOUND, PyTuple_GET_ITEM(args, 0)); } // }}} + +static PyObject* +get_bitmap_for_file(PyObject *self, PyObject *args) { + wchar_raii path; + long width = 256, height = 256; + if (!PyArg_ParseTuple(args, "O&|ll", py_to_wchar_no_none, &path, &width, &height)) return NULL; + scoped_com_initializer com; + if (!com.succeeded()) { PyErr_SetString(PyExc_OSError, "Failed to initialize COM"); return NULL; } + IShellItemImageFactory *pImageFactory; + HRESULT hr; + Py_BEGIN_ALLOW_THREADS + hr = SHCreateItemFromParsingName(path.ptr(), NULL, IID_PPV_ARGS(&pImageFactory)); + Py_END_ALLOW_THREADS + if (!SUCCEEDED(hr)) { + return error_from_hresult(hr, "Failed to create Shell Item from path"); + } + SIZE size = { width, height }; + HBITMAP hbmp; + Py_BEGIN_ALLOW_THREADS + hr = pImageFactory->GetImage(size, SIIGBF_BIGGERSIZEOK, &hbmp); + pImageFactory->Release(); + Py_END_ALLOW_THREADS + if (!SUCCEEDED(hr)) { + return error_from_hresult(hr, "Failed to get image from shell item"); + } + return (PyObject*)Handle_create(hbmp, BitmapHandle); +} + // Boilerplate {{{ static const char winutil_doc[] = "Defines utility methods to interface with windows."; @@ -1156,6 +1189,7 @@ static PyMethodDef winutil_methods[] = { M(load_library, METH_VARARGS), M(load_icons, METH_VARARGS), M(get_icon_for_file, METH_VARARGS), + M(get_bitmap_for_file, METH_VARARGS), M(parse_cmdline, METH_VARARGS), M(write_file, METH_VARARGS), M(wait_named_pipe, METH_VARARGS), @@ -1463,6 +1497,7 @@ exec_module(PyObject *m) { A(NormalHandle); A(ModuleHandle); A(IconHandle); + A(BitmapHandle); A(KF_FLAG_DEFAULT); A(KF_FLAG_FORCE_APP_DATA_REDIRECTION); From 805981333779063656e673ed3346031a1de6faff Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 4 Jan 2023 21:25:45 +0530 Subject: [PATCH 0069/2055] ... --- src/calibre/utils/windows/winutil.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/calibre/utils/windows/winutil.cpp b/src/calibre/utils/windows/winutil.cpp index 75147eac71..e03e3a2fce 100644 --- a/src/calibre/utils/windows/winutil.cpp +++ b/src/calibre/utils/windows/winutil.cpp @@ -1111,7 +1111,8 @@ get_icon_at_index(int shilsize, int index) { IImageListPtr spiml; HRESULT hr = SHGetImageList(shilsize, IID_PPV_ARGS(&spiml)); HICON hico = NULL; - if (SUCCEEDED(hr)) spiml->GetIcon(index, ILD_TRANSPARENT, &hico); + if (SUCCEEDED(hr)) spiml->GetIcon(index, ILD_TRANSPARENT | ILD_IMAGE, &hico); + spiml->Release(); return hico; } @@ -1130,14 +1131,14 @@ get_icon_for_file(PyObject *self, PyObject *args) { HICON icon; #define R(shil) { \ Py_BEGIN_ALLOW_THREADS \ - icon = get_icon_at_index(SHIL_JUMBO, fi.iIcon); \ + icon = get_icon_at_index(shil, fi.iIcon); \ Py_END_ALLOW_THREADS \ if (icon) return (PyObject*)Handle_create(icon, IconHandle); \ } R(SHIL_JUMBO); R(SHIL_EXTRALARGE); R(SHIL_LARGE); R(SHIL_SYSSMALL); R(SHIL_SMALL); #undef R return PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, ERROR_RESOURCE_TYPE_NOT_FOUND, PyTuple_GET_ITEM(args, 0)); -} // }}} +} static PyObject* @@ -1165,7 +1166,7 @@ get_bitmap_for_file(PyObject *self, PyObject *args) { return error_from_hresult(hr, "Failed to get image from shell item"); } return (PyObject*)Handle_create(hbmp, BitmapHandle); -} +} // }}} // Boilerplate {{{ static const char winutil_doc[] = "Defines utility methods to interface with windows."; From 42c283bdee493ac7ac68e537a07794cc4cd33717 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 4 Jan 2023 21:44:44 +0530 Subject: [PATCH 0070/2055] Use the more modern API for getting icons Qt's fromHBITMAP is totally broken so convert the bitmap to an HICON ourselves first. --- src/calibre/utils/open_with/windows.py | 4 +- src/calibre/utils/windows/winutil.cpp | 59 +++++++++----------------- 2 files changed, 22 insertions(+), 41 deletions(-) diff --git a/src/calibre/utils/open_with/windows.py b/src/calibre/utils/open_with/windows.py index 2a59c93890..79d0f40669 100644 --- a/src/calibre/utils/open_with/windows.py +++ b/src/calibre/utils/open_with/windows.py @@ -4,13 +4,14 @@ __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' +import os import re import sys from qt.core import QBuffer, QByteArray, QIODevice, QPixmap, Qt from calibre.gui2 import must_use_qt from calibre.utils.winreg.default_programs import split_commandline -from calibre_extensions import winutil, progress_indicator +from calibre_extensions import progress_indicator, winutil ICON_SIZE = 256 @@ -76,6 +77,7 @@ def load_icon_resource(icon_resource, as_data=False, size=ICON_SIZE): def load_icon_for_file(path: str, as_data=False, size=ICON_SIZE): + path = os.path.abspath(path) try: hicon = winutil.get_icon_for_file(path) except Exception: diff --git a/src/calibre/utils/windows/winutil.cpp b/src/calibre/utils/windows/winutil.cpp index e03e3a2fce..323f7644b4 100644 --- a/src/calibre/utils/windows/winutil.cpp +++ b/src/calibre/utils/windows/winutil.cpp @@ -1104,46 +1104,9 @@ load_icons(PyObject *self, PyObject *args) { return ans; } -_COM_SMARTPTR_TYPEDEF(IImageList, __uuidof(IImageList)); - -static HICON -get_icon_at_index(int shilsize, int index) { - IImageListPtr spiml; - HRESULT hr = SHGetImageList(shilsize, IID_PPV_ARGS(&spiml)); - HICON hico = NULL; - if (SUCCEEDED(hr)) spiml->GetIcon(index, ILD_TRANSPARENT | ILD_IMAGE, &hico); - spiml->Release(); - return hico; -} - static PyObject* get_icon_for_file(PyObject *self, PyObject *args) { wchar_raii path; - if (!PyArg_ParseTuple(args, "O&", py_to_wchar_no_none, &path)) return NULL; - scoped_com_initializer com; - if (!com.succeeded()) { PyErr_SetString(PyExc_OSError, "Failed to initialize COM"); return NULL; } - SHFILEINFO fi = {0}; - DWORD_PTR res; - Py_BEGIN_ALLOW_THREADS - res = SHGetFileInfoW(path.ptr(), 0, &fi, sizeof(fi), SHGFI_SYSICONINDEX); - Py_END_ALLOW_THREADS - if (!res) return PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, ERROR_RESOURCE_TYPE_NOT_FOUND, PyTuple_GET_ITEM(args, 0)); - HICON icon; -#define R(shil) { \ - Py_BEGIN_ALLOW_THREADS \ - icon = get_icon_at_index(shil, fi.iIcon); \ - Py_END_ALLOW_THREADS \ - if (icon) return (PyObject*)Handle_create(icon, IconHandle); \ -} - R(SHIL_JUMBO); R(SHIL_EXTRALARGE); R(SHIL_LARGE); R(SHIL_SYSSMALL); R(SHIL_SMALL); -#undef R - return PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, ERROR_RESOURCE_TYPE_NOT_FOUND, PyTuple_GET_ITEM(args, 0)); -} - - -static PyObject* -get_bitmap_for_file(PyObject *self, PyObject *args) { - wchar_raii path; long width = 256, height = 256; if (!PyArg_ParseTuple(args, "O&|ll", py_to_wchar_no_none, &path, &width, &height)) return NULL; scoped_com_initializer com; @@ -1159,13 +1122,30 @@ get_bitmap_for_file(PyObject *self, PyObject *args) { SIZE size = { width, height }; HBITMAP hbmp; Py_BEGIN_ALLOW_THREADS - hr = pImageFactory->GetImage(size, SIIGBF_BIGGERSIZEOK, &hbmp); + hr = pImageFactory->GetImage(size, SIIGBF_BIGGERSIZEOK | SIIGBF_SCALEUP, &hbmp); pImageFactory->Release(); Py_END_ALLOW_THREADS if (!SUCCEEDED(hr)) { return error_from_hresult(hr, "Failed to get image from shell item"); } - return (PyObject*)Handle_create(hbmp, BitmapHandle); + BITMAP bmp; + if (GetObject(hbmp, sizeof( BITMAP ), &bmp) == 0) { + DeleteObject(hbmp); + PyErr_SetString(PyExc_OSError, "Failed to load bitmap data from HBITMAP"); + return NULL; + } + HBITMAP hbmMask = CreateCompatibleBitmap(GetDC(NULL), bmp.bmWidth, bmp.bmHeight); + ICONINFO ii = {0}; + ii.fIcon = TRUE; + ii.hbmColor = hbmp; + ii.hbmMask = hbmMask; + HICON hIcon = CreateIconIndirect(&ii); + DeleteObject(hbmp); DeleteObject(hbmMask); + if (hIcon == NULL) { + PyErr_SetFromWindowsErr(GetLastError()); + return NULL; + } + return (PyObject*)Handle_create(hIcon, IconHandle); } // }}} // Boilerplate {{{ @@ -1190,7 +1170,6 @@ static PyMethodDef winutil_methods[] = { M(load_library, METH_VARARGS), M(load_icons, METH_VARARGS), M(get_icon_for_file, METH_VARARGS), - M(get_bitmap_for_file, METH_VARARGS), M(parse_cmdline, METH_VARARGS), M(write_file, METH_VARARGS), M(wait_named_pipe, METH_VARARGS), From 88d248b842daa88f1f198c2cd54e9205154f7181 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 4 Jan 2023 22:07:45 +0530 Subject: [PATCH 0071/2055] No need to use the github API to get transifex latest release version --- setup/unix-ci.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/setup/unix-ci.py b/setup/unix-ci.py index c98c11256b..1a6ba4e3dd 100644 --- a/setup/unix-ci.py +++ b/setup/unix-ci.py @@ -4,7 +4,6 @@ import glob import io -import json import os import shlex import subprocess @@ -110,16 +109,8 @@ def install_linux_deps(): run('sudo', 'apt-get', 'install', '-y', 'gettext', 'libgl1-mesa-dev', 'libxkbcommon-dev', 'libxkbcommon-x11-dev') -def get_tx_tarball_url(): - data = json.load(urlopen( - 'https://api.github.com/repos/transifex/cli/releases/latest')) - for asset in data['assets']: - if asset['name'] == 'tx-linux-amd64.tar.gz': - return asset['browser_download_url'] - - def get_tx(): - url = get_tx_tarball_url() + url = 'https://github.com/transifex/cli/releases/latest/download/tx-linux-amd64.tar.gz' print('Downloading:', url) with urlopen(url) as f: raw = f.read() From 928e96cf475f61f0f7a4a1a1d441fc9e067e1e37 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 5 Jan 2023 07:18:05 +0530 Subject: [PATCH 0072/2055] Extract icons only --- src/calibre/utils/windows/winutil.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/utils/windows/winutil.cpp b/src/calibre/utils/windows/winutil.cpp index 323f7644b4..f2a6608f6b 100644 --- a/src/calibre/utils/windows/winutil.cpp +++ b/src/calibre/utils/windows/winutil.cpp @@ -1122,7 +1122,7 @@ get_icon_for_file(PyObject *self, PyObject *args) { SIZE size = { width, height }; HBITMAP hbmp; Py_BEGIN_ALLOW_THREADS - hr = pImageFactory->GetImage(size, SIIGBF_BIGGERSIZEOK | SIIGBF_SCALEUP, &hbmp); + hr = pImageFactory->GetImage(size, SIIGBF_BIGGERSIZEOK | SIIGBF_SCALEUP | SIIGBF_ICONONLY, &hbmp); pImageFactory->Release(); Py_END_ALLOW_THREADS if (!SUCCEEDED(hr)) { From 89a5f2ffc443e1b16db12b57db7134f57ed35dcb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 5 Jan 2023 07:18:57 +0530 Subject: [PATCH 0073/2055] recipe icons --- recipes/unperiodico.png | Bin 0 -> 1245 bytes recipes/upi.png | Bin 0 -> 1714 bytes recipes/usatoday.png | Bin 0 -> 434 bytes recipes/valbybladet_dk.png | Bin 0 -> 300 bytes recipes/vancouver_province.png | Bin 0 -> 816 bytes recipes/vancouver_sun.png | Bin 0 -> 1346 bytes recipes/vanloesebladet_dk.png | Bin 0 -> 300 bytes recipes/veintitres.png | Bin 0 -> 119 bytes recipes/vesterbrobladet_dk.png | Bin 0 -> 300 bytes recipes/vic_times.png | Bin 0 -> 561 bytes recipes/villagevoice.png | Bin 0 -> 969 bytes recipes/vnexpress.png | Bin 0 -> 935 bytes recipes/voetbal_belgie.png | Bin 0 -> 2084 bytes recipes/xkcd.png | Bin 0 -> 330 bytes recipes/yakima_herald.png | Bin 0 -> 2477 bytes recipes/yementimes.png | Bin 0 -> 740 bytes recipes/ynet.png | Bin 0 -> 1323 bytes recipes/yomiuri_world.png | Bin 0 -> 1600 bytes recipes/zackzack.png | Bin 0 -> 2215 bytes recipes/zaobao.png | Bin 0 -> 1104 bytes recipes/zeitde_sub.png | Bin 0 -> 602 bytes recipes/zerocalcare.png | Bin 0 -> 846 bytes recipes/zita_be.png | Bin 0 -> 1223 bytes recipes/znadplanszy_pl.png | Bin 0 -> 1966 bytes recipes/zougla.png | Bin 0 -> 799 bytes 25 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 recipes/unperiodico.png create mode 100644 recipes/upi.png create mode 100644 recipes/usatoday.png create mode 100644 recipes/valbybladet_dk.png create mode 100644 recipes/vancouver_province.png create mode 100644 recipes/vancouver_sun.png create mode 100644 recipes/vanloesebladet_dk.png create mode 100644 recipes/veintitres.png create mode 100644 recipes/vesterbrobladet_dk.png create mode 100644 recipes/vic_times.png create mode 100644 recipes/villagevoice.png create mode 100644 recipes/vnexpress.png create mode 100644 recipes/voetbal_belgie.png create mode 100644 recipes/xkcd.png create mode 100644 recipes/yakima_herald.png create mode 100644 recipes/yementimes.png create mode 100644 recipes/ynet.png create mode 100644 recipes/yomiuri_world.png create mode 100644 recipes/zackzack.png create mode 100644 recipes/zaobao.png create mode 100644 recipes/zeitde_sub.png create mode 100644 recipes/zerocalcare.png create mode 100644 recipes/zita_be.png create mode 100644 recipes/znadplanszy_pl.png create mode 100644 recipes/zougla.png diff --git a/recipes/unperiodico.png b/recipes/unperiodico.png new file mode 100644 index 0000000000000000000000000000000000000000..a89aafe8b287ea369c818cd7c89bd3312716b6da GIT binary patch literal 1245 zcmV<31S0#1P)yGhpHOqX0B^oDYGoWIT8M8u$))D%}-u2Jq_vG?xofG?)3$?6!2g2+6mwo_)>;b!p|sV-?t%Mx}>4C2AX)K)mCahm%cCE zq%OA(pc2`flGEeb+gCt`NV|$sAJWWsf|~n%r5ld5=t8=kF)$;d`dDq&YWWuUS?y1W zh@WZZms;Rg!nZAX-K%K@oXhKuNXtv_ZvbTR$*BHU;;u;NYKTH-gda}?v=YD#QLu*c zoJ(8zE`Sfxt)y9J;N;U$aJryP)ctd<(viU)5kyxC8A*Evd@qj+;5V((_U*c6TGFlw z%i29xP)owTnjNaak|@PlFXnG&G*H`t0{2Cn6>KE4o{5WC3NIoOY{@qADLB@ywBf9MB9F<@PXew!A;@gG;AvPP;1oP zaOH)rG%0YqxiqCY@T?=Rrh?m^Z4__u@aaiZ5jp#osPQ=q42jHmqLy8Sb%`3%(Gsa; zPEbuED}Ev3WU&n(`+uA8^OQ)XEF6cF86a!qHJfOS=0dKU~?;g8x^bnLD_V}?P^xH#>{oD#x1Ro zbN;DJ=boL(YHcZ4glQg(L8`7D6!f*olx5OXxo`rq?Tw&9A>$oiCjN;#4} z_>-FRecd!UeB_Lu0hhD=gh;C~#kVu}t_4^J4`nWV;DY}EggdW^0$TKD00000NkvXX Hu0mjfnq*rD literal 0 HcmV?d00001 diff --git a/recipes/upi.png b/recipes/upi.png new file mode 100644 index 0000000000000000000000000000000000000000..7a5dc14d0c82b56be84945c53ce6f9bdc6805c54 GIT binary patch literal 1714 zcmaJ?Yfuwc6b|4}h6WsmSQJH<6(k^JH=95nl0rxV1PQ|x#s1*>b6&Zx;P!O~Tf})^+2x)zQ0+zw46nq1UFm{8W{b9SayZ7ER-*?V;9y|L@ zM0l{Bt&=T@M6wfy2&0G*xP${0ipv0lDn+g18hGHGUoNpXn;{UG zgW$a4tvvmxCl(#!2AUg z@wAF~Zj>-+J{oc2fr&V-;X;sJuczr5G)$WS(K#Fr1S1fFcoPV3U78w~8NAiHH47eu zs7|g`YH%f{2FxC1aabzO0|~9~lTc|Sk`Ip6y7^KOMS~174MeBGkV<9FY!0o%qtK5s zK8e;z(=;d)h3c?Wt(@pc{F((aQN8~UWCjvyxI#>xszTMcSjYnj3r(R^aDC}Qn9X2` zM0{Tnoh}k^U_ML8U@@72A~uJ^W-f5WY8@_9%h3g{@*`LHzg(_Bi^_0JE5)#sh5kh( zVmPKt#54ec(0l-@L?&0N%@z-Hb?3#RTIDWO5v0Xbz+8E_%1=mueffewK8wRgM6g+Y zpdgUR7I9#hjrg({EIK&PReU1#c`pAyxey@+GWYghy%3XA zyZhM72QMEy9atV_!*5Vh`p^v)CrNb}dFITN-C*W~hyRte?Rl=1rPw(N+P8i?dGLv| zC$)3>IYTp%GIH$E=z*f{nI$c1pLM5`>VI69xAkp}DcSErs$1GxF1_bb@Y%OP<+hXD z4E~vOef_45V6Jc$pAH=ay$ToXn-Qwn3 zO<6Zzj1=Y?kK{AiUKPdE{=3c<0X;9K?hajgJIhkY?QZWsnsYUN(Pjx~dgpui@v6eV z(x%WIDc0rv0U^{@A67xjt0RXmuVB7hOj@Q~!R_oEdQEO$&N1Pr&eHZOx9B@Q_1dgR zbeDR^CW8GiXJz%1Um6RIP2}m;D}H&;+FQx@Fr_tU&Vkh}%}A%-#ZY_kd=&; zzkC0Xu46_qvL(7F_x3^g=h+XxH|;sa?+b9W;DsQ=we|yvz1{1>Pa4FKWzwH+;sXaQ zjm6bt1(BuIpFJ-v_0Ap4Xk26J4lcKhtgfzlmQmJF*4yRe;J9>Rr8|_-3?dBG~zhwVfRU#rX~;D~qRTp~Rft!Yr&wONQf+$z@q`^It?P3KyOU+>!GS Dbta&< literal 0 HcmV?d00001 diff --git a/recipes/usatoday.png b/recipes/usatoday.png new file mode 100644 index 0000000000000000000000000000000000000000..605bf45a9bc7c7ff6ba4d520e6dd7e974e3756a1 GIT binary patch literal 434 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvg8-ipS0K$W<3Gb}7@7rU0LfYZ zowoj8d;fpWrT=$7{a0T3Kke}UpMU>5Ze>abYT+yi@(X6*3ZHfM?SJ*o+NRuZAi)BU z$YKTtZXpn6ymYtj4+8_Eu&0Y-h{nX^1O`UM$s0n_UD+%&_`I2$LW(8gm4HrEtSK{} z;o!v>vA~3v@6bW6Ljp`I6;zcnJq`$JTxipoz<6b18xxnrj6hZ%375-hsd~3Jo8{}e zC`@qFed~Mge0_DXpEJuOi8BpM_2<{m{V%69;mm_+-|T+hID7f^dH-gMX^J|!&;Oq` zw*P-OimgzJdBToq)4DgXX&6+0-xhv5@BO}Md*8<1+ibF6TJ`?)`PRkt*EjyQe|~=c zG%gd9xAEt;{rvp=-<_>iuf;C}7*9B`>HGKef40STpVco!nJ#d6HoyM+`S*V;|G$l2 zaMF)3J_W%F?D1%b}0001wNkl;sf5&<1F405h{bfEJh+ yo&eyOAiXZcz?VEhwUM69tV-%$C-|H{@H_xBhAKo`c1PJmj+=*NMNa1_ zQaV?n*6R10O^uSwLsXNej9nyM^3YaTQ>`byo%g-opU)rgxle{j80LosVgTS5&gVv= zO&{%qF=%pcoPLVd#ANTLLmY>@S7=Wsp<`$p{H@5)>pT5ekpHiSH}LpoN=^&jrlHP#NF?5 z_xJqqxO+W+`aHe;1AQK3u-`K{FgQGj3=IzW zHDxXYd=6b1aPDCN3otGB(o)`evF zIRDjaf4Ls!@gprV4&x+tBG~SCU`TFPpYP~s%l7T%(XOtdl0ldZvr5;ySDclewRcq0O33#m*T{y{sWU9n;-xH literal 0 HcmV?d00001 diff --git a/recipes/vancouver_sun.png b/recipes/vancouver_sun.png new file mode 100644 index 0000000000000000000000000000000000000000..673b2c4d61a8fbf548126dfd55d31dfc064ee3a2 GIT binary patch literal 1346 zcmV-I1-<%-P)X!+(T;NtUyR7y+X1pkaI~{5m1l_Axzi85Agfftmj zg!CH-Xe>uJtc31Z0O~jLeYq=|Kl9v;KR1gOrtTPl)Qp=O^lf~-%r!n(jy9?L?PM_m zh5iEcPzn?zphn7Nd9-Xy%W$h$mhbRgQVb z5{s_rm~_}Ryc!EEN3mEK`#wCm{#qBtnV9D0g#%~6u*gaDs^jxhOTa@{2sd^!jS^mJ zcY8UVWGF=|TPb`gq7L_GPCsDtp@qzPeD_Uqe@8gj8%eKZP7Z{)b#Z$=vA^9Ku&=$} zP9E&+oF1}0y8i^2y$VttDVa$nw$oK&B>8TuFHDFn-4{P!>T&0B_Dmi2*IA{f*IChk zHIiC6x;$UnNr{JS-J!nyi_@-g-{4Z@*){g%G!ZjB?cD zjE?n84O*rNxL1e#imwDJ_)QSICD;d1lc+DtnPd`DO?jo@`V3%7#H}u@1{bn0EjS&? z`xQpG`Mv~$bZ0j#D=Yw;vhwS1Y+9=pN$vWj)LKI;%QlMRk#VM~Z8SbVnpl9qy^E9Z z0Gco3LY3XUDS&@lUw2Wg0~4jEqrRQ<+n;~9hjE&OpE=lTQV6hxiC=#I)jdCMm>jrU zIFD%+h*d#qsx0v9b)zECNu`;{J4G+ z@Bd*s5T9$ZD;_UU_;h^1ZeXd}1UgxTfYMGlz-q7w*{~bT)9g1rj`~KR`0?9lXoIfD zacjWWU_tO-!J6DsUXb4qD1N@fVL-Za4$F`5Zm&F)3J_W%F?D1%b}0001wNkl;sf5&<1F405h{bfEJh+ yo&eyOAiXZcz?VEhwUM69tV-%$C-|H{@H_xBhA2 literal 0 HcmV?d00001 diff --git a/recipes/vesterbrobladet_dk.png b/recipes/vesterbrobladet_dk.png new file mode 100644 index 0000000000000000000000000000000000000000..05f107580a440e778ae50dddd11075d6fd1379df GIT binary patch literal 300 zcmV+{0n`48P)F)3J_W%F?D1%b}0001wNkl;sf5&<1F405h{bfEJh+ yo&eyOAiXZcz?VEhwUM69tV-%$C-|H{@H_xBhA=7k=FOSAcFmg8r%ss}8FzJbMukTh=;*dIHUIeW?%C3zDmscb@Fj{M40Z;OYMz@LB~8; z85kICJY5_^DsHt-W@b9#z~dTx+dlU8zW;B#&P3(k{Fa`yE4igFeb>5|mJ1!V+&nyzW^QFn&=yG5?=y zY;>Gw+dCl>=b{B4!-B#w|y0{^J zkzj)9v4;K?lG~VE9x-m-$6@f=aDCurfktOJsjz^idsFx)I4_--;uOGirm|<65U<)! z{U*Wl^Jjev|8UpUf9HodmSDTZsVkaF;#h2-xmH*D8rKLW9LSwGU0HLA*47z$N4{)e z2)jK|>+4l_OKBdqwYl{>rTLccU05-pY`=1E`B!`9$tO>!{%~64cK_Rc{mY9^*cU`)8E9O)|R+f`d7I*FQn1lv8LVC)DRfX44$rjF6*2UngEL;@O=OP literal 0 HcmV?d00001 diff --git a/recipes/villagevoice.png b/recipes/villagevoice.png new file mode 100644 index 0000000000000000000000000000000000000000..0717ac1e673151c553c0d205a31a6a28c35551d2 GIT binary patch literal 969 zcmW-g3rtgI6vyvHsI0V3v*Ltprc@`*e1STdi!8L1tRx$&lQB#d1PaA%4$Fw3Zc|o- z0kTLIL&&xO&SuC%tldbjrPM-8DNqQ*(ot;3z(RSnP$~4?+np{s_wn8D`~K(m|IWF! zoQ(I$#8@JNAmns5>tlFQLs#TBxU+*vI%`cxA)0#x< zo`LDLmy3GQ^OCxre_+ZNz|S>K@S1i00FL9qD;)+92h9zauTeG^b{^Z7i`^SsusOU* z73y7|%hb=-C+9aHZ)~w#zu=HQU4@Kjj-*O##Dl?2w?DOJgk3-C^!P#Tl28w7r*CTP zTfS?ZA!Uf#KSOz!?CzuAs&*9h_D|bDgjgsg@uW}mwnyX3FBLq7W!E3ffC4N#d`*@3 z-5_`bz@ipi()UBQO)t>q{ycf{w<-8&UUR*5MM)}@%BHN%17-jY&%@e=7Vm2o3Hc$Z z@^%AA0d4Fh`5XKv2FoV6hV94xmjzS+{_ysg%NzK2)w%z2Ke0e6nOp@(fyfBfAbyn0 z!tk!r9Fd2CU}@Xm0XzugWL_^=P#V^}emuKz0#Ge>x7Fc2etq!74K*CvjAe6gi2~{( z9$yAjUW<+|)WhB;`8;qI--CC&Ys4ZNTXuT`A2p1D5wK%-`XHV@cfcWog|5-1+t@7B z0_=lv08X#hLIccMUE0|f(O>of6_^FWDzOPFoA=A4&yPD?p<2*N`%Np(A>%sK4vwW& zY2I@CuXY;16!mKbbQHAKBQ)bEf}s1-S&YngM!ES^q^ahNLZc#u9qLM8oGcR|+v79Q zc#?ubjoR6X(CRq|&4XdB#O9Q6dC3K)xd(eM^^U39BdDxIYncnoX!^cq=-sy!CsQcD z3L=vi_=Gb@cE%j(uB@pL(A_&qtylNoYHHOR;&cplQ8$@Q)TiFz{$$50_~qiZV+q2N zVr2*Qb>6$G>OI}dbmH%E+O!^YuPI^9OK6J!i^4C7Mu%fQOG${)&@CN|vmEmAurUIw zj&`4Vt1pVJ7yt1o+MeSq(4H7pS7+pZ($KPLk(+q% zU{*xVq0u-}R#uYrR1_~QpzNl)i?0Y=7buc72qtuHvs1VIv}GXgMJ>eI}@<9kgEl@mX(WUyMWWh!oG#v3s880 zRj7j^J#h8F(}TbOB14A2kRdd%gxw(Ork8cIL`J&Um@PK45V(;gA)t(4;Y2P9kjOw_ z2r0pl_F&RpPSwSt{e8HiAFu0|)D2XBI3#cSMXw*#_5P|ejd%A=5dF5E0h`$}X|hcA z4NmqAq41tjt7pvSu}*rezjdK?F$m2+4JsOS0 zVsS(~zVB4p0kr6yN{M(MawJO9hKy&o@4y5VNI0%+{(f>g7!HTY)klw`PqdC6NC+GF z=`}PWDCHGb?ryJAd)8@-J(w{4*wQJhd>b)tc zY1Px5Ewt;=y(+x;N~2%dnEzDl?%&keW1XiI+%Mnx^z(Rf@`CPl{x)e#&08mm>r(0I zTHkbKh5jOsjt0nv)-bpu2-I}AFh~6 zzm{Ka+?7Q&i-$gG8|!6Q-+YysQ&3RA93C8HC+Sp-7b><;cUg}Az^tHnH2arjjDiu$ zCQvNZ7o?4@nNgYMdV0g4W)cBZ;&fj{RdDy&T;?% literal 0 HcmV?d00001 diff --git a/recipes/voetbal_belgie.png b/recipes/voetbal_belgie.png new file mode 100644 index 0000000000000000000000000000000000000000..a6c6ae19c59dfc3788e534e6d9dc89208cfee972 GIT binary patch literal 2084 zcmYLKdpr~B8-It~$IL<~bBP>fE{TH}jb^Da38Ryvn&gsfZbRmhkjq4Ba*OCJ5hs@v zmJSk8G={mHig6UnJql-jf1Ka*dB4y1`##U}zR#cE6sJ?ip_0my005{17DEs)atGp~ zf=s4TmjwhN6OP-0s$SK91Q*OjcL&nRli(MD76*_Uen4brL;z(001z&Q0AT?`c51~! z|8t1g-E6FC{bc3nA+uJ{mp0iflS^8hlF>nBj98sj?CIa2-+sT?%ZHhHCKnpy-?#b%n zY|(=B5zNmP+r9iP3E7EZ-qZ~@$LG$7tdfggxMTYE*6z=ji`?iowsk#s7A%Dj2~gGz z{-?DCgix_wz*t9il-Eg3-U8N}@WG&6 z9?;%T-MmK_8?(hxa?8rxl^3Z+b|A;wYnYWe1hirnN0gW1@ag5$lSKe~;cH>e{QK${ zzs#7|TL!G{iSSg4Of%cT)g@GBd&&)wf2^dWMBkB3VB_aco4C4^QP--dtGCQuXYr_j zCx-@dFQ2F@8!__l`ZFuAru%!>#vg@dQxR?yeZL@_F8$J(eb?A$no@q{&GcnfY%yQg z?mdAO`8F@M5k5X3tI}s}Oql4x5B{bM*Z=RqLX;a{Jv!J5blg7vO~=od#Yp?@a5OR#W;88ygKm58y}=jj@QS zAxm;9GC|c;<_rh=s>0kjVb}Z9AHNN-773PoJS3@u!@9h2-M9HPX|)tvLZ0Rng)ctY zZr`?S+K7#usN4-I=G(SnLL;qaTbw7!Cq;h8p3NO~X{NaE)Cvl^o?N*`zueKIv+dL^iAUILV2+UTr&Ouk^B{CQBy}Nf+mPtmV2* z{=i3*&e?C)>;u->Wzn@(sx$ppq=^#hubkROj;}AM?DnFst{2WeC!sFgb+2*hLC6uV z7PaD$Xl#K=!`O?(PR-9#MjNH+0q0bO(Y1FmjK|Z}1S6)lw_a`E1Hy z*-fDo1T~Y`@Ig(=X-$mgwo zF16nlJCbk^1Ytj>^-80anKD$ng}lq3s%>;&{eKr$^RyU|esLm` zm!iFmN1`_PGmiWiahzl`Z3edYQ5Q zaBYnl)6m-NU8gg~`pR5;WbRWmuCdhqCy5lLZMRHJ)68g4D5?bBedXhO+VKu?B@Lc+ zCq^C)JyP_3!0}w+ZmhAaVkv~hzML$d=7H74^}6lnlzh>L8EPo}Iqowr+|QVIaNLBW zy&TI6yflU8aVR`^qR%6uVMLd=28=LbU?!#+X)pxWsEFW}hi4dz9J=%xU!WW?>bcjh z(Y+I$9q%g%B5ZKJWtos=?bN@@Wt0*0yk3(AuZ8_(h@yS{&yW#Iac{=bk&yV&p)^LY zrUbI|4PF5x)2$X+uPm@}2=k&376*ZPHL2kR2c=e!rjc=kX)k?`;rqA}>foS}VtK7e zAEi%WutCYWU+q0|Bikv!Q+>m*q>8?E` z3oshdL3ogu))>YV0yIWL+9;yFSRA0I;p43NnM=CSRL!R%@1YEfI8mgbxD5zFj72|% zPUr&QJsj9>r)#5;b)bLrSGN^U($hC|nf;KI!kPaaB@7atqCxD`8~9!%Fx!ZM(Q@O| z9DzUeohIkE1QUCGrJ}^bvKU4#+KdYtSy-b4=@86dGWTS3AalHl2mvo8dQ?u`%VRKv zu*{*h>zk(Sb3qhJUPnUbj;B2^NL2V)&DruHQ}|`=Oa4oO9dbtREUtWDT`m`g5%@>% z=|{QYHc9pTTtfK;!3!9J5iT~@lRM?Pw9vfNJsmssIoN7j4F|2IQk>p4?tJQCcM4Nw H>mC0u$(FYU literal 0 HcmV?d00001 diff --git a/recipes/xkcd.png b/recipes/xkcd.png new file mode 100644 index 0000000000000000000000000000000000000000..c5e522e62c3f62b74eac65d2d52fe7e0aaa812b1 GIT binary patch literal 330 zcmV-Q0k!^#P)X}0>hZM?5CZUj#{c}IG^%lLTRU+ zK!<~b5&(UJ9}P7r!>~isP$@Mv0mczzMTUy170QPN`-|Z$L(Nh%($re9WibB3Dwaom zUa+dCY2QH9L+p0(0dQ$3Coqm7MM?wZN*T=N;kntwK4wexu~o-ZAF0_4#h8pqGi4v4 zu+wDNo3lbGqCUa>N}0Fzq3_qN^`Z_dZZRqF80NoO?O|fcMDkm^7+Ck=z^fG cMH$ZL5A8fsP(0R_wg3PC07*qoM6N<$g4-;Nj{pDw literal 0 HcmV?d00001 diff --git a/recipes/yakima_herald.png b/recipes/yakima_herald.png new file mode 100644 index 0000000000000000000000000000000000000000..7e8de676aa63e7e5a2e67fb24b132f82fbbc13c3 GIT binary patch literal 2477 zcmV;e2~zfnP)JT1eK~45mkE?ZfGm@rm9*RmI{#)2}Cat6(thT&?*-}DaEA3_Sk7W z&e*fhIdk@NzU6)2r;b(n`;mwM7>-5<-R^WU69Ng|MI<7GcL@fxVWH4qfw{Vx8A!K{t><2A(MUXkLz=-?4^Is zzx!{4=l*cvkq5J{e9asj?9DZ6pZ-*7I?KNDH9hF>uN-TA>i6{JtNx{Lq$|t*{xjwE z4gJx_?86UBQ5eEuuOCGb4~8Q{Y>zBP&paKz{Y`i7q58@S)oRhX_wS!rYW?zu;^m{| zk3T;B_BZX*zt)&8X!rPd}AjyK2fM z#cXXf9F187CbAqPI(?cSe5XStJ!S z>$SPIo6VHgUMT?T4q|ew1khR;V*-0pghFYpIDmqLO2wGiC>10Cq^(i7O&Achx*DB( z%>DFYN9%=?r>xdwv^KiXIsm2=m6LKHxX1#~hK*6e0I?`Um5Lhlcfa~YeDO=wY9*vI z%^HBd4R?rysL`nW!S5VA`}>m@U%dPLqvD(~N*N8M3^4w|e{5Y^_XK+LO$Ib8tCc5W ztyQm&;b`xbSNebQ+0z#;SQ2jyKmfEc26ssC%IM1Z4|afsm6bS-i!>u*R$6J}fcp8f zOJ`OC(>QWNKpUe?L=<4-u)qJ#m%g06{*(87^~<%nrpt0;Ebd_S3tAfmM70_L#ux^O z9ES~!E56tPU>c=lZnxUHX=(=oNSLx;Gqj1k%MUzo`p-VwbYbi7|1JoOG3pms{Xb>` zhys8-8v!8FF5iLN(y~K9@Q`$XqATe{fKcp_+=TxhxV$*ZqrQV2ZmU;sXZJl)o^9{lxC%oHFlQrIhYz7N)jAMm?Z4WP9Z zvH4`CY@z_ad9lb(=y+ZRJDjh(%%{pLegm z{e>5bn|>_w(lANqpYm{*Gk0%%{yDih-=A-nntO7vb?fD>^f4O0*8Jq(9=!VR`=9wt z*3HxRe5`%)z5?657ruUoBEJ9rsM;nnhXi2_Sw7 zzbpP`nD%_W$hA|hx1PTJFVQ_u%B<)5H&k3@w!tK>vqs4gE;VKIhunUjI&wC<{F1KR z6`gvLZHcf?VIRz-+JaV~3Z>IE5(bxmM97L86uWO^yKB1jUe!2O99#`1=3+7)^`?h= zb)M(ixbef&@^+!t5!}x)MeRhvJ8Dvtlrn zJP}>?X6(K*6vGRE-f9A#sj5$e^pnPdYe~OnzD^t{~e@XgTqO>_DX&0>$-N7O7o-|1t={Yb&bVk0aafZqFwB&32<)Z2$p6fCA|>EB3FvS`OQ+qY%80=UXREYc|ej>9E`D z%5->h_1-8dslp-4FwGNX@2k`n=x0BC*_yyKlj>4y`DiUEl%jN2FzY0#krY9SAcf1e zl<`Vw5)T3~;B$lku&{pXZFj7#lOz*3Vgdsqo30!?pW3)? ztE+4hMOnxv${(^!$730~GOL&wY3c@hRIci3or|m}h2Zm$GDRihUE!SgKpG)Yn_5Nb zc&43Kkx|Ad@St4wnjOu?K{m-Yb8o#fK7-|~BJOaMjYDT$3ML@D0t z9cEciv(JMRPN#W9qqo1+>C&yPUs}i(nm&#|AV>h<#fy+qW#J@sYCI_*c_Rr-j!1w2 zgu>URMXf)I#=}8|TSIri+Q-agf`Wz!^)A0m({0bu#8WV2jVF~Su_8vSh)F^S4uJ`P znx@1|*07);V3s#>U(G^NpuW?SPOraI8C3J1wC&V-srpe~xl7x*Q~iz0w$iAUjN_=2 z@uj`J?yNJ&(vu0QHd=^yC5fD_>$s%LRWZrPWkw}CgL<=)x0;1e7F?&Dc&YBt==%D= zUmcXjp5nas;D;X9<;A_vmCR9$dSzv0qA}$*?~ix!qJx#WA}f!zdpD60S%ya=7XbZ zE8Bh}_}mp@OK4OR3jVI%em^N4*hQ5z3+B{^C`d({oymI(v=a=LlD>1v&)?d-ksh{n r8NTV6;?I00000NkvXXu0mjfcEQ@J literal 0 HcmV?d00001 diff --git a/recipes/yementimes.png b/recipes/yementimes.png new file mode 100644 index 0000000000000000000000000000000000000000..489a608dfd7da50a2330c9775e453e2550b443bd GIT binary patch literal 740 zcmVFMd`=ji6<`1bba<>lt&FVzC^Yr!k`uqI;Gx$h~0003CNkl&6NR5Rv^R& zTI72Un`W?1xmTHDg5CO%3(zKn0Lud|@X{{$nIU5goyJTU3)H9pVh%#VIDj5(FJau0 zr*yfnrwl;trN*cd#+X6OJN>li3=rp_JdHk_D#>bwVNf2X02SRDmA6&4lp84%kelK6 zLXXsDw@^s(Z|FOgbhFY04UA7%6~OV|dt=Xs<2X)&Ns&yPw8mhHFo=My!h%Fq+igMxqH1B;N>wF97eE)F2DMv6 zrK+N8%c4{&6>3YOGy=&{K(dZgGlSF6uVXe=w#`(Ss-=A3_Jj8Unbrw}7EnT`K_s%=jvCR)ZT#Y~?2y7Z0 zTm?9GtZsP);)Ye^2OpEH0wnlO2jNC-E5es=zFB?m*tbt0j##RvNBWLCKz?e_I7<%pT4LZF<6 z!-r#_En9Rw@kCWOJ3QQ!jR1MRpj-v5eA?P1K!9u+Ak1X6-FIIMwzz2S-FF-2zhWcc zpH+Z}_nKKqISpfDre1#8+^JL5@x?P|EML6X5FkX3DB}GpK)}%~E6t`Mh$82oddlS& zUZ{?(*M8SZ`NZ_`J$zIJxSR7O{?XASKxN^!M<1=e&khea{PRUfBf%enxEfa@*2*uI zG*^#BWV*X;Edvy$r_GO!HVpqJ%<%6dzpwoea5tw{_|;Z6>|{l>+;o$#zV@0#dpnqU z;|(j>?1m8t#f9I(Py4CvduvZb={ZicvFGYqW!d?*{rl~F=_SdYo_H*q^~u11<*PX)>Y@ZoX;MA++q{XXE`#%kJswzMs18;DgNHZ zz;q2rgdm;P_tskqSFX79{PX4~CbZPC5sB{!omk*68J^r-=V-oe+Q#V2@aq=7SFn1N zjmnEEC0FmuLv&yzJ}AF@h7af{$`XAA8}IRQ%}I*kc0i4 z-pGDacf*L_9N#W*AkQ~S>`(^X`1iBghm h8&h8^Z!rH?@*i2d$ca#n>}mi2002ovPDHLkV1gMpjF$ia literal 0 HcmV?d00001 diff --git a/recipes/yomiuri_world.png b/recipes/yomiuri_world.png new file mode 100644 index 0000000000000000000000000000000000000000..95355421a4a4f972db7eab8117a27678e6e7bd96 GIT binary patch literal 1600 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 literal 0 HcmV?d00001 diff --git a/recipes/zerocalcare.png b/recipes/zerocalcare.png new file mode 100644 index 0000000000000000000000000000000000000000..983a91c3aea1fa5371667c96909484b245fd8f60 GIT binary patch literal 846 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 literal 0 HcmV?d00001 From 036bbdb7ff48d23233ce16ba64cff58a0cbf8116 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 5 Jan 2023 07:35:32 +0530 Subject: [PATCH 0074/2055] E-book viewer: When Read aloud is speaking, make the control bar translucent so that words under the bar are visible --- src/pyj/read_book/read_aloud.pyj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/pyj/read_book/read_aloud.pyj b/src/pyj/read_book/read_aloud.pyj index 37fbe7d341..6c5ede5a41 100644 --- a/src/pyj/read_book/read_aloud.pyj +++ b/src/pyj/read_book/read_aloud.pyj @@ -44,6 +44,10 @@ class ReadAloud: style='border: solid 1px currentColor; border-radius: 5px;' 'display: flex; flex-direction: column; margin: 1rem;' )) + container.appendChild(E.style( + f'#{self.bar_id}.speaking '+'{ opacity: 0.5 }\n\n', + f'#{self.bar_id}.speaking:hover '+'{ opacity: 1.0 }\n\n', + )) container.addEventListener('keydown', self.on_keydown, {'passive': False}) container.addEventListener('click', self.container_clicked, {'passive': False}) container.addEventListener('contextmenu', self.toggle, {'passive': False}) @@ -99,6 +103,10 @@ class ReadAloud: return self.container.style.alignItems = 'flex-end' if is_flow_mode() else 'flex-start' bar_container = self.bar + if self.state is PLAYING: + bar_container.classList.add('speaking') + else: + bar_container.classList.remove('speaking') clear(bar_container) bar_container.style.maxWidth = 'min(40rem, 80vw)' if self.supports_css_min_max else '40rem' bar_container.style.backgroundColor = get_color("window-background") From c7a61b2942f90746a825fa9cffa41720d96adc2e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 5 Jan 2023 11:28:43 +0530 Subject: [PATCH 0075/2055] Edit book: Switch to a new library (stylehint) for find problems in CSS as the old library was no longer maintained. --- resources/csslint.js | 10898 ------------------- resources/stylelint-bundle.min.js | 17 + resources/stylelint.js | 88 + setup/commands.py | 4 +- setup/csslint.py | 12 +- src/calibre/ebooks/oeb/polish/check/css.py | 122 +- 6 files changed, 154 insertions(+), 10987 deletions(-) delete mode 100644 resources/csslint.js create mode 100644 resources/stylelint-bundle.min.js create mode 100644 resources/stylelint.js diff --git a/resources/csslint.js b/resources/csslint.js deleted file mode 100644 index 642563f86f..0000000000 --- a/resources/csslint.js +++ /dev/null @@ -1,10898 +0,0 @@ -/*! -CSSLint v1.0.5 -Copyright (c) 2017 Nicole Sullivan and Nicholas C. Zakas. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the 'Software'), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -var CSSLint = (function(){ - var module = module || {}, - exports = exports || {}; - -/*! -Parser-Lib -Copyright (c) 2009-2016 Nicholas C. Zakas. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -/* Version v1.1.0, Build time: 6-December-2016 10:31:29 */ -var parserlib = (function () { -var require; -require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o). - * @namespace parserlib.css - * @class Combinator - * @extends parserlib.util.SyntaxUnit - * @constructor - * @param {String} text The text representation of the unit. - * @param {int} line The line of text on which the unit resides. - * @param {int} col The column of text on which the unit resides. - */ -function Combinator(text, line, col) { - - SyntaxUnit.call(this, text, line, col, Parser.COMBINATOR_TYPE); - - /** - * The type of modifier. - * @type String - * @property type - */ - this.type = "unknown"; - - //pretty simple - if (/^\s+$/.test(text)) { - this.type = "descendant"; - } else if (text === ">") { - this.type = "child"; - } else if (text === "+") { - this.type = "adjacent-sibling"; - } else if (text === "~") { - this.type = "sibling"; - } - -} - -Combinator.prototype = new SyntaxUnit(); -Combinator.prototype.constructor = Combinator; - - -},{"../util/SyntaxUnit":26,"./Parser":6}],3:[function(require,module,exports){ -"use strict"; - -module.exports = Matcher; - -var StringReader = require("../util/StringReader"); -var SyntaxError = require("../util/SyntaxError"); - -/** - * This class implements a combinator library for matcher functions. - * The combinators are described at: - * https://developer.mozilla.org/en-US/docs/Web/CSS/Value_definition_syntax#Component_value_combinators - */ -function Matcher(matchFunc, toString) { - this.match = function(expression) { - // Save/restore marks to ensure that failed matches always restore - // the original location in the expression. - var result; - expression.mark(); - result = matchFunc(expression); - if (result) { - expression.drop(); - } else { - expression.restore(); - } - return result; - }; - this.toString = typeof toString === "function" ? toString : function() { - return toString; - }; -} - -/** Precedence table of combinators. */ -Matcher.prec = { - MOD: 5, - SEQ: 4, - ANDAND: 3, - OROR: 2, - ALT: 1 -}; - -/** Simple recursive-descent grammar to build matchers from strings. */ -Matcher.parse = function(str) { - var reader, eat, expr, oror, andand, seq, mod, term, result; - reader = new StringReader(str); - eat = function(matcher) { - var result = reader.readMatch(matcher); - if (result === null) { - throw new SyntaxError( - "Expected "+matcher, reader.getLine(), reader.getCol()); - } - return result; - }; - expr = function() { - // expr = oror (" | " oror)* - var m = [ oror() ]; - while (reader.readMatch(" | ") !== null) { - m.push(oror()); - } - return m.length === 1 ? m[0] : Matcher.alt.apply(Matcher, m); - }; - oror = function() { - // oror = andand ( " || " andand)* - var m = [ andand() ]; - while (reader.readMatch(" || ") !== null) { - m.push(andand()); - } - return m.length === 1 ? m[0] : Matcher.oror.apply(Matcher, m); - }; - andand = function() { - // andand = seq ( " && " seq)* - var m = [ seq() ]; - while (reader.readMatch(" && ") !== null) { - m.push(seq()); - } - return m.length === 1 ? m[0] : Matcher.andand.apply(Matcher, m); - }; - seq = function() { - // seq = mod ( " " mod)* - var m = [ mod() ]; - while (reader.readMatch(/^ (?![&|\]])/) !== null) { - m.push(mod()); - } - return m.length === 1 ? m[0] : Matcher.seq.apply(Matcher, m); - }; - mod = function() { - // mod = term ( "?" | "*" | "+" | "#" | "{,}" )? - var m = term(); - if (reader.readMatch("?") !== null) { - return m.question(); - } else if (reader.readMatch("*") !== null) { - return m.star(); - } else if (reader.readMatch("+") !== null) { - return m.plus(); - } else if (reader.readMatch("#") !== null) { - return m.hash(); - } else if (reader.readMatch(/^\{\s*/) !== null) { - var min = eat(/^\d+/); - eat(/^\s*,\s*/); - var max = eat(/^\d+/); - eat(/^\s*\}/); - return m.braces(+min, +max); - } - return m; - }; - term = function() { - // term = | literal | "[ " expression " ]" - if (reader.readMatch("[ ") !== null) { - var m = expr(); - eat(" ]"); - return m; - } - return Matcher.fromType(eat(/^[^ ?*+#{]+/)); - }; - result = expr(); - if (!reader.eof()) { - throw new SyntaxError( - "Expected end of string", reader.getLine(), reader.getCol()); - } - return result; -}; - -/** - * Convert a string to a matcher (parsing simple alternations), - * or do nothing if the argument is already a matcher. - */ -Matcher.cast = function(m) { - if (m instanceof Matcher) { - return m; - } - return Matcher.parse(m); -}; - -/** - * Create a matcher for a single type. - */ -Matcher.fromType = function(type) { - // Late require of ValidationTypes to break a dependency cycle. - var ValidationTypes = require("./ValidationTypes"); - return new Matcher(function(expression) { - return expression.hasNext() && ValidationTypes.isType(expression, type); - }, type); -}; - -/** - * Create a matcher for one or more juxtaposed words, which all must - * occur, in the given order. - */ -Matcher.seq = function() { - var ms = Array.prototype.slice.call(arguments).map(Matcher.cast); - if (ms.length === 1) { - return ms[0]; - } - return new Matcher(function(expression) { - var i, result = true; - for (i = 0; result && i < ms.length; i++) { - result = ms[i].match(expression); - } - return result; - }, function(prec) { - var p = Matcher.prec.SEQ; - var s = ms.map(function(m) { - return m.toString(p); - }).join(" "); - if (prec > p) { - s = "[ " + s + " ]"; - } - return s; - }); -}; - -/** - * Create a matcher for one or more alternatives, where exactly one - * must occur. - */ -Matcher.alt = function() { - var ms = Array.prototype.slice.call(arguments).map(Matcher.cast); - if (ms.length === 1) { - return ms[0]; - } - return new Matcher(function(expression) { - var i, result = false; - for (i = 0; !result && i < ms.length; i++) { - result = ms[i].match(expression); - } - return result; - }, function(prec) { - var p = Matcher.prec.ALT; - var s = ms.map(function(m) { - return m.toString(p); - }).join(" | "); - if (prec > p) { - s = "[ " + s + " ]"; - } - return s; - }); -}; - -/** - * Create a matcher for two or more options. This implements the - * double bar (||) and double ampersand (&&) operators, as well as - * variants of && where some of the alternatives are optional. - * This will backtrack through even successful matches to try to - * maximize the number of items matched. - */ -Matcher.many = function(required) { - var ms = Array.prototype.slice.call(arguments, 1).reduce(function(acc, v) { - if (v.expand) { - // Insert all of the options for the given complex rule as - // individual options. - var ValidationTypes = require("./ValidationTypes"); - acc.push.apply(acc, ValidationTypes.complex[v.expand].options); - } else { - acc.push(Matcher.cast(v)); - } - return acc; - }, []); - - if (required === true) { - required = ms.map(function() { - return true; - }); - } - - var result = new Matcher(function(expression) { - var seen = [], max = 0, pass = 0; - var success = function(matchCount) { - if (pass === 0) { - max = Math.max(matchCount, max); - return matchCount === ms.length; - } else { - return matchCount === max; - } - }; - var tryMatch = function(matchCount) { - for (var i = 0; i < ms.length; i++) { - if (seen[i]) { - continue; - } - expression.mark(); - if (ms[i].match(expression)) { - seen[i] = true; - // Increase matchCount iff this was a required element - // (or if all the elements are optional) - if (tryMatch(matchCount + ((required === false || required[i]) ? 1 : 0))) { - expression.drop(); - return true; - } - // Backtrack: try *not* matching using this rule, and - // let's see if it leads to a better overall match. - expression.restore(); - seen[i] = false; - } else { - expression.drop(); - } - } - return success(matchCount); - }; - if (!tryMatch(0)) { - // Couldn't get a complete match, retrace our steps to make the - // match with the maximum # of required elements. - pass++; - tryMatch(0); - } - - if (required === false) { - return max > 0; - } - // Use finer-grained specification of which matchers are required. - for (var i = 0; i < ms.length; i++) { - if (required[i] && !seen[i]) { - return false; - } - } - return true; - }, function(prec) { - var p = required === false ? Matcher.prec.OROR : Matcher.prec.ANDAND; - var s = ms.map(function(m, i) { - if (required !== false && !required[i]) { - return m.toString(Matcher.prec.MOD) + "?"; - } - return m.toString(p); - }).join(required === false ? " || " : " && "); - if (prec > p) { - s = "[ " + s + " ]"; - } - return s; - }); - result.options = ms; - return result; -}; - -/** - * Create a matcher for two or more options, where all options are - * mandatory but they may appear in any order. - */ -Matcher.andand = function() { - var args = Array.prototype.slice.call(arguments); - args.unshift(true); - return Matcher.many.apply(Matcher, args); -}; - -/** - * Create a matcher for two or more options, where options are - * optional and may appear in any order, but at least one must be - * present. - */ -Matcher.oror = function() { - var args = Array.prototype.slice.call(arguments); - args.unshift(false); - return Matcher.many.apply(Matcher, args); -}; - -/** Instance methods on Matchers. */ -Matcher.prototype = { - constructor: Matcher, - // These are expected to be overridden in every instance. - match: function() { throw new Error("unimplemented"); }, - toString: function() { throw new Error("unimplemented"); }, - // This returns a standalone function to do the matching. - func: function() { return this.match.bind(this); }, - // Basic combinators - then: function(m) { return Matcher.seq(this, m); }, - or: function(m) { return Matcher.alt(this, m); }, - andand: function(m) { return Matcher.many(true, this, m); }, - oror: function(m) { return Matcher.many(false, this, m); }, - // Component value multipliers - star: function() { return this.braces(0, Infinity, "*"); }, - plus: function() { return this.braces(1, Infinity, "+"); }, - question: function() { return this.braces(0, 1, "?"); }, - hash: function() { - return this.braces(1, Infinity, "#", Matcher.cast(",")); - }, - braces: function(min, max, marker, optSep) { - var m1 = this, m2 = optSep ? optSep.then(this) : this; - if (!marker) { - marker = "{" + min + "," + max + "}"; - } - return new Matcher(function(expression) { - var result = true, i; - for (i = 0; i < max; i++) { - if (i > 0 && optSep) { - result = m2.match(expression); - } else { - result = m1.match(expression); - } - if (!result) { - break; - } - } - return i >= min; - }, function() { - return m1.toString(Matcher.prec.MOD) + marker; - }); - } -}; - -},{"../util/StringReader":24,"../util/SyntaxError":25,"./ValidationTypes":21}],4:[function(require,module,exports){ -"use strict"; - -module.exports = MediaFeature; - -var SyntaxUnit = require("../util/SyntaxUnit"); - -var Parser = require("./Parser"); - -/** - * Represents a media feature, such as max-width:500. - * @namespace parserlib.css - * @class MediaFeature - * @extends parserlib.util.SyntaxUnit - * @constructor - * @param {SyntaxUnit} name The name of the feature. - * @param {SyntaxUnit} value The value of the feature or null if none. - */ -function MediaFeature(name, value) { - - SyntaxUnit.call(this, "(" + name + (value !== null ? ":" + value : "") + ")", name.startLine, name.startCol, Parser.MEDIA_FEATURE_TYPE); - - /** - * The name of the media feature - * @type String - * @property name - */ - this.name = name; - - /** - * The value for the feature or null if there is none. - * @type SyntaxUnit - * @property value - */ - this.value = value; -} - -MediaFeature.prototype = new SyntaxUnit(); -MediaFeature.prototype.constructor = MediaFeature; - - -},{"../util/SyntaxUnit":26,"./Parser":6}],5:[function(require,module,exports){ -"use strict"; - -module.exports = MediaQuery; - -var SyntaxUnit = require("../util/SyntaxUnit"); - -var Parser = require("./Parser"); - -/** - * Represents an individual media query. - * @namespace parserlib.css - * @class MediaQuery - * @extends parserlib.util.SyntaxUnit - * @constructor - * @param {String} modifier The modifier "not" or "only" (or null). - * @param {String} mediaType The type of media (i.e., "print"). - * @param {Array} parts Array of selectors parts making up this selector. - * @param {int} line The line of text on which the unit resides. - * @param {int} col The column of text on which the unit resides. - */ -function MediaQuery(modifier, mediaType, features, line, col) { - - SyntaxUnit.call(this, (modifier ? modifier + " ": "") + (mediaType ? mediaType : "") + (mediaType && features.length > 0 ? " and " : "") + features.join(" and "), line, col, Parser.MEDIA_QUERY_TYPE); - - /** - * The media modifier ("not" or "only") - * @type String - * @property modifier - */ - this.modifier = modifier; - - /** - * The mediaType (i.e., "print") - * @type String - * @property mediaType - */ - this.mediaType = mediaType; - - /** - * The parts that make up the selector. - * @type Array - * @property features - */ - this.features = features; - -} - -MediaQuery.prototype = new SyntaxUnit(); -MediaQuery.prototype.constructor = MediaQuery; - - -},{"../util/SyntaxUnit":26,"./Parser":6}],6:[function(require,module,exports){ -"use strict"; - -module.exports = Parser; - -var EventTarget = require("../util/EventTarget"); -var SyntaxError = require("../util/SyntaxError"); -var SyntaxUnit = require("../util/SyntaxUnit"); - -var Combinator = require("./Combinator"); -var MediaFeature = require("./MediaFeature"); -var MediaQuery = require("./MediaQuery"); -var PropertyName = require("./PropertyName"); -var PropertyValue = require("./PropertyValue"); -var PropertyValuePart = require("./PropertyValuePart"); -var Selector = require("./Selector"); -var SelectorPart = require("./SelectorPart"); -var SelectorSubPart = require("./SelectorSubPart"); -var TokenStream = require("./TokenStream"); -var Tokens = require("./Tokens"); -var Validation = require("./Validation"); - -/** - * A CSS3 parser. - * @namespace parserlib.css - * @class Parser - * @constructor - * @param {Object} options (Optional) Various options for the parser: - * starHack (true|false) to allow IE6 star hack as valid, - * underscoreHack (true|false) to interpret leading underscores - * as IE6-7 targeting for known properties, ieFilters (true|false) - * to indicate that IE < 8 filters should be accepted and not throw - * syntax errors. - */ -function Parser(options) { - - //inherit event functionality - EventTarget.call(this); - - - this.options = options || {}; - - this._tokenStream = null; -} - -//Static constants -Parser.DEFAULT_TYPE = 0; -Parser.COMBINATOR_TYPE = 1; -Parser.MEDIA_FEATURE_TYPE = 2; -Parser.MEDIA_QUERY_TYPE = 3; -Parser.PROPERTY_NAME_TYPE = 4; -Parser.PROPERTY_VALUE_TYPE = 5; -Parser.PROPERTY_VALUE_PART_TYPE = 6; -Parser.SELECTOR_TYPE = 7; -Parser.SELECTOR_PART_TYPE = 8; -Parser.SELECTOR_SUB_PART_TYPE = 9; - -Parser.prototype = function() { - - var proto = new EventTarget(), //new prototype - prop, - additions = { - __proto__: null, - - //restore constructor - constructor: Parser, - - //instance constants - yuck - DEFAULT_TYPE : 0, - COMBINATOR_TYPE : 1, - MEDIA_FEATURE_TYPE : 2, - MEDIA_QUERY_TYPE : 3, - PROPERTY_NAME_TYPE : 4, - PROPERTY_VALUE_TYPE : 5, - PROPERTY_VALUE_PART_TYPE : 6, - SELECTOR_TYPE : 7, - SELECTOR_PART_TYPE : 8, - SELECTOR_SUB_PART_TYPE : 9, - - //----------------------------------------------------------------- - // Grammar - //----------------------------------------------------------------- - - _stylesheet: function() { - - /* - * stylesheet - * : [ CHARSET_SYM S* STRING S* ';' ]? - * [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* - * [ namespace [S|CDO|CDC]* ]* - * [ [ ruleset | media | page | font_face | keyframes_rule | supports_rule ] [S|CDO|CDC]* ]* - * ; - */ - - var tokenStream = this._tokenStream, - count, - token, - tt; - - this.fire("startstylesheet"); - - //try to read character set - this._charset(); - - this._skipCruft(); - - //try to read imports - may be more than one - while (tokenStream.peek() === Tokens.IMPORT_SYM) { - this._import(); - this._skipCruft(); - } - - //try to read namespaces - may be more than one - while (tokenStream.peek() === Tokens.NAMESPACE_SYM) { - this._namespace(); - this._skipCruft(); - } - - //get the next token - tt = tokenStream.peek(); - - //try to read the rest - while (tt > Tokens.EOF) { - - try { - - switch (tt) { - case Tokens.MEDIA_SYM: - this._media(); - this._skipCruft(); - break; - case Tokens.PAGE_SYM: - this._page(); - this._skipCruft(); - break; - case Tokens.FONT_FACE_SYM: - this._font_face(); - this._skipCruft(); - break; - case Tokens.KEYFRAMES_SYM: - this._keyframes(); - this._skipCruft(); - break; - case Tokens.VIEWPORT_SYM: - this._viewport(); - this._skipCruft(); - break; - case Tokens.DOCUMENT_SYM: - this._document(); - this._skipCruft(); - break; - case Tokens.SUPPORTS_SYM: - this._supports(); - this._skipCruft(); - break; - case Tokens.UNKNOWN_SYM: //unknown @ rule - tokenStream.get(); - if (!this.options.strict) { - - //fire error event - this.fire({ - type: "error", - error: null, - message: "Unknown @ rule: " + tokenStream.LT(0).value + ".", - line: tokenStream.LT(0).startLine, - col: tokenStream.LT(0).startCol - }); - - //skip braces - count=0; - while (tokenStream.advance([Tokens.LBRACE, Tokens.RBRACE]) === Tokens.LBRACE) { - count++; //keep track of nesting depth - } - - while (count) { - tokenStream.advance([Tokens.RBRACE]); - count--; - } - - } else { - //not a syntax error, rethrow it - throw new SyntaxError("Unknown @ rule.", tokenStream.LT(0).startLine, tokenStream.LT(0).startCol); - } - break; - case Tokens.S: - this._readWhitespace(); - break; - default: - if (!this._ruleset()) { - - //error handling for known issues - switch (tt) { - case Tokens.CHARSET_SYM: - token = tokenStream.LT(1); - this._charset(false); - throw new SyntaxError("@charset not allowed here.", token.startLine, token.startCol); - case Tokens.IMPORT_SYM: - token = tokenStream.LT(1); - this._import(false); - throw new SyntaxError("@import not allowed here.", token.startLine, token.startCol); - case Tokens.NAMESPACE_SYM: - token = tokenStream.LT(1); - this._namespace(false); - throw new SyntaxError("@namespace not allowed here.", token.startLine, token.startCol); - default: - tokenStream.get(); //get the last token - this._unexpectedToken(tokenStream.token()); - } - - } - } - } catch (ex) { - if (ex instanceof SyntaxError && !this.options.strict) { - this.fire({ - type: "error", - error: ex, - message: ex.message, - line: ex.line, - col: ex.col - }); - } else { - throw ex; - } - } - - tt = tokenStream.peek(); - } - - if (tt !== Tokens.EOF) { - this._unexpectedToken(tokenStream.token()); - } - - this.fire("endstylesheet"); - }, - - _charset: function(emit) { - var tokenStream = this._tokenStream, - charset, - token, - line, - col; - - if (tokenStream.match(Tokens.CHARSET_SYM)) { - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this._readWhitespace(); - tokenStream.mustMatch(Tokens.STRING); - - token = tokenStream.token(); - charset = token.value; - - this._readWhitespace(); - tokenStream.mustMatch(Tokens.SEMICOLON); - - if (emit !== false) { - this.fire({ - type: "charset", - charset:charset, - line: line, - col: col - }); - } - } - }, - - _import: function(emit) { - /* - * import - * : IMPORT_SYM S* - * [STRING|URI] S* media_query_list? ';' S* - */ - - var tokenStream = this._tokenStream, - uri, - importToken, - mediaList = []; - - //read import symbol - tokenStream.mustMatch(Tokens.IMPORT_SYM); - importToken = tokenStream.token(); - this._readWhitespace(); - - tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); - - //grab the URI value - uri = tokenStream.token().value.replace(/^(?:url\()?["']?([^"']+?)["']?\)?$/, "$1"); - - this._readWhitespace(); - - mediaList = this._media_query_list(); - - //must end with a semicolon - tokenStream.mustMatch(Tokens.SEMICOLON); - this._readWhitespace(); - - if (emit !== false) { - this.fire({ - type: "import", - uri: uri, - media: mediaList, - line: importToken.startLine, - col: importToken.startCol - }); - } - - }, - - _namespace: function(emit) { - /* - * namespace - * : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S* - */ - - var tokenStream = this._tokenStream, - line, - col, - prefix, - uri; - - //read import symbol - tokenStream.mustMatch(Tokens.NAMESPACE_SYM); - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - this._readWhitespace(); - - //it's a namespace prefix - no _namespace_prefix() method because it's just an IDENT - if (tokenStream.match(Tokens.IDENT)) { - prefix = tokenStream.token().value; - this._readWhitespace(); - } - - tokenStream.mustMatch([Tokens.STRING, Tokens.URI]); - /*if (!tokenStream.match(Tokens.STRING)){ - tokenStream.mustMatch(Tokens.URI); - }*/ - - //grab the URI value - uri = tokenStream.token().value.replace(/(?:url\()?["']([^"']+)["']\)?/, "$1"); - - this._readWhitespace(); - - //must end with a semicolon - tokenStream.mustMatch(Tokens.SEMICOLON); - this._readWhitespace(); - - if (emit !== false) { - this.fire({ - type: "namespace", - prefix: prefix, - uri: uri, - line: line, - col: col - }); - } - - }, - - _supports: function(emit) { - /* - * supports_rule - * : SUPPORTS_SYM S* supports_condition S* group_rule_body - * ; - */ - var tokenStream = this._tokenStream, - line, - col; - - if (tokenStream.match(Tokens.SUPPORTS_SYM)) { - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this._readWhitespace(); - this._supports_condition(); - this._readWhitespace(); - - tokenStream.mustMatch(Tokens.LBRACE); - this._readWhitespace(); - - if (emit !== false) { - this.fire({ - type: "startsupports", - line: line, - col: col - }); - } - - while (true) { - if (!this._ruleset()) { - break; - } - } - - tokenStream.mustMatch(Tokens.RBRACE); - this._readWhitespace(); - - this.fire({ - type: "endsupports", - line: line, - col: col - }); - } - }, - - _supports_condition: function() { - /* - * supports_condition - * : supports_negation | supports_conjunction | supports_disjunction | - * supports_condition_in_parens - * ; - */ - var tokenStream = this._tokenStream, - ident; - - if (tokenStream.match(Tokens.IDENT)) { - ident = tokenStream.token().value.toLowerCase(); - - if (ident === "not") { - tokenStream.mustMatch(Tokens.S); - this._supports_condition_in_parens(); - } else { - tokenStream.unget(); - } - } else { - this._supports_condition_in_parens(); - this._readWhitespace(); - - while (tokenStream.peek() === Tokens.IDENT) { - ident = tokenStream.LT(1).value.toLowerCase(); - if (ident === "and" || ident === "or") { - tokenStream.mustMatch(Tokens.IDENT); - this._readWhitespace(); - this._supports_condition_in_parens(); - this._readWhitespace(); - } - } - } - }, - - _supports_condition_in_parens: function() { - /* - * supports_condition_in_parens - * : ( '(' S* supports_condition S* ')' ) | supports_declaration_condition | - * general_enclosed - * ; - */ - var tokenStream = this._tokenStream, - ident; - - if (tokenStream.match(Tokens.LPAREN)) { - this._readWhitespace(); - if (tokenStream.match(Tokens.IDENT)) { - // look ahead for not keyword, if not given, continue with declaration condition. - ident = tokenStream.token().value.toLowerCase(); - if (ident === "not") { - this._readWhitespace(); - this._supports_condition(); - this._readWhitespace(); - tokenStream.mustMatch(Tokens.RPAREN); - } else { - tokenStream.unget(); - this._supports_declaration_condition(false); - } - } else { - this._supports_condition(); - this._readWhitespace(); - tokenStream.mustMatch(Tokens.RPAREN); - } - } else { - this._supports_declaration_condition(); - } - }, - - _supports_declaration_condition: function(requireStartParen) { - /* - * supports_declaration_condition - * : '(' S* declaration ')' - * ; - */ - var tokenStream = this._tokenStream; - - if (requireStartParen !== false) { - tokenStream.mustMatch(Tokens.LPAREN); - } - this._readWhitespace(); - this._declaration(); - tokenStream.mustMatch(Tokens.RPAREN); - }, - - _media: function() { - /* - * media - * : MEDIA_SYM S* media_query_list S* '{' S* ruleset* '}' S* - * ; - */ - var tokenStream = this._tokenStream, - line, - col, - mediaList;// = []; - - //look for @media - tokenStream.mustMatch(Tokens.MEDIA_SYM); - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this._readWhitespace(); - - mediaList = this._media_query_list(); - - tokenStream.mustMatch(Tokens.LBRACE); - this._readWhitespace(); - - this.fire({ - type: "startmedia", - media: mediaList, - line: line, - col: col - }); - - while (true) { - if (tokenStream.peek() === Tokens.PAGE_SYM) { - this._page(); - } else if (tokenStream.peek() === Tokens.FONT_FACE_SYM) { - this._font_face(); - } else if (tokenStream.peek() === Tokens.VIEWPORT_SYM) { - this._viewport(); - } else if (tokenStream.peek() === Tokens.DOCUMENT_SYM) { - this._document(); - } else if (tokenStream.peek() === Tokens.SUPPORTS_SYM) { - this._supports(); - } else if (tokenStream.peek() === Tokens.MEDIA_SYM) { - this._media(); - } else if (!this._ruleset()) { - break; - } - } - - tokenStream.mustMatch(Tokens.RBRACE); - this._readWhitespace(); - - this.fire({ - type: "endmedia", - media: mediaList, - line: line, - col: col - }); - }, - - - //CSS3 Media Queries - _media_query_list: function() { - /* - * media_query_list - * : S* [media_query [ ',' S* media_query ]* ]? - * ; - */ - var tokenStream = this._tokenStream, - mediaList = []; - - - this._readWhitespace(); - - if (tokenStream.peek() === Tokens.IDENT || tokenStream.peek() === Tokens.LPAREN) { - mediaList.push(this._media_query()); - } - - while (tokenStream.match(Tokens.COMMA)) { - this._readWhitespace(); - mediaList.push(this._media_query()); - } - - return mediaList; - }, - - /* - * Note: "expression" in the grammar maps to the _media_expression - * method. - - */ - _media_query: function() { - /* - * media_query - * : [ONLY | NOT]? S* media_type S* [ AND S* expression ]* - * | expression [ AND S* expression ]* - * ; - */ - var tokenStream = this._tokenStream, - type = null, - ident = null, - token = null, - expressions = []; - - if (tokenStream.match(Tokens.IDENT)) { - ident = tokenStream.token().value.toLowerCase(); - - //since there's no custom tokens for these, need to manually check - if (ident !== "only" && ident !== "not") { - tokenStream.unget(); - ident = null; - } else { - token = tokenStream.token(); - } - } - - this._readWhitespace(); - - if (tokenStream.peek() === Tokens.IDENT) { - type = this._media_type(); - if (token === null) { - token = tokenStream.token(); - } - } else if (tokenStream.peek() === Tokens.LPAREN) { - if (token === null) { - token = tokenStream.LT(1); - } - expressions.push(this._media_expression()); - } - - if (type === null && expressions.length === 0) { - return null; - } else { - this._readWhitespace(); - while (tokenStream.match(Tokens.IDENT)) { - if (tokenStream.token().value.toLowerCase() !== "and") { - this._unexpectedToken(tokenStream.token()); - } - - this._readWhitespace(); - expressions.push(this._media_expression()); - } - } - - return new MediaQuery(ident, type, expressions, token.startLine, token.startCol); - }, - - //CSS3 Media Queries - _media_type: function() { - /* - * media_type - * : IDENT - * ; - */ - return this._media_feature(); - }, - - /** - * Note: in CSS3 Media Queries, this is called "expression". - * Renamed here to avoid conflict with CSS3 Selectors - * definition of "expression". Also note that "expr" in the - * grammar now maps to "expression" from CSS3 selectors. - * @method _media_expression - * @private - */ - _media_expression: function() { - /* - * expression - * : '(' S* media_feature S* [ ':' S* expr ]? ')' S* - * ; - */ - var tokenStream = this._tokenStream, - feature = null, - token, - expression = null; - - tokenStream.mustMatch(Tokens.LPAREN); - - feature = this._media_feature(); - this._readWhitespace(); - - if (tokenStream.match(Tokens.COLON)) { - this._readWhitespace(); - token = tokenStream.LT(1); - expression = this._expression(); - } - - tokenStream.mustMatch(Tokens.RPAREN); - this._readWhitespace(); - - return new MediaFeature(feature, expression ? new SyntaxUnit(expression, token.startLine, token.startCol) : null); - }, - - //CSS3 Media Queries - _media_feature: function() { - /* - * media_feature - * : IDENT - * ; - */ - var tokenStream = this._tokenStream; - - this._readWhitespace(); - - tokenStream.mustMatch(Tokens.IDENT); - - return SyntaxUnit.fromToken(tokenStream.token()); - }, - - //CSS3 Paged Media - _page: function() { - /* - * page: - * PAGE_SYM S* IDENT? pseudo_page? S* - * '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* - * ; - */ - var tokenStream = this._tokenStream, - line, - col, - identifier = null, - pseudoPage = null; - - //look for @page - tokenStream.mustMatch(Tokens.PAGE_SYM); - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this._readWhitespace(); - - if (tokenStream.match(Tokens.IDENT)) { - identifier = tokenStream.token().value; - - //The value 'auto' may not be used as a page name and MUST be treated as a syntax error. - if (identifier.toLowerCase() === "auto") { - this._unexpectedToken(tokenStream.token()); - } - } - - //see if there's a colon upcoming - if (tokenStream.peek() === Tokens.COLON) { - pseudoPage = this._pseudo_page(); - } - - this._readWhitespace(); - - this.fire({ - type: "startpage", - id: identifier, - pseudo: pseudoPage, - line: line, - col: col - }); - - this._readDeclarations(true, true); - - this.fire({ - type: "endpage", - id: identifier, - pseudo: pseudoPage, - line: line, - col: col - }); - - }, - - //CSS3 Paged Media - _margin: function() { - /* - * margin : - * margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S* - * ; - */ - var tokenStream = this._tokenStream, - line, - col, - marginSym = this._margin_sym(); - - if (marginSym) { - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this.fire({ - type: "startpagemargin", - margin: marginSym, - line: line, - col: col - }); - - this._readDeclarations(true); - - this.fire({ - type: "endpagemargin", - margin: marginSym, - line: line, - col: col - }); - return true; - } else { - return false; - } - }, - - //CSS3 Paged Media - _margin_sym: function() { - - /* - * margin_sym : - * TOPLEFTCORNER_SYM | - * TOPLEFT_SYM | - * TOPCENTER_SYM | - * TOPRIGHT_SYM | - * TOPRIGHTCORNER_SYM | - * BOTTOMLEFTCORNER_SYM | - * BOTTOMLEFT_SYM | - * BOTTOMCENTER_SYM | - * BOTTOMRIGHT_SYM | - * BOTTOMRIGHTCORNER_SYM | - * LEFTTOP_SYM | - * LEFTMIDDLE_SYM | - * LEFTBOTTOM_SYM | - * RIGHTTOP_SYM | - * RIGHTMIDDLE_SYM | - * RIGHTBOTTOM_SYM - * ; - */ - - var tokenStream = this._tokenStream; - - if (tokenStream.match([Tokens.TOPLEFTCORNER_SYM, Tokens.TOPLEFT_SYM, - Tokens.TOPCENTER_SYM, Tokens.TOPRIGHT_SYM, Tokens.TOPRIGHTCORNER_SYM, - Tokens.BOTTOMLEFTCORNER_SYM, Tokens.BOTTOMLEFT_SYM, - Tokens.BOTTOMCENTER_SYM, Tokens.BOTTOMRIGHT_SYM, - Tokens.BOTTOMRIGHTCORNER_SYM, Tokens.LEFTTOP_SYM, - Tokens.LEFTMIDDLE_SYM, Tokens.LEFTBOTTOM_SYM, Tokens.RIGHTTOP_SYM, - Tokens.RIGHTMIDDLE_SYM, Tokens.RIGHTBOTTOM_SYM])) { - return SyntaxUnit.fromToken(tokenStream.token()); - } else { - return null; - } - - }, - - _pseudo_page: function() { - /* - * pseudo_page - * : ':' IDENT - * ; - */ - - var tokenStream = this._tokenStream; - - tokenStream.mustMatch(Tokens.COLON); - tokenStream.mustMatch(Tokens.IDENT); - - //TODO: CSS3 Paged Media says only "left", "center", and "right" are allowed - - return tokenStream.token().value; - }, - - _font_face: function() { - /* - * font_face - * : FONT_FACE_SYM S* - * '{' S* declaration [ ';' S* declaration ]* '}' S* - * ; - */ - var tokenStream = this._tokenStream, - line, - col; - - //look for @page - tokenStream.mustMatch(Tokens.FONT_FACE_SYM); - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this._readWhitespace(); - - this.fire({ - type: "startfontface", - line: line, - col: col - }); - - this._readDeclarations(true); - - this.fire({ - type: "endfontface", - line: line, - col: col - }); - }, - - _viewport: function() { - /* - * viewport - * : VIEWPORT_SYM S* - * '{' S* declaration? [ ';' S* declaration? ]* '}' S* - * ; - */ - var tokenStream = this._tokenStream, - line, - col; - - tokenStream.mustMatch(Tokens.VIEWPORT_SYM); - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - - this._readWhitespace(); - - this.fire({ - type: "startviewport", - line: line, - col: col - }); - - this._readDeclarations(true); - - this.fire({ - type: "endviewport", - line: line, - col: col - }); - - }, - - _document: function() { - /* - * document - * : DOCUMENT_SYM S* - * _document_function [ ',' S* _document_function ]* S* - * '{' S* ruleset* '}' - * ; - */ - - var tokenStream = this._tokenStream, - token, - functions = [], - prefix = ""; - - tokenStream.mustMatch(Tokens.DOCUMENT_SYM); - token = tokenStream.token(); - if (/^@\-([^\-]+)\-/.test(token.value)) { - prefix = RegExp.$1; - } - - this._readWhitespace(); - functions.push(this._document_function()); - - while (tokenStream.match(Tokens.COMMA)) { - this._readWhitespace(); - functions.push(this._document_function()); - } - - tokenStream.mustMatch(Tokens.LBRACE); - this._readWhitespace(); - - this.fire({ - type: "startdocument", - functions: functions, - prefix: prefix, - line: token.startLine, - col: token.startCol - }); - - var ok = true; - while (ok) { - switch (tokenStream.peek()) { - case Tokens.PAGE_SYM: - this._page(); - break; - case Tokens.FONT_FACE_SYM: - this._font_face(); - break; - case Tokens.VIEWPORT_SYM: - this._viewport(); - break; - case Tokens.MEDIA_SYM: - this._media(); - break; - case Tokens.KEYFRAMES_SYM: - this._keyframes(); - break; - case Tokens.DOCUMENT_SYM: - this._document(); - break; - default: - ok = Boolean(this._ruleset()); - } - } - - tokenStream.mustMatch(Tokens.RBRACE); - token = tokenStream.token(); - this._readWhitespace(); - - this.fire({ - type: "enddocument", - functions: functions, - prefix: prefix, - line: token.startLine, - col: token.startCol - }); - }, - - _document_function: function() { - /* - * document_function - * : function | URI S* - * ; - */ - - var tokenStream = this._tokenStream, - value; - - if (tokenStream.match(Tokens.URI)) { - value = tokenStream.token().value; - this._readWhitespace(); - } else { - value = this._function(); - } - - return value; - }, - - _operator: function(inFunction) { - - /* - * operator (outside function) - * : '/' S* | ',' S* | /( empty )/ - * operator (inside function) - * : '/' S* | '+' S* | '*' S* | '-' S* /( empty )/ - * ; - */ - - var tokenStream = this._tokenStream, - token = null; - - if (tokenStream.match([Tokens.SLASH, Tokens.COMMA]) || - (inFunction && tokenStream.match([Tokens.PLUS, Tokens.STAR, Tokens.MINUS]))) { - token = tokenStream.token(); - this._readWhitespace(); - } - return token ? PropertyValuePart.fromToken(token) : null; - - }, - - _combinator: function() { - - /* - * combinator - * : PLUS S* | GREATER S* | TILDE S* | S+ - * ; - */ - - var tokenStream = this._tokenStream, - value = null, - token; - - if (tokenStream.match([Tokens.PLUS, Tokens.GREATER, Tokens.TILDE])) { - token = tokenStream.token(); - value = new Combinator(token.value, token.startLine, token.startCol); - this._readWhitespace(); - } - - return value; - }, - - _unary_operator: function() { - - /* - * unary_operator - * : '-' | '+' - * ; - */ - - var tokenStream = this._tokenStream; - - if (tokenStream.match([Tokens.MINUS, Tokens.PLUS])) { - return tokenStream.token().value; - } else { - return null; - } - }, - - _property: function() { - - /* - * property - * : IDENT S* - * ; - */ - - var tokenStream = this._tokenStream, - value = null, - hack = null, - tokenValue, - token, - line, - col; - - //check for star hack - throws error if not allowed - if (tokenStream.peek() === Tokens.STAR && this.options.starHack) { - tokenStream.get(); - token = tokenStream.token(); - hack = token.value; - line = token.startLine; - col = token.startCol; - } - - if (tokenStream.match(Tokens.IDENT)) { - token = tokenStream.token(); - tokenValue = token.value; - - //check for underscore hack - no error if not allowed because it's valid CSS syntax - if (tokenValue.charAt(0) === "_" && this.options.underscoreHack) { - hack = "_"; - tokenValue = tokenValue.substring(1); - } - - value = new PropertyName(tokenValue, hack, (line||token.startLine), (col||token.startCol)); - this._readWhitespace(); - } - - return value; - }, - - //Augmented with CSS3 Selectors - _ruleset: function() { - /* - * ruleset - * : selectors_group - * '{' S* declaration? [ ';' S* declaration? ]* '}' S* - * ; - */ - - var tokenStream = this._tokenStream, - tt, - selectors; - - - /* - * Error Recovery: If even a single selector fails to parse, - * then the entire ruleset should be thrown away. - */ - try { - selectors = this._selectors_group(); - } catch (ex) { - if (ex instanceof SyntaxError && !this.options.strict) { - - //fire error event - this.fire({ - type: "error", - error: ex, - message: ex.message, - line: ex.line, - col: ex.col - }); - - //skip over everything until closing brace - tt = tokenStream.advance([Tokens.RBRACE]); - if (tt === Tokens.RBRACE) { - //if there's a right brace, the rule is finished so don't do anything - } else { - //otherwise, rethrow the error because it wasn't handled properly - throw ex; - } - - } else { - //not a syntax error, rethrow it - throw ex; - } - - //trigger parser to continue - return true; - } - - //if it got here, all selectors parsed - if (selectors) { - - this.fire({ - type: "startrule", - selectors: selectors, - line: selectors[0].line, - col: selectors[0].col - }); - - this._readDeclarations(true); - - this.fire({ - type: "endrule", - selectors: selectors, - line: selectors[0].line, - col: selectors[0].col - }); - - } - - return selectors; - - }, - - //CSS3 Selectors - _selectors_group: function() { - - /* - * selectors_group - * : selector [ COMMA S* selector ]* - * ; - */ - var tokenStream = this._tokenStream, - selectors = [], - selector; - - selector = this._selector(); - if (selector !== null) { - - selectors.push(selector); - while (tokenStream.match(Tokens.COMMA)) { - this._readWhitespace(); - selector = this._selector(); - if (selector !== null) { - selectors.push(selector); - } else { - this._unexpectedToken(tokenStream.LT(1)); - } - } - } - - return selectors.length ? selectors : null; - }, - - //CSS3 Selectors - _selector: function() { - /* - * selector - * : simple_selector_sequence [ combinator simple_selector_sequence ]* - * ; - */ - - var tokenStream = this._tokenStream, - selector = [], - nextSelector = null, - combinator = null, - ws = null; - - //if there's no simple selector, then there's no selector - nextSelector = this._simple_selector_sequence(); - if (nextSelector === null) { - return null; - } - - selector.push(nextSelector); - - do { - - //look for a combinator - combinator = this._combinator(); - - if (combinator !== null) { - selector.push(combinator); - nextSelector = this._simple_selector_sequence(); - - //there must be a next selector - if (nextSelector === null) { - this._unexpectedToken(tokenStream.LT(1)); - } else { - - //nextSelector is an instance of SelectorPart - selector.push(nextSelector); - } - } else { - - //if there's not whitespace, we're done - if (this._readWhitespace()) { - - //add whitespace separator - ws = new Combinator(tokenStream.token().value, tokenStream.token().startLine, tokenStream.token().startCol); - - //combinator is not required - combinator = this._combinator(); - - //selector is required if there's a combinator - nextSelector = this._simple_selector_sequence(); - if (nextSelector === null) { - if (combinator !== null) { - this._unexpectedToken(tokenStream.LT(1)); - } - } else { - - if (combinator !== null) { - selector.push(combinator); - } else { - selector.push(ws); - } - - selector.push(nextSelector); - } - } else { - break; - } - - } - } while (true); - - return new Selector(selector, selector[0].line, selector[0].col); - }, - - //CSS3 Selectors - _simple_selector_sequence: function() { - /* - * simple_selector_sequence - * : [ type_selector | universal ] - * [ HASH | class | attrib | pseudo | negation ]* - * | [ HASH | class | attrib | pseudo | negation ]+ - * ; - */ - - var tokenStream = this._tokenStream, - - //parts of a simple selector - elementName = null, - modifiers = [], - - //complete selector text - selectorText= "", - - //the different parts after the element name to search for - components = [ - //HASH - function() { - return tokenStream.match(Tokens.HASH) ? - new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : - null; - }, - this._class, - this._attrib, - this._pseudo, - this._negation - ], - i = 0, - len = components.length, - component = null, - line, - col; - - - //get starting line and column for the selector - line = tokenStream.LT(1).startLine; - col = tokenStream.LT(1).startCol; - - elementName = this._type_selector(); - if (!elementName) { - elementName = this._universal(); - } - - if (elementName !== null) { - selectorText += elementName; - } - - while (true) { - - //whitespace means we're done - if (tokenStream.peek() === Tokens.S) { - break; - } - - //check for each component - while (i < len && component === null) { - component = components[i++].call(this); - } - - if (component === null) { - - //we don't have a selector - if (selectorText === "") { - return null; - } else { - break; - } - } else { - i = 0; - modifiers.push(component); - selectorText += component.toString(); - component = null; - } - } - - - return selectorText !== "" ? - new SelectorPart(elementName, modifiers, selectorText, line, col) : - null; - }, - - //CSS3 Selectors - _type_selector: function() { - /* - * type_selector - * : [ namespace_prefix ]? element_name - * ; - */ - - var tokenStream = this._tokenStream, - ns = this._namespace_prefix(), - elementName = this._element_name(); - - if (!elementName) { - /* - * Need to back out the namespace that was read due to both - * type_selector and universal reading namespace_prefix - * first. Kind of hacky, but only way I can figure out - * right now how to not change the grammar. - */ - if (ns) { - tokenStream.unget(); - if (ns.length > 1) { - tokenStream.unget(); - } - } - - return null; - } else { - if (ns) { - elementName.text = ns + elementName.text; - elementName.col -= ns.length; - } - return elementName; - } - }, - - //CSS3 Selectors - _class: function() { - /* - * class - * : '.' IDENT - * ; - */ - - var tokenStream = this._tokenStream, - token; - - if (tokenStream.match(Tokens.DOT)) { - tokenStream.mustMatch(Tokens.IDENT); - token = tokenStream.token(); - return new SelectorSubPart("." + token.value, "class", token.startLine, token.startCol - 1); - } else { - return null; - } - - }, - - //CSS3 Selectors - _element_name: function() { - /* - * element_name - * : IDENT - * ; - */ - - var tokenStream = this._tokenStream, - token; - - if (tokenStream.match(Tokens.IDENT)) { - token = tokenStream.token(); - return new SelectorSubPart(token.value, "elementName", token.startLine, token.startCol); - - } else { - return null; - } - }, - - //CSS3 Selectors - _namespace_prefix: function() { - /* - * namespace_prefix - * : [ IDENT | '*' ]? '|' - * ; - */ - var tokenStream = this._tokenStream, - value = ""; - - //verify that this is a namespace prefix - if (tokenStream.LA(1) === Tokens.PIPE || tokenStream.LA(2) === Tokens.PIPE) { - - if (tokenStream.match([Tokens.IDENT, Tokens.STAR])) { - value += tokenStream.token().value; - } - - tokenStream.mustMatch(Tokens.PIPE); - value += "|"; - - } - - return value.length ? value : null; - }, - - //CSS3 Selectors - _universal: function() { - /* - * universal - * : [ namespace_prefix ]? '*' - * ; - */ - var tokenStream = this._tokenStream, - value = "", - ns; - - ns = this._namespace_prefix(); - if (ns) { - value += ns; - } - - if (tokenStream.match(Tokens.STAR)) { - value += "*"; - } - - return value.length ? value : null; - - }, - - //CSS3 Selectors - _attrib: function() { - /* - * attrib - * : '[' S* [ namespace_prefix ]? IDENT S* - * [ [ PREFIXMATCH | - * SUFFIXMATCH | - * SUBSTRINGMATCH | - * '=' | - * INCLUDES | - * DASHMATCH ] S* [ IDENT | STRING ] S* - * ]? ']' - * ; - */ - - var tokenStream = this._tokenStream, - value = null, - ns, - token; - - if (tokenStream.match(Tokens.LBRACKET)) { - token = tokenStream.token(); - value = token.value; - value += this._readWhitespace(); - - ns = this._namespace_prefix(); - - if (ns) { - value += ns; - } - - tokenStream.mustMatch(Tokens.IDENT); - value += tokenStream.token().value; - value += this._readWhitespace(); - - if (tokenStream.match([Tokens.PREFIXMATCH, Tokens.SUFFIXMATCH, Tokens.SUBSTRINGMATCH, - Tokens.EQUALS, Tokens.INCLUDES, Tokens.DASHMATCH])) { - - value += tokenStream.token().value; - value += this._readWhitespace(); - - tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]); - value += tokenStream.token().value; - value += this._readWhitespace(); - } - - tokenStream.mustMatch(Tokens.RBRACKET); - - return new SelectorSubPart(value + "]", "attribute", token.startLine, token.startCol); - } else { - return null; - } - }, - - //CSS3 Selectors - _pseudo: function() { - - /* - * pseudo - * : ':' ':'? [ IDENT | functional_pseudo ] - * ; - */ - - var tokenStream = this._tokenStream, - pseudo = null, - colons = ":", - line, - col; - - if (tokenStream.match(Tokens.COLON)) { - - if (tokenStream.match(Tokens.COLON)) { - colons += ":"; - } - - if (tokenStream.match(Tokens.IDENT)) { - pseudo = tokenStream.token().value; - line = tokenStream.token().startLine; - col = tokenStream.token().startCol - colons.length; - } else if (tokenStream.peek() === Tokens.FUNCTION) { - line = tokenStream.LT(1).startLine; - col = tokenStream.LT(1).startCol - colons.length; - pseudo = this._functional_pseudo(); - } - - if (pseudo) { - pseudo = new SelectorSubPart(colons + pseudo, "pseudo", line, col); - } else { - var startLine = tokenStream.LT(1).startLine, - startCol = tokenStream.LT(0).startCol; - throw new SyntaxError("Expected a `FUNCTION` or `IDENT` after colon at line " + startLine + ", col " + startCol + ".", startLine, startCol); - } - } - - return pseudo; - }, - - //CSS3 Selectors - _functional_pseudo: function() { - /* - * functional_pseudo - * : FUNCTION S* expression ')' - * ; - */ - - var tokenStream = this._tokenStream, - value = null; - - if (tokenStream.match(Tokens.FUNCTION)) { - value = tokenStream.token().value; - value += this._readWhitespace(); - value += this._expression(); - tokenStream.mustMatch(Tokens.RPAREN); - value += ")"; - } - - return value; - }, - - //CSS3 Selectors - _expression: function() { - /* - * expression - * : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ - * ; - */ - - var tokenStream = this._tokenStream, - value = ""; - - while (tokenStream.match([Tokens.PLUS, Tokens.MINUS, Tokens.DIMENSION, - Tokens.NUMBER, Tokens.STRING, Tokens.IDENT, Tokens.LENGTH, - Tokens.FREQ, Tokens.ANGLE, Tokens.TIME, - Tokens.RESOLUTION, Tokens.SLASH])) { - - value += tokenStream.token().value; - value += this._readWhitespace(); - } - - return value.length ? value : null; - - }, - - //CSS3 Selectors - _negation: function() { - /* - * negation - * : NOT S* negation_arg S* ')' - * ; - */ - - var tokenStream = this._tokenStream, - line, - col, - value = "", - arg, - subpart = null; - - if (tokenStream.match(Tokens.NOT)) { - value = tokenStream.token().value; - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - value += this._readWhitespace(); - arg = this._negation_arg(); - value += arg; - value += this._readWhitespace(); - tokenStream.match(Tokens.RPAREN); - value += tokenStream.token().value; - - subpart = new SelectorSubPart(value, "not", line, col); - subpart.args.push(arg); - } - - return subpart; - }, - - //CSS3 Selectors - _negation_arg: function() { - /* - * negation_arg - * : type_selector | universal | HASH | class | attrib | pseudo - * ; - */ - - var tokenStream = this._tokenStream, - args = [ - this._type_selector, - this._universal, - function() { - return tokenStream.match(Tokens.HASH) ? - new SelectorSubPart(tokenStream.token().value, "id", tokenStream.token().startLine, tokenStream.token().startCol) : - null; - }, - this._class, - this._attrib, - this._pseudo - ], - arg = null, - i = 0, - len = args.length, - line, - col, - part; - - line = tokenStream.LT(1).startLine; - col = tokenStream.LT(1).startCol; - - while (i < len && arg === null) { - - arg = args[i].call(this); - i++; - } - - //must be a negation arg - if (arg === null) { - this._unexpectedToken(tokenStream.LT(1)); - } - - //it's an element name - if (arg.type === "elementName") { - part = new SelectorPart(arg, [], arg.toString(), line, col); - } else { - part = new SelectorPart(null, [arg], arg.toString(), line, col); - } - - return part; - }, - - _declaration: function() { - - /* - * declaration - * : property ':' S* expr prio? - * | /( empty )/ - * ; - */ - - var tokenStream = this._tokenStream, - property = null, - expr = null, - prio = null, - invalid = null, - propertyName= ""; - - property = this._property(); - if (property !== null) { - - tokenStream.mustMatch(Tokens.COLON); - this._readWhitespace(); - - expr = this._expr(); - - //if there's no parts for the value, it's an error - if (!expr || expr.length === 0) { - this._unexpectedToken(tokenStream.LT(1)); - } - - prio = this._prio(); - - /* - * If hacks should be allowed, then only check the root - * property. If hacks should not be allowed, treat - * _property or *property as invalid properties. - */ - propertyName = property.toString(); - if (this.options.starHack && property.hack === "*" || - this.options.underscoreHack && property.hack === "_") { - - propertyName = property.text; - } - - try { - this._validateProperty(propertyName, expr); - } catch (ex) { - invalid = ex; - } - - this.fire({ - type: "property", - property: property, - value: expr, - important: prio, - line: property.line, - col: property.col, - invalid: invalid - }); - - return true; - } else { - return false; - } - }, - - _prio: function() { - /* - * prio - * : IMPORTANT_SYM S* - * ; - */ - - var tokenStream = this._tokenStream, - result = tokenStream.match(Tokens.IMPORTANT_SYM); - - this._readWhitespace(); - return result; - }, - - _expr: function(inFunction) { - /* - * expr - * : term [ operator term ]* - * ; - */ - - var values = [], - //valueParts = [], - value = null, - operator = null; - - value = this._term(inFunction); - if (value !== null) { - - values.push(value); - - do { - operator = this._operator(inFunction); - - //if there's an operator, keep building up the value parts - if (operator) { - values.push(operator); - } /*else { - //if there's not an operator, you have a full value - values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); - valueParts = []; - }*/ - - value = this._term(inFunction); - - if (value === null) { - break; - } else { - values.push(value); - } - } while (true); - } - - //cleanup - /*if (valueParts.length) { - values.push(new PropertyValue(valueParts, valueParts[0].line, valueParts[0].col)); - }*/ - - return values.length > 0 ? new PropertyValue(values, values[0].line, values[0].col) : null; - }, - - _term: function(inFunction) { - - /* - * term - * : unary_operator? - * [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* | - * TIME S* | FREQ S* | function | ie_function ] - * | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor - * ; - */ - - var tokenStream = this._tokenStream, - unary = null, - value = null, - endChar = null, - part = null, - token, - line, - col; - - //returns the operator or null - unary = this._unary_operator(); - if (unary !== null) { - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - } - - //exception for IE filters - if (tokenStream.peek() === Tokens.IE_FUNCTION && this.options.ieFilters) { - - value = this._ie_function(); - if (unary === null) { - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - } - - //see if it's a simple block - } else if (inFunction && tokenStream.match([Tokens.LPAREN, Tokens.LBRACE, Tokens.LBRACKET])) { - - token = tokenStream.token(); - endChar = token.endChar; - value = token.value + this._expr(inFunction).text; - if (unary === null) { - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - } - tokenStream.mustMatch(Tokens.type(endChar)); - value += endChar; - this._readWhitespace(); - - //see if there's a simple match - } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH, - Tokens.ANGLE, Tokens.TIME, - Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])) { - - value = tokenStream.token().value; - if (unary === null) { - line = tokenStream.token().startLine; - col = tokenStream.token().startCol; - // Correct potentially-inaccurate IDENT parsing in - // PropertyValuePart constructor. - part = PropertyValuePart.fromToken(tokenStream.token()); - } - this._readWhitespace(); - } else { - - //see if it's a color - token = this._hexcolor(); - if (token === null) { - - //if there's no unary, get the start of the next token for line/col info - if (unary === null) { - line = tokenStream.LT(1).startLine; - col = tokenStream.LT(1).startCol; - } - - //has to be a function - if (value === null) { - - /* - * This checks for alpha(opacity=0) style of IE - * functions. IE_FUNCTION only presents progid: style. - */ - if (tokenStream.LA(3) === Tokens.EQUALS && this.options.ieFilters) { - value = this._ie_function(); - } else { - value = this._function(); - } - } - - /*if (value === null) { - return null; - //throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " + tokenStream.token().startCol + "."); - }*/ - - } else { - value = token.value; - if (unary === null) { - line = token.startLine; - col = token.startCol; - } - } - - } - - return part !== null ? part : value !== null ? - new PropertyValuePart(unary !== null ? unary + value : value, line, col) : - null; - - }, - - _function: function() { - - /* - * function - * : FUNCTION S* expr ')' S* - * ; - */ - - var tokenStream = this._tokenStream, - functionText = null, - expr = null, - lt; - - if (tokenStream.match(Tokens.FUNCTION)) { - functionText = tokenStream.token().value; - this._readWhitespace(); - expr = this._expr(true); - functionText += expr; - - //START: Horrible hack in case it's an IE filter - if (this.options.ieFilters && tokenStream.peek() === Tokens.EQUALS) { - do { - - if (this._readWhitespace()) { - functionText += tokenStream.token().value; - } - - //might be second time in the loop - if (tokenStream.LA(0) === Tokens.COMMA) { - functionText += tokenStream.token().value; - } - - tokenStream.match(Tokens.IDENT); - functionText += tokenStream.token().value; - - tokenStream.match(Tokens.EQUALS); - functionText += tokenStream.token().value; - - //functionText += this._term(); - lt = tokenStream.peek(); - while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN) { - tokenStream.get(); - functionText += tokenStream.token().value; - lt = tokenStream.peek(); - } - } while (tokenStream.match([Tokens.COMMA, Tokens.S])); - } - - //END: Horrible Hack - - tokenStream.match(Tokens.RPAREN); - functionText += ")"; - this._readWhitespace(); - } - - return functionText; - }, - - _ie_function: function() { - - /* (My own extension) - * ie_function - * : IE_FUNCTION S* IDENT '=' term [S* ','? IDENT '=' term]+ ')' S* - * ; - */ - - var tokenStream = this._tokenStream, - functionText = null, - lt; - - //IE function can begin like a regular function, too - if (tokenStream.match([Tokens.IE_FUNCTION, Tokens.FUNCTION])) { - functionText = tokenStream.token().value; - - do { - - if (this._readWhitespace()) { - functionText += tokenStream.token().value; - } - - //might be second time in the loop - if (tokenStream.LA(0) === Tokens.COMMA) { - functionText += tokenStream.token().value; - } - - tokenStream.match(Tokens.IDENT); - functionText += tokenStream.token().value; - - tokenStream.match(Tokens.EQUALS); - functionText += tokenStream.token().value; - - //functionText += this._term(); - lt = tokenStream.peek(); - while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN) { - tokenStream.get(); - functionText += tokenStream.token().value; - lt = tokenStream.peek(); - } - } while (tokenStream.match([Tokens.COMMA, Tokens.S])); - - tokenStream.match(Tokens.RPAREN); - functionText += ")"; - this._readWhitespace(); - } - - return functionText; - }, - - _hexcolor: function() { - /* - * There is a constraint on the color that it must - * have either 3 or 6 hex-digits (i.e., [0-9a-fA-F]) - * after the "#"; e.g., "#000" is OK, but "#abcd" is not. - * - * hexcolor - * : HASH S* - * ; - */ - - var tokenStream = this._tokenStream, - token = null, - color; - - if (tokenStream.match(Tokens.HASH)) { - - //need to do some validation here - - token = tokenStream.token(); - color = token.value; - if (!/#[a-f0-9]{3,6}/i.test(color)) { - throw new SyntaxError("Expected a hex color but found '" + color + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); - } - this._readWhitespace(); - } - - return token; - }, - - //----------------------------------------------------------------- - // Animations methods - //----------------------------------------------------------------- - - _keyframes: function() { - - /* - * keyframes: - * : KEYFRAMES_SYM S* keyframe_name S* '{' S* keyframe_rule* '}' { - * ; - */ - var tokenStream = this._tokenStream, - token, - tt, - name, - prefix = ""; - - tokenStream.mustMatch(Tokens.KEYFRAMES_SYM); - token = tokenStream.token(); - if (/^@\-([^\-]+)\-/.test(token.value)) { - prefix = RegExp.$1; - } - - this._readWhitespace(); - name = this._keyframe_name(); - - this._readWhitespace(); - tokenStream.mustMatch(Tokens.LBRACE); - - this.fire({ - type: "startkeyframes", - name: name, - prefix: prefix, - line: token.startLine, - col: token.startCol - }); - - this._readWhitespace(); - tt = tokenStream.peek(); - - //check for key - while (tt === Tokens.IDENT || tt === Tokens.PERCENTAGE) { - this._keyframe_rule(); - this._readWhitespace(); - tt = tokenStream.peek(); - } - - this.fire({ - type: "endkeyframes", - name: name, - prefix: prefix, - line: token.startLine, - col: token.startCol - }); - - this._readWhitespace(); - tokenStream.mustMatch(Tokens.RBRACE); - this._readWhitespace(); - - }, - - _keyframe_name: function() { - - /* - * keyframe_name: - * : IDENT - * | STRING - * ; - */ - var tokenStream = this._tokenStream; - - tokenStream.mustMatch([Tokens.IDENT, Tokens.STRING]); - return SyntaxUnit.fromToken(tokenStream.token()); - }, - - _keyframe_rule: function() { - - /* - * keyframe_rule: - * : key_list S* - * '{' S* declaration [ ';' S* declaration ]* '}' S* - * ; - */ - var keyList = this._key_list(); - - this.fire({ - type: "startkeyframerule", - keys: keyList, - line: keyList[0].line, - col: keyList[0].col - }); - - this._readDeclarations(true); - - this.fire({ - type: "endkeyframerule", - keys: keyList, - line: keyList[0].line, - col: keyList[0].col - }); - - }, - - _key_list: function() { - - /* - * key_list: - * : key [ S* ',' S* key]* - * ; - */ - var tokenStream = this._tokenStream, - keyList = []; - - //must be least one key - keyList.push(this._key()); - - this._readWhitespace(); - - while (tokenStream.match(Tokens.COMMA)) { - this._readWhitespace(); - keyList.push(this._key()); - this._readWhitespace(); - } - - return keyList; - }, - - _key: function() { - /* - * There is a restriction that IDENT can be only "from" or "to". - * - * key - * : PERCENTAGE - * | IDENT - * ; - */ - - var tokenStream = this._tokenStream, - token; - - if (tokenStream.match(Tokens.PERCENTAGE)) { - return SyntaxUnit.fromToken(tokenStream.token()); - } else if (tokenStream.match(Tokens.IDENT)) { - token = tokenStream.token(); - - if (/from|to/i.test(token.value)) { - return SyntaxUnit.fromToken(token); - } - - tokenStream.unget(); - } - - //if it gets here, there wasn't a valid token, so time to explode - this._unexpectedToken(tokenStream.LT(1)); - }, - - //----------------------------------------------------------------- - // Helper methods - //----------------------------------------------------------------- - - /** - * Not part of CSS grammar, but useful for skipping over - * combination of white space and HTML-style comments. - * @return {void} - * @method _skipCruft - * @private - */ - _skipCruft: function() { - while (this._tokenStream.match([Tokens.S, Tokens.CDO, Tokens.CDC])) { - //noop - } - }, - - /** - * Not part of CSS grammar, but this pattern occurs frequently - * in the official CSS grammar. Split out here to eliminate - * duplicate code. - * @param {Boolean} checkStart Indicates if the rule should check - * for the left brace at the beginning. - * @param {Boolean} readMargins Indicates if the rule should check - * for margin patterns. - * @return {void} - * @method _readDeclarations - * @private - */ - _readDeclarations: function(checkStart, readMargins) { - /* - * Reads the pattern - * S* '{' S* declaration [ ';' S* declaration ]* '}' S* - * or - * S* '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* - * Note that this is how it is described in CSS3 Paged Media, but is actually incorrect. - * A semicolon is only necessary following a declaration if there's another declaration - * or margin afterwards. - */ - var tokenStream = this._tokenStream, - tt; - - - this._readWhitespace(); - - if (checkStart) { - tokenStream.mustMatch(Tokens.LBRACE); - } - - this._readWhitespace(); - - try { - - while (true) { - - if (tokenStream.match(Tokens.SEMICOLON) || (readMargins && this._margin())) { - //noop - } else if (this._declaration()) { - if (!tokenStream.match(Tokens.SEMICOLON)) { - break; - } - } else { - break; - } - - //if ((!this._margin() && !this._declaration()) || !tokenStream.match(Tokens.SEMICOLON)){ - // break; - //} - this._readWhitespace(); - } - - tokenStream.mustMatch(Tokens.RBRACE); - this._readWhitespace(); - - } catch (ex) { - if (ex instanceof SyntaxError && !this.options.strict) { - - //fire error event - this.fire({ - type: "error", - error: ex, - message: ex.message, - line: ex.line, - col: ex.col - }); - - //see if there's another declaration - tt = tokenStream.advance([Tokens.SEMICOLON, Tokens.RBRACE]); - if (tt === Tokens.SEMICOLON) { - //if there's a semicolon, then there might be another declaration - this._readDeclarations(false, readMargins); - } else if (tt !== Tokens.RBRACE) { - //if there's a right brace, the rule is finished so don't do anything - //otherwise, rethrow the error because it wasn't handled properly - throw ex; - } - - } else { - //not a syntax error, rethrow it - throw ex; - } - } - - }, - - /** - * In some cases, you can end up with two white space tokens in a - * row. Instead of making a change in every function that looks for - * white space, this function is used to match as much white space - * as necessary. - * @method _readWhitespace - * @return {String} The white space if found, empty string if not. - * @private - */ - _readWhitespace: function() { - - var tokenStream = this._tokenStream, - ws = ""; - - while (tokenStream.match(Tokens.S)) { - ws += tokenStream.token().value; - } - - return ws; - }, - - - /** - * Throws an error when an unexpected token is found. - * @param {Object} token The token that was found. - * @method _unexpectedToken - * @return {void} - * @private - */ - _unexpectedToken: function(token) { - throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); - }, - - /** - * Helper method used for parsing subparts of a style sheet. - * @return {void} - * @method _verifyEnd - * @private - */ - _verifyEnd: function() { - if (this._tokenStream.LA(1) !== Tokens.EOF) { - this._unexpectedToken(this._tokenStream.LT(1)); - } - }, - - //----------------------------------------------------------------- - // Validation methods - //----------------------------------------------------------------- - _validateProperty: function(property, value) { - Validation.validate(property, value); - }, - - //----------------------------------------------------------------- - // Parsing methods - //----------------------------------------------------------------- - - parse: function(input) { - this._tokenStream = new TokenStream(input, Tokens); - this._stylesheet(); - }, - - parseStyleSheet: function(input) { - //just passthrough - return this.parse(input); - }, - - parseMediaQuery: function(input) { - this._tokenStream = new TokenStream(input, Tokens); - var result = this._media_query(); - - //if there's anything more, then it's an invalid selector - this._verifyEnd(); - - //otherwise return result - return result; - }, - - /** - * Parses a property value (everything after the semicolon). - * @return {parserlib.css.PropertyValue} The property value. - * @throws parserlib.util.SyntaxError If an unexpected token is found. - * @method parserPropertyValue - */ - parsePropertyValue: function(input) { - - this._tokenStream = new TokenStream(input, Tokens); - this._readWhitespace(); - - var result = this._expr(); - - //okay to have a trailing white space - this._readWhitespace(); - - //if there's anything more, then it's an invalid selector - this._verifyEnd(); - - //otherwise return result - return result; - }, - - /** - * Parses a complete CSS rule, including selectors and - * properties. - * @param {String} input The text to parser. - * @return {Boolean} True if the parse completed successfully, false if not. - * @method parseRule - */ - parseRule: function(input) { - this._tokenStream = new TokenStream(input, Tokens); - - //skip any leading white space - this._readWhitespace(); - - var result = this._ruleset(); - - //skip any trailing white space - this._readWhitespace(); - - //if there's anything more, then it's an invalid selector - this._verifyEnd(); - - //otherwise return result - return result; - }, - - /** - * Parses a single CSS selector (no comma) - * @param {String} input The text to parse as a CSS selector. - * @return {Selector} An object representing the selector. - * @throws parserlib.util.SyntaxError If an unexpected token is found. - * @method parseSelector - */ - parseSelector: function(input) { - - this._tokenStream = new TokenStream(input, Tokens); - - //skip any leading white space - this._readWhitespace(); - - var result = this._selector(); - - //skip any trailing white space - this._readWhitespace(); - - //if there's anything more, then it's an invalid selector - this._verifyEnd(); - - //otherwise return result - return result; - }, - - /** - * Parses an HTML style attribute: a set of CSS declarations - * separated by semicolons. - * @param {String} input The text to parse as a style attribute - * @return {void} - * @method parseStyleAttribute - */ - parseStyleAttribute: function(input) { - input += "}"; // for error recovery in _readDeclarations() - this._tokenStream = new TokenStream(input, Tokens); - this._readDeclarations(); - } - }; - - //copy over onto prototype - for (prop in additions) { - if (Object.prototype.hasOwnProperty.call(additions, prop)) { - proto[prop] = additions[prop]; - } - } - - return proto; -}(); - - -/* -nth - : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? | - ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S* - ; -*/ - -},{"../util/EventTarget":23,"../util/SyntaxError":25,"../util/SyntaxUnit":26,"./Combinator":2,"./MediaFeature":4,"./MediaQuery":5,"./PropertyName":8,"./PropertyValue":9,"./PropertyValuePart":11,"./Selector":13,"./SelectorPart":14,"./SelectorSubPart":15,"./TokenStream":17,"./Tokens":18,"./Validation":19}],7:[function(require,module,exports){ -"use strict"; - -/* exported Properties */ - -var Properties = module.exports = { - __proto__: null, - - //A - "align-items" : "flex-start | flex-end | center | baseline | stretch", - "align-content" : "flex-start | flex-end | center | space-between | space-around | stretch", - "align-self" : "auto | flex-start | flex-end | center | baseline | stretch", - "all" : "initial | inherit | unset", - "-webkit-align-items" : "flex-start | flex-end | center | baseline | stretch", - "-webkit-align-content" : "flex-start | flex-end | center | space-between | space-around | stretch", - "-webkit-align-self" : "auto | flex-start | flex-end | center | baseline | stretch", - "alignment-adjust" : "auto | baseline | before-edge | text-before-edge | middle | central | after-edge | text-after-edge | ideographic | alphabetic | hanging | mathematical | | ", - "alignment-baseline" : "auto | baseline | use-script | before-edge | text-before-edge | after-edge | text-after-edge | central | middle | ideographic | alphabetic | hanging | mathematical", - "animation" : 1, - "animation-delay" : "detailed description.').format(rule['url']) + m = rule_metadata.get(rule_id) + if m and 'url' in m: + ans.HELP += '. ' + _('See detailed description.').format(m['url']) return ans -def csslint_js(): - ans = getattr(csslint_js, 'ans', None) +def stylelint_js(): + ans = getattr(stylelint_js, 'ans', None) if ans is None: - ans = csslint_js.ans = P('csslint.js', data=True, allow_user_override=False).decode('utf-8') + ''' - - window.check_css = function(src) { - var rules = CSSLint.getRules(); - var ruleset = {}; - var ignored_rules = { - 'order-alphabetical': 1, - 'font-sizes': 1, - 'zero-units': 1, - 'bulletproof-font-face': 1, - 'import': 1, - 'box-model': 1, - 'adjoining-classes': 1, - 'box-sizing': 1, - 'compatible-vendor-prefixes': 1, - 'text-indent': 1, - 'unique-headings': 1, - 'fallback-colors': 1, - 'font-faces': 1, - 'regex-selectors': 1, - 'universal-selector': 1, - 'unqualified-attributes': 1, - 'overqualified-elements': 1, - 'shorthand': 1, - 'duplicate-background-images': 1, - 'floats': 1, - 'ids': 1, - 'gradients': 1 - }; - var error_rules = { - 'known-properties': 1, - 'duplicate-properties': 1, - 'vendor-prefix': 1 - }; - - for (var i = 0; i < rules.length; i++) { - var rule = rules[i]; - if (!ignored_rules[rule.id] && rule.browsers === "All") ruleset[rule.id] = error_rules[rule.id] ? 2 : 1; - } - var result = CSSLint.verify(src, ruleset); - return result; - } - document.title = 'ready'; - ''' + ans = stylelint_js.ans = ( + ('stylelint-bundle.min.js', P('stylelint-bundle.min.js', data=True, allow_user_override=False).decode('utf-8')), + ('stylelint.js', P('stylelint.js', data=True, allow_user_override=False).decode('utf-8')), + ) return ans @@ -117,11 +79,12 @@ def create_profile(): if ans is None: ans = create_profile.ans = QWebEngineProfile(QApplication.instance()) setup_profile(ans) - s = QWebEngineScript() - s.setName('csslint.js') - s.setSourceCode(csslint_js()) - s.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld) - ans.scripts().insert(s) + for (name, code) in stylelint_js(): + s = QWebEngineScript() + s.setName(name) + s.setSourceCode(code) + s.setWorldId(QWebEngineScript.ScriptWorldId.ApplicationWorld) + ans.scripts().insert(s) return ans @@ -134,22 +97,23 @@ class Worker(QWebEnginePage): QWebEnginePage.__init__(self, create_profile(), QApplication.instance()) self.titleChanged.connect(self.title_changed) secure_webengine(self.settings()) - self.console_messages = [] self.ready = False self.working = False self.pending = None self.setHtml('') def title_changed(self, new_title): + new_title = new_title.partition(':')[0] if new_title == 'ready': self.ready = True if self.pending is not None: self.check_css(self.pending) self.pending = None + elif new_title == 'checked': + self.runJavaScript('window.get_css_results()', QWebEngineScript.ScriptWorldId.ApplicationWorld, self.check_done) def javaScriptConsoleMessage(self, level, msg, lineno, source_id): msg = f'{source_id}:{lineno}:{msg}' - self.console_messages.append(msg) try: print(msg) except Exception: @@ -157,9 +121,8 @@ class Worker(QWebEnginePage): def check_css(self, src): self.working = True - self.console_messages = [] self.runJavaScript( - f'window.check_css({json.dumps(src)})', QWebEngineScript.ScriptWorldId.ApplicationWorld, self.check_done) + f'window.check_css({json.dumps(src)})', QWebEngineScript.ScriptWorldId.ApplicationWorld) def check_css_when_ready(self, src): if self.ready: @@ -168,9 +131,10 @@ class Worker(QWebEnginePage): self.working = True self.pending = src - def check_done(self, result): + def check_done(self, results): self.working = False - self.work_done.emit(self, result) + for result in results: + self.work_done.emit(self, result) class Pool: @@ -208,11 +172,9 @@ class Pool: break def work_done(self, worker, result): - if not isinstance(result, dict): - result = worker.console_messages self.results[worker.result_idx] = result self.assign_work() - if not self.pending and not [w for w in self.workers if w.working]: + if not self.pending and not any(w for w in self.workers if w.working): self.working = False def shutdown(self): @@ -245,16 +207,16 @@ def check_css(jobs): return errors results = pool.check_css([j.css for j in jobs]) for job, result in zip(jobs, results): - if isinstance(result, dict): - for msg in result['messages']: - err = message_to_error(msg, job.name, job.line_offset) - if err is not None: - errors.append(err) - elif isinstance(result, list) and result: + if result['type'] == 'error': errors.append(CSSParseError(_('Failed to process CSS in {name} with errors: {errors}').format( - name=job.name, errors='\n'.join(result)), job.name)) - else: - errors.append(CSSParseError(_('Failed to process CSS in {name}').format(name=job.name), job.name)) + name=job.name, errors=result['error']), job.name)) + continue + result = json.loads(result['results']['output']) + rule_metadata = result['rule_metadata'] + for msg in result['results']['warnings']: + err = message_to_error(msg, job.name, job.line_offset, rule_metadata) + if err is not None: + errors.append(err) return errors From 5be6c74f8048b743dd412227632befc13ce80107 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 5 Jan 2023 12:18:04 +0530 Subject: [PATCH 0076/2055] Mark stylelint bundle as vendored --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 9f2732947f..f518dcced2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -46,6 +46,7 @@ resources/coffee-script.js linguist-vendored=true resources/csscolorparser.js linguist-vendored=true resources/viewer/hyphen* linguist-vendored=true resources/viewer/jquery* linguist-vendored=true +resources/stylelint-bundle.min.js linguist-vendored=true src/hunspell linguist-vendored=true # Mark generated files From 49cd1944dbd6fbd902d9d3bd72e7351bd0ffc496 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 5 Jan 2023 12:44:25 +0530 Subject: [PATCH 0077/2055] Edit book: Set semantics: Fix error when setting the "Notes" semantic --- src/calibre/gui2/tweak_book/widgets.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/tweak_book/widgets.py b/src/calibre/gui2/tweak_book/widgets.py index f77a922912..034d34bae2 100644 --- a/src/calibre/gui2/tweak_book/widgets.py +++ b/src/calibre/gui2/tweak_book/widgets.py @@ -986,7 +986,12 @@ class InsertSemantics(Dialog): return title for item_type, (name, frag) in self.changes.items(): - set_guide_item(container, self.epubtype_guide_map[item_type], title_for_type(item_type), name, frag=frag) + guide_type = self.epubtype_guide_map.get(item_type) + if not guide_type: + if container.opf_version_parsed.major < 3: + raise KeyError(_('Cannot set {} type semantics in EPUB 2 or AZW3 books').format(name)) + continue + set_guide_item(container, guide_type, title_for_type(item_type), name, frag=frag) if container.opf_version_parsed.major > 2: final = self.original_nav_map.copy() From f08b238986be251d2181b3fe6c01a027d617a260 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 5 Jan 2023 13:52:25 +0530 Subject: [PATCH 0078/2055] Edit book: Check book: Allow automatic fixing of various simple CSS errors --- resources/stylelint.js | 3 +- src/calibre/ebooks/oeb/polish/check/css.py | 37 +++++++---- src/calibre/ebooks/oeb/polish/check/main.py | 72 +++++++++++++++++---- 3 files changed, 86 insertions(+), 26 deletions(-) diff --git a/resources/stylelint.js b/resources/stylelint.js index f53d110357..9d7c1710f7 100644 --- a/resources/stylelint.js +++ b/resources/stylelint.js @@ -10,9 +10,10 @@ window.stylelint_results = []; -window.check_css = function(src) { +window.check_css = function(src, fix) { stylelint.lint({ code: src, + fix: fix, config: { rules: { 'annotation-no-unknown': true, diff --git a/src/calibre/ebooks/oeb/polish/check/css.py b/src/calibre/ebooks/oeb/polish/check/css.py index b97400cf3a..7aa3aac121 100644 --- a/src/calibre/ebooks/oeb/polish/check/css.py +++ b/src/calibre/ebooks/oeb/polish/check/css.py @@ -22,14 +22,17 @@ from calibre.utils.webengine import secure_webengine, setup_profile class CSSParseError(BaseError): level = ERROR is_parsing_error = True + FIXABLE_CSS_ERROR = False class CSSError(BaseError): level = ERROR + FIXABLE_CSS_ERROR = False class CSSWarning(BaseError): level = WARN + FIXABLE_CSS_ERROR = False def as_int_or_none(x): @@ -57,10 +60,15 @@ def message_to_error(message, name, line_offset, rule_metadata): line += line_offset ans = cls(title, name, line, col) ans.HELP = message.get('text') or '' + if ans.HELP: + ans.HELP += '. ' ans.css_rule_id = rule_id - m = rule_metadata.get(rule_id) - if m and 'url' in m: - ans.HELP += '. ' + _('See detailed description.').format(m['url']) + m = rule_metadata.get(rule_id) or {} + if 'url' in m: + ans.HELP += _('See detailed description.').format(m['url']) + ' ' + if m.get('fixable'): + ans.FIXABLE_CSS_ERROR = True + ans.HELP += _('This error will be automatically fixed if you click "Try to correct all fixable errors" below.') return ans @@ -107,7 +115,7 @@ class Worker(QWebEnginePage): if new_title == 'ready': self.ready = True if self.pending is not None: - self.check_css(self.pending) + self.check_css(*self.pending) self.pending = None elif new_title == 'checked': self.runJavaScript('window.get_css_results()', QWebEngineScript.ScriptWorldId.ApplicationWorld, self.check_done) @@ -119,17 +127,17 @@ class Worker(QWebEnginePage): except Exception: pass - def check_css(self, src): + def check_css(self, src, fix=False): self.working = True self.runJavaScript( - f'window.check_css({json.dumps(src)})', QWebEngineScript.ScriptWorldId.ApplicationWorld) + f'window.check_css({json.dumps(src)}, {"true" if fix else "false"})', QWebEngineScript.ScriptWorldId.ApplicationWorld) - def check_css_when_ready(self, src): + def check_css_when_ready(self, src, fix=False): if self.ready: - self.check_css(src) + self.check_css(src, fix) else: self.working = True - self.pending = src + self.pending = src, fix def check_done(self, results): self.working = False @@ -148,7 +156,8 @@ class Pool: w.work_done.connect(self.work_done) self.workers.append(w) - def check_css(self, css_sources): + def check_css(self, css_sources, fix=False): + self.doing_fix = fix self.pending = list(enumerate(css_sources)) self.results = list(repeat(None, len(css_sources))) self.working = True @@ -166,7 +175,7 @@ class Pool: if not w.working: idx, src = self.pending.pop() w.result_idx = idx - w.check_css_when_ready(src) + w.check_css_when_ready(src, self.doing_fix) break else: break @@ -191,14 +200,14 @@ class Pool: pool = Pool() shutdown = pool.shutdown atexit.register(shutdown) -Job = namedtuple('Job', 'name css line_offset') +Job = namedtuple('Job', 'name css line_offset fix_data') -def create_job(name, css, line_offset=0, is_declaration=False): +def create_job(name, css, line_offset=0, is_declaration=False, fix_data=None): if is_declaration: css = 'div{\n' + css + '\n}' line_offset -= 1 - return Job(name, css, line_offset) + return Job(name, css, line_offset, fix_data) def check_css(jobs): diff --git a/src/calibre/ebooks/oeb/polish/check/main.py b/src/calibre/ebooks/oeb/polish/check/main.py index 1acc5bb9f4..fe6e5b1e28 100644 --- a/src/calibre/ebooks/oeb/polish/check/main.py +++ b/src/calibre/ebooks/oeb/polish/check/main.py @@ -4,21 +4,23 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' -from polyglot.builtins import iteritems +from collections import namedtuple from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES -from calibre.ebooks.oeb.polish.utils import guess_type -from calibre.ebooks.oeb.polish.cover import is_raster_image -from calibre.ebooks.oeb.polish.check.base import run_checkers, WARN -from calibre.ebooks.oeb.polish.check.parsing import ( - check_filenames, check_xml_parsing, fix_style_tag, - check_html_size, check_ids, check_markup, EmptyFile, check_encoding_declarations) -from calibre.ebooks.oeb.polish.check.images import check_raster_images -from calibre.ebooks.oeb.polish.check.links import check_links, check_mimetypes, check_link_destinations +from calibre.ebooks.oeb.polish.check.base import WARN, run_checkers from calibre.ebooks.oeb.polish.check.fonts import check_fonts +from calibre.ebooks.oeb.polish.check.images import check_raster_images +from calibre.ebooks.oeb.polish.check.links import ( + check_link_destinations, check_links, check_mimetypes, +) from calibre.ebooks.oeb.polish.check.opf import check_opf -from polyglot.builtins import as_unicode - +from calibre.ebooks.oeb.polish.check.parsing import ( + EmptyFile, check_encoding_declarations, check_filenames, check_html_size, check_ids, + check_markup, check_xml_parsing, fix_style_tag, +) +from calibre.ebooks.oeb.polish.cover import is_raster_image +from calibre.ebooks.oeb.polish.utils import guess_type +from polyglot.builtins import as_unicode, iteritems XML_TYPES = frozenset(map(guess_type, ('a.xml', 'a.svg', 'a.opf', 'a.ncx'))) | {'application/oebps-page-map+xml'} @@ -105,6 +107,48 @@ def run_checks(container): return errors +CSSFix = namedtuple('CSSFix', 'original_css elem attribute') + + +def fix_css(container): + from calibre.ebooks.oeb.polish.check.css import create_job, pool + jobs = [] + + for name, mt in iteritems(container.mime_map): + if mt in OEB_STYLES: + css = container.raw_data(name, decode=True) + jobs.append(create_job(name, css, fix_data=CSSFix(css, None, ''))) + elif mt in OEB_DOCS: + root = container.parsed(name) + for style in root.xpath('//*[local-name()="style"]'): + if style.get('type', 'text/css') == 'text/css' and style.text: + jobs.append(create_job(name, style.text, fix_data=CSSFix(style.text, style, ''))) + for elem in root.xpath('//*[@style]'): + raw = elem.get('style') + if raw: + jobs.append(create_job(name, raw, is_declaration=True, fix_data=CSSFix(raw, elem, 'style'))) + results = pool.check_css([j.css for j in jobs], fix=True) + changed = False + for job, result in zip(jobs, results): + if result['type'] == 'error': + continue + fx = job.fix_data + fixed_css = result['results']['output'] + if fixed_css == fx.original_css: + continue + changed = True + if fx.elem is None: + with container.open(job.name, 'wb') as f: + f.write(fixed_css.encode('utf-8')) + else: + if fx.attribute: + fx.elem.set(fx.attribute, ' '.join(fixed_css.splitlines()[1:-1])) + else: + fx.elem.text = fixed_css + container.dirty(job.name) + return changed + + def fix_errors(container, errors): # Fix parsing changed = False @@ -121,11 +165,17 @@ def fix_errors(container, errors): changed = True + has_fixable_css_errors = False for err in errors: + if getattr(err, 'FIXABLE_CSS_ERROR', False): + has_fixable_css_errors = True if err.INDIVIDUAL_FIX: if err(container) is not False: # Assume changed unless fixer explicitly says no change (this # is because sometimes I forget to return True, and it is # better to have a false positive than a false negative) changed = True + if has_fixable_css_errors: + if fix_css(container): + changed = True return changed From 939e2a12372f4484c2f963a335de8d7272a0b84e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 5 Jan 2023 14:13:19 +0530 Subject: [PATCH 0079/2055] Content server: Fix auto full screen not working when continuing to read books with user account enabled. Fixes #2001880 [[Content Server] Not entering fullscreen automatically](https://bugs.launchpad.net/calibre/+bug/2001880) --- src/pyj/book_list/home.pyj | 18 ++++++++++-------- src/pyj/book_list/router.pyj | 11 +++++++++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/pyj/book_list/home.pyj b/src/pyj/book_list/home.pyj index 29f05c6edc..41750e0ccd 100644 --- a/src/pyj/book_list/home.pyj +++ b/src/pyj/book_list/home.pyj @@ -10,7 +10,7 @@ from book_list.globals import get_db from book_list.library_data import ( all_libraries, last_virtual_library_for, sync_library_books ) -from book_list.router import open_book, open_book_url, update_window_title +from book_list.router import open_book, update_window_title from book_list.top_bar import add_button, create_top_bar from book_list.ui import set_default_panel_handler, show_panel from dom import add_extra_css, build_rule, clear, ensure_id, set_css, unique_id @@ -51,8 +51,8 @@ def show_cover(blob, name, mt, book): img.src = window.URL.createObjectURL(blob) -def read_book(library_id, book_id, fmt): - open_book(book_id, fmt, library_id) +def read_book(library_id, book_id, fmt, extra_query): + open_book(book_id, fmt, library_id, extra_query=extra_query) def get_last_read_position(last_read_positions, prev_last_read): @@ -129,13 +129,15 @@ def show_recent_for_user(container_id): container = document.getElementById(container_id) images = prepare_recent_container(container) for item in recently_read_by_user.items[:3]: - q = {'library_id': item.library_id} + q = {} if item.cfi: q.bookpos = item.cfi - url_to_read = open_book_url(item.book_id, item.format, q) + rb = read_book.bind(None, item.library_id, item.book_id, item.format, q) img = E.img(alt=item.tooltip, src=absolute_path(f'get/cover/{item.book_id}/{item.library_id}')) - images.appendChild(E.div(style='margin: 0 1em', - E.a(title=item.tooltip, href=url_to_read, img))) + images.appendChild(E.div( + style='margin: 0 1em', + E.a(title=item.tooltip, href='javascript:void(0)', img, onclick=rb) + )) img.onerror = def(err): failed = err.target failed.parentNode.parentNode.style.display = 'none' @@ -160,7 +162,7 @@ def show_recent_stage2(books): img_id = ensure_id(img) images.appendChild(E.div(style='margin: 0 1em', E.a(img, href='javascript: void(0)', title=img.alt, - onclick=read_book.bind(None, book.key[0], book.key[1], book.key[2]) + onclick=read_book.bind(None, book.key[0], book.key[1], book.key[2], None) ), )) if book.cover_name: diff --git a/src/pyj/book_list/router.pyj b/src/pyj/book_list/router.pyj index a8984ba675..fe272d0f1a 100644 --- a/src/pyj/book_list/router.pyj +++ b/src/pyj/book_list/router.pyj @@ -56,7 +56,7 @@ def apply_url(ignore_handler): handler(data) -def open_book(book_id, fmt, library_id=None, replace=False): +def request_full_screen_if_wanted(): opt = get_session_data().get('fullscreen_when_opening') has_touch = v'"ontouchstart" in window' at_left = window.screenLeft is 0 @@ -65,8 +65,15 @@ def open_book(book_id, fmt, library_id=None, replace=False): # Note that full screen requests only succeed if they are in response to a # user action like clicking/tapping a button request_full_screen() + + +def open_book(book_id, fmt, library_id=None, replace=False, extra_query=None): + request_full_screen_if_wanted() library_id = library_id or current_library_id() - push_state({'book_id':book_id, 'fmt':fmt, 'library_id':library_id}, replace=replace, mode=read_book_mode) + q = {'book_id':book_id, 'fmt':fmt, 'library_id':library_id} + if extra_query and jstype(extra_query) is 'object': + Object.assign(q, extra_query) + push_state(q, replace=replace, mode=read_book_mode) def open_book_url(book_id, fmt, extra_query): From 7ab33d233b6dacb3f50378b0a7cb57c444bbd215 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 5 Jan 2023 20:22:21 +0530 Subject: [PATCH 0080/2055] pep8 --- recipes/hna.recipe | 1 + 1 file changed, 1 insertion(+) diff --git a/recipes/hna.recipe b/recipes/hna.recipe index 4649e420b3..814476dd9e 100644 --- a/recipes/hna.recipe +++ b/recipes/hna.recipe @@ -7,6 +7,7 @@ Fetch Hessisch Niedersachsische Allgemeine. from calibre.web.feeds.news import BasicNewsRecipe, classes + class hnaDe(BasicNewsRecipe): title = 'HNA' From ca93c57bfded1cd37be44b63c8af7f06fed30768 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 6 Jan 2023 08:16:57 +0530 Subject: [PATCH 0081/2055] version 6.11.0 --- Changelog.txt | 64 +++++++++++++++++++++++++++++++++++++++- src/calibre/constants.py | 2 +- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/Changelog.txt b/Changelog.txt index 4e77546b7e..c5cbba21dd 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -6,7 +6,7 @@ # to the ticket list. # Also, each release can have new and improved recipes. -# {{{ 6.x.0 2022-xx-xx +# {{{ 6.x.0 2023-xx-xx # # :: new features # @@ -23,6 +23,68 @@ # - title by author # }}} +{{{ 6.11.0 2023-01-06 + +:: new features + +- Edit book: Check book: Allow automatic fixing of various simple CSS errors + +- E-book viewer: When Read aloud is speaking, make the control bar translucent so that words under the bar are visible + +- Edit book: Switch to a new library (stylelint) for find problems in CSS as the old library was no longer maintained. + +- Edit book: File browser: Allow using keyboard shortcuts to re-order the spine + +- [1982532] calibredb list: Allow specifying multiple fields for --sort-by + +- [2000037] Check library: Allow opening the book folder easily + +:: bug fixes + +- Fix windows not being moved onto the current monitor when they were previously visible on a removed monitor that was to the left of the current monitor + +- [1999995] Book list: Fix a regression in the previous release that broke drag and drop of multiple books + +- [2000877] Fix detection of Tolino Vision 6 on macOS/Linux + +- [2001880] Content server: Fix auto full screen not working when continuing to read books with user account enabled + +- Edit book: Set semantics: Fix error when setting the "Notes" semantic + +- [1999956] HTMLZ output: Fix images referred to in CSS stylesheets not being converted + +- [2000881] Book details panel: Fix HTML comment tags in the comments breaking display of book details + +- Content server home page: When showing recently read books from across devices hide the entries for which loading the cover fails + +- Windows Text-to-speech: Dont fail to configure if one of the voices has no defined language + +- Fix a regression in calibre 5 that broke using a file for the --extra-css option of ebook-convert + +- Content server FTS: Fix page header bar not visible + +- Content server: Fix identifiers from third party metadata download plugins not becoming clickable links on the book details page + +- Edit book: Warn when saving will overwrite a read-only file + +- Fix restoring geometry of maximized/fullscreen dialogs forcing them visible + +- [1999936] Fix a regression in the previous release that caused spurious error message when doing some out of band searches + +- Fix a regression in the previous release that broke choosing new programs for the Open with function + +:: improved recipes +- PC World +- HNA +- Caravan Magazine +- Harvard Business Review +- Various Israeli news sources + +:: new recipes +- NHK News - by Richard A. Steps +- Globes in English by barakplasma +}}} + {{{ 6.10.0 2022-12-16 :: new features diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 8f39aa076b..3f82bb6d91 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -5,7 +5,7 @@ from functools import lru_cache import sys, locale, codecs, os, collections, collections.abc __appname__ = 'calibre' -numeric_version = (6, 10, 0) +numeric_version = (6, 11, 0) __version__ = '.'.join(map(str, numeric_version)) git_version = None __author__ = "Kovid Goyal " From 21e8b068135f64cbb76676c0b512d9ebf4407a73 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Fri, 6 Jan 2023 15:38:29 +0000 Subject: [PATCH 0082/2055] New template function: switch_if() --- manual/template_lang.rst | 1 + src/calibre/utils/formatter.py | 26 ++++++++++++++++++++++++ src/calibre/utils/formatter_functions.py | 26 +++++++++++++++++++++++- 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/manual/template_lang.rst b/manual/template_lang.rst index e1894e4995..028a75fd29 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -610,6 +610,7 @@ In `GPM` the functions described in `Single Function Mode` all require an additi * ``strlen(value)`` -- Returns the length of the string ``value``. * ``substr(str, start, end)`` -- returns the ``start``'th through the ``end``'th characters of ``str``. The first character in ``str`` is the zero'th character. If ``end`` is negative, then it indicates that many characters counting from the right. If ``end`` is zero, then it indicates the last character. For example, ``substr('12345', 1, 0)`` returns ``'2345'``, and ``substr('12345', 1, -1)`` returns ``'234'``. * ``subtract(x, y)`` -- returns ``x - y``. Throws an exception if either ``x`` or ``y`` are not numbers. This function can usually be replaced by the ``-`` operator. +* ``switch_if([test_expression, value_expression,]+ else_expression)`` -- for each ``test_expression, value_expression`` pair, checks if ``test_expression`` is True (non-empty) and if so returns the result of ``value_expression``. If no ``test_expression`` is True then the result of ``else_expression` is returned. You can have as many ``test_expression, value_expressio`` pairs as you want. * ``today()`` -- return a date+time string for today (now). This value is designed for use in `format_date` or `days_between`, but can be manipulated like any other string. The date is in `ISO `_ date/time format. * ``template(x)`` -- evaluates ``x`` as a template. The evaluation is done in its own context, meaning that variables are not shared between the caller and the template evaluation. * ``to_hex(val)`` -- returns the string ``val`` encoded in hex. This is useful when constructing calibre URLs. diff --git a/src/calibre/utils/formatter.py b/src/calibre/utils/formatter.py index e02b8f9b3f..dad2816e20 100644 --- a/src/calibre/utils/formatter.py +++ b/src/calibre/utils/formatter.py @@ -55,6 +55,7 @@ class Node: NODE_LOCAL_FUNCTION_CALL = 29 NODE_RANGE = 30 NODE_SWITCH = 31 + NODE_SWITCH_IF = 32 def __init__(self, line_number, name): self.my_line_number = line_number @@ -289,6 +290,13 @@ class SwitchNode(Node): self.expression_list = expression_list +class SwitchIfNode(Node): + def __init__(self, line_number, expression_list): + Node.__init__(self, line_number, 'switch_if()') + self.node_type = self.NODE_SWITCH_IF + self.expression_list = expression_list + + class ContainsNode(Node): def __init__(self, line_number, arguments): Node.__init__(self, line_number, 'contains()') @@ -687,6 +695,8 @@ class _Parser: lambda ln, args: FirstNonEmptyNode(ln, args)), 'switch': (lambda args: len(args) >= 3 and (len(args) %2) == 0, lambda ln, args: SwitchNode(ln, args)), + 'switch_if': (lambda args: len(args) > 0 and (len(args) %2) == 1, + lambda ln, args: SwitchIfNode(ln, args)), 'assign': (lambda args: len(args) == 2 and len(args[0]) == 1 and args[0][0].node_type == Node.NODE_RVALUE, lambda ln, args: AssignNode(ln, args[0][0].name, args[1])), 'contains': (lambda args: len(args) == 4, @@ -1303,6 +1313,21 @@ class _Interpreter: self.break_reporter(prog.node_name, res, prog.line_number) return res + def do_node_switch_if(self, prog): + for i in range(0, len(prog.expression_list)-1, 2): + tst = self.expr(prog.expression_list[i]) + if self.break_reporter: + self.break_reporter("switch_if(): test expr", tst, prog.line_number) + if tst: + res = self.expr(prog.expression_list[i+1]) + if self.break_reporter: + self.break_reporter("switch_if(): value expr", res, prog.line_number) + return res + res = self.expr(prog.expression_list[-1]) + if (self.break_reporter): + self.break_reporter("switch_if(): default expr", res, prog.line_number) + return res + def do_node_strcat(self, prog): res = ''.join([self.expr(expr) for expr in prog.expression_list]) if self.break_reporter: @@ -1519,6 +1544,7 @@ class _Interpreter: Node.NODE_CALL_STORED_TEMPLATE: do_node_stored_template_call, Node.NODE_FIRST_NON_EMPTY: do_node_first_non_empty, Node.NODE_SWITCH: do_node_switch, + Node.NODE_SWITCH_IF: do_node_switch_if, Node.NODE_FOR: do_node_for, Node.NODE_RANGE: do_node_range, Node.NODE_GLOBALS: do_node_globals, diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 533eab5d94..64e77abb65 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -667,6 +667,30 @@ class BuiltinSwitch(BuiltinFormatterFunction): i += 2 +class BuiltinSwitchIf(BuiltinFormatterFunction): + name = 'switch_if' + arg_count = -1 + category = 'Iterating over values' + __doc__ = doc = _('switch_if([test_expression, value_expression,]+ else_expression) -- ' + 'for each "test_expression, value_expression" pair, checks if test_expression ' + 'is True (non-empty) and if so returns the result of value_expression. ' + 'If no test_expression is True then the result of else_expression is returned. ' + 'You can have as many "test_expression, value_expression" pairs as you want.') + + def evaluate(self, formatter, kwargs, mi, locals, *args): + if (len(args) % 2) != 1: + raise ValueError(_('switch_if requires an odd number of arguments')) + # We shouldn't get here because the function is inlined. However, someone + # might call it directly. + i = 0 + while i < len(args): + if i + 1 >= len(args): + return args[i] + if args[i]: + return args[i+1] + i += 2 + + class BuiltinStrcatMax(BuiltinFormatterFunction): name = 'strcat_max' arg_count = -1 @@ -2384,7 +2408,7 @@ _formatter_builtins = [ BuiltinSetGlobals(), BuiltinShorten(), BuiltinStrcat(), BuiltinStrcatMax(), BuiltinStrcmp(), BuiltinStrcmpcase(), BuiltinStrInList(), BuiltinStrlen(), BuiltinSubitems(), BuiltinSublist(),BuiltinSubstr(), BuiltinSubtract(), BuiltinSwapAroundArticles(), - BuiltinSwapAroundComma(), BuiltinSwitch(), + BuiltinSwapAroundComma(), BuiltinSwitch(), BuiltinSwitchIf(), BuiltinTemplate(), BuiltinTest(), BuiltinTitlecase(), BuiltinToday(), BuiltinToHex(), BuiltinTransliterate(), BuiltinUppercase(), BuiltinUrlsFromIdentifiers(), BuiltinUserCategories(), BuiltinVirtualLibraries(), BuiltinAnnotationCount() From 37fd1d521af403d6bc08a832e0f4c9892b96a813 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 7 Jan 2023 13:17:54 +0530 Subject: [PATCH 0083/2055] Update Irish Independent and Irish Times --- recipes/irish_independent.recipe | 24 ++++++++++++++---------- recipes/irish_times.recipe | 3 ++- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/recipes/irish_independent.recipe b/recipes/irish_independent.recipe index 5eb2de46f0..562016afee 100644 --- a/recipes/irish_independent.recipe +++ b/recipes/irish_independent.recipe @@ -4,7 +4,7 @@ __copyright__ = '2009 Neil Grogan' # Irish Independent Recipe # -from calibre.web.feeds.news import BasicNewsRecipe +from calibre.web.feeds.news import BasicNewsRecipe, classes class IrishIndependent(BasicNewsRecipe): @@ -17,13 +17,16 @@ class IrishIndependent(BasicNewsRecipe): remove_tags_before = dict(id='article') remove_tags_after = [dict(name='div', attrs={'class': 'toolsBottom'})] no_stylesheets = True + keep_only_tags = [ + classes('n-content1 n-content2 n-content3'), + ] + remove_tags_after = classes('quick-subscribe') remove_tags = [ - dict(name='div', attrs={'class': 'toolsBottom'}), - dict(name='div', attrs={'class': 'toolsTop'}), - dict(name='div', attrs={'class': 'boxRelated'}), - dict(name='div', attrs={'class': 'section first'}), - dict(name='div', attrs={'class': 'tabIt'}), - dict(name='div', attrs={'class': 'inner'}) + classes('icon1 icon-close c-lightbox1-side c-socials1 social-embed-consent-wall n-split1-side c-footer1'), + dict(attrs={'data-ad-slot': True}), + dict(attrs={'data-lightbox': True}), + dict(name='form'), + dict(attrs={'data-urn': lambda x: x and ':video:' in x}), ] feeds = [ @@ -40,6 +43,7 @@ class IrishIndependent(BasicNewsRecipe): (u'Weather', u'http://www.independent.ie/weather/rss') ] -# If text only articles are desired -# def print_version(self, url): -# return '%s?service=Print' % url + def preprocess_html(self, soup): + for img in soup.findAll(attrs={'data-src': True}): + img['src'] = img['data-src'] + return soup diff --git a/recipes/irish_times.recipe b/recipes/irish_times.recipe index f4771a9bbc..31732eec2e 100644 --- a/recipes/irish_times.recipe +++ b/recipes/irish_times.recipe @@ -36,7 +36,8 @@ class IrishTimes(BasicNewsRecipe): classes('lead-art-wrapper article-body-wrapper'), ] remove_tags = [ - dict(name='button') + dict(name='button'), + classes('sm-promo-headline'), ] remove_attributes = ['width', 'height'] From 7a829f484afda16992326f3bb7080a8cd6b1d0a9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 7 Jan 2023 13:39:35 +0530 Subject: [PATCH 0084/2055] Windows MTP device driver: Ignore failure to enumerate objects inside non-root folders There are apparently a lot of devices out there that fail in this way. So rather than aborting the scan simply ignore the folder. --- src/calibre/devices/mtp/windows/content_enumeration.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/mtp/windows/content_enumeration.cpp b/src/calibre/devices/mtp/windows/content_enumeration.cpp index 411d5dc210..3c1b8503eb 100644 --- a/src/calibre/devices/mtp/windows/content_enumeration.cpp +++ b/src/calibre/devices/mtp/windows/content_enumeration.cpp @@ -320,6 +320,7 @@ find_objects_in(CComPtr &content, CComPtr children; HRESULT hr = S_OK, hr2 = S_OK; + *enum_failed = false; Py_BEGIN_ALLOW_THREADS; hr = content->EnumObjects(0, parent_id, NULL, &children); @@ -338,7 +339,6 @@ find_objects_in(CComPtr &content, CComPtr Date: Sun, 8 Jan 2023 10:32:43 +0530 Subject: [PATCH 0085/2055] Fix #1814 (Update choose_library.ui) --- src/calibre/gui2/dialogs/choose_library.py | 21 +-- src/calibre/gui2/dialogs/choose_library.ui | 151 +++++++++++---------- 2 files changed, 91 insertions(+), 81 deletions(-) diff --git a/src/calibre/gui2/dialogs/choose_library.py b/src/calibre/gui2/dialogs/choose_library.py index 5771e41704..f03e1836c3 100644 --- a/src/calibre/gui2/dialogs/choose_library.py +++ b/src/calibre/gui2/dialogs/choose_library.py @@ -5,17 +5,17 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, errno -from threading import Thread, Event - -from qt.core import QDialog, QTimer, Qt, pyqtSignal +import errno +import os +from qt.core import QDialog, Qt, QTimer, pyqtSignal +from threading import Event, Thread +from calibre import force_unicode, isbytestring, patheq +from calibre.constants import filesystem_encoding, get_portable_base, iswindows +from calibre.gui2 import choose_dir, error_dialog from calibre.gui2.dialogs.choose_library_ui import Ui_Dialog from calibre.gui2.dialogs.progress import ProgressDialog as PD -from calibre.gui2 import error_dialog, choose_dir -from calibre.constants import (filesystem_encoding, iswindows, - get_portable_base) -from calibre import isbytestring, patheq, force_unicode +from calibre.utils.localization import localize_user_manual_link class ProgressDialog(PD): @@ -43,8 +43,11 @@ class ProgressDialog(PD): class ChooseLibrary(QDialog, Ui_Dialog): def __init__(self, db, callback, parent): - QDialog.__init__(self, parent) + super().__init__(parent) self.setupUi(self) + self.nas_warning.setText(self.nas_warning.text().format(localize_user_manual_link( + 'https://manual.calibre-ebook.com/faq.html#i-am-getting-errors-with-my-calibre-library-on-a-networked-drive-nas'))) + self.nas_warning.setOpenExternalLinks(True) self.db = db self.new_db = None self.callback = callback diff --git a/src/calibre/gui2/dialogs/choose_library.ui b/src/calibre/gui2/dialogs/choose_library.ui index b2e2f47ad5..7cef13cd49 100644 --- a/src/calibre/gui2/dialogs/choose_library.ui +++ b/src/calibre/gui2/dialogs/choose_library.ui @@ -28,66 +28,7 @@ - - - - New &location: - - - location - - - - - - - Use the previously &existing library at the new location - - - true - - - - - - - - - &Create an empty library at the new location - - - - - - - Copy the custom columns, saved searches, column widths, plugboards, -user categories, and other information from the old to the new library - - - &Copy structure from the current library - - - - - - - - - &Move the current library to new location - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - Qt::Vertical @@ -100,19 +41,6 @@ user categories, and other information from the old to the new library - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -137,6 +65,55 @@ user categories, and other information from the old to the new library + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Use the previously &existing library at the new location + + + true + + + + + + + + + &Create an empty library at the new location + + + + + + + Copy the custom columns, saved searches, column widths, plugboards, +user categories, and other information from the old to the new library + + + &Copy structure from the current library + + + + + + + + + &Move the current library to new location + + + @@ -147,6 +124,36 @@ user categories, and other information from the old to the new library + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + New &location: + + + location + + + + + + + Note that putting the calibre library on a Networked drive <a href="{}">is not safe</a>. + + + From 0ebd840d6abdc8fc38398c275321fa140c37b774 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 8 Jan 2023 13:38:33 +0530 Subject: [PATCH 0086/2055] Update India Today Outlook Magazine and Live Mint --- recipes/india_today.recipe | 15 ++++----------- recipes/livemint.recipe | 12 ++++++++---- recipes/outlook_india.recipe | 21 +++++++++++++++------ 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/recipes/india_today.recipe b/recipes/india_today.recipe index 33dfa40716..957187c015 100644 --- a/recipes/india_today.recipe +++ b/recipes/india_today.recipe @@ -63,11 +63,10 @@ class IndiaToday(BasicNewsRecipe): sections = {} date = soup.find(attrs={'class':lambda x: x and x.startswith('MagazineEdition_edition__date')}) - edition = soup.find(attrs={'class':lambda x: x and x.startswith('MagazineEdition_magazineprime')}) - self.timefmt =' (' + self.tag_to_string(edition) + ') [' + self.tag_to_string(date).strip() + ']' - p = edition.findNext('p') - if p: - self.description = self.tag_to_string(p).strip() + edition = soup.find(attrs={'class':'prime__magazine'}) + self.timefmt = '(' + self.tag_to_string(edition).strip() +') [' + self.tag_to_string(date).strip() + ']' + if p := edition.findNext('p'): + self.description = self.tag_to_string(p) self.log('Downloading Issue: ', self.timefmt) for tag in soup.findAll('div', attrs={'class': lambda x: x and 'NoCard_story__grid__' in x}): @@ -125,11 +124,5 @@ class IndiaToday(BasicNewsRecipe): quo.name = 'blockquote' return soup - def populate_article_metadata(self, article, soup, first): - if first and hasattr(self, 'add_toc_thumbnail'): - image = soup.find('img', src=True, attrs={'class':'i-amphtml-fill-content'}) - if image is not None: - self.add_toc_thumbnail(article, image['src']) - def print_version(self, url): return url.replace('.in/','.in/amp/') diff --git a/recipes/livemint.recipe b/recipes/livemint.recipe index a112968816..520d9bd54b 100644 --- a/recipes/livemint.recipe +++ b/recipes/livemint.recipe @@ -24,9 +24,14 @@ class LiveMint(BasicNewsRecipe): remove_empty_feeds = True - if is_saturday: + def get_cover_url(self): + soup = self.index_to_soup( + 'https://www.magzter.com/IN/HT-Digital-Streams-Ltd./Mint-Mumbai/Newspaper/' + ) + for citem in soup.findAll('meta', content=lambda s: s and s.endswith('view/3.jpg')): + return citem['content'] - cover_url = 'https://epsfs.hindustantimes.com/MINT/2022/04/16/Delhi/Delhi/5_01/bf867ea1_01_mr.jpg' + if is_saturday: keep_only_tags = [ dict(name='h1'), @@ -54,14 +59,13 @@ class LiveMint(BasicNewsRecipe): img['src'] = img['data-img'] return soup else: - # some wsj articles wont load + extra_css = ''' #img-cap {font-size:small; text-align:center;} #auth-info {font-size:small; text-align:center;} .highlights {font-style:italic;} .summary{font-style:italic; color:#404040;} ''' - cover_url = 'https://epsfs.hindustantimes.com/MINT/2022/04/05/Delhi/Delhi/5_01/1ec7ad14_01_mr.jpg' keep_only_tags = [ dict(name='h1'), diff --git a/recipes/outlook_india.recipe b/recipes/outlook_india.recipe index 8c4f5bb7e1..f88714f386 100644 --- a/recipes/outlook_india.recipe +++ b/recipes/outlook_india.recipe @@ -18,13 +18,17 @@ class outlook(BasicNewsRecipe): remove_attributes = ['height', 'width', 'style'] ignore_duplicate_articles = {'url'} resolve_internal_links = True - masthead_url = 'https://www.outlookindia.com/images/home_new_v4/logo_outlook.svg' + extra_css = ''' + .story-summary{font-style:italic; color:#202020;} + .author_wrapper, .relatedCategory{font-size:small; color:#404040;} + #figcap{font-size:small; text-align:center;} + ''' keep_only_tags = [classes('__story_detail')] remove_tags = [ classes( - 'social_sharing_article left_trending left-sticky __tag_links' - ' next_prev_stories downarrow uparrow more_from_author_links next prev __related_stories_thumbs' + 'social_sharing_article left_trending left-sticky __tag_links next_prev_stories ' + 'downarrow uparrow more_from_author_links next prev __related_stories_thumbs' ) ] @@ -33,8 +37,8 @@ class outlook(BasicNewsRecipe): div = soup.find('div', attrs={'class':'wrapper'}) a = div.find('a', href=lambda x: x and x.startswith('/magazine/issue/')) url = a['href'] - self.log('Downloading issue:', url) - self.timefmt = ' [' + self.tag_to_string(a) + ']' + self.timefmt = ' [' + self.tag_to_string(a.find('p')).strip() + ']' + self.log('Downloading issue:', url, self.timefmt) soup = self.index_to_soup('https://www.outlookindia.com' + url) cover = soup.find(**classes('listingPage_lead_story')) self.cover_url = cover.find('img', attrs={'src': True})['src'] @@ -42,7 +46,7 @@ class outlook(BasicNewsRecipe): for h3 in soup.findAll(['h3', 'h4'], attrs={'class': 'tk-kepler-std-condensed-subhead'}): - a = h3.find('a', href=lambda x: x) + a = h3.find('a', href=True) url = a['href'] title = self.tag_to_string(a) desc = '' @@ -55,6 +59,11 @@ class outlook(BasicNewsRecipe): ans.append({'title': title, 'url': url, 'description': desc}) return [('Articles', ans)] + def preprocess_html(self,soup): + for fig in soup.findAll('figure'): + fig['id'] = 'figcap' + return soup + def preprocess_raw_html(self, raw, *a): return raw m = re.search('.*?script.*?>', raw, flags=re.DOTALL) From ff1952b8b785770289b918462dfb408fe10b38dc Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 8 Jan 2023 11:26:46 +0000 Subject: [PATCH 0087/2055] Bug #2002195: Search breaks if non-number used in numeric-sorted column. This fix is really an enhancement, adding better error presentation to the GUI search box. --- src/calibre/db/search.py | 55 +++++++++++++++++----------------- src/calibre/gui2/layout.py | 5 ++++ src/calibre/gui2/search_box.py | 11 +++++++ 3 files changed, 44 insertions(+), 27 deletions(-) diff --git a/src/calibre/db/search.py b/src/calibre/db/search.py index afbbe7de1e..efb507c4d7 100644 --- a/src/calibre/db/search.py +++ b/src/calibre/db/search.py @@ -56,36 +56,36 @@ def _match(query, value, matchkind, use_primary_find_in_search=True, case_sensit else: internal_match_ok = False for t in value: - try: # ignore regexp exceptions, required because search-ahead tries before typing is finished - if not case_sensitive: - t = icu_lower(t) - if (matchkind == EQUALS_MATCH): - if internal_match_ok: - if query == t: - return True - return sq in [c.strip() for c in t.split('.') if c.strip()] - elif query[0] == '.': - if t.startswith(query[1:]): - ql = len(query) - 1 - if (len(t) == ql) or (t[ql:ql+1] == '.'): - return True - elif query == t: + if not case_sensitive: + t = icu_lower(t) + if (matchkind == EQUALS_MATCH): + if internal_match_ok: + if query == t: return True - elif matchkind == REGEXP_MATCH: - flags = regex.UNICODE | regex.VERSION1 | regex.FULLCASE | (0 if case_sensitive else regex.IGNORECASE) + return sq in [c.strip() for c in t.split('.') if c.strip()] + elif query[0] == '.': + if t.startswith(query[1:]): + ql = len(query) - 1 + if (len(t) == ql) or (t[ql:ql+1] == '.'): + return True + elif query == t: + return True + elif matchkind == REGEXP_MATCH: + flags = regex.UNICODE | regex.VERSION1 | regex.FULLCASE | (0 if case_sensitive else regex.IGNORECASE) + try: if regex.search(query, t, flags) is not None: return True - elif matchkind == ACCENT_MATCH: - if primary_contains(query, t): + except regex.error as e: + raise ParseException(_('Invalid regular expression: {}').format(str(e))) + elif matchkind == ACCENT_MATCH: + if primary_contains(query, t): + return True + elif matchkind == CONTAINS_MATCH: + if not case_sensitive and use_primary_find_in_search: + if primary_no_punc_contains(query, t): return True - elif matchkind == CONTAINS_MATCH: - if not case_sensitive and use_primary_find_in_search: - if primary_no_punc_contains(query, t): - return True - elif query in t: - return True - except regex.error: - pass + elif query in t: + return True return False # }}} @@ -298,7 +298,8 @@ class NumericSearch: # {{{ try: v = cast(val) except Exception: - v = None + raise ParseException( + _('Non-numeric value in column {0}: {1}').format(location, val)) if v: v = adjust(v) if relop(v, q): diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index da49012515..51e8145548 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -242,6 +242,11 @@ class SearchBar(QFrame): # {{{ _('Advanced search'), default_keys=("Shift+Ctrl+F",), action=ac) + # This error icon will be placed after the clear button icon + parent.search.parse_error_action = ac = parent.search.add_action('dialog_error.png', QLineEdit.ActionPosition.TrailingPosition) + parent.addAction(ac) + ac.setVisible(False) + self.search_button = QToolButton() self.search_button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextOnly) self.search_button.setIcon(QIcon.ic('search.png')) diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 7f26d8c0d0..5a42cd62f7 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -142,6 +142,7 @@ class SearchBox2(QComboBox): # {{{ self.setMinimumContentsLength(25) self._in_a_search = False self.tool_tip_text = self.toolTip() + self.parse_error_action = None def add_action(self, icon, position=QLineEdit.ActionPosition.TrailingPosition): if not isinstance(icon, QIcon): @@ -180,6 +181,7 @@ class SearchBox2(QComboBox): # {{{ return self.currentText() def clear(self, emit_search=True): + self.show_parse_error_action(False) self.normalize_state() self.setEditText('') if emit_search: @@ -191,9 +193,17 @@ class SearchBox2(QComboBox): # {{{ self.clear() self.setFocus(Qt.FocusReason.OtherFocusReason) + def show_parse_error_action(self, to_show, tooltip=''): + try: + self.parse_error_action.setVisible(to_show) + self.parse_error_action.setToolTip(tooltip) + except Exception: + pass + def search_done(self, ok): if isinstance(ok, string_or_bytes): self.setToolTip(ok) + self.show_parse_error_action(True, tooltip=ok) ok = False if not str(self.currentText()).strip(): self.clear(emit_search=False) @@ -223,6 +233,7 @@ class SearchBox2(QComboBox): # {{{ # Comes from the combobox itself def keyPressEvent(self, event): + self.show_parse_error_action(False) k = event.key() if k in (Qt.Key.Key_Enter, Qt.Key.Key_Return): return self.do_search() From 883acf589a53854da11293e78f108f39c806db9d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 8 Jan 2023 21:18:41 +0530 Subject: [PATCH 0088/2055] ... --- src/calibre/gui2/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index a67dc74680..151f5b59ab 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -1292,8 +1292,8 @@ class Application(QApplication): return ans def stylesheet_for_line_edit(self, is_error=False): - return 'QLineEdit { border: 2px solid %s; border-radius: 3px }' % ( - '#FF2400' if is_error else '#50c878') + col = '#FF2400' if is_error else '#50c878' + return f'QLineEdit {{ border: 2px solid {col}; border-radius: 3px }}' def _send_file_open_events(self): with self._file_open_lock: From aa13031cc010f3bb8814cf0574fa9e9201dc1b09 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Sun, 8 Jan 2023 16:03:06 +0000 Subject: [PATCH 0089/2055] Sorry, missed a place where I needed to clear the search error icon. --- src/calibre/gui2/search_box.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 5a42cd62f7..4f2b5dc3ff 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -176,6 +176,7 @@ class SearchBox2(QComboBox): # {{{ def normalize_state(self): self.setToolTip(self.tool_tip_text) self.line_edit.setStyleSheet('') + self.show_parse_error_action(False) def text(self): return self.currentText() From f7f0cf059f5d272560aa6e59f1c621f4047bccc7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 9 Jan 2023 18:09:06 +0530 Subject: [PATCH 0090/2055] Bump Qt version to 6.4.2 --- bypy/sources.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/bypy/sources.json b/bypy/sources.json index c69d4ba43d..3ecadf8980 100644 --- a/bypy/sources.json +++ b/bypy/sources.json @@ -478,51 +478,51 @@ { "name": "qt-base", - "version": "6.3.1", + "version": "6.4.2", "hashes": { - "unix": "sha256:0a64421d9c2469c2c48490a032ab91d547017c9cc171f3f8070bc31888f24e03" + "unix": "sha256:a88bc6cedbb34878a49a622baa79cace78cfbad4f95fdbd3656ddb21c705525d" } }, { "name": "qt-svg", "hashes": { - "unix": "sha256:7b19f418e6f7b8e23344082dd04440aacf5da23c5a73980ba22ae4eba4f87df7" + "unix": "sha256:b746af3cb1793621d8ed7eae38d9ad5a15541dc2742031069f2ae3fe87590314" } }, { "name": "qt-shadertools", "hashes": { - "unix": "sha256:59b77176961528cc7b0c9325134655e273aa87b4cb386c0f4683d8f2852e435a" + "unix": "sha256:fa65bff84d4e9c2cb4cbf6fb098207e0e23d863dbe675eb277034a29c226a217" } }, { "name": "qt-declarative", "hashes": { - "unix": "sha256:03e7694123820fcca397f95ce312e0b7f3039493c8754c836da098a1a04346e8" + "unix": "sha256:a4bdd983de4e9cbca0f85b767dbdd8598711554e370a06da8f509ded4430f5bd" } }, { "name": "qt-imageformats", "hashes": { - "unix": "sha256:ad0312b8dfbbb67f729bfadbfcd47246ee4a128b717731ba158c41d01fde212f" + "unix": "sha256:fc5f999ae0779a67d5507956d4dd315386eb81cf6ccba632de039bb9eee11707" } }, { "name": "qt-webchannel", "hashes": { - "unix": "sha256:aaa20ac23f86992721b7ee487c379a3fd68caa8cdcea0a77a37e0d8b47ff2668" + "unix": "sha256:06657b2b2509f26c733b7c40da0dbb8571a215b97f99685a6fc3bc51dcbebd87" } }, { "name": "qt-positioning", "hashes": { - "unix": "sha256:06788e4ecae1920094b30e4046f0abd23c8189d8a51e9c939b02f0b6abe2e86c" + "unix": "sha256:7f01baf5ba877af5b211c9d32e6075019f00d9d7a2ba81bb0f10ca759e9aef82" } }, @@ -530,21 +530,21 @@ "name": "qt-wayland", "os": "linux", "hashes": { - "unix": "sha256:6f14fea2d172a5b4170be3efcb0e58535f6605b61bcd823f6d5c9d165bb8c0f0" + "unix": "sha256:24cf1a0af751ab1637b4815d5c5f3704483d5fa7bedbd3519e6fc020d8be135f" } }, { "name": "qt-sensors", "hashes": { - "unix": "sha256:4b240b59edba9a42b4735758a25f279a26841b982864e7b38f6ef0b81e0d60cc" + "unix": "sha256:455619ff28a39f4caba49c9e1952fbcfafc8ffc893b437d653d5465a077ee656" } }, { "name": "qt-webengine", "hashes": { - "unix": "sha256:ad7a33b21a956deda37c587d50f821ca3816403ae31ba9b5d59d01561ad66e47" + "unix": "sha256:ffa945518d1cc8d9ee73523e8d9c2090844f5a2d9c7eac05c4ad079472a119c9" } }, From d7c31f4a81620f09d4896d05c1f179c479c64986 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 9 Jan 2023 18:13:56 +0530 Subject: [PATCH 0091/2055] Automated fixes from ruff --- bypy/macos/__main__.py | 3 +- setup/linux-installer.py | 3 +- setup/plugins_mirror.py | 3 +- src/calibre/__init__.py | 3 +- src/calibre/customize/__init__.py | 2 +- src/calibre/customize/ui.py | 3 +- src/calibre/db/adding.py | 15 +++++--- src/calibre/db/backend.py | 6 ++-- src/calibre/db/fields.py | 3 +- src/calibre/db/lazy.py | 3 +- src/calibre/db/legacy.py | 3 +- src/calibre/db/search.py | 24 ++++++++----- src/calibre/db/tests/filesystem.py | 3 +- src/calibre/db/tests/legacy.py | 9 +++-- src/calibre/db/tests/reading.py | 6 ++-- src/calibre/db/tests/writing.py | 17 +++++---- src/calibre/db/utils.py | 3 +- src/calibre/db/write.py | 6 ++-- src/calibre/devices/android/driver.py | 1 - src/calibre/ebooks/chardet.py | 6 ++-- src/calibre/ebooks/comic/input.py | 6 ++-- .../ebooks/conversion/plugins/html_output.py | 3 +- src/calibre/ebooks/djvu/djvubzzdec.py | 6 ++-- src/calibre/ebooks/docx/fields.py | 9 +++-- src/calibre/ebooks/docx/writer/styles.py | 3 +- src/calibre/ebooks/epub/cfi/parse.py | 3 +- src/calibre/ebooks/lrf/html/convert_from.py | 3 +- src/calibre/ebooks/metadata/book/base.py | 6 ++-- src/calibre/ebooks/metadata/lrx.py | 3 +- src/calibre/ebooks/metadata/opf2.py | 3 +- src/calibre/ebooks/metadata/rb.py | 3 +- src/calibre/ebooks/metadata/sources/amazon.py | 3 +- .../ebooks/metadata/sources/identify.py | 3 +- src/calibre/ebooks/metadata/xmp.py | 3 +- src/calibre/ebooks/mobi/debug/mobi8.py | 3 +- src/calibre/ebooks/mobi/reader/mobi6.py | 3 +- src/calibre/ebooks/mobi/utils.py | 3 +- src/calibre/ebooks/mobi/writer8/header.py | 9 +++-- src/calibre/ebooks/mobi/writer8/index.py | 3 +- src/calibre/ebooks/oeb/polish/main.py | 3 +- src/calibre/ebooks/oeb/polish/utils.py | 6 ++-- src/calibre/ebooks/rtf2xml/paragraph_def.py | 2 -- src/calibre/ebooks/rtf2xml/tokenize.py | 1 - .../gui2/device_drivers/configwidget.py | 12 ++++--- .../device_drivers/tabbed_device_config.py | 12 ++++--- src/calibre/gui2/dialogs/custom_recipes.py | 3 +- src/calibre/gui2/icon_theme.py | 3 +- src/calibre/gui2/lrf_renderer/text.py | 12 ++++--- src/calibre/gui2/metadata/diff.py | 3 +- src/calibre/gui2/metadata/single_download.py | 6 ++-- src/calibre/gui2/tag_browser/ui.py | 3 +- src/calibre/gui2/tweak_book/diff/highlight.py | 3 +- src/calibre/gui2/tweak_book/diff/main.py | 3 +- .../gui2/tweak_book/editor/smarts/html.py | 3 +- .../gui2/tweak_book/editor/smarts/python.py | 3 +- .../gui2/tweak_book/editor/smarts/utils.py | 3 +- .../gui2/tweak_book/editor/snippets.py | 9 +++-- .../gui2/tweak_book/editor/syntax/base.py | 6 ++-- src/calibre/gui2/tweak_book/live_css.py | 3 +- src/calibre/gui2/tweak_book/widgets.py | 3 +- src/calibre/gui2/viewer/bookmarks.py | 3 +- src/calibre/gui2/viewer/search.py | 6 ++-- src/calibre/library/caches.py | 35 ++++++++++++------- .../library/catalogs/epub_mobi_builder.py | 2 +- src/calibre/library/database2.py | 27 +++++++++----- src/calibre/linux.py | 3 +- src/calibre/spell/import_from.py | 3 +- src/calibre/utils/bibtex.py | 1 - src/calibre/utils/date.py | 3 +- src/calibre/utils/icu.py | 3 +- src/calibre/utils/icu_test.py | 3 +- src/calibre/utils/search_query_parser_test.py | 6 ++-- src/calibre/utils/wmf/parse.py | 1 - src/css_selectors/tests.py | 3 +- 74 files changed, 260 insertions(+), 141 deletions(-) diff --git a/bypy/macos/__main__.py b/bypy/macos/__main__.py index 3627540407..497ea0828f 100644 --- a/bypy/macos/__main__.py +++ b/bypy/macos/__main__.py @@ -810,7 +810,8 @@ def main(args, ext_dir, test_runner): build_dir = abspath(join(mkdtemp('frozen-'), APPNAME + '.app')) inc_dir = abspath(mkdtemp('include')) if args.skip_tests: - test_runner = lambda *a: None + def test_runner(*a): + return None Freeze(build_dir, ext_dir, inc_dir, test_runner, dont_strip=args.dont_strip, sign_installers=args.sign_installers, notarize=args.notarize) diff --git a/setup/linux-installer.py b/setup/linux-installer.py index 26ad07c26c..a7a4c1b4ce 100644 --- a/setup/linux-installer.py +++ b/setup/linux-installer.py @@ -40,7 +40,8 @@ if py3: from urllib.parse import urlparse from urllib.request import BaseHandler, build_opener, Request, urlopen, getproxies, addinfourl import http.client as httplib - encode_for_subprocess = lambda x: x + def encode_for_subprocess(x): + return x else: from future_builtins import map from urlparse import urlparse diff --git a/setup/plugins_mirror.py b/setup/plugins_mirror.py index ea580e78bf..53b7c0cb24 100644 --- a/setup/plugins_mirror.py +++ b/setup/plugins_mirror.py @@ -156,7 +156,8 @@ def load_plugins_index(): def convert_node(fields, x, names={}, import_data=None): name = x.__class__.__name__ - conv = lambda x:convert_node(fields, x, names=names, import_data=import_data) + def conv(x): + return convert_node(fields, x, names=names, import_data=import_data) if name == 'Str': return x.s.decode('utf-8') if isinstance(x.s, bytes) else x.s elif name == 'Num': diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index bea87b67ef..8be1057beb 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -77,7 +77,8 @@ def to_unicode(raw, encoding='utf-8', errors='strict'): def patheq(p1, p2): p = os.path - d = lambda x : p.normcase(p.normpath(p.realpath(p.normpath(x)))) + def d(x): + return p.normcase(p.normpath(p.realpath(p.normpath(x)))) if not p1 or not p2: return False return d(p1) == d(p2) diff --git a/src/calibre/customize/__init__.py b/src/calibre/customize/__init__.py index d92d29ba64..0863226e05 100644 --- a/src/calibre/customize/__init__.py +++ b/src/calibre/customize/__init__.py @@ -552,7 +552,7 @@ class CatalogPlugin(Plugin): # {{{ from calibre.customize.ui import config from calibre.ptempfile import PersistentTemporaryDirectory - if not type(self) in builtin_plugins and self.name not in config['disabled_plugins']: + if type(self) not in builtin_plugins and self.name not in config['disabled_plugins']: files_to_copy = [f"{self.name.lower()}.{ext}" for ext in ["ui","py"]] resources = zipfile.ZipFile(self.plugin_path,'r') diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index 1378237326..3d77efd9d2 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -180,7 +180,8 @@ def _run_filetype_plugins(path_to_file, ft=None, occasion='preprocess'): print('Running file type plugin %s failed with traceback:'%plugin.name, file=oe) traceback.print_exc(file=oe) sys.stdout, sys.stderr = oo, oe - x = lambda j: os.path.normpath(os.path.normcase(j)) + def x(j): + return os.path.normpath(os.path.normcase(j)) if occasion == 'postprocess' and x(nfp) != x(path_to_file): shutil.copyfile(nfp, path_to_file) nfp = path_to_file diff --git a/src/calibre/db/adding.py b/src/calibre/db/adding.py index 3b14633078..372b19e936 100644 --- a/src/calibre/db/adding.py +++ b/src/calibre/db/adding.py @@ -41,18 +41,23 @@ def compile_rule(rule): if 'with' in mt: q = icu_lower(rule['query']) if 'startswith' in mt: - func = lambda filename: icu_lower(filename).startswith(q) + def func(filename): + return icu_lower(filename).startswith(q) else: - func = lambda filename: icu_lower(filename).endswith(q) + def func(filename): + return icu_lower(filename).endswith(q) elif 'glob' in mt: q = compile_glob(rule['query']) - func = lambda filename: q.match(filename) is not None + def func(filename): + return (q.match(filename) is not None) else: q = re.compile(rule['query']) - func = lambda filename: q.match(filename) is not None + def func(filename): + return (q.match(filename) is not None) ans = func if mt.startswith('not_'): - ans = lambda filename: not func(filename) + def ans(filename): + return (not func(filename)) return ans, rule['action'] == 'add' diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 12d82963b7..0cd4a35db3 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1262,7 +1262,8 @@ class DB: import codecs from apsw import Shell if callback is None: - callback = lambda x: x + def callback(x): + return x uv = int(self.user_version) with TemporaryFile(suffix='.sql') as fname: @@ -1587,7 +1588,8 @@ class DB: def compress_covers(self, path_map, jpeg_quality, progress_callback): cpath_map = {} if not progress_callback: - progress_callback = lambda book_id, old_sz, new_sz: None + def progress_callback(book_id, old_sz, new_sz): + return None for book_id, path in path_map.items(): path = os.path.abspath(os.path.join(self.library_path, path, 'cover.jpg')) try: diff --git a/src/calibre/db/fields.py b/src/calibre/db/fields.py index 5e0a947869..49da62475d 100644 --- a/src/calibre/db/fields.py +++ b/src/calibre/db/fields.py @@ -47,7 +47,8 @@ def numeric_sort_key(defval, x): return x if type(x) in (int, float) else defval -IDENTITY = lambda x: x +def IDENTITY(x): + return x class InvalidLinkTable(Exception): diff --git a/src/calibre/db/lazy.py b/src/calibre/db/lazy.py index 36040a6ec2..617c590ecf 100644 --- a/src/calibre/db/lazy.py +++ b/src/calibre/db/lazy.py @@ -223,7 +223,8 @@ def has_cover_getter(dbref, book_id, cache): return ret -fmt_custom = lambda x:list(x) if isinstance(x, tuple) else x +def fmt_custom(x): + return (list(x) if isinstance(x, tuple) else x) def custom_getter(field, dbref, book_id, cache): diff --git a/src/calibre/db/legacy.py b/src/calibre/db/legacy.py index a17e15ab85..7567050030 100644 --- a/src/calibre/db/legacy.py +++ b/src/calibre/db/legacy.py @@ -181,7 +181,8 @@ class LibraryDatabase: self.is_second_db = is_second_db if progress_callback is None: - progress_callback = lambda x, y:True + def progress_callback(x, y): + return True self.listeners = set() backend = self.backend = create_backend(library_path, default_prefs=default_prefs, diff --git a/src/calibre/db/search.py b/src/calibre/db/search.py index f4b8e8da41..8e7b4b106d 100644 --- a/src/calibre/db/search.py +++ b/src/calibre/db/search.py @@ -237,9 +237,11 @@ class NumericSearch: # {{{ dt = datatype if is_many and query in {'true', 'false'}: - valcheck = lambda x: True + def valcheck(x): + return True if datatype == 'rating': - valcheck = lambda x: x is not None and x > 0 + def valcheck(x): + return (x is not None and x > 0) found = set() for val, book_ids in field_iter(): if valcheck(val): @@ -248,14 +250,18 @@ class NumericSearch: # {{{ if query == 'false': if location == 'cover': - relop = lambda x,y: not bool(x) + def relop(x, y): + return (not bool(x)) else: - relop = lambda x,y: x is None + def relop(x, y): + return (x is None) elif query == 'true': if location == 'cover': - relop = lambda x,y: bool(x) + def relop(x, y): + return bool(x) else: - relop = lambda x,y: x is not None + def relop(x, y): + return (x is not None) else: for k, relop in iteritems(self.operators): if query.startswith(k): @@ -265,8 +271,10 @@ class NumericSearch: # {{{ relop = self.operators['='] if dt == 'rating': - cast = lambda x: 0 if x is None else int(x) - adjust = lambda x: x // 2 + def cast(x): + return (0 if x is None else int(x)) + def adjust(x): + return (x // 2) else: # Datatype is empty if the source is a template. Assume float cast = float if dt in ('float', 'composite', 'half-rating', '') else int diff --git a/src/calibre/db/tests/filesystem.py b/src/calibre/db/tests/filesystem.py index e0703c1d19..363aa5af5c 100644 --- a/src/calibre/db/tests/filesystem.py +++ b/src/calibre/db/tests/filesystem.py @@ -186,7 +186,8 @@ class FilesystemTest(BaseTest): def test_find_books_in_directory(self): from calibre.db.adding import find_books_in_directory, compile_rule - strip = lambda files: frozenset({os.path.basename(x) for x in files}) + def strip(files): + return frozenset({os.path.basename(x) for x in files}) def q(one, two): one, two = {strip(a) for a in one}, {strip(b) for b in two} diff --git a/src/calibre/db/tests/legacy.py b/src/calibre/db/tests/legacy.py index b6dee63e4e..194f682b23 100644 --- a/src/calibre/db/tests/legacy.py +++ b/src/calibre/db/tests/legacy.py @@ -57,7 +57,8 @@ def run_funcs(self, db, ndb, funcs): if callable(meth): meth(*args) else: - fmt = lambda x:x + def fmt(x): + return x if meth[0] in {'!', '@', '#', '+', '$', '-', '%'}: if meth[0] != '+': fmt = {'!':dict, '@':lambda x:frozenset(x or ()), '#':lambda x:set((x or '').split(',')), @@ -256,12 +257,14 @@ class LegacyTest(BaseTest): 'books_in_series_of':[(0,), (1,), (2,)], 'books_with_same_title':[(Metadata(db.title(0)),), (Metadata(db.title(1)),), (Metadata('1234'),)], }): - fmt = lambda x: x + def fmt(x): + return x if meth[0] in {'!', '@'}: fmt = {'!':dict, '@':frozenset}[meth[0]] meth = meth[1:] elif meth == 'get_authors_with_ids': - fmt = lambda val:{x[0]:tuple(x[1:]) for x in val} + def fmt(val): + return {x[0]: tuple(x[1:]) for x in val} for a in args: self.assertEqual(fmt(getattr(db, meth)(*a)), fmt(getattr(ndb, meth)(*a)), f'The method: {meth}() returned different results for argument {a}') diff --git a/src/calibre/db/tests/reading.py b/src/calibre/db/tests/reading.py index 8dc85f4c66..907844d0c8 100644 --- a/src/calibre/db/tests/reading.py +++ b/src/calibre/db/tests/reading.py @@ -612,9 +612,11 @@ class ReadingTest(BaseTest): self.assertSetEqual(set(mi.custom_field_keys()), set(pmi.custom_field_keys())) for field in STANDARD_METADATA_FIELDS | {'#series_index'}: - f = lambda x: x + def f(x): + return x if field == 'formats': - f = lambda x: x if x is None else tuple(x) + def f(x): + return (x if x is None else tuple(x)) self.assertEqual(f(getattr(mi, field)), f(getattr(pmi, field)), f'Standard field: {field} not the same for book {book_id}') self.assertEqual(mi.format_field(field), pmi.format_field(field), diff --git a/src/calibre/db/tests/writing.py b/src/calibre/db/tests/writing.py index 014a1eb970..5dbfe5c2ef 100644 --- a/src/calibre/db/tests/writing.py +++ b/src/calibre/db/tests/writing.py @@ -23,20 +23,23 @@ class WritingTest(BaseTest): def create_getter(self, name, getter=None): if getter is None: if name.endswith('_index'): - ans = lambda db:partial(db.get_custom_extra, index_is_id=True, - label=name[1:].replace('_index', '')) + def ans(db): + return partial(db.get_custom_extra, index_is_id=True, label=name[1:].replace('_index', '')) else: - ans = lambda db:partial(db.get_custom, label=name[1:], - index_is_id=True) + def ans(db): + return partial(db.get_custom, label=name[1:], index_is_id=True) else: - ans = lambda db:partial(getattr(db, getter), index_is_id=True) + def ans(db): + return partial(getattr(db, getter), index_is_id=True) return ans def create_setter(self, name, setter=None): if setter is None: - ans = lambda db:partial(db.set_custom, label=name[1:], commit=True) + def ans(db): + return partial(db.set_custom, label=name[1:], commit=True) else: - ans = lambda db:partial(getattr(db, setter), commit=True) + def ans(db): + return partial(getattr(db, setter), commit=True) return ans def create_test(self, name, vals, getter=None, setter=None): diff --git a/src/calibre/db/utils.py b/src/calibre/db/utils.py index 2ac8a0e0a8..04dbc96ea5 100644 --- a/src/calibre/db/utils.py +++ b/src/calibre/db/utils.py @@ -408,7 +408,8 @@ def atof(string): def type_safe_sort_key_function(keyfunc=None): if keyfunc is None: - keyfunc = lambda x: x + def keyfunc(x): + return x sentinel = object() first_value = sentinel diff --git a/src/calibre/db/write.py b/src/calibre/db/write.py index 21965fab39..5f97dd80ce 100644 --- a/src/calibre/db/write.py +++ b/src/calibre/db/write.py @@ -164,11 +164,13 @@ def get_adapter(name, metadata): elif dt == 'comments': ans = single_text elif dt == 'rating': - ans = lambda x: None if x in {None, 0} else min(10, max(0, adapt_number(int, x))) + def ans(x): + return (None if x in {None, 0} else min(10, max(0, adapt_number(int, x)))) elif dt == 'enumeration': ans = single_text elif dt == 'composite': - ans = lambda x: x + def ans(x): + return x if name == 'title': return lambda x: ans(x) or _('Unknown') diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 268b2c3663..facd064009 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -48,7 +48,6 @@ class ANDROID(USBMS): 0x0cf5 : HTC_BCDS, 0x2910 : HTC_BCDS, 0xe77 : HTC_BCDS, - 0xff9 : HTC_BCDS, 0x0001 : [0x255], }, diff --git a/src/calibre/ebooks/chardet.py b/src/calibre/ebooks/chardet.py index afa0d9b4d8..01bc822dda 100644 --- a/src/calibre/ebooks/chardet.py +++ b/src/calibre/ebooks/chardet.py @@ -45,9 +45,11 @@ def strip_encoding_declarations(raw, limit=50*1024, preserve_newlines=False): is_binary = isinstance(raw, bytes) if preserve_newlines: if is_binary: - sub = lambda m: b'\n' * m.group().count(b'\n') + def sub(m): + return (b'\n' * m.group().count(b'\n')) else: - sub = lambda m: '\n' * m.group().count('\n') + def sub(m): + return ('\n' * m.group().count('\n')) else: sub = b'' if is_binary else '' for pat in lazy_encoding_pats(is_binary): diff --git a/src/calibre/ebooks/comic/input.py b/src/calibre/ebooks/comic/input.py index 1881916c89..5728f6699b 100644 --- a/src/calibre/ebooks/comic/input.py +++ b/src/calibre/ebooks/comic/input.py @@ -63,9 +63,11 @@ def find_pages(dir, sort_on_mtime=False, verbose=False): # levels, in which case simply use the filenames. basename = os.path.basename if len(sep_counts) > 1 else lambda x: x if sort_on_mtime: - key = lambda x:os.stat(x).st_mtime + def key(x): + return os.stat(x).st_mtime else: - key = lambda x:numeric_sort_key(basename(x)) + def key(x): + return numeric_sort_key(basename(x)) pages.sort(key=key) if verbose: diff --git a/src/calibre/ebooks/conversion/plugins/html_output.py b/src/calibre/ebooks/conversion/plugins/html_output.py index 19fc47bd9c..1694ef29ef 100644 --- a/src/calibre/ebooks/conversion/plugins/html_output.py +++ b/src/calibre/ebooks/conversion/plugins/html_output.py @@ -193,7 +193,8 @@ class HTMLOutput(OutputFormatPlugin): # render template templite = Templite(template_html_data) - toc = lambda: self.generate_html_toc(oeb_book, path, output_dir) + def toc(): + return self.generate_html_toc(oeb_book, path, output_dir) t = templite.render(ebookContent=ebook_content, prevLink=prevLink, nextLink=nextLink, has_toc=bool(oeb_book.toc.count()), toc=toc, diff --git a/src/calibre/ebooks/djvu/djvubzzdec.py b/src/calibre/ebooks/djvu/djvubzzdec.py index f5a0e9c06a..1e1e9eb3bd 100644 --- a/src/calibre/ebooks/djvu/djvubzzdec.py +++ b/src/calibre/ebooks/djvu/djvubzzdec.py @@ -500,8 +500,10 @@ class BZZDecoder(): # Decode mtfno = 3 markerpos = -1 - zc = lambda i: self.zpcodec_decode(self.ctx, i) - dc = lambda i, bits: self.decode_binary(self.ctx, i, bits) + def zc(i): + return self.zpcodec_decode(self.ctx, i) + def dc(i, bits): + return self.decode_binary(self.ctx, i, bits) for i in range(self.xsize): ctxid = CTXIDS - 1 if ctxid > mtfno: diff --git a/src/calibre/ebooks/docx/fields.py b/src/calibre/ebooks/docx/fields.py index a1da9b3fa5..95012eeb2c 100644 --- a/src/calibre/ebooks/docx/fields.py +++ b/src/calibre/ebooks/docx/fields.py @@ -247,7 +247,8 @@ def test_parse_fields(return_tests=False): class TestParseFields(unittest.TestCase): def test_hyperlink(self): - ae = lambda x, y: self.assertEqual(parse_hyperlink(x, None), y) + def ae(x, y): + return self.assertEqual(parse_hyperlink(x, None), y) ae(r'\l anchor1', {'anchor':'anchor1'}) ae(r'www.calibre-ebook.com', {'url':'www.calibre-ebook.com'}) ae(r'www.calibre-ebook.com \t target \o tt', {'url':'www.calibre-ebook.com', 'target':'target', 'title': 'tt'}) @@ -255,13 +256,15 @@ def test_parse_fields(return_tests=False): ae(r'xxxx \y yyyy', {'url': 'xxxx'}) def test_xe(self): - ae = lambda x, y: self.assertEqual(parse_xe(x, None), y) + def ae(x, y): + return self.assertEqual(parse_xe(x, None), y) ae(r'"some name"', {'text':'some name'}) ae(r'name \b \i', {'text':'name', 'bold':None, 'italic':None}) ae(r'xxx \y a', {'text':'xxx', 'yomi':'a'}) def test_index(self): - ae = lambda x, y: self.assertEqual(parse_index(x, None), y) + def ae(x, y): + return self.assertEqual(parse_index(x, None), y) ae(r'', {}) ae(r'\b \c 1', {'bookmark':None, 'columns-per-page': '1'}) diff --git a/src/calibre/ebooks/docx/writer/styles.py b/src/calibre/ebooks/docx/writer/styles.py index c91feb180a..b341475be4 100644 --- a/src/calibre/ebooks/docx/writer/styles.py +++ b/src/calibre/ebooks/docx/writer/styles.py @@ -64,7 +64,8 @@ class CombinedStyle: def serialize(self, styles, normal_style): makeelement = self.namespace.makeelement - w = lambda x: '{{{}}}{}'.format(self.namespace.namespaces['w'], x) + def w(x): + return '{{{}}}{}'.format(self.namespace.namespaces['w'], x) block = makeelement(styles, 'w:style', w_styleId=self.id, w_type='paragraph') makeelement(block, 'w:name', w_val=self.name) makeelement(block, 'w:qFormat') diff --git a/src/calibre/ebooks/epub/cfi/parse.py b/src/calibre/ebooks/epub/cfi/parse.py index e348098c11..e3ddc41de2 100644 --- a/src/calibre/ebooks/epub/cfi/parse.py +++ b/src/calibre/ebooks/epub/cfi/parse.py @@ -28,7 +28,8 @@ class Parser: # No leading zeros, except for numbers in (0, 1) and no trailing zeros for the fractional part frac = r'\.[0-9]*[1-9]' number = r'(?:[1-9][0-9]*(?:{0})?)|(?:0{0})|(?:0)'.format(frac) - c = lambda x:regex.compile(x, flags=regex.VERSION1) + def c(x): + return regex.compile(x, flags=regex.VERSION1) # A step of the form /integer self.step_pat = c(r'/(%s)' % integer) diff --git a/src/calibre/ebooks/lrf/html/convert_from.py b/src/calibre/ebooks/lrf/html/convert_from.py index 5748feed44..5f67b0ebf1 100644 --- a/src/calibre/ebooks/lrf/html/convert_from.py +++ b/src/calibre/ebooks/lrf/html/convert_from.py @@ -811,7 +811,8 @@ class HTMLConverter: for x, y in [('\xad', ''), ('\xa0', ' '), ('\ufb00', 'ff'), ('\ufb01', 'fi'), ('\ufb02', 'fl'), ('\ufb03', 'ffi'), ('\ufb04', 'ffl')]: src = src.replace(x, y) - valigner = lambda x: x + def valigner(x): + return x if 'vertical-align' in css: valign = css['vertical-align'] if valign in ('sup', 'super', 'sub'): diff --git a/src/calibre/ebooks/metadata/book/base.py b/src/calibre/ebooks/metadata/book/base.py index 2d14faf4ff..bc17b256f0 100644 --- a/src/calibre/ebooks/metadata/book/base.py +++ b/src/calibre/ebooks/metadata/book/base.py @@ -54,8 +54,10 @@ def reset_field_metadata(): field_metadata = FieldMetadata() -ck = lambda typ: icu_lower(typ).strip().replace(':', '').replace(',', '') -cv = lambda val: val.strip().replace(',', '|') +def ck(typ): + return icu_lower(typ).strip().replace(':', '').replace(',', '') +def cv(val): + return val.strip().replace(',', '|') class Metadata: diff --git a/src/calibre/ebooks/metadata/lrx.py b/src/calibre/ebooks/metadata/lrx.py index 71d631cf02..809dd122f5 100644 --- a/src/calibre/ebooks/metadata/lrx.py +++ b/src/calibre/ebooks/metadata/lrx.py @@ -38,7 +38,8 @@ def short_be(buf): def get_metadata(f): - read = lambda at, amount: _read(f, at, amount) + def read(at, amount): + return _read(f, at, amount) f.seek(0) buf = f.read(12) if buf[4:] == b'ftypLRX2': diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index 4b84705628..ce3eb80a4c 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -1652,7 +1652,8 @@ def metadata_to_opf(mi, as_string=True, default_lang=None): if mi.tags: for tag in mi.tags: factory(DC('subject'), tag) - meta = lambda n, c: factory('meta', name='calibre:'+n, content=c) + def meta(n, c): + return factory('meta', name='calibre:' + n, content=c) if getattr(mi, 'author_link_map', None) is not None: meta('author_link_map', dump_dict(mi.author_link_map)) if mi.series: diff --git a/src/calibre/ebooks/metadata/rb.py b/src/calibre/ebooks/metadata/rb.py index bdca8a7c0f..4140ab3c55 100644 --- a/src/calibre/ebooks/metadata/rb.py +++ b/src/calibre/ebooks/metadata/rb.py @@ -21,7 +21,8 @@ def get_metadata(stream): return mi stream.read(10) - read_i32 = lambda: struct.unpack('H', x) +def zeroes(x): + return (b'\x00' * x) +def nulls(x): + return (b'\xff' * x) +def short(x): + return pack(b'>H', x) class Header(OrderedDict): diff --git a/src/calibre/ebooks/mobi/writer8/index.py b/src/calibre/ebooks/mobi/writer8/index.py index b56eb02774..ef20c8966d 100644 --- a/src/calibre/ebooks/mobi/writer8/index.py +++ b/src/calibre/ebooks/mobi/writer8/index.py @@ -13,7 +13,8 @@ from calibre.ebooks.mobi.writer8.header import Header TagMeta_ = namedtuple('TagMeta', 'name number values_per_entry bitmask end_flag') -TagMeta = lambda x:TagMeta_(*x) +def TagMeta(x): + return TagMeta_(*x) EndTagTable = TagMeta(('eof', 0, 0, 0, 1)) # map of mask to number of shifts needed, works with 1 bit and two-bit wide masks diff --git a/src/calibre/ebooks/oeb/polish/main.py b/src/calibre/ebooks/oeb/polish/main.py index 43fac8ed43..d445a959e0 100644 --- a/src/calibre/ebooks/oeb/polish/main.py +++ b/src/calibre/ebooks/oeb/polish/main.py @@ -161,7 +161,8 @@ def update_metadata(ebook, new_opf): def polish_one(ebook, opts, report, customization=None): - rt = lambda x: report('\n### ' + x) + def rt(x): + return report('\n### ' + x) jacket = None changed = False customization = customization or CUSTOMIZATION.copy() diff --git a/src/calibre/ebooks/oeb/polish/utils.py b/src/calibre/ebooks/oeb/polish/utils.py index d2dd7e916d..955237ce07 100644 --- a/src/calibre/ebooks/oeb/polish/utils.py +++ b/src/calibre/ebooks/oeb/polish/utils.py @@ -248,7 +248,8 @@ def apply_func_to_match_groups(match, func=icu_upper, handle_entities=handle_ent found_groups = False i = 0 parts, pos = [], match.start() - f = lambda text:handle_entities(text, func) + def f(text): + return handle_entities(text, func) while True: i += 1 try: @@ -268,7 +269,8 @@ def apply_func_to_match_groups(match, func=icu_upper, handle_entities=handle_ent def apply_func_to_html_text(match, func=icu_upper, handle_entities=handle_entities): ''' Apply the specified function only to text between HTML tag definitions. ''' - f = lambda text:handle_entities(text, func) + def f(text): + return handle_entities(text, func) parts = re.split(r'(<[^>]+>)', match.group()) parts = (x if x.startswith('<') else f(x) for x in parts) return ''.join(parts) diff --git a/src/calibre/ebooks/rtf2xml/paragraph_def.py b/src/calibre/ebooks/rtf2xml/paragraph_def.py index 53407eb433..acb2281a05 100644 --- a/src/calibre/ebooks/rtf2xml/paragraph_def.py +++ b/src/calibre/ebooks/rtf2xml/paragraph_def.py @@ -116,8 +116,6 @@ if another paragraph_def is found, the state changes to collect_tokens. 'sect-note_' : 'endnotes-in-section', # list=> ls 'list-text_' : 'list-text', - # this line must be wrong because it duplicates an earlier one - 'list-text_' : 'list-text', 'list______' : 'list', 'list-lev-d' : 'list-level-definition', 'list-cardi' : 'list-cardinal-numbering', diff --git a/src/calibre/ebooks/rtf2xml/tokenize.py b/src/calibre/ebooks/rtf2xml/tokenize.py index d19f3ef61f..fcd2fb3dd2 100644 --- a/src/calibre/ebooks/rtf2xml/tokenize.py +++ b/src/calibre/ebooks/rtf2xml/tokenize.py @@ -136,7 +136,6 @@ class Tokenize: "&": "&", "<": "<", ">": ">", - "\\~": "\\~ ", "\\_": "\\_ ", "\\:": "\\: ", "\\-": "\\- ", diff --git a/src/calibre/gui2/device_drivers/configwidget.py b/src/calibre/gui2/device_drivers/configwidget.py index 8f16f01ad0..6bad19f38c 100644 --- a/src/calibre/gui2/device_drivers/configwidget.py +++ b/src/calibre/gui2/device_drivers/configwidget.py @@ -70,11 +70,15 @@ class ConfigWidget(QWidget, Ui_ConfigWidget): if isinstance(extra_customization_message, list): self.opt_extra_customization = [] if len(extra_customization_message) > 6: - row_func = lambda x, y: ((x//2) * 2) + y - col_func = lambda x: x%2 + def row_func(x, y): + return (x // 2 * 2 + y) + def col_func(x): + return (x % 2) else: - row_func = lambda x, y: x*2 + y - col_func = lambda x: 0 + def row_func(x, y): + return (x * 2 + y) + def col_func(x): + return 0 for i, m in enumerate(extra_customization_message): label_text, tt = parse_msg(m) diff --git a/src/calibre/gui2/device_drivers/tabbed_device_config.py b/src/calibre/gui2/device_drivers/tabbed_device_config.py index 6a87f0da62..5e3789ee3b 100644 --- a/src/calibre/gui2/device_drivers/tabbed_device_config.py +++ b/src/calibre/gui2/device_drivers/tabbed_device_config.py @@ -274,11 +274,15 @@ class ExtraCustomization(DeviceConfigTab): # {{{ if isinstance(extra_customization_message, list): self.opt_extra_customization = [] if len(extra_customization_message) > 6: - row_func = lambda x, y: ((x//2) * 2) + y - col_func = lambda x: x%2 + def row_func(x, y): + return (x // 2 * 2 + y) + def col_func(x): + return (x % 2) else: - row_func = lambda x, y: x*2 + y - col_func = lambda x: 0 + def row_func(x, y): + return (x * 2 + y) + def col_func(x): + return 0 for i, m in enumerate(extra_customization_message): label_text, tt = parse_msg(m) diff --git a/src/calibre/gui2/dialogs/custom_recipes.py b/src/calibre/gui2/dialogs/custom_recipes.py index 96909a9510..afca6b7470 100644 --- a/src/calibre/gui2/dialogs/custom_recipes.py +++ b/src/calibre/gui2/dialogs/custom_recipes.py @@ -567,7 +567,8 @@ class CustomRecipes(Dialog): l.addWidget(self.bb) self.list_actions = [] - la = lambda *args:self.list_actions.append(args) + def la(*args): + return self.list_actions.append(args) la('plus.png', _('&New recipe'), _('Create a new recipe from scratch'), self.add_recipe) la('news.png', _('Customize &builtin recipe'), _('Customize a builtin news download source'), self.customize_recipe) la('document_open.png', _('Load recipe from &file'), _('Load a recipe from a file'), self.load_recipe) diff --git a/src/calibre/gui2/icon_theme.py b/src/calibre/gui2/icon_theme.py index ac989db981..ff2fca785c 100644 --- a/src/calibre/gui2/icon_theme.py +++ b/src/calibre/gui2/icon_theme.py @@ -127,7 +127,8 @@ def read_theme_from_folder(path): return int(x) except Exception: return -1 - g = lambda x, defval='': metadata.get(x, defval) + def g(x, defval=''): + return metadata.get(x, defval) theme = Theme(g('title'), g('author'), safe_int(g('version', -1)), g('description'), g('license', 'Unknown'), g('url', None)) ans = Report(path, name_map, extra, missing, theme) diff --git a/src/calibre/gui2/lrf_renderer/text.py b/src/calibre/gui2/lrf_renderer/text.py index fa6356f3ea..6158af6e1c 100644 --- a/src/calibre/gui2/lrf_renderer/text.py +++ b/src/calibre/gui2/lrf_renderer/text.py @@ -11,10 +11,14 @@ from calibre.ebooks.lrf.fonts import LIBERATION_FONT_MAP from calibre.ebooks.hyphenate import hyphenate_word from polyglot.builtins import string_or_bytes -WEIGHT_MAP = lambda wt : int((wt/10)-1) -NULL = lambda a, b: a -COLOR = lambda a, b: QColor(*a) -WEIGHT = lambda a, b: WEIGHT_MAP(a) +def WEIGHT_MAP(wt): + return int(wt / 10 - 1) +def NULL(a, b): + return a +def COLOR(a, b): + return QColor(*a) +def WEIGHT(a, b): + return WEIGHT_MAP(a) class PixmapItem(QGraphicsPixmapItem): diff --git a/src/calibre/gui2/metadata/diff.py b/src/calibre/gui2/metadata/diff.py index 73bcd650b5..c59ddeeee4 100644 --- a/src/calibre/gui2/metadata/diff.py +++ b/src/calibre/gui2/metadata/diff.py @@ -783,7 +783,8 @@ if __name__ == '__main__': ids = sorted(db.all_ids(), reverse=True) ids = tuple(zip(ids[0::2], ids[1::2])) gm = partial(db.get_metadata, index_is_id=True, get_cover=True, cover_as_data=True) - get_metadata = lambda x:list(map(gm, ids[x])) + def get_metadata(x): + return list(map(gm, ids[x])) d = CompareMany(list(range(len(ids))), get_metadata, db.field_metadata, db=db) d.exec() for changed, mi in itervalues(d.accepted): diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index 40928ecc0e..4cfad54656 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -181,7 +181,8 @@ class ResultsModel(QAbstractTableModel): # {{{ return None def sort(self, col, order=Qt.SortOrder.AscendingOrder): - key = lambda x: x + def key(x): + return x if col == 0: key = attrgetter('gui_rank') elif col == 1: @@ -196,7 +197,8 @@ class ResultsModel(QAbstractTableModel): # {{{ elif col == 3: key = attrgetter('has_cached_cover_url') elif key == 4: - key = lambda x: bool(x.comments) + def key(x): + return bool(x.comments) self.beginResetModel() self.results.sort(key=key, reverse=order==Qt.SortOrder.AscendingOrder) diff --git a/src/calibre/gui2/tag_browser/ui.py b/src/calibre/gui2/tag_browser/ui.py index 0d52e5a425..bd7f07b26e 100644 --- a/src/calibre/gui2/tag_browser/ui.py +++ b/src/calibre/gui2/tag_browser/ui.py @@ -267,7 +267,8 @@ class TagBrowserMixin: # {{{ db = self.current_db if category == 'series': - key = lambda x:sort_key(title_sort(x)) + def key(x): + return sort_key(title_sort(x)) else: key = sort_key diff --git a/src/calibre/gui2/tweak_book/diff/highlight.py b/src/calibre/gui2/tweak_book/diff/highlight.py index ff758a38d5..07c6a757b9 100644 --- a/src/calibre/gui2/tweak_book/diff/highlight.py +++ b/src/calibre/gui2/tweak_book/diff/highlight.py @@ -69,7 +69,8 @@ def pygments_lexer(filename): from pygments.util import ClassNotFound except ImportError: return None - glff = lambda n: get_lexer_for_filename(n, stripnl=False) + def glff(n): + return get_lexer_for_filename(n, stripnl=False) try: return glff(filename) except ClassNotFound: diff --git a/src/calibre/gui2/tweak_book/diff/main.py b/src/calibre/gui2/tweak_book/diff/main.py index 7257379d95..04e8340fac 100644 --- a/src/calibre/gui2/tweak_book/diff/main.py +++ b/src/calibre/gui2/tweak_book/diff/main.py @@ -423,7 +423,8 @@ class Diff(Dialog): self.busy.setVisible(True) return True - kwargs = lambda name: {'context':self.context, 'beautify':self.beautify, 'syntax':syntax_map.get(name, None)} + def kwargs(name): + return {'context': self.context, 'beautify': self.beautify, 'syntax': syntax_map.get(name, None)} if isinstance(changed_names, dict): for name, other_name in sorted(iteritems(changed_names), key=lambda x:numeric_sort_key(x[0])): diff --git a/src/calibre/gui2/tweak_book/editor/smarts/html.py b/src/calibre/gui2/tweak_book/editor/smarts/html.py index aee0d952f3..733fc66e95 100644 --- a/src/calibre/gui2/tweak_book/editor/smarts/html.py +++ b/src/calibre/gui2/tweak_book/editor/smarts/html.py @@ -85,7 +85,8 @@ def find_closest_containing_tag(block, offset, max_tags=sys.maxsize): ''' Find the closest containing tag. To find it, we search for the first opening tag that does not have a matching closing tag before the specified position. Search through at most max_tags. ''' - prev_tag_boundary = lambda b, o: next_tag_boundary(b, o, forward=False) + def prev_tag_boundary(b, o): + return next_tag_boundary(b, o, forward=False) block, boundary = prev_tag_boundary(block, offset) if block is None: diff --git a/src/calibre/gui2/tweak_book/editor/smarts/python.py b/src/calibre/gui2/tweak_book/editor/smarts/python.py index 991ad921fa..4a4fcd3770 100644 --- a/src/calibre/gui2/tweak_book/editor/smarts/python.py +++ b/src/calibre/gui2/tweak_book/editor/smarts/python.py @@ -13,7 +13,8 @@ from calibre.gui2.tweak_book.editor.smarts.utils import ( get_text_before_cursor, get_leading_whitespace_on_block as lw, smart_home, smart_backspace, smart_tab) -get_leading_whitespace_on_block = lambda editor, previous=False: expand_tabs(lw(editor, previous=previous)) +def get_leading_whitespace_on_block(editor, previous=False): + return expand_tabs(lw(editor, previous=previous)) tw = 4 # The tab width (hardcoded to the pep8 value) diff --git a/src/calibre/gui2/tweak_book/editor/smarts/utils.py b/src/calibre/gui2/tweak_book/editor/smarts/utils.py index ebaa9d9c1e..ff1f2a65f8 100644 --- a/src/calibre/gui2/tweak_book/editor/smarts/utils.py +++ b/src/calibre/gui2/tweak_book/editor/smarts/utils.py @@ -16,7 +16,8 @@ def get_text_around_cursor(editor, before=True): get_text_before_cursor = get_text_around_cursor -get_text_after_cursor = lambda editor: get_text_around_cursor(editor, before=False) +def get_text_after_cursor(editor): + return get_text_around_cursor(editor, before=False) def is_cursor_on_wrapped_line(editor): diff --git a/src/calibre/gui2/tweak_book/editor/snippets.py b/src/calibre/gui2/tweak_book/editor/snippets.py index 2f1a474975..80490f3f6c 100644 --- a/src/calibre/gui2/tweak_book/editor/snippets.py +++ b/src/calibre/gui2/tweak_book/editor/snippets.py @@ -26,7 +26,8 @@ from calibre.utils.icu import string_length as strlen from calibre.utils.localization import localize_user_manual_link from polyglot.builtins import codepoint_to_chr, iteritems, itervalues -string_length = lambda x: strlen(str(x)) # Needed on narrow python builds, as subclasses of unicode dont work +def string_length(x): + return strlen(str(x)) # Needed on narrow python builds, as subclasses of unicode dont work KEY = Qt.Key.Key_J MODIFIER = Qt.KeyboardModifier.MetaModifier if ismacos else Qt.KeyboardModifier.ControlModifier @@ -97,10 +98,12 @@ def escape_funcs(): if escape is None: escapem = {('\\' + x):codepoint_to_chr(i+1) for i, x in enumerate('\\${}')} escape_pat = re.compile('|'.join(map(re.escape, escapem))) - escape = lambda x: escape_pat.sub(lambda m: escapem[m.group()], x.replace(r'\\', '\x01')) + def escape(x): + return escape_pat.sub(lambda m: escapem[m.group()], x.replace('\\\\', '\x01')) unescapem = {v:k[1] for k, v in iteritems(escapem)} unescape_pat = re.compile('|'.join(unescapem)) - unescape = lambda x:unescape_pat.sub(lambda m:unescapem[m.group()], x) + def unescape(x): + return unescape_pat.sub(lambda m: unescapem[m.group()], x) return escape, unescape diff --git a/src/calibre/gui2/tweak_book/editor/syntax/base.py b/src/calibre/gui2/tweak_book/editor/syntax/base.py index e7f8e87772..72e9aff14f 100644 --- a/src/calibre/gui2/tweak_book/editor/syntax/base.py +++ b/src/calibre/gui2/tweak_book/editor/syntax/base.py @@ -64,9 +64,11 @@ class SimpleUserData(QTextBlockUserData): class SyntaxHighlighter: - create_formats_func = lambda highlighter: {} + def create_formats_func(highlighter): + return {} spell_attributes = () - tag_ok_for_spell = lambda x: False + def tag_ok_for_spell(x): + return False user_data_factory = SimpleUserData def __init__(self): diff --git a/src/calibre/gui2/tweak_book/live_css.py b/src/calibre/gui2/tweak_book/live_css.py index 576ba52ec3..cefe9c5a76 100644 --- a/src/calibre/gui2/tweak_book/live_css.py +++ b/src/calibre/gui2/tweak_book/live_css.py @@ -142,7 +142,8 @@ class Declaration(QWidget): def do_layout(self): fm = self.fontMetrics() - bounding_rect = lambda text: fm.boundingRect(0, 0, 10000, 10000, Cell.FLAGS, text) + def bounding_rect(text): + return fm.boundingRect(0, 0, 10000, 10000, Cell.FLAGS, text) line_spacing = 2 side_margin = Cell.SIDE_MARGIN self.rows = [] diff --git a/src/calibre/gui2/tweak_book/widgets.py b/src/calibre/gui2/tweak_book/widgets.py index 034d34bae2..bc9e2bb889 100644 --- a/src/calibre/gui2/tweak_book/widgets.py +++ b/src/calibre/gui2/tweak_book/widgets.py @@ -806,7 +806,8 @@ class InsertSemantics(Dialog): return QSize(800, 600) def create_known_type_map(self): - _ = lambda x: x + def _(x): + return x self.epubtype_guide_map = {v: k for k, v in guide_epubtype_map.items()} self.known_type_map = { 'titlepage': _('Title page'), diff --git a/src/calibre/gui2/viewer/bookmarks.py b/src/calibre/gui2/viewer/bookmarks.py index 62b5fec5c2..16483bb4ff 100644 --- a/src/calibre/gui2/viewer/bookmarks.py +++ b/src/calibre/gui2/viewer/bookmarks.py @@ -172,7 +172,8 @@ class BookmarkManager(QWidget): def set_bookmarks(self, bookmarks=()): csb = self.current_sort_by if csb in ('name', 'title'): - sk = lambda x: primary_sort_key(x['title']) + def sk(x): + return primary_sort_key(x['title']) elif csb == 'timestamp': sk = itemgetter('timestamp') else: diff --git a/src/calibre/gui2/viewer/search.py b/src/calibre/gui2/viewer/search.py index 8129a5fc7e..112d381118 100644 --- a/src/calibre/gui2/viewer/search.py +++ b/src/calibre/gui2/viewer/search.py @@ -363,9 +363,11 @@ def search_in_name(name, search_query, ctx_size=75): else: spans = [] - miter = lambda: spans + def miter(): + return spans if raw: - a = lambda s, l: spans.append((s, s + l)) + def a(s, l): + return spans.append((s, s + l)) primary_collator_without_punctuation().find_all(search_query.text, raw, a, search_query.mode == 'word') for (start, end) in miter(): diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index ebab78c06c..fc4434b799 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -437,21 +437,26 @@ class ResultCache(SearchQueryParser): # {{{ if val_func is None: loc = self.field_metadata[location]['rec_index'] - val_func = lambda item, loc=loc: item[loc] + def val_func(item, loc=loc): + return item[loc] q = '' cast = adjust = lambda x: x dt = self.field_metadata[location]['datatype'] if query == 'false': if dt == 'rating' or location == 'cover': - relop = lambda x,y: not bool(x) + def relop(x, y): + return (not bool(x)) else: - relop = lambda x,y: x is None + def relop(x, y): + return (x is None) elif query == 'true': if dt == 'rating' or location == 'cover': - relop = lambda x,y: bool(x) + def relop(x, y): + return bool(x) else: - relop = lambda x,y: x is not None + def relop(x, y): + return (x is not None) else: relop = None for k in self.numeric_search_relops.keys(): @@ -462,14 +467,19 @@ class ResultCache(SearchQueryParser): # {{{ (p, relop) = self.numeric_search_relops['='] if dt == 'int': - cast = lambda x: int(x) + def cast(x): + return int(x) elif dt == 'rating': - cast = lambda x: 0 if x is None else int(x) - adjust = lambda x: x//2 + def cast(x): + return (0 if x is None else int(x)) + def adjust(x): + return (x // 2) elif dt in ('float', 'composite'): - cast = lambda x : float(x) + def cast(x): + return float(x) else: # count operation - cast = (lambda x: int(x)) + def cast(x): + return int(x) if len(query) > 1: mult = query[-1:].lower() @@ -720,9 +730,8 @@ class ResultCache(SearchQueryParser): # {{{ if fm['is_multiple'] and \ len(query) > 1 and query.startswith('#') and \ query[1:1] in '=<>!': - vf = lambda item, loc=fm['rec_index'], \ - ms=fm['is_multiple']['cache_to_list']:\ - len(item[loc].split(ms)) if item[loc] is not None else 0 + def vf(item, loc=fm['rec_index'], ms=fm['is_multiple']['cache_to_list']): + return (len(item[loc].split(ms)) if item[loc] is not None else 0) return self.get_numeric_matches(location, query[1:], candidates, val_func=vf) diff --git a/src/calibre/library/catalogs/epub_mobi_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py index 6e3da4bbf4..18c706dde9 100644 --- a/src/calibre/library/catalogs/epub_mobi_builder.py +++ b/src/calibre/library/catalogs/epub_mobi_builder.py @@ -1261,7 +1261,7 @@ class CatalogBuilder: else: # Validate custom field is usable as a genre source field_md = self.db.metadata_for_field(self.opts.genre_source_field) - if field_md is None or not field_md['datatype'] in ['enumeration', 'text']: + if field_md is None or field_md['datatype'] not in ['enumeration', 'text']: all_custom_fields = self.db.custom_field_keys() eligible_custom_fields = [] for cf in all_custom_fields: diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 61a714cb4a..200f95e584 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -188,7 +188,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): # we need to do it before we call initialize_dynamic if apply_default_prefs and default_prefs is not None: if progress_callback is None: - progress_callback = lambda x, y: True + def progress_callback(x, y): + return True dbprefs = DBPrefs(self) progress_callback(None, len(default_prefs)) for i, key in enumerate(default_prefs): @@ -1972,32 +1973,39 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): icon_map[category] = icon datatype = cat['datatype'] - avgr = lambda x: 0.0 if x.rc == 0 else x.rt/x.rc + def avgr(x): + return (0.0 if x.rc == 0 else x.rt / x.rc) # Duplicate the build of items below to avoid using a lambda func # in the main Tag loop. Saves a few % if datatype == 'rating': - formatter = (lambda x:'\u2605'*int(x//2)) - avgr = lambda x: x.n + def formatter(x): + return ('★' * int(x // 2)) + def avgr(x): + return x.n # eliminate the zero ratings line as well as count == 0 items = [v for v in tcategories[category].values() if v.c > 0 and v.n != 0] elif category == 'authors': # Clean up the authors strings to human-readable form - formatter = (lambda x: x.replace('|', ',')) + def formatter(x): + return x.replace('|', ',') items = [v for v in tcategories[category].values() if v.c > 0] elif category == 'languages': # Use a human readable language string formatter = calibre_langcode_to_name items = [v for v in tcategories[category].values() if v.c > 0] else: - formatter = (lambda x:str(x)) + def formatter(x): + return str(x) items = [v for v in tcategories[category].values() if v.c > 0] # sort the list if sort == 'name': - kf = lambda x:sort_key(x.s) + def kf(x): + return sort_key(x.s) reverse=False elif sort == 'popularity': - kf = lambda x: x.c + def kf(x): + return x.c reverse=True else: kf = avgr @@ -3597,7 +3605,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): def move_library_to(self, newloc, progress=None): if progress is None: - progress = lambda x:x + def progress(x): + return x if not os.path.exists(newloc): os.makedirs(newloc) old_dirs = set() diff --git a/src/calibre/linux.py b/src/calibre/linux.py index f02c6b9eee..0843c0ea08 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -1136,7 +1136,8 @@ X-GNOME-UsesNotifications=true def get_appdata(): - _ = lambda x: x # Make sure the text below is not translated, but is marked for translation + def _(x): + return x # Make sure the text below is not translated, but is marked for translation return { 'calibre-gui': { 'name':'calibre', diff --git a/src/calibre/spell/import_from.py b/src/calibre/spell/import_from.py index 72a5a2c63d..90d3fb10fe 100644 --- a/src/calibre/spell/import_from.py +++ b/src/calibre/spell/import_from.py @@ -19,7 +19,8 @@ NS_MAP = { 'manifest': 'http://openoffice.org/2001/manifest', } -XPath = lambda x: etree.XPath(x, namespaces=NS_MAP) +def XPath(x): + return etree.XPath(x, namespaces=NS_MAP) BUILTIN_LOCALES = {'en-US', 'en-GB', 'es-ES'} diff --git a/src/calibre/utils/bibtex.py b/src/calibre/utils/bibtex.py index 4063bb24b6..7784b07fd4 100644 --- a/src/calibre/utils/bibtex.py +++ b/src/calibre/utils/bibtex.py @@ -2167,7 +2167,6 @@ utf8enc2latex_mapping = { # {{{ # Items from simple list '\u0106': "{\\a\\'C}", '\u0408': '{\\CYRJE}', - '\u20ac': '{\\texteuro}', '\u2191': '{\\textuparrow}', '\u0493': '{\\cyrghcrs}', '\u2116': '{\\textnumero}', diff --git a/src/calibre/utils/date.py b/src/calibre/utils/date.py index b401f03a3e..d9045d2b5f 100644 --- a/src/calibre/utils/date.py +++ b/src/calibre/utils/date.py @@ -151,7 +151,8 @@ def dt_factory(time_t, assume_utc=False, as_utc=True): return dt.astimezone(_utc_tz if as_utc else _local_tz) -safeyear = lambda x: min(max(x, MINYEAR), MAXYEAR) +def safeyear(x): + return min(max(x, MINYEAR), MAXYEAR) def qt_to_dt(qdate_or_qdatetime, as_utc=True): diff --git a/src/calibre/utils/icu.py b/src/calibre/utils/icu.py index 7b63d09528..6ee1c4ed8a 100644 --- a/src/calibre/utils/icu.py +++ b/src/calibre/utils/icu.py @@ -231,7 +231,8 @@ def capitalize(x): try: swapcase = _icu.swap_case except AttributeError: # For people running from source - swapcase = lambda x:x.swapcase() + def swapcase(x): + return x.swapcase() find = make_two_arg_func(collator, 'find') primary_find = make_two_arg_func(primary_collator, 'find') diff --git a/src/calibre/utils/icu_test.py b/src/calibre/utils/icu_test.py index b0edf01b76..f7d6377c40 100644 --- a/src/calibre/utils/icu_test.py +++ b/src/calibre/utils/icu_test.py @@ -120,7 +120,8 @@ class TestICU(unittest.TestCase): self.ae((0, 5), icu.primary_no_punc_find('abcd', 'ab cd')) # test find all m = [] - a = lambda p,l : m.append((p, l)) + def a(p, l): + return m.append((p, l)) icu.primary_collator_without_punctuation().find_all('a', 'a a🐱a', a) self.ae(m, [(0, 1), (2, 1), (5, 1)]) # test find whole words diff --git a/src/calibre/utils/search_query_parser_test.py b/src/calibre/utils/search_query_parser_test.py index 9f6a00576c..acfd90f2b1 100644 --- a/src/calibre/utils/search_query_parser_test.py +++ b/src/calibre/utils/search_query_parser_test.py @@ -334,9 +334,11 @@ class Tester(SearchQueryParser): if location in self.fields.keys(): getter = operator.itemgetter(self.fields[location]) elif location == 'all': - getter = lambda y: ''.join(x if x else '' for x in y) + def getter(y): + return ''.join(x if x else '' for x in y) else: - getter = lambda x: '' + def getter(x): + return '' if not query: return set() diff --git a/src/calibre/utils/wmf/parse.py b/src/calibre/utils/wmf/parse.py index 3881dfbdc0..59b23cf53f 100644 --- a/src/calibre/utils/wmf/parse.py +++ b/src/calibre/utils/wmf/parse.py @@ -108,7 +108,6 @@ class WMF: 247: 'CreatePalette', 248: 'CreateBrush', 322: 'DibCreatePatternBrush', - 496: 'DeleteObject', 505: 'CreatePatternBrush', 762: 'CreatePenIndirect', 763: 'CreateFontIndirect', diff --git a/src/css_selectors/tests.py b/src/css_selectors/tests.py index c06cc8737e..e07c6cbcfe 100644 --- a/src/css_selectors/tests.py +++ b/src/css_selectors/tests.py @@ -745,7 +745,8 @@ by William Shakespeare def test_select_shakespeare(self): document = html.document_fromstring(self.HTML_SHAKESPEARE) select = Select(document) - count = lambda s: sum(1 for r in select(s)) + def count(s): + return sum(1 for r in select(s)) # Data borrowed from http://mootools.net/slickspeed/ From a1d21e36322f0c0615d9440b6ccfb68dfde7f5bc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 9 Jan 2023 19:13:32 +0530 Subject: [PATCH 0092/2055] Manual fixes for misreports from ruff --- =template.py | 5 ----- src/calibre/db/search.py | 6 ++++-- src/calibre/db/tests/legacy.py | 10 ++++++---- src/calibre/db/tests/reading.py | 5 +++-- src/calibre/ebooks/lrf/html/convert_from.py | 2 +- src/calibre/gui2/metadata/single_download.py | 5 +++-- src/calibre/library/database2.py | 3 ++- 7 files changed, 19 insertions(+), 17 deletions(-) delete mode 100644 =template.py diff --git a/=template.py b/=template.py deleted file mode 100644 index 1d4fcfab6c..0000000000 --- a/=template.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=utf-8 -# License: GPL v3 Copyright: %YEAR%, %USER% <%MAIL%> - -%HERE% diff --git a/src/calibre/db/search.py b/src/calibre/db/search.py index 8e7b4b106d..60d48cee3f 100644 --- a/src/calibre/db/search.py +++ b/src/calibre/db/search.py @@ -237,11 +237,12 @@ class NumericSearch: # {{{ dt = datatype if is_many and query in {'true', 'false'}: - def valcheck(x): - return True if datatype == 'rating': def valcheck(x): return (x is not None and x > 0) + else: + def valcheck(x): + return True found = set() for val, book_ids in field_iter(): if valcheck(val): @@ -273,6 +274,7 @@ class NumericSearch: # {{{ if dt == 'rating': def cast(x): return (0 if x is None else int(x)) + def adjust(x): return (x // 2) else: diff --git a/src/calibre/db/tests/legacy.py b/src/calibre/db/tests/legacy.py index 194f682b23..2ba66b3ae8 100644 --- a/src/calibre/db/tests/legacy.py +++ b/src/calibre/db/tests/legacy.py @@ -57,8 +57,6 @@ def run_funcs(self, db, ndb, funcs): if callable(meth): meth(*args) else: - def fmt(x): - return x if meth[0] in {'!', '@', '#', '+', '$', '-', '%'}: if meth[0] != '+': fmt = {'!':dict, '@':lambda x:frozenset(x or ()), '#':lambda x:set((x or '').split(',')), @@ -68,6 +66,9 @@ def run_funcs(self, db, ndb, funcs): fmt = args[-1] args = args[:-1] meth = meth[1:] + else: + def fmt(x): + return x res1, res2 = fmt(getattr(db, meth)(*args)), fmt(getattr(ndb, meth)(*args)) self.assertEqual(res1, res2, f'The method: {meth}() returned different results for argument {args}') # }}} @@ -257,14 +258,15 @@ class LegacyTest(BaseTest): 'books_in_series_of':[(0,), (1,), (2,)], 'books_with_same_title':[(Metadata(db.title(0)),), (Metadata(db.title(1)),), (Metadata('1234'),)], }): - def fmt(x): - return x if meth[0] in {'!', '@'}: fmt = {'!':dict, '@':frozenset}[meth[0]] meth = meth[1:] elif meth == 'get_authors_with_ids': def fmt(val): return {x[0]: tuple(x[1:]) for x in val} + else: + def fmt(x): + return x for a in args: self.assertEqual(fmt(getattr(db, meth)(*a)), fmt(getattr(ndb, meth)(*a)), f'The method: {meth}() returned different results for argument {a}') diff --git a/src/calibre/db/tests/reading.py b/src/calibre/db/tests/reading.py index 907844d0c8..a6daf73d18 100644 --- a/src/calibre/db/tests/reading.py +++ b/src/calibre/db/tests/reading.py @@ -612,11 +612,12 @@ class ReadingTest(BaseTest): self.assertSetEqual(set(mi.custom_field_keys()), set(pmi.custom_field_keys())) for field in STANDARD_METADATA_FIELDS | {'#series_index'}: - def f(x): - return x if field == 'formats': def f(x): return (x if x is None else tuple(x)) + else: + def f(x): + return x self.assertEqual(f(getattr(mi, field)), f(getattr(pmi, field)), f'Standard field: {field} not the same for book {book_id}') self.assertEqual(mi.format_field(field), pmi.format_field(field), diff --git a/src/calibre/ebooks/lrf/html/convert_from.py b/src/calibre/ebooks/lrf/html/convert_from.py index 5f67b0ebf1..589ba395ec 100644 --- a/src/calibre/ebooks/lrf/html/convert_from.py +++ b/src/calibre/ebooks/lrf/html/convert_from.py @@ -817,7 +817,7 @@ class HTMLConverter: valign = css['vertical-align'] if valign in ('sup', 'super', 'sub'): fp['fontsize'] = int(fp['fontsize']) * 5 // 3 - valigner = Sub if valign == 'sub' else Sup + valigner = Sub if valign == 'sub' else Sup # noqa normal_font_size = int(fp['fontsize']) if variant == 'small-caps': diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index 4cfad54656..e31f42e62b 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -181,8 +181,6 @@ class ResultsModel(QAbstractTableModel): # {{{ return None def sort(self, col, order=Qt.SortOrder.AscendingOrder): - def key(x): - return x if col == 0: key = attrgetter('gui_rank') elif col == 1: @@ -199,6 +197,9 @@ class ResultsModel(QAbstractTableModel): # {{{ elif key == 4: def key(x): return bool(x.comments) + else: + def key(x): + return x self.beginResetModel() self.results.sort(key=key, reverse=order==Qt.SortOrder.AscendingOrder) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 200f95e584..1632921287 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1973,6 +1973,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): icon_map[category] = icon datatype = cat['datatype'] + def avgr(x): return (0.0 if x.rc == 0 else x.rt / x.rc) # Duplicate the build of items below to avoid using a lambda func @@ -1980,7 +1981,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if datatype == 'rating': def formatter(x): return ('★' * int(x // 2)) - def avgr(x): + def avgr(x): # noqa return x.n # eliminate the zero ratings line as well as count == 0 items = [v for v in tcategories[category].values() if v.c > 0 and v.n != 0] From 474de99ec2b122101ed80ecafed815a5b2e5d79f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 9 Jan 2023 19:50:12 +0530 Subject: [PATCH 0093/2055] Fix calls to pgettext() to use the installed translator --- src/calibre/gui2/keyboard.py | 2 +- src/calibre/gui2/search_restriction_mixin.py | 3 +-- src/calibre/gui2/store/search/models.py | 2 +- src/calibre/gui2/tweak_book/file_list.py | 2 +- src/calibre/gui2/tweak_book/ui.py | 3 +-- src/calibre/utils/localization.py | 25 ++++++++++++++++---- 6 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/calibre/gui2/keyboard.py b/src/calibre/gui2/keyboard.py index 486786f4ad..0eb9ce1056 100644 --- a/src/calibre/gui2/keyboard.py +++ b/src/calibre/gui2/keyboard.py @@ -7,7 +7,6 @@ __docformat__ = 'restructuredtext en' from collections import OrderedDict from functools import partial -from gettext import pgettext from qt.core import (QObject, QKeySequence, QAbstractItemModel, QModelIndex, QItemSelectionModel, Qt, QStyledItemDelegate, QTextDocument, QStyle, pyqtSignal, QFrame, QAbstractItemView, QMenu, @@ -19,6 +18,7 @@ from calibre.constants import DEBUG from calibre import prints, prepare_string_for_xml from calibre.utils.icu import sort_key, lower from calibre.gui2 import error_dialog, info_dialog +from calibre.utils.localization import pgettext from calibre.utils.search_query_parser import SearchQueryParser, ParseException from calibre.gui2.search_box import SearchBox2 from polyglot.builtins import iteritems, itervalues diff --git a/src/calibre/gui2/search_restriction_mixin.py b/src/calibre/gui2/search_restriction_mixin.py index 6cf33e8f10..f738601677 100644 --- a/src/calibre/gui2/search_restriction_mixin.py +++ b/src/calibre/gui2/search_restriction_mixin.py @@ -5,7 +5,6 @@ __license__ = 'GPL v3' __copyright__ = '2013, Kovid Goyal ' from functools import partial -from gettext import pgettext from qt.core import ( QAbstractItemView, QAction, QComboBox, QDialog, QDialogButtonBox, QFrame, QGridLayout, QIcon, QLabel, QLineEdit, QListView, QMenu, QRadioButton, QSize, @@ -16,7 +15,7 @@ from calibre.gui2 import error_dialog, gprefs, question_dialog from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.widgets import ComboBoxWithHelp from calibre.utils.icu import sort_key -from calibre.utils.localization import localize_user_manual_link +from calibre.utils.localization import localize_user_manual_link, pgettext from calibre.utils.search_query_parser import ParseException diff --git a/src/calibre/gui2/store/search/models.py b/src/calibre/gui2/store/search/models.py index e2f4e12fc4..ed63c66e27 100644 --- a/src/calibre/gui2/store/search/models.py +++ b/src/calibre/gui2/store/search/models.py @@ -4,7 +4,6 @@ __docformat__ = 'restructuredtext en' import re, string from operator import attrgetter -from gettext import pgettext from qt.core import (Qt, QAbstractItemModel, QPixmap, QModelIndex, QSize, pyqtSignal, QIcon, QApplication) @@ -15,6 +14,7 @@ from calibre.gui2.store.search_result import SearchResult from calibre.gui2.store.search.download_thread import DetailsThreadPool, \ CoverThreadPool from calibre.utils.icu import sort_key +from calibre.utils.localization import pgettext from calibre.utils.search_query_parser import SearchQueryParser diff --git a/src/calibre/gui2/tweak_book/file_list.py b/src/calibre/gui2/tweak_book/file_list.py index 38efccf934..437058eb76 100644 --- a/src/calibre/gui2/tweak_book/file_list.py +++ b/src/calibre/gui2/tweak_book/file_list.py @@ -8,7 +8,6 @@ import sys import textwrap from collections import Counter, OrderedDict, defaultdict from functools import lru_cache, partial -from gettext import pgettext from qt.core import ( QAbstractItemView, QApplication, QCheckBox, QDialog, QDialogButtonBox, QFont, QFormLayout, QGridLayout, QIcon, QInputDialog, QItemSelectionModel, QLabel, @@ -37,6 +36,7 @@ from calibre.gui2.tweak_book import ( from calibre.gui2.tweak_book.editor import syntax_from_mime from calibre.gui2.tweak_book.templates import template_for from calibre.utils.fonts.utils import get_font_names +from calibre.utils.localization import pgettext from calibre.utils.icu import numeric_sort_key from calibre_extensions.progress_indicator import set_no_activate_on_click from polyglot.binary import as_hex_unicode diff --git a/src/calibre/gui2/tweak_book/ui.py b/src/calibre/gui2/tweak_book/ui.py index d250432ed7..13a76fe119 100644 --- a/src/calibre/gui2/tweak_book/ui.py +++ b/src/calibre/gui2/tweak_book/ui.py @@ -6,7 +6,6 @@ __copyright__ = '2013, Kovid Goyal ' import os from functools import partial -from gettext import pgettext from itertools import product from qt.core import ( QAction, QApplication, QColor, QDockWidget, QEvent, QHBoxLayout, QIcon, QLabel, @@ -49,7 +48,7 @@ from calibre.gui2.tweak_book.toc import TOCViewer from calibre.gui2.tweak_book.undo import CheckpointView from calibre.utils.icu import ord_string, sort_key from calibre.utils.localization import ( - localize_user_manual_link, localize_website_link + localize_user_manual_link, localize_website_link, pgettext ) from calibre.utils.unicode_names import character_name_from_code from polyglot.builtins import iteritems, itervalues diff --git a/src/calibre/utils/localization.py b/src/calibre/utils/localization.py index 1d4917b7ec..0eec843531 100644 --- a/src/calibre/utils/localization.py +++ b/src/calibre/utils/localization.py @@ -260,25 +260,40 @@ def translator_for_lang(lang): return {'translator': t, 'iso639_translator': iso639, 'lcdata': lcdata} +default_translator = NullTranslations() + + +def _(x: str) -> str: + return default_translator.gettext(x) + + +def ngettext(singular: str, plural: str, n: int) -> str: + return default_translator.ngettext(singular, plural, n) + + +def pgettext(context: str, msg: str) -> str: + return default_translator.pgettext(context, msg) + + def set_translators(): - global _lang_trans, lcdata + global _lang_trans, lcdata, default_translator # To test different translations invoke as # CALIBRE_OVERRIDE_LANG=de_DE.utf8 program lang = get_lang() if lang: q = translator_for_lang(lang) - t = q['translator'] + default_translator = q['translator'] _lang_trans = q['iso639_translator'] if q['lcdata']: lcdata = q['lcdata'] else: - t = NullTranslations() + default_translator = NullTranslations() try: - set_translators.lang = t.info().get('language') + set_translators.lang = default_translator.info().get('language') except Exception: pass - t.install(names=('ngettext',)) + default_translator.install(names=('ngettext',)) # Now that we have installed a translator, we have to retranslate the help # for the global prefs object as it was instantiated in get_lang(), before # the translator was installed. From 4e4928be1a471e515098194f43e200fd58cd1897 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 9 Jan 2023 19:59:05 +0530 Subject: [PATCH 0094/2055] Get rid of lopen() --- recipes/go_comics.recipe | 2 +- src/calibre/db/adding.py | 4 +- src/calibre/db/backend.py | 30 +++++++------- src/calibre/db/cache.py | 12 +++--- src/calibre/db/cli/cmd_add.py | 10 ++--- src/calibre/db/cli/cmd_export.py | 2 +- src/calibre/db/cli/cmd_list.py | 2 +- src/calibre/db/cli/cmd_restore_database.py | 2 +- src/calibre/db/cli/cmd_set_metadata.py | 2 +- src/calibre/db/cli/main.py | 4 +- src/calibre/db/utils.py | 4 +- src/calibre/devices/__init__.py | 2 +- src/calibre/devices/android/driver.py | 8 ++-- src/calibre/devices/cli.py | 6 +-- src/calibre/devices/cybook/driver.py | 4 +- src/calibre/devices/hanvon/driver.py | 2 +- src/calibre/devices/kindle/apnx.py | 6 +-- .../apnx_page_generator/i_page_generator.py | 2 +- src/calibre/devices/kindle/bookmark.py | 12 +++--- src/calibre/devices/kindle/driver.py | 14 +++---- src/calibre/devices/kobo/driver.py | 12 +++--- src/calibre/devices/misc.py | 8 ++-- src/calibre/devices/mtp/driver.py | 4 +- src/calibre/devices/mtp/unix/sysfs.py | 4 +- src/calibre/devices/nook/driver.py | 4 +- src/calibre/devices/prs505/driver.py | 4 +- src/calibre/devices/prs505/sony_cache.py | 12 +++--- src/calibre/devices/prst1/driver.py | 2 +- src/calibre/devices/scanner.py | 2 +- .../devices/smart_device_app/driver.py | 8 ++-- src/calibre/devices/usbms/cli.py | 6 +-- src/calibre/devices/usbms/device.py | 2 +- src/calibre/devices/usbms/driver.py | 10 ++--- src/calibre/ebooks/chm/reader.py | 2 +- src/calibre/ebooks/comic/input.py | 6 +-- src/calibre/ebooks/conversion/config.py | 2 +- .../ebooks/conversion/plugins/chm_input.py | 2 +- .../ebooks/conversion/plugins/epub_input.py | 12 +++--- .../ebooks/conversion/plugins/epub_output.py | 2 +- .../ebooks/conversion/plugins/fb2_output.py | 2 +- .../ebooks/conversion/plugins/htmlz_output.py | 2 +- .../ebooks/conversion/plugins/mobi_input.py | 4 +- .../ebooks/conversion/plugins/oeb_output.py | 4 +- .../ebooks/conversion/plugins/pdb_output.py | 2 +- .../ebooks/conversion/plugins/pdf_input.py | 6 +-- .../ebooks/conversion/plugins/pml_input.py | 8 ++-- .../ebooks/conversion/plugins/pml_output.py | 4 +- .../ebooks/conversion/plugins/rb_output.py | 2 +- .../ebooks/conversion/plugins/recipe_input.py | 4 +- .../ebooks/conversion/plugins/rtf_input.py | 2 +- .../ebooks/conversion/plugins/rtf_output.py | 2 +- .../ebooks/conversion/plugins/snb_output.py | 2 +- .../ebooks/conversion/plugins/tcr_output.py | 2 +- src/calibre/ebooks/conversion/plumber.py | 8 ++-- src/calibre/ebooks/docx/cleanup.py | 2 +- src/calibre/ebooks/docx/to_html.py | 6 +-- src/calibre/ebooks/metadata/book/serialize.py | 2 +- src/calibre/ebooks/metadata/epub.py | 4 +- src/calibre/ebooks/metadata/meta.py | 12 +++--- src/calibre/ebooks/metadata/toc.py | 2 +- src/calibre/ebooks/mobi/reader/mobi6.py | 4 +- src/calibre/ebooks/mobi/writer2/resources.py | 2 +- src/calibre/ebooks/oeb/base.py | 4 +- src/calibre/ebooks/oeb/iterator/spine.py | 2 +- src/calibre/ebooks/oeb/polish/container.py | 22 +++++----- src/calibre/ebooks/oeb/polish/cover.py | 6 +-- src/calibre/ebooks/oeb/polish/download.py | 4 +- src/calibre/ebooks/oeb/polish/images.py | 4 +- src/calibre/ebooks/oeb/polish/tests/base.py | 8 ++-- .../ebooks/oeb/polish/tests/parsing.py | 2 +- src/calibre/ebooks/pdb/plucker/reader.py | 6 +-- src/calibre/ebooks/pdf/pdftohtml.py | 8 ++-- src/calibre/ebooks/tweak.py | 4 +- src/calibre/ebooks/txt/processor.py | 2 +- src/calibre/gui2/actions/add.py | 2 +- src/calibre/gui2/actions/edit_metadata.py | 2 +- src/calibre/gui2/comments_editor.py | 4 +- src/calibre/gui2/device.py | 8 ++-- src/calibre/gui2/email.py | 2 +- src/calibre/gui2/icon_theme.py | 8 ++-- src/calibre/gui2/main.py | 4 +- src/calibre/gui2/preferences/coloring.py | 4 +- src/calibre/gui2/preferences/server.py | 12 +++--- src/calibre/gui2/save.py | 6 +-- src/calibre/gui2/tweak_book/boss.py | 2 +- .../gui2/tweak_book/editor/smarts/html.py | 2 +- src/calibre/gui2/tweak_book/editor/text.py | 2 +- src/calibre/gui2/ui.py | 4 +- src/calibre/gui2/viewer/bookmarks.py | 4 +- src/calibre/gui2/viewer/convert_book.py | 2 +- src/calibre/gui2/viewer/main.py | 2 +- src/calibre/gui2/viewer/web_view.py | 2 +- src/calibre/gui2/widgets.py | 2 +- src/calibre/gui2/widgets2.py | 2 +- src/calibre/library/caches.py | 2 +- .../library/catalogs/epub_mobi_builder.py | 8 ++-- src/calibre/library/database.py | 2 +- src/calibre/library/database2.py | 40 +++++++++---------- src/calibre/library/save_to_disk.py | 10 ++--- src/calibre/srv/books.py | 6 +-- src/calibre/srv/code.py | 2 +- src/calibre/srv/embedded.py | 2 +- src/calibre/srv/jobs.py | 2 +- src/calibre/srv/render_book.py | 16 ++++---- src/calibre/srv/standalone.py | 6 +-- src/calibre/utils/exim.py | 14 +++---- src/calibre/utils/filenames.py | 4 +- src/calibre/utils/fonts/scanner.py | 4 +- src/calibre/utils/fonts/utils.py | 2 +- src/calibre/utils/hyphenation/dictionaries.py | 2 +- src/calibre/utils/img.py | 14 +++---- src/calibre/utils/imghdr.py | 4 +- src/calibre/utils/ipc/pool.py | 2 +- src/calibre/utils/ipc/server.py | 2 +- src/calibre/utils/ipc/worker.py | 2 +- src/calibre/utils/lock.py | 2 +- src/calibre/utils/magick/draw.py | 2 +- src/calibre/utils/magick/legacy.py | 4 +- src/calibre/utils/rapydscript.py | 18 ++++----- src/calibre/utils/tdir_in_cache.py | 4 +- src/calibre/web/feeds/news.py | 2 +- src/calibre/web/fetch/utils.py | 4 +- 122 files changed, 325 insertions(+), 327 deletions(-) diff --git a/recipes/go_comics.recipe b/recipes/go_comics.recipe index 26217ac8f1..740e4317c9 100644 --- a/recipes/go_comics.recipe +++ b/recipes/go_comics.recipe @@ -573,7 +573,7 @@ class GoComics(BasicNewsRecipe): fname = ascii_filename('%03d_%s' % (num, title)).replace(' ', '_') path = os.path.join(self.gocomics_dir, fname) html = '{h1}