This commit is contained in:
GRiker 2013-01-25 04:37:48 -07:00
commit efd98fe54c
104 changed files with 44914 additions and 40761 deletions

View File

@ -19,6 +19,64 @@
# new recipes:
# - title:
- version: 0.9.16
date: 2013-01-25
new features:
- title: "News download: Add support for logging in to sites that require javascript for their logins."
tickets: [1101809]
- title: "News download: Do not convert all downloaded images to JPG format. This fixes the problem of PNG images with transparent backgrounds being rendered with black backgrounds"
- title: "CHM Input: Support hierarchical table of contents. Do not generate an inline table of contents when a metadata table of contents is present. Also correctly decode the text in the table of contents"
- title: "Get Books: Add the beam-ebooks.de store"
- title: "Make custom yes/no columns using icons put text values under the icons."
- title: "Driver for LG E400 and SayCoolA710"
tickets: [1103741,1104528]
- title: "Speed up device connection when there are lots of books on the device by not generating cover thumbnails unless they are actually needed."
- title: "Have the metadata download dialog remember its last used size."
tickets: [1101150]
bug fixes:
- title: "Fix deleting a custom column that was used in a column coloring rule makes the column coloring preferences panel unusable"
tickets: [1103504]
- title: "Store caches outside the config directory for non-portable calibre installs"
- title: "PDF Output: Dont crash if the user has a font on his system that is missing the OS/2 table"
tickets: [1102403]
- title: "Conversion: Do not error out because of an error in user supplied search replace rules."
tickets: [1102647]
- title: "Conversion: Replace all non-ascii characters in CSS class names, as they cause problems with some broken EPUB renderers."
tickets: [1102587]
- title: "Do not choke when reading metadata from MOBI files with incorrectly encoded metadata fields"
- title: "Conversion: Preserve ToC entries that point nowhere instead of causing them to point to a non-existent file"
- title: "E-book viewer: Allow entries in the Table of Contents that do not point anywhere, instead of just ignoring them."
- title: "Content server: Fix the 'Previous' link in the mobile version of the content server webpage skipping an entry"
tickets: [1101124]
improved recipes:
- TSN
- St. Louis Post Dispatch
- Metro UK
- Michelle Malkin
- Barrons
new recipes:
- title: Contemporary Argentine Writers
author: Darko Miletic
- version: 0.9.15
date: 2013-01-18

View File

@ -0,0 +1,35 @@
__license__ = 'GPL v3'
__copyright__ = '2013, Darko Miletic <darko.miletic at gmail.com>'
'''
contemporaryargentinewriters.wordpress.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class contemporaryargentinewriters(BasicNewsRecipe):
title = 'Contemporary Argentine Writers'
__author__ = 'Darko Miletic'
description = 'Short stories by Argentine writers (and others) translated into English'
publisher = 'Dario Bard'
category = 'fiction, literature, Argentina, english'
oldest_article = 25
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'utf8'
use_embedded_content = True
language = 'en_AR'
remove_empty_feeds = True
publication_type = 'blog'
extra_css = """
body{font-family: Arial,Helvetica,sans-serif }
img{margin-bottom: 0.4em; display:block}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
feeds = [(u'Posts', u'http://contemporaryargentinewriters.wordpress.com/feed/')]

View File

@ -12,14 +12,14 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
"PO-Revision-Date: 2012-12-31 12:50+0000\n"
"PO-Revision-Date: 2013-01-19 20:28+0000\n"
"Last-Translator: Ferran Rius <frius64@hotmail.com>\n"
"Language-Team: Catalan <linux@softcatala.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-01-01 04:45+0000\n"
"X-Generator: Launchpad (build 16378)\n"
"X-Launchpad-Export-Date: 2013-01-20 04:36+0000\n"
"X-Generator: Launchpad (build 16430)\n"
"Language: ca\n"
#. name for aaa
@ -20216,7 +20216,7 @@ msgstr "Panjabi"
#. name for pao
msgid "Paiute; Northern"
msgstr ""
msgstr "Paiute Nord"
#. name for pap
msgid "Papiamento"
@ -20280,7 +20280,7 @@ msgstr "Popoloca; Coyotepec"
#. name for pbg
msgid "Paraujano"
msgstr ""
msgstr "Paraujano"
#. name for pbh
msgid "E'ñapa Woromaipu"
@ -20308,31 +20308,31 @@ msgstr ""
#. name for pbr
msgid "Pangwa"
msgstr ""
msgstr "Pangwa"
#. name for pbs
msgid "Pame; Central"
msgstr ""
msgstr "Pame; Central"
#. name for pbt
msgid "Pashto; Southern"
msgstr ""
msgstr "Paixtú; Meridional"
#. name for pbu
msgid "Pashto; Northern"
msgstr ""
msgstr "Paixtú; Septentrional"
#. name for pbv
msgid "Pnar"
msgstr ""
msgstr "Pnar"
#. name for pby
msgid "Pyu"
msgstr ""
msgstr "Pyu (Nova Guinea Papua)"
#. name for pbz
msgid "Palu"
msgstr ""
msgstr "Palu"
#. name for pca
msgid "Popoloca; Santa Inés Ahuatempan"
@ -20340,7 +20340,7 @@ msgstr "Popoloca; Ahuatempan"
#. name for pcb
msgid "Pear"
msgstr ""
msgstr "Pear"
#. name for pcc
msgid "Bouyei"
@ -20348,23 +20348,23 @@ msgstr ""
#. name for pcd
msgid "Picard"
msgstr ""
msgstr "Picard"
#. name for pce
msgid "Palaung; Ruching"
msgstr ""
msgstr "Palaung; Pale"
#. name for pcf
msgid "Paliyan"
msgstr ""
msgstr "Paliyà"
#. name for pcg
msgid "Paniya"
msgstr ""
msgstr "Paniya"
#. name for pch
msgid "Pardhan"
msgstr ""
msgstr "Pardhan"
#. name for pci
msgid "Duruwa"
@ -20372,7 +20372,7 @@ msgstr ""
#. name for pcj
msgid "Parenga"
msgstr ""
msgstr "Parenga"
#. name for pck
msgid "Chin; Paite"
@ -20380,7 +20380,7 @@ msgstr "Chin; Paite"
#. name for pcl
msgid "Pardhi"
msgstr ""
msgstr "Pardhi"
#. name for pcm
msgid "Pidgin; Nigerian"
@ -20388,19 +20388,19 @@ msgstr "Crioll nigerià"
#. name for pcn
msgid "Piti"
msgstr ""
msgstr "Piti"
#. name for pcp
msgid "Pacahuara"
msgstr ""
msgstr "Pacahuara"
#. name for pcr
msgid "Panang"
msgstr ""
msgstr "Panang"
#. name for pcw
msgid "Pyapun"
msgstr ""
msgstr "Pyapun"
#. name for pda
msgid "Anam"
@ -20412,19 +20412,19 @@ msgstr "Alemany; Pensilvàmia"
#. name for pdi
msgid "Pa Di"
msgstr ""
msgstr "Padi"
#. name for pdn
msgid "Podena"
msgstr ""
msgstr "Podena"
#. name for pdo
msgid "Padoe"
msgstr ""
msgstr "Padoe"
#. name for pdt
msgid "Plautdietsch"
msgstr ""
msgstr "Plautdeitsch"
#. name for pdu
msgid "Kayan"
@ -20436,7 +20436,7 @@ msgstr ""
#. name for peb
msgid "Pomo; Eastern"
msgstr ""
msgstr "Pomo; Oriental"
#. name for ped
msgid "Mala (Papua New Guinea)"
@ -20448,11 +20448,11 @@ msgstr ""
#. name for pef
msgid "Pomo; Northeastern"
msgstr ""
msgstr "Pomo; Nordoriental"
#. name for peg
msgid "Pengo"
msgstr ""
msgstr "Pengo"
#. name for peh
msgid "Bonan"
@ -20464,23 +20464,23 @@ msgstr ""
#. name for pej
msgid "Pomo; Northern"
msgstr ""
msgstr "Pomo; Septentrional"
#. name for pek
msgid "Penchal"
msgstr ""
msgstr "Penchal"
#. name for pel
msgid "Pekal"
msgstr ""
msgstr "Pekal"
#. name for pem
msgid "Phende"
msgstr ""
msgstr "Phende"
#. name for peo
msgid "Persian; Old (ca. 600-400 B.C.)"
msgstr ""
msgstr "Persa Antic"
#. name for pep
msgid "Kunja"
@ -20488,39 +20488,39 @@ msgstr ""
#. name for peq
msgid "Pomo; Southern"
msgstr ""
msgstr "Pomo; Meridional"
#. name for pes
msgid "Persian; Iranian"
msgstr ""
msgstr "Farsi; Occidental"
#. name for pev
msgid "Pémono"
msgstr ""
msgstr "Pemono"
#. name for pex
msgid "Petats"
msgstr ""
msgstr "Petats"
#. name for pey
msgid "Petjo"
msgstr ""
msgstr "Petjo"
#. name for pez
msgid "Penan; Eastern"
msgstr ""
msgstr "Penan; Oriental"
#. name for pfa
msgid "Pááfang"
msgstr ""
msgstr "Paafang"
#. name for pfe
msgid "Peere"
msgstr ""
msgstr "Peere"
#. name for pfl
msgid "Pfaelzisch"
msgstr ""
msgstr "Pfaelzisch"
#. name for pga
msgid "Creole Arabic; Sudanese"
@ -20528,11 +20528,11 @@ msgstr "Àrab crioll; sudanès"
#. name for pgg
msgid "Pangwali"
msgstr ""
msgstr "Pangwali"
#. name for pgi
msgid "Pagi"
msgstr ""
msgstr "Pagi"
#. name for pgk
msgid "Rerep"
@ -20544,83 +20544,83 @@ msgstr "Irlandès; primitiu"
#. name for pgn
msgid "Paelignian"
msgstr ""
msgstr "Paelignià"
#. name for pgs
msgid "Pangseng"
msgstr ""
msgstr "Pangseng"
#. name for pgu
msgid "Pagu"
msgstr ""
msgstr "Pagu"
#. name for pgy
msgid "Pongyong"
msgstr ""
msgstr "Pongyong"
#. name for pha
msgid "Pa-Hng"
msgstr ""
msgstr "Baheng"
#. name for phd
msgid "Phudagi"
msgstr ""
msgstr "Phudagi"
#. name for phg
msgid "Phuong"
msgstr ""
msgstr "Phuong"
#. name for phh
msgid "Phukha"
msgstr ""
msgstr "Phula"
#. name for phk
msgid "Phake"
msgstr ""
msgstr "Phake"
#. name for phl
msgid "Phalura"
msgstr ""
msgstr "Phalura"
#. name for phm
msgid "Phimbi"
msgstr ""
msgstr "Phimbi"
#. name for phn
msgid "Phoenician"
msgstr ""
msgstr "Fenici"
#. name for pho
msgid "Phunoi"
msgstr ""
msgstr "Phunoi"
#. name for phq
msgid "Phana'"
msgstr ""
msgstr "Phana"
#. name for phr
msgid "Pahari-Potwari"
msgstr ""
msgstr "Pahari; Potwari"
#. name for pht
msgid "Phu Thai"
msgstr ""
msgstr "Phu Thai"
#. name for phu
msgid "Phuan"
msgstr ""
msgstr "Phuan"
#. name for phv
msgid "Pahlavani"
msgstr ""
msgstr "Pahlavani"
#. name for phw
msgid "Phangduwali"
msgstr ""
msgstr "Phangduwali"
#. name for pia
msgid "Pima Bajo"
msgstr ""
msgstr "Pima Baix"
#. name for pib
msgid "Yine"
@ -20628,35 +20628,35 @@ msgstr ""
#. name for pic
msgid "Pinji"
msgstr ""
msgstr "Pinji"
#. name for pid
msgid "Piaroa"
msgstr ""
msgstr "Piaroa"
#. name for pie
msgid "Piro"
msgstr ""
msgstr "Piro"
#. name for pif
msgid "Pingelapese"
msgstr ""
msgstr "Pingelapès"
#. name for pig
msgid "Pisabo"
msgstr ""
msgstr "Pisabo"
#. name for pih
msgid "Pitcairn-Norfolk"
msgstr ""
msgstr "Pitcairn-Norfolk"
#. name for pii
msgid "Pini"
msgstr ""
msgstr "Pini"
#. name for pij
msgid "Pijao"
msgstr ""
msgstr "Pijao"
#. name for pil
msgid "Yom"
@ -20664,59 +20664,59 @@ msgstr ""
#. name for pim
msgid "Powhatan"
msgstr ""
msgstr "Powhatan"
#. name for pin
msgid "Piame"
msgstr ""
msgstr "Piame"
#. name for pio
msgid "Piapoco"
msgstr ""
msgstr "Piapoco"
#. name for pip
msgid "Pero"
msgstr ""
msgstr "Pero"
#. name for pir
msgid "Piratapuyo"
msgstr ""
msgstr "Piratapuyo"
#. name for pis
msgid "Pijin"
msgstr ""
msgstr "Pijin"
#. name for pit
msgid "Pitta Pitta"
msgstr ""
msgstr "Pita Pita"
#. name for piu
msgid "Pintupi-Luritja"
msgstr ""
msgstr "Pintupi-Luritja"
#. name for piv
msgid "Pileni"
msgstr ""
msgstr "Pileni"
#. name for piw
msgid "Pimbwe"
msgstr ""
msgstr "Pimbwe"
#. name for pix
msgid "Piu"
msgstr ""
msgstr "Piu"
#. name for piy
msgid "Piya-Kwonci"
msgstr ""
msgstr "Piya-Kwonci"
#. name for piz
msgid "Pije"
msgstr ""
msgstr "Pije"
#. name for pjt
msgid "Pitjantjatjara"
msgstr ""
msgstr "Pitjantjatjara"
#. name for pka
msgid "Prākrit; Ardhamāgadhī"
@ -20724,31 +20724,31 @@ msgstr ""
#. name for pkb
msgid "Pokomo"
msgstr ""
msgstr "Pokomo"
#. name for pkc
msgid "Paekche"
msgstr ""
msgstr "Paekche"
#. name for pkg
msgid "Pak-Tong"
msgstr ""
msgstr "Pak-Tong"
#. name for pkh
msgid "Pankhu"
msgstr ""
msgstr "Pankhu"
#. name for pkn
msgid "Pakanha"
msgstr ""
msgstr "Pakanha"
#. name for pko
msgid "Pökoot"
msgstr ""
msgstr "Pokoot"
#. name for pkp
msgid "Pukapuka"
msgstr ""
msgstr "Pukapuka"
#. name for pkr
msgid "Kurumba; Attapady"
@ -20764,7 +20764,7 @@ msgstr ""
#. name for pku
msgid "Paku"
msgstr ""
msgstr "Paku"
#. name for pla
msgid "Miani"
@ -20772,7 +20772,7 @@ msgstr ""
#. name for plb
msgid "Polonombauk"
msgstr ""
msgstr "Polonombauk"
#. name for plc
msgid "Palawano; Central"
@ -20808,7 +20808,7 @@ msgstr ""
#. name for pll
msgid "Palaung; Shwe"
msgstr ""
msgstr "Palaung; Shwe"
#. name for pln
msgid "Palenquero"
@ -20900,7 +20900,7 @@ msgstr ""
#. name for pmm
msgid "Pomo"
msgstr ""
msgstr "Pol"
#. name for pmn
msgid "Pam"
@ -20912,7 +20912,7 @@ msgstr ""
#. name for pmq
msgid "Pame; Northern"
msgstr ""
msgstr "Pame; Septentrional"
#. name for pmr
msgid "Paynamar"
@ -20944,7 +20944,7 @@ msgstr "Malai; Papua"
#. name for pmz
msgid "Pame; Southern"
msgstr ""
msgstr "Pame; Meridional"
#. name for pna
msgid "Punan Bah-Biau"
@ -20960,7 +20960,7 @@ msgstr ""
#. name for pne
msgid "Penan; Western"
msgstr ""
msgstr "Penan; Occidental"
#. name for png
msgid "Pongu"
@ -21068,7 +21068,7 @@ msgstr "Polonès"
#. name for pom
msgid "Pomo; Southeastern"
msgstr ""
msgstr "Pomo; Sudoriental"
#. name for pon
msgid "Pohnpeian"
@ -21076,7 +21076,7 @@ msgstr ""
#. name for poo
msgid "Pomo; Central"
msgstr ""
msgstr "Pomo; Central"
#. name for pop
msgid "Pwapwa"
@ -21244,7 +21244,7 @@ msgstr ""
#. name for prs
msgid "Persian; Afghan"
msgstr ""
msgstr "Farsi; Oriental"
#. name for prt
msgid "Phai"
@ -21332,7 +21332,7 @@ msgstr ""
#. name for pst
msgid "Pashto; Central"
msgstr ""
msgstr "Paixtú; Central"
#. name for psu
msgid "Prākrit; Sauraseni"
@ -21884,7 +21884,7 @@ msgstr ""
#. name for rbb
msgid "Palaung; Rumai"
msgstr ""
msgstr "Palaung; Rumai"
#. name for rbk
msgid "Bontok; Northern"

View File

@ -18,14 +18,14 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
"PO-Revision-Date: 2012-11-08 15:28+0000\n"
"Last-Translator: Elmux <bla.mail@gmx.net>\n"
"PO-Revision-Date: 2013-01-19 18:12+0000\n"
"Last-Translator: Dennis Baudys <Unknown>\n"
"Language-Team: German <debian-l10n-german@lists.debian.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-11-09 04:39+0000\n"
"X-Generator: Launchpad (build 16250)\n"
"X-Launchpad-Export-Date: 2013-01-20 04:36+0000\n"
"X-Generator: Launchpad (build 16430)\n"
"Language: de\n"
#. name for aaa
@ -1432,11 +1432,11 @@ msgstr "Arrarnta; Westlich"
#. name for arg
msgid "Aragonese"
msgstr "Aragonese"
msgstr "Aragonesisch"
#. name for arh
msgid "Arhuaco"
msgstr ""
msgstr "Arhuaco"
#. name for ari
msgid "Arikara"
@ -17702,15 +17702,15 @@ msgstr ""
#. name for nau
msgid "Nauru"
msgstr "Nauruanisch"
msgstr "Nauruisch"
#. name for nav
msgid "Navajo"
msgstr ""
msgstr "Navajo"
#. name for naw
msgid "Nawuri"
msgstr ""
msgstr "Nawuri"
#. name for nax
msgid "Nakwi"
@ -29894,7 +29894,7 @@ msgstr ""
#. name for yor
msgid "Yoruba"
msgstr "Joruba"
msgstr "Yoruba"
#. name for yos
msgid "Yos"

View File

@ -13,14 +13,14 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
"PO-Revision-Date: 2012-10-20 00:57+0000\n"
"Last-Translator: Ida Leter <iatheia@yandex.ru>\n"
"PO-Revision-Date: 2013-01-21 14:06+0000\n"
"Last-Translator: Don Miguel <bmv@mail.ru>\n"
"Language-Team: Russian <debian-l10n-russian@lists.debian.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-10-21 04:41+0000\n"
"X-Generator: Launchpad (build 16165)\n"
"X-Launchpad-Export-Date: 2013-01-22 04:46+0000\n"
"X-Generator: Launchpad (build 16430)\n"
"Language: ru\n"
#. name for aaa
@ -181,7 +181,7 @@ msgstr "Айта; Абенлен"
#. name for abq
msgid "Abaza"
msgstr ""
msgstr "Абазинский"
#. name for abr
msgid "Abron"
@ -189,7 +189,7 @@ msgstr "Аброн"
#. name for abs
msgid "Malay; Ambonese"
msgstr ""
msgstr "Малайский"
#. name for abt
msgid "Ambulas"
@ -205,31 +205,31 @@ msgstr ""
#. name for abw
msgid "Pal"
msgstr ""
msgstr "Палский"
#. name for abx
msgid "Inabaknon"
msgstr ""
msgstr "Абакнонский"
#. name for aby
msgid "Aneme Wake"
msgstr ""
msgstr "Анеме Ваке"
#. name for abz
msgid "Abui"
msgstr ""
msgstr "Абуи"
#. name for aca
msgid "Achagua"
msgstr ""
msgstr "Ачагуа"
#. name for acb
msgid "Áncá"
msgstr ""
msgstr "Анка"
#. name for acd
msgid "Gikyode"
msgstr ""
msgstr "Гикиод"
#. name for ace
msgid "Achinese"
@ -261,7 +261,7 @@ msgstr ""
#. name for acn
msgid "Achang"
msgstr ""
msgstr "Ачанг"
#. name for acp
msgid "Acipa; Eastern"

View File

@ -4,7 +4,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = u'calibre'
numeric_version = (0, 9, 15)
numeric_version = (0, 9, 16)
__version__ = u'.'.join(map(unicode, numeric_version))
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"

View File

@ -1280,6 +1280,17 @@ class StoreBNStore(StoreBase):
headquarters = 'US'
formats = ['NOOK']
class StoreBeamEBooksDEStore(StoreBase):
name = 'Beam EBooks DE'
author = 'Charles Haley'
description = u'Bei uns finden Sie: Tausende deutschsprachige eBooks; Alle eBooks ohne hartes DRM; PDF, ePub und Mobipocket Format; Sofortige Verfügbarkeit - 24 Stunden am Tag; Günstige Preise; eBooks für viele Lesegeräte, PC,Mac und Smartphones; Viele Gratis eBooks'
actual_plugin = 'calibre.gui2.store.stores.beam_ebooks_de_plugin:BeamEBooksDEStore'
drm_free_only = True
headquarters = 'DE'
formats = ['EPUB', 'MOBI', 'PDF']
affiliate = True
class StoreBeWriteStore(StoreBase):
name = 'BeWrite Books'
description = u'Publishers of fine books. Highly selective and editorially driven. Does not offer: books for children or exclusively YA, erotica, swords-and-sorcery fantasy and space-opera-style science fiction. All other genres are represented.'
@ -1669,6 +1680,7 @@ plugins += [
StoreAmazonUKKindleStore,
StoreBaenWebScriptionStore,
StoreBNStore,
StoreBeamEBooksDEStore,
StoreBeWriteStore,
StoreBiblioStore,
StoreBookotekaStore,

View File

@ -354,6 +354,10 @@ class Cache(object):
def pref(self, name, default=None):
return self.backend.prefs.get(name, default)
@write_api
def set_pref(self, name, val):
self.backend.prefs.set(name, val)
@api
def get_metadata(self, book_id,
get_cover=False, get_user_categories=True, cover_as_data=False):

View File

@ -7,14 +7,17 @@ __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import copy
from functools import partial
from operator import attrgetter
from future_builtins import map
from calibre.library.field_metadata import TagsIcons
from calibre.utils.config_base import tweaks
from calibre.utils.icu import sort_key
from calibre.utils.search_query_parser import saved_searches
CATEGORY_SORTS = { 'name', 'popularity', 'rating' }
CATEGORY_SORTS = ('name', 'popularity', 'rating') # This has to be a tuple not a set
class Tag(object):
@ -53,8 +56,7 @@ class Tag(object):
def find_categories(field_metadata):
for category, cat in field_metadata.iteritems():
if (cat['is_category'] and cat['kind'] not in {'user', 'search'} and
category != 'news'):
if (cat['is_category'] and cat['kind'] not in {'user', 'search'}):
yield (category, cat['is_multiple'].get('cache_to_list', None), False)
elif (cat['datatype'] == 'composite' and
cat['display'].get('make_category', False)):
@ -88,6 +90,39 @@ def create_tag_class(category, fm, icon_map):
tooltip=tooltip, is_editable=is_editable,
category=category)
def clean_user_categories(dbcache):
user_cats = dbcache.pref('user_categories', {})
new_cats = {}
for k in user_cats:
comps = [c.strip() for c in k.split('.') if c.strip()]
if len(comps) == 0:
i = 1
while True:
if unicode(i) not in user_cats:
new_cats[unicode(i)] = user_cats[k]
break
i += 1
else:
new_cats['.'.join(comps)] = user_cats[k]
try:
if new_cats != user_cats:
dbcache.set_pref('user_categories', new_cats)
except:
pass
return new_cats
def sort_categories(items, sort):
reverse = True
if sort == 'popularity':
key=attrgetter('count')
elif sort == 'rating':
key=attrgetter('avg_rating')
else:
key=lambda x:sort_key(x.sort or x.name)
reverse=False
items.sort(key=key, reverse=reverse)
return items
def get_categories(dbcache, sort='name', book_ids=None, icon_map=None):
if icon_map is not None and type(icon_map) != TagsIcons:
raise TypeError('icon_map passed to get_categories must be of type TagIcons')
@ -100,19 +135,98 @@ def get_categories(dbcache, sort='name', book_ids=None, icon_map=None):
categories = {}
book_ids = frozenset(book_ids) if book_ids else book_ids
get_metadata = partial(dbcache._get_metadata, get_user_categories=False)
bids = None
for category, is_multiple, is_composite in find_categories(fm):
tag_class = create_tag_class(category, fm, icon_map)
if is_composite:
if bids is None:
bids = dbcache._all_book_ids() if book_ids is None else book_ids
cats = dbcache.fields[category].get_composite_categories(
tag_class, book_rating_map, bids, is_multiple, get_metadata)
elif category == 'news':
cats = dbcache.fields['tags'].get_news_category(tag_class, book_ids)
else:
cats = dbcache.fields[category].get_categories(
tag_class, book_rating_map, lang_map, book_ids)
if sort == 'popularity':
key=attrgetter('count')
elif sort == 'rating':
key=attrgetter('avg_rating')
else:
key=lambda x:sort_key(x.sort or x.name)
cats.sort(key=key)
sort_categories(cats, sort)
categories[category] = cats
# Needed for legacy databases that have multiple ratings that
# map to n stars
for r in categories['rating']:
for x in tuple(categories['rating']):
if r.name == x.name and r.id != x.id:
r.id_set |= x.id_set
r.count = r.count + x.count
categories['rating'].remove(x)
break
# User categories
user_categories = clean_user_categories(dbcache).copy()
if user_categories:
# We want to use same node in the user category as in the source
# category. To do that, we need to find the original Tag node. There is
# a time/space tradeoff here. By converting the tags into a map, we can
# do the verification in the category loop much faster, at the cost of
# temporarily duplicating the categories lists.
taglist = {}
for c, items in categories.iteritems():
taglist[c] = dict(map(lambda t:(icu_lower(t.name), t), items))
muc = dbcache.pref('grouped_search_make_user_categories', [])
gst = dbcache.pref('grouped_search_terms', {})
for c in gst:
if c not in muc:
continue
user_categories[c] = []
for sc in gst[c]:
if sc in categories.keys():
for t in categories[sc]:
user_categories[c].append([t.name, sc, 0])
gst_icon = icon_map['gst'] if icon_map else None
for user_cat in sorted(user_categories.iterkeys(), key=sort_key):
items = []
names_seen = {}
for name, label, ign in user_categories[user_cat]:
n = icu_lower(name)
if label in taglist and n in taglist[label]:
if user_cat in gst:
# for gst items, make copy and consolidate the tags by name.
if n in names_seen:
t = names_seen[n]
t.id_set |= taglist[label][n].id_set
t.count += taglist[label][n].count
t.tooltip = t.tooltip.replace(')', ', ' + label + ')')
else:
t = copy.copy(taglist[label][n])
t.icon = gst_icon
names_seen[t.name] = t
items.append(t)
else:
items.append(taglist[label][n])
# else: do nothing, to not include nodes w zero counts
cat_name = '@' + user_cat # add the '@' to avoid name collision
# Not a problem if we accumulate entries in the icon map
if icon_map is not None:
icon_map[cat_name] = icon_map['user:']
categories[cat_name] = sort_categories(items, sort)
#### Finally, the saved searches category ####
items = []
icon = None
if icon_map and 'search' in icon_map:
icon = icon_map['search']
ss = saved_searches()
for srch in ss.names():
items.append(Tag(srch, tooltip=ss.lookup(srch),
sort=srch, icon=icon, category='search',
is_editable=False))
if len(items):
categories['search'] = items
return categories

View File

@ -186,6 +186,25 @@ class CompositeField(OneToOneField):
for val, book_ids in val_map.iteritems():
yield val, book_ids
def get_composite_categories(self, tag_class, book_rating_map, book_ids,
is_multiple, get_metadata):
ans = []
id_map = defaultdict(set)
for book_id in book_ids:
val = self.get_value_with_cache(book_id, get_metadata)
vals = [x.strip() for x in val.split(is_multiple)] if is_multiple else [val]
for val in vals:
if val:
id_map[val].add(book_id)
for item_id, item_book_ids in id_map.iteritems():
ratings = tuple(r for r in (book_rating_map.get(book_id, 0) for
book_id in item_book_ids) if r > 0)
avg = sum(ratings)/len(ratings) if ratings else 0
c = tag_class(item_id, id=item_id, sort=item_id, avg=avg,
id_set=item_book_ids, count=len(item_book_ids))
ans.append(c)
return ans
class OnDeviceField(OneToOneField):
def __init__(self, name, table):
@ -441,6 +460,34 @@ class SeriesField(ManyToOneField):
val = self.table.id_map[item_id]
return title_sort(val, order=tss, lang=lang)
class TagsField(ManyToManyField):
def get_news_category(self, tag_class, book_ids=None):
news_id = None
ans = []
for item_id, val in self.table.id_map.iteritems():
if val == _('News'):
news_id = item_id
break
if news_id is None:
return ans
news_books = self.table.col_book_map[news_id]
if book_ids is not None:
news_books = news_books.intersection(book_ids)
if not news_books:
return ans
for item_id, item_book_ids in self.table.col_book_map.iteritems():
item_book_ids = item_book_ids.intersection(news_books)
if item_book_ids:
name = self.category_formatter(self.table.id_map[item_id])
if name == _('News'):
continue
c = tag_class(name, id=item_id, sort=name,
id_set=item_book_ids, count=len(item_book_ids))
ans.append(c)
return ans
def create_field(name, table):
cls = {
ONE_ONE : OneToOneField,
@ -455,6 +502,8 @@ def create_field(name, table):
cls = FormatsField
elif name == 'identifiers':
cls = IdentifiersField
elif name == 'tags':
cls = TagsField
elif table.metadata['datatype'] == 'composite':
cls = CompositeField
elif table.metadata['datatype'] == 'series':

View File

@ -17,7 +17,6 @@ from calibre.utils.icu import primary_find
from calibre.utils.localization import lang_map, canonicalize_lang
from calibre.utils.search_query_parser import SearchQueryParser, ParseException
# TODO: Thread safety of saved searches
CONTAINS_MATCH = 0
EQUALS_MATCH = 1
REGEXP_MATCH = 2

View File

@ -98,10 +98,10 @@ class CompositeTable(OneToOneTable):
self.book_col_map = {}
d = self.metadata['display']
self.composite_template = ['composite_template']
self.contains_html = d['contains_html']
self.make_category = d['make_category']
self.composite_sort = d['composite_sort']
self.use_decorations = d['use_decorations']
self.contains_html = d.get('contains_html', False)
self.make_category = d.get('make_category', False)
self.composite_sort = d.get('composite_sort', False)
self.use_decorations = d.get('use_decorations', False)
class ManyToOneTable(Table):

Binary file not shown.

View File

@ -95,7 +95,7 @@ class ReadingTest(BaseTest):
'series' : 'A Series One',
'series_index': 2.0,
'rating': 6.0,
'tags': ('Tag One',),
'tags': ('Tag One', 'News'),
'formats':(),
'identifiers': {'test':'two'},
'timestamp': datetime.datetime(2011, 9, 6, 6, 0,

View File

@ -93,6 +93,7 @@ class ANDROID(USBMS):
# Google
0x18d1 : {
0x0001 : [0x0223, 0x230, 0x9999],
0x0002 : [0x9999],
0x0003 : [0x0230, 0x9999],
0x4e11 : [0x0100, 0x226, 0x227],
0x4e12 : [0x0100, 0x226, 0x227],

View File

@ -35,7 +35,7 @@ from calibre.library.server import server_config as content_server_config
from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.ipc import eintr_retry_call
from calibre.utils.config import from_json, tweaks
from calibre.utils.date import isoformat, now, UNDEFINED_DATE
from calibre.utils.date import isoformat, now
from calibre.utils.filenames import ascii_filename as sanitize, shorten_components_to
from calibre.utils.mdns import (publish as publish_zeroconf, unpublish as
unpublish_zeroconf, get_all_ips)

View File

@ -1430,7 +1430,10 @@ def metadata_to_opf(mi, as_string=True, default_lang=None):
elem = metadata.makeelement(tag, attrib=attrib)
elem.tail = '\n'+(' '*8)
if text:
try:
elem.text = text.strip()
except ValueError:
elem.text = clean_ascii_chars(text.strip())
metadata.append(elem)
factory(DC('title'), mi.title)

View File

@ -87,6 +87,10 @@ class Page(QWebPage): # {{{
def javaScriptAlert(self, frame, msg):
self.log(unicode(msg))
def shouldInterruptJavaScript(self):
return False
# }}}
def draw_image_page(page_rect, painter, p, preserve_aspect_ratio=True):

View File

@ -634,13 +634,12 @@ class BooksModel(QAbstractTableModel): # {{{
def bool_type_decorator(r, idx=-1, bool_cols_are_tristate=True):
val = force_to_bool(self.db.data[r][idx])
if val is None:
if not bool_cols_are_tristate:
if val is None or not val:
return self.bool_no_icon
return NONE
if val:
return self.bool_yes_icon
if val is None:
return self.bool_blank_icon
return self.bool_no_icon
def ondevice_decorator(r, idx=-1):

View File

@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
store_version = 1 # Needed for dynamic plugin loading
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import urllib2
from contextlib import closing
from lxml import html
from PyQt4.Qt import QUrl
from calibre import browser
from calibre.gui2 import open_url
from calibre.gui2.store import StorePlugin
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.web_store_dialog import WebStoreDialog
class BeamEBooksDEStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
url = 'http://klick.affiliwelt.net/klick.php?bannerid=10072&pid=32307&prid=908'
url_details = ('http://klick.affiliwelt.net/klick.php?'
'bannerid=66820&pid=32307&prid=908&feedid=27&prdid={0}')
if external or self.config.get('open_external', False):
if detail_item:
url = url_details.format(detail_item)
open_url(QUrl(url))
else:
detail_url = None
if detail_item:
detail_url = url_details.format(detail_item)
d = WebStoreDialog(self.gui, url, parent, detail_url)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()
def search(self, query, max_results=10, timeout=60):
url = 'http://www.beam-ebooks.de/suchergebnis.php?Type=&sw=' + urllib2.quote(query)
br = browser()
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read())
for data in doc.xpath('//table[tr/td/div[@class="stil2"]]'):
if counter <= 0:
break
id_ = ''.join(data.xpath('./tr/td[1]/a/@href')).strip()
if not id_:
continue
id_ = id_[7:]
cover_url = ''.join(data.xpath('./tr/td[1]/a/img/@src'))
if cover_url:
cover_url = 'http://www.beam-ebooks.de' + cover_url
temp = ''.join(data.xpath('./tr/td[1]/a/img/@alt'))
colon = temp.find(':')
if not temp.startswith('eBook') or colon < 0:
continue
author = temp[5:colon]
title = temp[colon+1:]
price = ''.join(data.xpath('./tr/td[3]/text()'))
pdf = data.xpath(
'boolean(./tr/td[3]/a/img[contains(@alt, "PDF")]/@alt)')
epub = data.xpath(
'boolean(./tr/td[3]/a/img[contains(@alt, "ePub")]/@alt)')
mobi = data.xpath(
'boolean(./tr/td[3]/a/img[contains(@alt, "Mobipocket")]/@alt)')
counter -= 1
s = SearchResult()
s.cover_url = cover_url
s.title = title.strip()
s.author = author.strip()
s.price = price
s.drm = SearchResult.DRM_UNLOCKED
s.detail_item = id_
formats = []
if epub:
formats.append('ePub')
if pdf:
formats.append('PDF')
if mobi:
formats.append('MOBI')
s.formats = ', '.join(formats)
yield s

View File

@ -44,7 +44,7 @@ from calibre.utils.recycle_bin import delete_file, delete_tree
from calibre.utils.formatter_functions import load_user_template_functions
from calibre.db.errors import NoSuchFormat
from calibre.db.lazy import FormatMetadata, FormatsList
from calibre.db.categories import Tag
from calibre.db.categories import Tag, CATEGORY_SORTS
from calibre.utils.localization import (canonicalize_lang,
calibre_langcode_to_name)
@ -1644,7 +1644,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
########## data structures for get_categories
CATEGORY_SORTS = ('name', 'popularity', 'rating')
CATEGORY_SORTS = CATEGORY_SORTS
MATCH_TYPE = ('any', 'all')
class TCat_Tag(object):

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More