Make series index a float and add a column for publish date

This commit is contained in:
Kovid Goyal 2009-05-30 23:37:05 -07:00
parent 597fd045a9
commit 3585977bb3
18 changed files with 465 additions and 369 deletions

View File

@ -42,6 +42,31 @@ def title_sort(title):
title = title.replace(prep, '') + ', ' + prep title = title.replace(prep, '') + ', ' + prep
return title.strip() return title.strip()
coding = zip(
[1000,900,500,400,100,90,50,40,10,9,5,4,1],
["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"]
)
def roman(num):
if num <= 0 or num >= 4000 or int(num) != num:
return str(num)
result = []
for d, r in coding:
while num >= d:
result.append(r)
num -= d
return ''.join(result)
def fmt_sidx(i, fmt='%.2f', use_roman=False):
if i is None:
i = 1
if int(i) == i:
return roman(i) if use_roman else '%d'%i
return fmt%i
class Resource(object): class Resource(object):
''' '''
@ -187,7 +212,8 @@ class MetaInformation(object):
'publisher', 'series', 'series_index', 'rating', 'publisher', 'series', 'series_index', 'rating',
'isbn', 'tags', 'cover_data', 'application_id', 'guide', 'isbn', 'tags', 'cover_data', 'application_id', 'guide',
'manifest', 'spine', 'toc', 'cover', 'language', 'manifest', 'spine', 'toc', 'cover', 'language',
'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc'): 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc',
'pubdate'):
if hasattr(mi, attr): if hasattr(mi, attr):
setattr(ans, attr, getattr(mi, attr)) setattr(ans, attr, getattr(mi, attr))
@ -212,7 +238,7 @@ class MetaInformation(object):
for x in ('author_sort', 'title_sort', 'comments', 'category', 'publisher', for x in ('author_sort', 'title_sort', 'comments', 'category', 'publisher',
'series', 'series_index', 'rating', 'isbn', 'language', 'series', 'series_index', 'rating', 'isbn', 'language',
'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover', 'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover',
'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc' 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate'
): ):
setattr(self, x, getattr(mi, x, None)) setattr(self, x, getattr(mi, x, None))
@ -231,7 +257,7 @@ class MetaInformation(object):
'publisher', 'series', 'series_index', 'rating', 'publisher', 'series', 'series_index', 'rating',
'isbn', 'application_id', 'manifest', 'spine', 'toc', 'isbn', 'application_id', 'manifest', 'spine', 'toc',
'cover', 'language', 'guide', 'book_producer', 'cover', 'language', 'guide', 'book_producer',
'timestamp', 'lccn', 'lcc', 'ddc'): 'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate'):
if hasattr(mi, attr): if hasattr(mi, attr):
val = getattr(mi, attr) val = getattr(mi, attr)
if val is not None: if val is not None:
@ -262,8 +288,8 @@ class MetaInformation(object):
try: try:
x = float(self.series_index) x = float(self.series_index)
except ValueError: except ValueError:
x = 1.0 x = 1
return '%d'%x if int(x) == x else '%.2f'%x return fmt_sidx(x)
def authors_from_string(self, raw): def authors_from_string(self, raw):
self.authors = string_to_authors(raw) self.authors = string_to_authors(raw)
@ -299,6 +325,8 @@ class MetaInformation(object):
fmt('Rating', self.rating) fmt('Rating', self.rating)
if self.timestamp is not None: if self.timestamp is not None:
fmt('Timestamp', self.timestamp.isoformat(' ')) fmt('Timestamp', self.timestamp.isoformat(' '))
if self.pubdate is not None:
fmt('Published', self.pubdate.isoformat(' '))
if self.lccn: if self.lccn:
fmt('LCCN', unicode(self.lccn)) fmt('LCCN', unicode(self.lccn))
if self.lcc: if self.lcc:
@ -327,6 +355,8 @@ class MetaInformation(object):
ans += [(_('Language'), unicode(self.language))] ans += [(_('Language'), unicode(self.language))]
if self.timestamp is not None: if self.timestamp is not None:
ans += [(_('Timestamp'), unicode(self.timestamp.isoformat(' ')))] ans += [(_('Timestamp'), unicode(self.timestamp.isoformat(' ')))]
if self.pubdate is not None:
ans += [(_('Published'), unicode(self.pubdate.isoformat(' ')))]
for i, x in enumerate(ans): for i, x in enumerate(ans):
ans[i] = u'<tr><td><b>%s</b></td><td>%s</td></tr>'%x ans[i] = u'<tr><td><b>%s</b></td><td>%s</td></tr>'%x
return u'<table>%s</table>'%u'\n'.join(ans) return u'<table>%s</table>'%u'\n'.join(ans)

View File

@ -5,7 +5,7 @@ __copyright__ = '2008, Anatoly Shipitsin <norguhtar at gmail.com>'
'''Read meta information from fb2 files''' '''Read meta information from fb2 files'''
import sys, os, mimetypes import mimetypes
from base64 import b64decode from base64 import b64decode
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup
@ -42,7 +42,7 @@ def get_metadata(stream):
if series: if series:
mi.series = series.get('name', None) mi.series = series.get('name', None)
try: try:
mi.series_index = int(series.get('number', None)) mi.series_index = float(series.get('number', None))
except (TypeError, ValueError): except (TypeError, ValueError):
pass pass
if cdata: if cdata:

View File

@ -145,7 +145,7 @@ def metadata_from_filename(name, pat=None):
pass pass
try: try:
si = match.group('series_index') si = match.group('series_index')
mi.series_index = int(si) mi.series_index = float(si)
except (IndexError, ValueError, TypeError): except (IndexError, ValueError, TypeError):
pass pass
try: try:

View File

@ -2,8 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''Read/Write metadata from Open Packaging Format (.opf) files.''' '''Read/Write metadata from Open Packaging Format (.opf) files.'''
import sys, re, os, glob import re, os
import cStringIO
import uuid import uuid
from urllib import unquote, quote from urllib import unquote, quote
@ -374,7 +373,7 @@ class OPF(MetaInformation):
s = self.metadata.find('series-index') s = self.metadata.find('series-index')
if s and s.string: if s and s.string:
try: try:
return int(str(s.string).strip()) return float(str(s.string).strip())
except: except:
return None return None
return None return None

View File

@ -9,15 +9,16 @@
<dc:creator opf:role="aut" py:for="i, author in enumerate(mi.authors)" py:attrs="{'opf:file-as':mi.author_sort} if mi.author_sort and i == 0 else {}">${author}</dc:creator> <dc:creator opf:role="aut" py:for="i, author in enumerate(mi.authors)" py:attrs="{'opf:file-as':mi.author_sort} if mi.author_sort and i == 0 else {}">${author}</dc:creator>
<dc:contributor opf:role="bkp" py:with="attrs={'opf:file-as':__appname__}" py:attrs="attrs">${'%s (%s)'%(__appname__, __version__)} [http://${__appname__}.kovidgoyal.net]</dc:contributor> <dc:contributor opf:role="bkp" py:with="attrs={'opf:file-as':__appname__}" py:attrs="attrs">${'%s (%s)'%(__appname__, __version__)} [http://${__appname__}.kovidgoyal.net]</dc:contributor>
<dc:identifier opf:scheme="${__appname__}" id="${__appname__}_id">${mi.application_id}</dc:identifier> <dc:identifier opf:scheme="${__appname__}" id="${__appname__}_id">${mi.application_id}</dc:identifier>
<dc:date py:if="getattr(mi, 'timestamp', None) is not None">${mi.timestamp.isoformat()}</dc:date> <dc:date py:if="getattr(mi, 'pubdate', None) is not None">${mi.pubdate.isoformat()}</dc:date>
<dc:language>${mi.language if mi.language else 'UND'}</dc:language> <dc:language>${mi.language if mi.language else 'UND'}</dc:language>
<dc:type py:if="getattr(mi, 'category', False)">${mi.category}</dc:type> <dc:type py:if="getattr(mi, 'category', False)">${mi.category}</dc:type>
<dc:description py:if="mi.comments">${mi.comments}</dc:description> <dc:description py:if="mi.comments">${mi.comments}</dc:description>
<dc:publisher py:if="mi.publisher">${mi.publisher}</dc:publisher> <dc:publisher py:if="mi.publisher">${mi.publisher}</dc:publisher>
<dc:identifier opf:scheme="ISBN" py:if="mi.isbn">${mi.isbn}</dc:identifier> <dc:identifier opf:scheme="ISBN" py:if="mi.isbn">${mi.isbn}</dc:identifier>
<meta py:if="mi.series is not None" name="calibre:series" content="${mi.series}"/> <meta py:if="mi.series is not None" name="calibre:series" content="${mi.series}"/>
<meta py:if="mi.series_index is not None" name="calibre:series_index" content="${mi.series_index}"/> <meta py:if="mi.series_index is not None" name="calibre:series_index" content="${mi.format_series_index()}"/>
<meta py:if="mi.rating is not None" name="calibre:rating" content="${mi.rating}"/> <meta py:if="mi.rating is not None" name="calibre:rating" content="${mi.rating}"/>
<meta py:if="mi.timestamp is not None" name="calibre:timestamp" content="${mi.timestamp.isoformat()}"/>
<py:for each="tag in mi.tags"> <py:for each="tag in mi.tags">
<dc:subject py:if="mi.tags is not None">${tag}</dc:subject> <dc:subject py:if="mi.tags is not None">${tag}</dc:subject>
</py:for> </py:for>

View File

@ -442,9 +442,10 @@ class OPF(object):
comments = MetadataField('description') comments = MetadataField('description')
category = MetadataField('category') category = MetadataField('category')
series = MetadataField('series', is_dc=False) series = MetadataField('series', is_dc=False)
series_index = MetadataField('series_index', is_dc=False, formatter=int, none_is=1) series_index = MetadataField('series_index', is_dc=False, formatter=float, none_is=1)
rating = MetadataField('rating', is_dc=False, formatter=int) rating = MetadataField('rating', is_dc=False, formatter=int)
timestamp = MetadataField('date', formatter=parser.parse) pubdate = MetadataField('date', formatter=parser.parse)
timestamp = MetadataField('timestamp', is_dc=False, formatter=parser.parse)
def __init__(self, stream, basedir=os.getcwdu(), unquote_urls=True): def __init__(self, stream, basedir=os.getcwdu(), unquote_urls=True):

View File

@ -65,7 +65,7 @@ class Jacket(object):
comments = comments.replace('\r\n', '\n').replace('\n\n', '<br/><br/>') comments = comments.replace('\r\n', '\n').replace('\n\n', '<br/><br/>')
series = '<b>Series: </b>' + mi.series if mi.series else '' series = '<b>Series: </b>' + mi.series if mi.series else ''
if series and mi.series_index is not None: if series and mi.series_index is not None:
series += ' [%s]'%mi.series_index series += ' [%s]'%mi.format_series_index()
tags = mi.tags tags = mi.tags
if not tags: if not tags:
try: try:

View File

@ -58,7 +58,7 @@ class MergeMetadata(object):
m.add('creator', mi.book_producer, role='bkp') m.add('creator', mi.book_producer, role='bkp')
if mi.series_index is not None: if mi.series_index is not None:
m.clear('series_index') m.clear('series_index')
m.add('series_index', '%.2f'%mi.series_index) m.add('series_index', mi.format_series_index())
if mi.rating is not None: if mi.rating is not None:
m.clear('rating') m.clear('rating')
m.add('rating', '%.2f'%mi.rating) m.add('rating', '%.2f'%mi.rating)

View File

@ -19,7 +19,8 @@ from calibre.ebooks.metadata import MetaInformation
NONE = QVariant() #: Null value to return from the data function of item models NONE = QVariant() #: Null value to return from the data function of item models
ALL_COLUMNS = ['title', 'authors', 'size', 'timestamp', 'rating', 'publisher', 'tags', 'series'] ALL_COLUMNS = ['title', 'authors', 'size', 'timestamp', 'rating', 'publisher',
'tags', 'series', 'pubdate']
def _config(): def _config():
c = Config('gui', 'preferences for the calibre GUI') c = Config('gui', 'preferences for the calibre GUI')

View File

@ -119,7 +119,8 @@ class Widget(QWidget):
elif isinstance(g, XPathEdit): elif isinstance(g, XPathEdit):
g.edit.setText(val if val else '') g.edit.setText(val if val else '')
else: else:
raise Exception('Can\'t set value %s in %s'%(repr(val), type(g))) raise Exception('Can\'t set value %s in %s'%(repr(val),
unicode(g.objectName())))
self.post_set_value(g, val) self.post_set_value(g, val)
def set_help(self, msg): def set_help(self, msg):

View File

@ -83,7 +83,7 @@ class MetadataWidget(Widget, Ui_Form):
comments = unicode(self.comment.toPlainText()).strip() comments = unicode(self.comment.toPlainText()).strip()
if comments: if comments:
mi.comments = comments mi.comments = comments
mi.series_index = int(self.series_index.value()) mi.series_index = float(self.series_index.value())
if self.series.currentIndex() > -1: if self.series.currentIndex() > -1:
mi.series = unicode(self.series.currentText()).strip() mi.series = unicode(self.series.currentText()).strip()
tags = [t.strip() for t in unicode(self.tags.text()).strip().split(',')] tags = [t.strip() for t in unicode(self.tags.text()).strip().split(',')]

View File

@ -1,7 +1,8 @@
<ui version="4.0" > <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class> <class>Form</class>
<widget class="QWidget" name="Form" > <widget class="QWidget" name="Form">
<property name="geometry" > <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
@ -9,59 +10,89 @@
<height>500</height> <height>500</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout" > <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QGroupBox" name="groupBox_4" > <widget class="QGroupBox" name="groupBox_4">
<property name="title" > <property name="title">
<string>Book Cover</string> <string>Book Cover</string>
</property> </property>
<layout class="QGridLayout" name="_2" > <layout class="QGridLayout" name="_2">
<item row="1" column="0" > <item row="0" column="0">
<layout class="QVBoxLayout" name="_4" > <layout class="QHBoxLayout" name="_3">
<property name="spacing" > <item>
<widget class="ImageView" name="cover">
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../../../../../../calibre/gui2/images.qrc">:/images/book.svg</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="opt_prefer_metadata_cover">
<property name="text">
<string>Use cover from &amp;source file</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<layout class="QVBoxLayout" name="_4">
<property name="spacing">
<number>6</number> <number>6</number>
</property> </property>
<property name="margin" > <property name="margin">
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
<widget class="QLabel" name="label_5" > <widget class="QLabel" name="label_5">
<property name="text" > <property name="text">
<string>Change &amp;cover image:</string> <string>Change &amp;cover image:</string>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>cover_path</cstring> <cstring>cover_path</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="_5" > <layout class="QHBoxLayout" name="_5">
<property name="spacing" > <property name="spacing">
<number>6</number> <number>6</number>
</property> </property>
<property name="margin" > <property name="margin">
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
<widget class="QLineEdit" name="cover_path" > <widget class="QLineEdit" name="cover_path">
<property name="readOnly" > <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QToolButton" name="cover_button" > <widget class="QToolButton" name="cover_button">
<property name="toolTip" > <property name="toolTip">
<string>Browse for an image to use as the cover of this book.</string> <string>Browse for an image to use as the cover of this book.</string>
</property> </property>
<property name="text" > <property name="text">
<string>...</string> <string>...</string>
</property> </property>
<property name="icon" > <property name="icon">
<iconset resource="../../../../../../calibre/gui2/images.qrc" > <iconset resource="../../../../../../calibre/gui2/images.qrc">
<normaloff>:/images/document_open.svg</normaloff>:/images/document_open.svg</iconset> <normaloff>:/images/document_open.svg</normaloff>:/images/document_open.svg</iconset>
</property> </property>
</widget> </widget>
@ -70,243 +101,204 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="2" column="0" >
<widget class="QCheckBox" name="opt_prefer_metadata_cover" >
<property name="text" >
<string>Use cover from &amp;source file</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" >
<layout class="QHBoxLayout" name="_3" >
<item>
<widget class="ImageView" name="cover" >
<property name="text" >
<string/>
</property>
<property name="pixmap" >
<pixmap resource="../../../../../../calibre/gui2/images.qrc" >:/images/book.svg</pixmap>
</property>
<property name="scaledContents" >
<bool>true</bool>
</property>
<property name="alignment" >
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
<zorder>opt_prefer_metadata_cover</zorder> <zorder>opt_prefer_metadata_cover</zorder>
<zorder></zorder> <zorder></zorder>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout_2" > <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<layout class="QGridLayout" name="_7" > <layout class="QGridLayout" name="_7">
<item row="0" column="0" > <item row="0" column="0">
<widget class="QLabel" name="label" > <widget class="QLabel" name="label">
<property name="text" > <property name="text">
<string>&amp;Title: </string> <string>&amp;Title: </string>
</property> </property>
<property name="alignment" > <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>title</cstring> <cstring>title</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1" > <item row="0" column="1">
<widget class="QLineEdit" name="title" > <widget class="QLineEdit" name="title">
<property name="toolTip" > <property name="toolTip">
<string>Change the title of this book</string> <string>Change the title of this book</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0" > <item row="1" column="0">
<widget class="QLabel" name="label_2" > <widget class="QLabel" name="label_2">
<property name="text" > <property name="text">
<string>&amp;Author(s): </string> <string>&amp;Author(s): </string>
</property> </property>
<property name="alignment" > <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>author</cstring> <cstring>author</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1" > <item row="1" column="1">
<widget class="QLineEdit" name="author" > <widget class="QLineEdit" name="author">
<property name="sizePolicy" > <property name="sizePolicy">
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" > <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>1</horstretch> <horstretch>1</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="toolTip" > <property name="toolTip">
<string>Change the author(s) of this book. Multiple authors should be separated by an &amp;. If the author name contains an &amp;, use &amp;&amp; to represent it.</string> <string>Change the author(s) of this book. Multiple authors should be separated by an &amp;. If the author name contains an &amp;, use &amp;&amp; to represent it.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" > <item row="2" column="0">
<widget class="QLabel" name="label_6" > <widget class="QLabel" name="label_6">
<property name="text" > <property name="text">
<string>Author So&amp;rt:</string> <string>Author So&amp;rt:</string>
</property> </property>
<property name="alignment" > <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>author_sort</cstring> <cstring>author_sort</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1" > <item row="2" column="1">
<widget class="QLineEdit" name="author_sort" > <widget class="QLineEdit" name="author_sort">
<property name="sizePolicy" > <property name="sizePolicy">
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" > <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="toolTip" > <property name="toolTip">
<string>Change the author(s) of this book. Multiple authors should be separated by a comma</string> <string>Change the author(s) of this book. Multiple authors should be separated by a comma</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" > <item row="3" column="0">
<widget class="QLabel" name="label_3" > <widget class="QLabel" name="label_3">
<property name="text" > <property name="text">
<string>&amp;Publisher: </string> <string>&amp;Publisher: </string>
</property> </property>
<property name="alignment" > <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>publisher</cstring> <cstring>publisher</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1" > <item row="3" column="1">
<widget class="QLineEdit" name="publisher" > <widget class="QLineEdit" name="publisher">
<property name="toolTip" > <property name="toolTip">
<string>Change the publisher of this book</string> <string>Change the publisher of this book</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0" > <item row="4" column="0">
<widget class="QLabel" name="label_4" > <widget class="QLabel" name="label_4">
<property name="text" > <property name="text">
<string>Ta&amp;gs: </string> <string>Ta&amp;gs: </string>
</property> </property>
<property name="alignment" > <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>tags</cstring> <cstring>tags</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1" > <item row="4" column="1">
<widget class="QLineEdit" name="tags" > <widget class="QLineEdit" name="tags">
<property name="toolTip" > <property name="toolTip">
<string>Tags categorize the book. This is particularly useful while searching. &lt;br>&lt;br>They can be any words or phrases, separated by commas.</string> <string>Tags categorize the book. This is particularly useful while searching. &lt;br&gt;&lt;br&gt;They can be any words or phrases, separated by commas.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0" > <item row="5" column="0">
<widget class="QLabel" name="label_7" > <widget class="QLabel" name="label_7">
<property name="text" > <property name="text">
<string>&amp;Series:</string> <string>&amp;Series:</string>
</property> </property>
<property name="textFormat" > <property name="textFormat">
<enum>Qt::PlainText</enum> <enum>Qt::PlainText</enum>
</property> </property>
<property name="alignment" > <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>series</cstring> <cstring>series</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1" > <item row="5" column="1">
<widget class="QComboBox" name="series" > <widget class="QComboBox" name="series">
<property name="sizePolicy" > <property name="sizePolicy">
<sizepolicy vsizetype="Fixed" hsizetype="Preferred" > <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>10</horstretch> <horstretch>10</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="toolTip" > <property name="toolTip">
<string>List of known series. You can add new series.</string> <string>List of known series. You can add new series.</string>
</property> </property>
<property name="whatsThis" > <property name="whatsThis">
<string>List of known series. You can add new series.</string> <string>List of known series. You can add new series.</string>
</property> </property>
<property name="editable" > <property name="editable">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="insertPolicy" > <property name="insertPolicy">
<enum>QComboBox::InsertAlphabetically</enum> <enum>QComboBox::InsertAlphabetically</enum>
</property> </property>
<property name="sizeAdjustPolicy" > <property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum> <enum>QComboBox::AdjustToContents</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1" > <item row="6" column="1">
<widget class="QSpinBox" name="series_index" > <widget class="QDoubleSpinBox" name="series_index">
<property name="enabled" > <property name="prefix">
<bool>true</bool>
</property>
<property name="toolTip" >
<string>Series index.</string>
</property>
<property name="whatsThis" >
<string>Series index.</string>
</property>
<property name="prefix" >
<string>Book </string> <string>Book </string>
</property> </property>
<property name="minimum" > <property name="maximum">
<number>1</number> <double>9999.989999999999782</double>
</property> </property>
<property name="maximum" > <property name="value">
<number>10000</number> <double>1.000000000000000</double>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="groupBox_2" > <widget class="QGroupBox" name="groupBox_2">
<property name="sizePolicy" > <property name="sizePolicy">
<sizepolicy vsizetype="Minimum" hsizetype="Minimum" > <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="maximumSize" > <property name="maximumSize">
<size> <size>
<width>16777215</width> <width>16777215</width>
<height>200</height> <height>200</height>
</size> </size>
</property> </property>
<property name="title" > <property name="title">
<string>Comments</string> <string>Comments</string>
</property> </property>
<layout class="QGridLayout" name="_8" > <layout class="QGridLayout" name="_8">
<item row="0" column="0" > <item row="0" column="0">
<widget class="QTextEdit" name="comment" > <widget class="QTextEdit" name="comment">
<property name="maximumSize" > <property name="maximumSize">
<size> <size>
<width>16777215</width> <width>16777215</width>
<height>180</height> <height>180</height>
@ -329,8 +321,8 @@
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../../../../../../calibre/gui2/images.qrc" /> <include location="../../../../../../calibre/gui2/images.qrc"/>
<include location="../images.qrc" /> <include location="../images.qrc"/>
</resources> </resources>
<connections/> <connections/>
</ui> </ui>

View File

@ -177,7 +177,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1" colspan="2"> <item row="3" column="1">
<widget class="QSpinBox" name="rating"> <widget class="QSpinBox" name="rating">
<property name="toolTip"> <property name="toolTip">
<string>Rating of this book. 0-5 stars</string> <string>Rating of this book. 0-5 stars</string>
@ -309,28 +309,6 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="7" column="1" colspan="2">
<widget class="QSpinBox" name="series_index">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Series index.</string>
</property>
<property name="whatsThis">
<string>Series index.</string>
</property>
<property name="prefix">
<string>Book </string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
</widget>
</item>
<item row="8" column="0"> <item row="8" column="0">
<widget class="QLabel" name="label_9"> <widget class="QLabel" name="label_9">
<property name="text"> <property name="text">
@ -357,6 +335,19 @@
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLineEdit" name="authors"/> <widget class="QLineEdit" name="authors"/>
</item> </item>
<item row="7" column="1">
<widget class="QDoubleSpinBox" name="series_index">
<property name="enabled">
<bool>false</bool>
</property>
<property name="prefix">
<string>Book </string>
</property>
<property name="maximum">
<double>9999.989999999999782</double>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -640,7 +631,6 @@
<tabstop>series</tabstop> <tabstop>series</tabstop>
<tabstop>tag_editor_button</tabstop> <tabstop>tag_editor_button</tabstop>
<tabstop>remove_series_button</tabstop> <tabstop>remove_series_button</tabstop>
<tabstop>series_index</tabstop>
<tabstop>isbn</tabstop> <tabstop>isbn</tabstop>
<tabstop>comments</tabstop> <tabstop>comments</tabstop>
<tabstop>fetch_metadata_button</tabstop> <tabstop>fetch_metadata_button</tabstop>

View File

@ -20,7 +20,7 @@ from calibre.gui2 import NONE, TableView, qstring_to_unicode, config, \
error_dialog error_dialog
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
from calibre.ebooks.metadata import string_to_authors from calibre.ebooks.metadata import string_to_authors, fmt_sidx
class LibraryDelegate(QItemDelegate): class LibraryDelegate(QItemDelegate):
COLOR = QColor("blue") COLOR = QColor("blue")
@ -98,40 +98,38 @@ class DateDelegate(QStyledItemDelegate):
qde.setCalendarPopup(True) qde.setCalendarPopup(True)
return qde return qde
class BooksModel(QAbstractTableModel): class PubDateDelegate(QStyledItemDelegate):
coding = zip(
[1000,900,500,400,100,90,50,40,10,9,5,4,1],
["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"]
)
def displayText(self, val, locale):
return val.toDate().toString('MMM yyyy')
def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index)
qde.setDisplayFormat('MM yyyy')
qde.setMinimumDate(QDate(101,1,1))
qde.setCalendarPopup(True)
return qde
class BooksModel(QAbstractTableModel):
headers = { headers = {
'title' : _("Title"), 'title' : _("Title"),
'authors' : _("Author(s)"), 'authors' : _("Author(s)"),
'size' : _("Size (MB)"), 'size' : _("Size (MB)"),
'timestamp' : _("Date"), 'timestamp' : _("Date"),
'pubdate' : _('Published'),
'rating' : _('Rating'), 'rating' : _('Rating'),
'publisher' : _("Publisher"), 'publisher' : _("Publisher"),
'tags' : _("Tags"), 'tags' : _("Tags"),
'series' : _("Series"), 'series' : _("Series"),
} }
@classmethod
def roman(cls, num):
if num <= 0 or num >= 4000 or int(num) != num:
return str(num)
result = []
for d, r in cls.coding:
while num >= d:
result.append(r)
num -= d
return ''.join(result)
def __init__(self, parent=None, buffer=40): def __init__(self, parent=None, buffer=40):
QAbstractTableModel.__init__(self, parent) QAbstractTableModel.__init__(self, parent)
self.db = None self.db = None
self.column_map = config['column_map'] self.column_map = config['column_map']
self.editable_cols = ['title', 'authors', 'rating', 'publisher', self.editable_cols = ['title', 'authors', 'rating', 'publisher',
'tags', 'series', 'timestamp'] 'tags', 'series', 'timestamp', 'pubdate']
self.default_image = QImage(':/images/book.svg') self.default_image = QImage(':/images/book.svg')
self.sorted_on = ('timestamp', Qt.AscendingOrder) self.sorted_on = ('timestamp', Qt.AscendingOrder)
self.last_search = '' # The last search performed on this model self.last_search = '' # The last search performed on this model
@ -157,8 +155,12 @@ class BooksModel(QAbstractTableModel):
tidx = self.column_map.index('timestamp') tidx = self.column_map.index('timestamp')
except ValueError: except ValueError:
tidx = -1 tidx = -1
try:
pidx = self.column_map.index('pubdate')
except ValueError:
pidx = -1
self.emit(SIGNAL('columns_sorted(int,int)'), idx, tidx) self.emit(SIGNAL('columns_sorted(int,int,int)'), idx, tidx, pidx)
def set_database(self, db): def set_database(self, db):
@ -186,8 +188,8 @@ class BooksModel(QAbstractTableModel):
self.db = None self.db = None
self.reset() self.reset()
def add_books(self, paths, formats, metadata, uris=[], add_duplicates=False): def add_books(self, paths, formats, metadata, add_duplicates=False):
ret = self.db.add_books(paths, formats, metadata, uris, ret = self.db.add_books(paths, formats, metadata,
add_duplicates=add_duplicates) add_duplicates=add_duplicates)
self.count_changed() self.count_changed()
return ret return ret
@ -313,7 +315,7 @@ class BooksModel(QAbstractTableModel):
series = self.db.series(idx) series = self.db.series(idx)
if series: if series:
sidx = self.db.series_index(idx) sidx = self.db.series_index(idx)
sidx = self.__class__.roman(sidx) if self.use_roman_numbers else str(sidx) sidx = fmt_sidx(sidx, use_roman = self.use_roman_numbers)
data[_('Series')] = _('Book <font face="serif">%s</font> of %s.')%(sidx, series) data[_('Series')] = _('Book <font face="serif">%s</font> of %s.')%(sidx, series)
return data return data
@ -492,6 +494,7 @@ class BooksModel(QAbstractTableModel):
ridx = FIELD_MAP['rating'] ridx = FIELD_MAP['rating']
pidx = FIELD_MAP['publisher'] pidx = FIELD_MAP['publisher']
tmdx = FIELD_MAP['timestamp'] tmdx = FIELD_MAP['timestamp']
pddx = FIELD_MAP['pubdate']
srdx = FIELD_MAP['series'] srdx = FIELD_MAP['series']
tgdx = FIELD_MAP['tags'] tgdx = FIELD_MAP['tags']
siix = FIELD_MAP['series_index'] siix = FIELD_MAP['series_index']
@ -508,6 +511,12 @@ class BooksModel(QAbstractTableModel):
dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight) dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight)
return QDate(dt.year, dt.month, dt.day) return QDate(dt.year, dt.month, dt.day)
def pubdate(r):
dt = self.db.data[r][pddx]
if dt:
dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight)
return QDate(dt.year, dt.month, dt.day)
def rating(r): def rating(r):
r = self.db.data[r][ridx] r = self.db.data[r][ridx]
r = r/2 if r else 0 r = r/2 if r else 0
@ -526,8 +535,8 @@ class BooksModel(QAbstractTableModel):
def series(r): def series(r):
series = self.db.data[r][srdx] series = self.db.data[r][srdx]
if series: if series:
return series + ' [%d]'%self.db.data[r][siix] idx = fmt_sidx(self.db.data[r][siix])
return series + ' [%s]'%idx
def size(r): def size(r):
size = self.db.data[r][sidx] size = self.db.data[r][sidx]
if size: if size:
@ -538,6 +547,7 @@ class BooksModel(QAbstractTableModel):
'authors' : authors, 'authors' : authors,
'size' : size, 'size' : size,
'timestamp': timestamp, 'timestamp': timestamp,
'pubdate' : pubdate,
'rating' : rating, 'rating' : rating,
'publisher': publisher, 'publisher': publisher,
'tags' : tags, 'tags' : tags,
@ -577,7 +587,7 @@ class BooksModel(QAbstractTableModel):
if column not in self.editable_cols: if column not in self.editable_cols:
return False return False
val = int(value.toInt()[0]) if column == 'rating' else \ val = int(value.toInt()[0]) if column == 'rating' else \
value.toDate() if column == 'timestamp' else \ value.toDate() if column in ('timestamp', 'pubdate') else \
unicode(value.toString()) unicode(value.toString())
id = self.db.id(row) id = self.db.id(row)
if column == 'rating': if column == 'rating':
@ -585,10 +595,10 @@ class BooksModel(QAbstractTableModel):
val *= 2 val *= 2
self.db.set_rating(id, val) self.db.set_rating(id, val)
elif column == 'series': elif column == 'series':
pat = re.compile(r'\[(\d+)\]') pat = re.compile(r'\[([.0-9]+)\]')
match = pat.search(val) match = pat.search(val)
if match is not None: if match is not None:
self.db.set_series_index(id, int(match.group(1))) self.db.set_series_index(id, float(match.group(1)))
val = pat.sub('', val) val = pat.sub('', val)
val = val.strip() val = val.strip()
if val: if val:
@ -598,6 +608,11 @@ class BooksModel(QAbstractTableModel):
return False return False
dt = datetime(val.year(), val.month(), val.day()) + timedelta(seconds=time.timezone) - timedelta(hours=time.daylight) dt = datetime(val.year(), val.month(), val.day()) + timedelta(seconds=time.timezone) - timedelta(hours=time.daylight)
self.db.set_timestamp(id, dt) self.db.set_timestamp(id, dt)
elif column == 'pubdate':
if val.isNull() or not val.isValid():
return False
dt = datetime(val.year(), val.month(), val.day()) + timedelta(seconds=time.timezone) - timedelta(hours=time.daylight)
self.db.set_pubdate(id, dt)
else: else:
self.db.set(row, column, val) self.db.set(row, column, val)
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \ self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
@ -625,29 +640,35 @@ class BooksView(TableView):
TableView.__init__(self, parent) TableView.__init__(self, parent)
self.rating_delegate = LibraryDelegate(self) self.rating_delegate = LibraryDelegate(self)
self.timestamp_delegate = DateDelegate(self) self.timestamp_delegate = DateDelegate(self)
self.pubdate_delegate = PubDateDelegate(self)
self.display_parent = parent self.display_parent = parent
self._model = modelcls(self) self._model = modelcls(self)
self.setModel(self._model) self.setModel(self._model)
self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSortingEnabled(True) self.setSortingEnabled(True)
try: try:
self.columns_sorted(self._model.column_map.index('rating'), cm = self._model.column_map
self._model.column_map.index('timestamp')) self.columns_sorted(cm.index('rating') if 'rating' in cm else -1,
cm.index('timestamp') if 'timestamp' in cm else -1,
cm.index('pubdate') if 'pubdate' in cm else -1)
except ValueError: except ValueError:
pass pass
QObject.connect(self.selectionModel(), SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'), QObject.connect(self.selectionModel(), SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'),
self._model.current_changed) self._model.current_changed)
self.connect(self._model, SIGNAL('columns_sorted(int, int)'), self.columns_sorted, Qt.QueuedConnection) self.connect(self._model, SIGNAL('columns_sorted(int,int,int)'),
self.columns_sorted, Qt.QueuedConnection)
def columns_sorted(self, rating_col, timestamp_col): def columns_sorted(self, rating_col, timestamp_col, pubdate_col):
for i in range(self.model().columnCount(None)): for i in range(self.model().columnCount(None)):
if self.itemDelegateForColumn(i) in (self.rating_delegate, if self.itemDelegateForColumn(i) in (self.rating_delegate,
self.timestamp_delegate): self.timestamp_delegate, self.pubdate_delegate):
self.setItemDelegateForColumn(i, self.itemDelegate()) self.setItemDelegateForColumn(i, self.itemDelegate())
if rating_col > -1: if rating_col > -1:
self.setItemDelegateForColumn(rating_col, self.rating_delegate) self.setItemDelegateForColumn(rating_col, self.rating_delegate)
if timestamp_col > -1: if timestamp_col > -1:
self.setItemDelegateForColumn(timestamp_col, self.timestamp_delegate) self.setItemDelegateForColumn(timestamp_col, self.timestamp_delegate)
if pubdate_col > -1:
self.setItemDelegateForColumn(pubdate_col, self.pubdate_delegate)
def set_context_menu(self, edit_metadata, send_to_device, convert, view, def set_context_menu(self, edit_metadata, send_to_device, convert, view,
save, open_folder, book_details, similar_menu=None): save, open_folder, book_details, similar_menu=None):

View File

@ -991,9 +991,9 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
else: else:
ans = self.conn.get('SELECT series_index FROM books WHERE id=?', (index,), all=False) ans = self.conn.get('SELECT series_index FROM books WHERE id=?', (index,), all=False)
try: try:
return int(ans) return float(ans)
except: except:
return 1 return 1.0
def books_in_series(self, series_id): def books_in_series(self, series_id):
''' '''

View File

@ -50,7 +50,8 @@ copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'publisher':3, 'rating':4, 'timestamp':5, FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'publisher':3, 'rating':4, 'timestamp':5,
'size':6, 'tags':7, 'comments':8, 'series':9, 'series_index':10, 'size':6, 'tags':7, 'comments':8, 'series':9, 'series_index':10,
'sort':11, 'author_sort':12, 'formats':13, 'isbn':14, 'path':15} 'sort':11, 'author_sort':12, 'formats':13, 'isbn':14, 'path':15,
'lccn':16, 'pubdate':17, 'flags':18}
INDEX_MAP = dict(zip(FIELD_MAP.values(), FIELD_MAP.keys())) INDEX_MAP = dict(zip(FIELD_MAP.values(), FIELD_MAP.keys()))
@ -472,6 +473,53 @@ class LibraryDatabase2(LibraryDatabase):
FROM books; FROM books;
''') ''')
def upgrade_version_4(self):
'Rationalize books table'
self.conn.executescript('''
BEGIN TRANSACTION;
CREATE TEMPORARY TABLE
books_backup(id,title,sort,timestamp,series_index,author_sort,isbn,path);
INSERT INTO books_backup SELECT id,title,sort,timestamp,series_index,author_sort,isbn,path FROM books;
DROP TABLE books;
CREATE TABLE books ( id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL DEFAULT 'Unknown' COLLATE NOCASE,
sort TEXT COLLATE NOCASE,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
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 "",
flags INTEGER NOT NULL DEFAULT 1
);
INSERT INTO
books (id,title,sort,timestamp,pubdate,series_index,author_sort,isbn,path)
SELECT id,title,sort,timestamp,timestamp,series_index,author_sort,isbn,path FROM books_backup;
DROP TABLE books_backup;
DROP VIEW meta;
CREATE VIEW meta AS
SELECT id, title,
(SELECT concat(name) FROM authors WHERE authors.id IN (SELECT author from books_authors_link WHERE book=books.id)) authors,
(SELECT name FROM publishers WHERE publishers.id IN (SELECT publisher from books_publishers_link WHERE book=books.id)) publisher,
(SELECT rating FROM ratings WHERE ratings.id IN (SELECT rating from books_ratings_link WHERE book=books.id)) rating,
timestamp,
(SELECT MAX(uncompressed_size) FROM data WHERE book=books.id) size,
(SELECT concat(name) FROM tags WHERE tags.id IN (SELECT tag from books_tags_link WHERE book=books.id)) tags,
(SELECT text FROM comments WHERE book=books.id) comments,
(SELECT name FROM series WHERE series.id IN (SELECT series FROM books_series_link WHERE book=books.id)) series,
series_index,
sort,
author_sort,
(SELECT concat(format) FROM data WHERE data.book=books.id) formats,
isbn,
path,
lccn,
pubdate,
flags
FROM books;
''')
def last_modified(self): def last_modified(self):
''' Return last modified time as a UTC datetime object''' ''' Return last modified time as a UTC datetime object'''
@ -610,6 +658,16 @@ class LibraryDatabase2(LibraryDatabase):
return img return img
return f if as_file else f.read() return f if as_file else f.read()
def timestamp(self, index, index_is_id=False):
if index_is_id:
return self.conn.get('SELECT timestamp FROM meta WHERE id=?', (index,), all=False)
return self.data[index][FIELD_MAP['timestamp']]
def pubdate(self, index, index_is_id=False):
if index_is_id:
return self.conn.get('SELECT pubdate FROM meta WHERE id=?', (index,), all=False)
return self.data[index][FIELD_MAP['pubdate']]
def get_metadata(self, idx, index_is_id=False, get_cover=False): def get_metadata(self, idx, index_is_id=False, get_cover=False):
''' '''
Convenience method to return metadata as a L{MetaInformation} object. Convenience method to return metadata as a L{MetaInformation} object.
@ -621,6 +679,7 @@ class LibraryDatabase2(LibraryDatabase):
mi.comments = self.comments(idx, index_is_id=index_is_id) mi.comments = self.comments(idx, index_is_id=index_is_id)
mi.publisher = self.publisher(idx, index_is_id=index_is_id) mi.publisher = self.publisher(idx, index_is_id=index_is_id)
mi.timestamp = self.timestamp(idx, index_is_id=index_is_id) mi.timestamp = self.timestamp(idx, index_is_id=index_is_id)
mi.pubdate = self.pubdate(idx, index_is_id=index_is_id)
tags = self.tags(idx, index_is_id=index_is_id) tags = self.tags(idx, index_is_id=index_is_id)
if tags: if tags:
mi.tags = [i.strip() for i in tags.split(',')] mi.tags = [i.strip() for i in tags.split(',')]
@ -917,7 +976,7 @@ class LibraryDatabase2(LibraryDatabase):
self.set_comment(id, mi.comments, notify=False) self.set_comment(id, mi.comments, notify=False)
if mi.isbn and mi.isbn.strip(): if mi.isbn and mi.isbn.strip():
self.set_isbn(id, mi.isbn, notify=False) self.set_isbn(id, mi.isbn, notify=False)
if mi.series_index and mi.series_index > 0: if mi.series_index:
self.set_series_index(id, mi.series_index, notify=False) self.set_series_index(id, mi.series_index, notify=False)
if getattr(mi, 'timestamp', None) is not None: if getattr(mi, 'timestamp', None) is not None:
self.set_timestamp(id, mi.timestamp, notify=False) self.set_timestamp(id, mi.timestamp, notify=False)
@ -983,6 +1042,15 @@ class LibraryDatabase2(LibraryDatabase):
if notify: if notify:
self.notify('metadata', [id]) self.notify('metadata', [id])
def set_pubdate(self, id, dt, notify=True):
if dt:
self.conn.execute('UPDATE books SET pubdate=? WHERE id=?', (dt, id))
self.data.set(id, FIELD_MAP['pubdate'], dt, row_is_id=True)
self.conn.commit()
if notify:
self.notify('metadata', [id])
def set_publisher(self, id, publisher, notify=True): def set_publisher(self, id, publisher, notify=True):
self.conn.execute('DELETE FROM books_publishers_link WHERE book=?',(id,)) self.conn.execute('DELETE FROM books_publishers_link WHERE book=?',(id,))
self.conn.execute('DELETE FROM publishers WHERE (SELECT COUNT(id) FROM books_publishers_link WHERE publisher=publishers.id) < 1') self.conn.execute('DELETE FROM publishers WHERE (SELECT COUNT(id) FROM books_publishers_link WHERE publisher=publishers.id) < 1')
@ -1103,17 +1171,11 @@ class LibraryDatabase2(LibraryDatabase):
def set_series_index(self, id, idx, notify=True): def set_series_index(self, id, idx, notify=True):
if idx is None: if idx is None:
idx = 1 idx = 1.0
idx = int(idx) idx = float(idx)
self.conn.execute('UPDATE books SET series_index=? WHERE id=?', (int(idx), id)) self.conn.execute('UPDATE books SET series_index=? WHERE id=?', (idx, id))
self.conn.commit() self.conn.commit()
try: self.data.set(id, FIELD_MAP['series_index'], idx, row_is_id=True)
row = self.row(id)
if row is not None:
self.data.set(row, 10, idx)
except ValueError:
pass
self.data.set(id, FIELD_MAP['series_index'], int(idx), row_is_id=True)
if notify: if notify:
self.notify('metadata', [id]) self.notify('metadata', [id])
@ -1156,7 +1218,7 @@ class LibraryDatabase2(LibraryDatabase):
stream.seek(0) stream.seek(0)
mi = get_metadata(stream, format, use_libprs_metadata=False) mi = get_metadata(stream, format, use_libprs_metadata=False)
stream.seek(0) stream.seek(0)
mi.series_index = 1 mi.series_index = 1.0
mi.tags = [_('News'), recipe.title] mi.tags = [_('News'), recipe.title]
obj = self.conn.execute('INSERT INTO books(title, author_sort) VALUES (?, ?)', obj = self.conn.execute('INSERT INTO books(title, author_sort) VALUES (?, ?)',
(mi.title, mi.authors[0])) (mi.title, mi.authors[0]))
@ -1188,7 +1250,7 @@ class LibraryDatabase2(LibraryDatabase):
def create_book_entry(self, mi, cover=None, add_duplicates=True): def create_book_entry(self, mi, cover=None, add_duplicates=True):
if not add_duplicates and self.has_book(mi): if not add_duplicates and self.has_book(mi):
return None return None
series_index = 1 if mi.series_index is None else mi.series_index series_index = 1.0 if mi.series_index is None else mi.series_index
aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors) aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors)
title = mi.title title = mi.title
if isinstance(aus, str): if isinstance(aus, str):
@ -1207,33 +1269,29 @@ class LibraryDatabase2(LibraryDatabase):
return id return id
def add_books(self, paths, formats, metadata, uris=[], add_duplicates=True): def add_books(self, paths, formats, metadata, add_duplicates=True):
''' '''
Add a book to the database. The result cache is not updated. Add a book to the database. The result cache is not updated.
:param:`paths` List of paths to book files or file-like objects :param:`paths` List of paths to book files or file-like objects
''' '''
formats, metadata, uris = iter(formats), iter(metadata), iter(uris) formats, metadata = iter(formats), iter(metadata)
duplicates = [] duplicates = []
ids = [] ids = []
for path in paths: for path in paths:
mi = metadata.next() mi = metadata.next()
format = formats.next() format = formats.next()
try:
uri = uris.next()
except StopIteration:
uri = None
if not add_duplicates and self.has_book(mi): if not add_duplicates and self.has_book(mi):
duplicates.append((path, format, mi, uri)) duplicates.append((path, format, mi))
continue continue
series_index = 1 if mi.series_index is None else mi.series_index series_index = 1.0 if mi.series_index is None else mi.series_index
aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors) aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors)
title = mi.title title = mi.title
if isinstance(aus, str): if isinstance(aus, str):
aus = aus.decode(preferred_encoding, 'replace') aus = aus.decode(preferred_encoding, 'replace')
if isinstance(title, str): if isinstance(title, str):
title = title.decode(preferred_encoding) title = title.decode(preferred_encoding)
obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)', obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)',
(title, uri, series_index, aus)) (title, series_index, aus))
id = obj.lastrowid id = obj.lastrowid
self.data.books_added([id], self.conn) self.data.books_added([id], self.conn)
ids.append(id) ids.append(id)
@ -1251,12 +1309,11 @@ class LibraryDatabase2(LibraryDatabase):
paths = list(duplicate[0] for duplicate in duplicates) paths = list(duplicate[0] for duplicate in duplicates)
formats = list(duplicate[1] for duplicate in duplicates) formats = list(duplicate[1] for duplicate in duplicates)
metadata = list(duplicate[2] for duplicate in duplicates) metadata = list(duplicate[2] for duplicate in duplicates)
uris = list(duplicate[3] for duplicate in duplicates) return (paths, formats, metadata), len(ids)
return (paths, formats, metadata, uris), len(ids)
return None, len(ids) return None, len(ids)
def import_book(self, mi, formats, notify=True): def import_book(self, mi, formats, notify=True):
series_index = 1 if mi.series_index is None else mi.series_index series_index = 1.0 if mi.series_index is None else mi.series_index
if not mi.title: if not mi.title:
mi.title = _('Unknown') mi.title = _('Unknown')
if not mi.authors: if not mi.authors:
@ -1266,8 +1323,8 @@ class LibraryDatabase2(LibraryDatabase):
aus = aus.decode(preferred_encoding, 'replace') aus = aus.decode(preferred_encoding, 'replace')
title = mi.title if isinstance(mi.title, unicode) else \ title = mi.title if isinstance(mi.title, unicode) else \
mi.title.decode(preferred_encoding, 'replace') mi.title.decode(preferred_encoding, 'replace')
obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)', obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)',
(title, None, series_index, aus)) (title, series_index, aus))
id = obj.lastrowid id = obj.lastrowid
self.data.books_added([id], self.conn) self.data.books_added([id], self.conn)
self.set_path(id, True) self.set_path(id, True)
@ -1368,12 +1425,12 @@ class LibraryDatabase2(LibraryDatabase):
QCoreApplication.processEvents() QCoreApplication.processEvents()
db.conn.row_factory = lambda cursor, row : tuple(row) db.conn.row_factory = lambda cursor, row : tuple(row)
db.conn.text_factory = lambda x : unicode(x, 'utf-8', 'replace') db.conn.text_factory = lambda x : unicode(x, 'utf-8', 'replace')
books = db.conn.get('SELECT id, title, sort, timestamp, uri, series_index, author_sort, isbn FROM books ORDER BY id ASC') books = db.conn.get('SELECT id, title, sort, timestamp, series_index, author_sort, isbn FROM books ORDER BY id ASC')
progress.setAutoReset(False) progress.setAutoReset(False)
progress.setRange(0, len(books)) progress.setRange(0, len(books))
for book in books: for book in books:
self.conn.execute('INSERT INTO books(id, title, sort, timestamp, uri, series_index, author_sort, isbn) VALUES(?, ?, ?, ?, ?, ?, ?, ?);', book) self.conn.execute('INSERT INTO books(id, title, sort, timestamp, series_index, author_sort, isbn) VALUES(?, ?, ?, ?, ?, ?, ?, ?);', book)
tables = ''' tables = '''
authors ratings tags series books_tags_link authors ratings tags series books_tags_link

View File

@ -25,6 +25,7 @@ from calibre.library.database2 import LibraryDatabase2, FIELD_MAP
from calibre.utils.config import config_dir from calibre.utils.config import config_dir
from calibre.utils.mdns import publish as publish_zeroconf, \ from calibre.utils.mdns import publish as publish_zeroconf, \
stop_server as stop_zeroconf stop_server as stop_zeroconf
from calibre.ebooks.metadata import fmt_sidx
build_time = datetime.strptime(build_time, '%d %m %Y %H%M%S') build_time = datetime.strptime(build_time, '%d %m %Y %H%M%S')
server_resources['jquery.js'] = jquery server_resources['jquery.js'] = jquery
@ -271,7 +272,7 @@ class LibraryServer(object):
@expose @expose
def stanza(self): def stanza(self):
' Feeds to read calibre books on a ipod with stanza.' 'Feeds to read calibre books on a ipod with stanza.'
books = [] books = []
for record in iter(self.db): for record in iter(self.db):
r = record[FIELD_MAP['formats']] r = record[FIELD_MAP['formats']]
@ -289,8 +290,8 @@ class LibraryServer(object):
extra.append('TAGS: %s<br />'%', '.join(tags.split(','))) extra.append('TAGS: %s<br />'%', '.join(tags.split(',')))
series = record[FIELD_MAP['series']] series = record[FIELD_MAP['series']]
if series: if series:
extra.append('SERIES: %s [%d]<br />'%(series, extra.append('SERIES: %s [%s]<br />'%(series,
record[FIELD_MAP['series_index']])) fmt_sidx(record[FIELD_MAP['series_index']])))
fmt = 'epub' if 'EPUB' in r else 'pdb' fmt = 'epub' if 'EPUB' in r else 'pdb'
mimetype = guess_type('dummy.'+fmt)[0] mimetype = guess_type('dummy.'+fmt)[0]
books.append(self.STANZA_ENTRY.generate( books.append(self.STANZA_ENTRY.generate(
@ -339,6 +340,7 @@ class LibraryServer(object):
for record in items[start:start+num]: for record in items[start:start+num]:
aus = record[2] if record[2] else __builtins__._('Unknown') aus = record[2] if record[2] else __builtins__._('Unknown')
authors = '|'.join([i.replace('|', ',') for i in aus.split(',')]) authors = '|'.join([i.replace('|', ',') for i in aus.split(',')])
r[10] = fmt_sidx(r[10])
books.append(book.generate(r=record, authors=authors).render('xml').decode('utf-8')) books.append(book.generate(r=record, authors=authors).render('xml').decode('utf-8'))
updated = self.db.last_modified() updated = self.db.last_modified()

1
todo
View File

@ -2,6 +2,7 @@
* Refactor web.fetch.simple to use per connection timeouts via the timeout kwarg for mechanize.open * Refactor web.fetch.simple to use per connection timeouts via the timeout kwarg for mechanize.open
* Rationalize books table. Add a pubdate column, remove the uri column (and associated support in add_books) and convert series_index to a float. * Rationalize books table. Add a pubdate column, remove the uri column (and associated support in add_books) and convert series_index to a float.
- test adding/recusrsize adding and adding of duplicates
* Testing framework * Testing framework