mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
KG updates
This commit is contained in:
commit
19f91a689b
@ -82,6 +82,10 @@ body {
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
text-shadow: #27211b 1px 1px 1px;
|
||||
-moz-box-shadow: 5px 5px 5px #222;
|
||||
-webkit-box-shadow: 5px 5px 5px #222;
|
||||
box-shadow: 5px 5px 5px #222;
|
||||
|
||||
}
|
||||
|
||||
#nav-container {
|
||||
@ -208,6 +212,7 @@ h2.library_name {
|
||||
-moz-box-shadow: 5px 5px 5px #ccc;
|
||||
-webkit-box-shadow: 5px 5px 5px #ccc;
|
||||
box-shadow: 5px 5px 5px #ccc;
|
||||
text-shadow: #27211b 1px 1px 1px;
|
||||
|
||||
}
|
||||
|
||||
|
@ -105,7 +105,6 @@ function init_sort_combobox() {
|
||||
|
||||
// }}}
|
||||
|
||||
|
||||
function init() {
|
||||
$("#container").corner("30px");
|
||||
$("#header").corner("30px");
|
||||
|
BIN
resources/images/news/rstones.png
Normal file
BIN
resources/images/news/rstones.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
82
resources/recipes/rstones.recipe
Normal file
82
resources/recipes/rstones.recipe
Normal file
@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = 'Tony Stegall'
|
||||
__copyright__ = '2010, Tony Stegall or Tonythebookworm on mobileread.com'
|
||||
__version__ = 'v1.01'
|
||||
__date__ = '07, October 2010'
|
||||
__description__ = 'Rolling Stones Mag'
|
||||
|
||||
'''
|
||||
http://www.rollingstone.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class RollingStones(BasicNewsRecipe):
|
||||
__author__ = 'Tony Stegall'
|
||||
description = 'Rolling Stones Mag'
|
||||
cover_url = 'http://gallery.celebritypro.com/data/media/648/kid-rock-rolling-stone-cover.jpg'
|
||||
masthead_url = 'http://origin.myfonts.com/s/ec/cc-200804/Rolling_Stone-logo.gif'
|
||||
|
||||
|
||||
title = 'Rolling Stones Mag'
|
||||
category = 'Music Reviews, Movie Reviews, entertainment news'
|
||||
|
||||
language = 'en'
|
||||
timefmt = '[%a, %d %b, %Y]'
|
||||
|
||||
oldest_article = 15
|
||||
max_articles_per_feed = 25
|
||||
use_embedded_content = False
|
||||
no_stylesheets = True
|
||||
|
||||
remove_javascript = True
|
||||
#####################################################################################
|
||||
# cleanup section #
|
||||
#####################################################################################
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':['c65l']}),
|
||||
dict(name='div', attrs={'id':['col1']}),
|
||||
|
||||
|
||||
]
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class': ['storyActions upper','storyActions lowerArticleNav']}),
|
||||
dict(name='div', attrs={'id': ['comments','related']}),
|
||||
]
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'News', u'http://www.rollingstone.com/siteServices/rss/allNews'),
|
||||
(u'Blogs', u'http://www.rollingstone.com/siteServices/rss/allBlogs'),
|
||||
(u'Movie Reviews', u'http://www.rollingstone.com/siteServices/rss/movieReviews'),
|
||||
(u'Album Reviews', u'http://www.rollingstone.com/siteServices/rss/albumReviews'),
|
||||
(u'Song Reviews', u'http://www.rollingstone.com/siteServices/rss/songReviews'),
|
||||
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
||||
def get_article_url(self, article):
|
||||
return article.get('guid', None)
|
||||
|
||||
|
||||
def append_page(self, soup, appendtag, position):
|
||||
'''
|
||||
Some are the articles are multipage so the below function
|
||||
will get the articles that have <next>
|
||||
'''
|
||||
pager = soup.find('li',attrs={'class':'next'})
|
||||
if pager:
|
||||
nexturl = pager.a['href']
|
||||
soup2 = self.index_to_soup(nexturl)
|
||||
texttag = soup2.find('div', attrs={'id':'storyTextContainer'})
|
||||
for it in texttag.findAll(style=True):
|
||||
del it['style']
|
||||
newpos = len(texttag.contents)
|
||||
self.append_page(soup2,texttag,newpos)
|
||||
texttag.extract()
|
||||
appendtag.insert(position,texttag)
|
||||
|
||||
|
@ -94,7 +94,7 @@ class PageProcessor(list):
|
||||
from calibre.utils.magick import PixelWand
|
||||
for i, wand in enumerate(self.pages):
|
||||
pw = PixelWand()
|
||||
pw.color = 'white'
|
||||
pw.color = '#ffffff'
|
||||
|
||||
wand.set_border_color(pw)
|
||||
if self.rotate:
|
||||
|
@ -169,7 +169,7 @@ class EditMetadataAction(InterfaceAction):
|
||||
self.gui.tags_view.blockSignals(False)
|
||||
if changed:
|
||||
m = self.gui.library_view.model()
|
||||
m.resort(reset=False)
|
||||
m.refresh(reset=False)
|
||||
m.research()
|
||||
self.gui.tags_view.recount()
|
||||
if self.gui.cover_flow:
|
||||
|
@ -263,7 +263,7 @@
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>00</height>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
@ -357,13 +357,13 @@ from the value in the box</string>
|
||||
</item>
|
||||
<item row="12" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="change_title_to_title_case">
|
||||
<property name="text">
|
||||
<string>Change title to title case</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Force the title to be in title case. If both this and swap authors are checked,
|
||||
title and author are swapped before the title case is set</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Change title to title case</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0" colspan="2">
|
||||
@ -486,15 +486,15 @@ Future conversion of these books will use the default settings.</string>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="HistoryLineEdit" name="search_for">
|
||||
<property name="toolTip">
|
||||
<string>Enter the what you are looking for, either plain text or a regular expression, depending on the mode</string>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>100</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Enter the what you are looking for, either plain text or a regular expression, depending on the mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
@ -656,6 +656,14 @@ nothing should be put between the original text and the inserted text</string>
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="gridLayoutWidget_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>122</width>
|
||||
<height>34</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="testgrid">
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_31">
|
||||
@ -733,14 +741,33 @@ nothing should be put between the original text and the inserted text</string>
|
||||
<tabstop>author_sort</tabstop>
|
||||
<tabstop>rating</tabstop>
|
||||
<tabstop>publisher</tabstop>
|
||||
<tabstop>tag_editor_button</tabstop>
|
||||
<tabstop>tags</tabstop>
|
||||
<tabstop>tag_editor_button</tabstop>
|
||||
<tabstop>remove_tags</tabstop>
|
||||
<tabstop>remove_all_tags</tabstop>
|
||||
<tabstop>series</tabstop>
|
||||
<tabstop>clear_series</tabstop>
|
||||
<tabstop>autonumber_series</tabstop>
|
||||
<tabstop>series_numbering_restarts</tabstop>
|
||||
<tabstop>series_start_number</tabstop>
|
||||
<tabstop>remove_format</tabstop>
|
||||
<tabstop>remove_conversion_settings</tabstop>
|
||||
<tabstop>swap_title_and_author</tabstop>
|
||||
<tabstop>change_title_to_title_case</tabstop>
|
||||
<tabstop>button_box</tabstop>
|
||||
<tabstop>central_widget</tabstop>
|
||||
<tabstop>search_field</tabstop>
|
||||
<tabstop>search_mode</tabstop>
|
||||
<tabstop>search_for</tabstop>
|
||||
<tabstop>case_sensitive</tabstop>
|
||||
<tabstop>replace_with</tabstop>
|
||||
<tabstop>replace_func</tabstop>
|
||||
<tabstop>destination_field</tabstop>
|
||||
<tabstop>replace_mode</tabstop>
|
||||
<tabstop>comma_separated</tabstop>
|
||||
<tabstop>scrollArea11</tabstop>
|
||||
<tabstop>test_text</tabstop>
|
||||
<tabstop>test_result</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../../../resources/images.qrc"/>
|
||||
|
@ -29,7 +29,7 @@ class TagDelegate(QItemDelegate): # {{{
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
item = index.internalPointer()
|
||||
if item.type != TagTreeItem.TAG:
|
||||
if True or item.type != TagTreeItem.TAG:
|
||||
QItemDelegate.paint(self, painter, option, index)
|
||||
return
|
||||
r = option.rect
|
||||
@ -89,7 +89,8 @@ class TagsView(QTreeView): # {{{
|
||||
self.hidden_categories = config['tag_browser_hidden_categories']
|
||||
self._model = TagsModel(db, parent=self,
|
||||
hidden_categories=self.hidden_categories,
|
||||
search_restriction=None)
|
||||
search_restriction=None,
|
||||
drag_drop_finished=self.drag_drop_finished)
|
||||
self.sort_by = sort_by
|
||||
self.tag_match = tag_match
|
||||
self.db = db
|
||||
@ -109,103 +110,6 @@ class TagsView(QTreeView): # {{{
|
||||
def database_changed(self, event, ids):
|
||||
self.refresh_required.emit()
|
||||
|
||||
def dragEnterEvent(self, event):
|
||||
md = event.mimeData()
|
||||
if md.hasFormat("application/calibre+from_library"):
|
||||
event.setDropAction(Qt.CopyAction)
|
||||
event.accept()
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
def dragMoveEvent(self, event):
|
||||
allowed = False
|
||||
idx = self.indexAt(event.pos())
|
||||
m = self.model()
|
||||
p = m.parent(idx)
|
||||
if idx.isValid() and p.isValid():
|
||||
item = m.data(p, Qt.UserRole)
|
||||
fm = self.db.metadata_for_field(item.category_key)
|
||||
if item.category_key in \
|
||||
('tags', 'series', 'authors', 'rating', 'publisher') or\
|
||||
(fm['is_custom'] and \
|
||||
fm['datatype'] in ['text', 'rating', 'series']):
|
||||
allowed = True
|
||||
if allowed:
|
||||
event.acceptProposedAction()
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
def dropEvent(self, event):
|
||||
idx = self.indexAt(event.pos())
|
||||
m = self.model()
|
||||
p = m.parent(idx)
|
||||
if idx.isValid() and p.isValid():
|
||||
item = m.data(p, Qt.UserRole)
|
||||
if item.type == TagTreeItem.CATEGORY:
|
||||
fm = self.db.metadata_for_field(item.category_key)
|
||||
if item.category_key in \
|
||||
('tags', 'series', 'authors', 'rating', 'publisher') or\
|
||||
(fm['is_custom'] and \
|
||||
fm['datatype'] in ['text', 'rating', 'series']):
|
||||
child = m.data(idx, Qt.UserRole)
|
||||
md = event.mimeData()
|
||||
mime = 'application/calibre+from_library'
|
||||
ids = list(map(int, str(md.data(mime)).split()))
|
||||
self.handle_drop(item, child, ids)
|
||||
event.accept()
|
||||
return
|
||||
event.ignore()
|
||||
|
||||
def handle_drop(self, parent, child, ids):
|
||||
# print 'Dropped ids:', ids, parent.category_key, child.tag.name
|
||||
key = parent.category_key
|
||||
if (key == 'authors' and len(ids) >= 5):
|
||||
if not confirm('<p>'+_('Changing the authors for several books can '
|
||||
'take a while. Are you sure?')
|
||||
+'</p>', 'tag_browser_drop_authors', self):
|
||||
return
|
||||
elif len(ids) > 15:
|
||||
if not confirm('<p>'+_('Changing the metadata for that many books '
|
||||
'can take a while. Are you sure?')
|
||||
+'</p>', 'tag_browser_many_changes', self):
|
||||
return
|
||||
|
||||
fm = self.db.metadata_for_field(key)
|
||||
is_multiple = fm['is_multiple']
|
||||
val = child.tag.name
|
||||
for id in ids:
|
||||
mi = self.db.get_metadata(id, index_is_id=True)
|
||||
|
||||
# Prepare to ignore the author, unless it is changed. Title is
|
||||
# always ignored -- see the call to set_metadata
|
||||
set_authors = False
|
||||
|
||||
# Author_sort cannot change explicitly. Changing the author might
|
||||
# change it.
|
||||
mi.author_sort = None # Never will change by itself.
|
||||
|
||||
if key == 'authors':
|
||||
mi.authors = [val]
|
||||
set_authors=True
|
||||
elif fm['datatype'] == 'rating':
|
||||
mi.set(key, len(val) * 2)
|
||||
elif fm['is_custom'] and fm['datatype'] == 'series':
|
||||
mi.set(key, val, extra=1.0)
|
||||
elif is_multiple:
|
||||
new_val = mi.get(key, [])
|
||||
if val in new_val:
|
||||
# Fortunately, only one field can change, so the continue
|
||||
# won't break anything
|
||||
continue
|
||||
new_val.append(val)
|
||||
mi.set(key, new_val)
|
||||
else:
|
||||
mi.set(key, val)
|
||||
self.db.set_metadata(id, mi, set_title=False,
|
||||
set_authors=set_authors, commit=False)
|
||||
self.db.commit()
|
||||
self.drag_drop_finished.emit(ids)
|
||||
|
||||
@property
|
||||
def match_all(self):
|
||||
return self.tag_match and self.tag_match.currentIndex() > 0
|
||||
@ -374,7 +278,8 @@ class TagsView(QTreeView): # {{{
|
||||
try:
|
||||
self._model = TagsModel(self.db, parent=self,
|
||||
hidden_categories=self.hidden_categories,
|
||||
search_restriction=self.search_restriction)
|
||||
search_restriction=self.search_restriction,
|
||||
drag_drop_finished=self.drag_drop_finished)
|
||||
self.setModel(self._model)
|
||||
except:
|
||||
# The DB must be gone. Set the model to None and hope that someone
|
||||
@ -469,7 +374,8 @@ class TagTreeItem(object): # {{{
|
||||
|
||||
class TagsModel(QAbstractItemModel): # {{{
|
||||
|
||||
def __init__(self, db, parent, hidden_categories=None, search_restriction=None):
|
||||
def __init__(self, db, parent, hidden_categories=None,
|
||||
search_restriction=None, drag_drop_finished=None):
|
||||
QAbstractItemModel.__init__(self, parent)
|
||||
|
||||
# must do this here because 'QPixmap: Must construct a QApplication
|
||||
@ -487,6 +393,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
':user' : QIcon(I('drawer.png')),
|
||||
'search' : QIcon(I('search.png'))})
|
||||
self.categories_with_ratings = ['authors', 'series', 'publisher', 'tags']
|
||||
self.drag_drop_finished = drag_drop_finished
|
||||
|
||||
self.icon_state_map = [None, QIcon(I('plus.png')), QIcon(I('minus.png'))]
|
||||
self.db = db
|
||||
@ -519,6 +426,82 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
tag.avg_rating = None
|
||||
TagTreeItem(parent=c, data=tag, icon_map=self.icon_state_map)
|
||||
|
||||
def mimeTypes(self):
|
||||
return ["application/calibre+from_library"]
|
||||
|
||||
def dropMimeData(self, md, action, row, column, parent):
|
||||
if not md.hasFormat("application/calibre+from_library") or \
|
||||
action != Qt.CopyAction:
|
||||
return False
|
||||
idx = parent
|
||||
p = self.parent(idx)
|
||||
if idx.isValid() and p.isValid():
|
||||
item = self.data(p, Qt.UserRole)
|
||||
fm = self.db.metadata_for_field(item.category_key)
|
||||
if item.category_key in \
|
||||
('tags', 'series', 'authors', 'rating', 'publisher') or \
|
||||
(fm['is_custom'] and \
|
||||
fm['datatype'] in ['text', 'rating', 'series']):
|
||||
child = self.data(idx, Qt.UserRole)
|
||||
mime = 'application/calibre+from_library'
|
||||
ids = list(map(int, str(md.data(mime)).split()))
|
||||
self.handle_drop(item, child, ids)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def handle_drop(self, parent, child, ids):
|
||||
# print 'Dropped ids:', ids, parent.category_key, child.tag.name
|
||||
key = parent.category_key
|
||||
if (key == 'authors' and len(ids) >= 5):
|
||||
if not confirm('<p>'+_('Changing the authors for several books can '
|
||||
'take a while. Are you sure?')
|
||||
+'</p>', 'tag_browser_drop_authors', self.parent()):
|
||||
return
|
||||
elif len(ids) > 15:
|
||||
if not confirm('<p>'+_('Changing the metadata for that many books '
|
||||
'can take a while. Are you sure?')
|
||||
+'</p>', 'tag_browser_many_changes', self.parent()):
|
||||
return
|
||||
|
||||
fm = self.db.metadata_for_field(key)
|
||||
is_multiple = fm['is_multiple']
|
||||
val = child.tag.name
|
||||
for id in ids:
|
||||
mi = self.db.get_metadata(id, index_is_id=True)
|
||||
|
||||
# Prepare to ignore the author, unless it is changed. Title is
|
||||
# always ignored -- see the call to set_metadata
|
||||
set_authors = False
|
||||
|
||||
# Author_sort cannot change explicitly. Changing the author might
|
||||
# change it.
|
||||
mi.author_sort = None # Never will change by itself.
|
||||
|
||||
if key == 'authors':
|
||||
mi.authors = [val]
|
||||
set_authors=True
|
||||
elif fm['datatype'] == 'rating':
|
||||
mi.set(key, len(val) * 2)
|
||||
elif fm['is_custom'] and fm['datatype'] == 'series':
|
||||
mi.set(key, val, extra=1.0)
|
||||
elif is_multiple:
|
||||
new_val = mi.get(key, [])
|
||||
if val in new_val:
|
||||
# Fortunately, only one field can change, so the continue
|
||||
# won't break anything
|
||||
continue
|
||||
new_val.append(val)
|
||||
mi.set(key, new_val)
|
||||
else:
|
||||
mi.set(key, val)
|
||||
self.db.set_metadata(id, mi, set_title=False,
|
||||
set_authors=set_authors, commit=False)
|
||||
self.db.commit()
|
||||
self.drag_drop_finished.emit(ids)
|
||||
|
||||
|
||||
|
||||
def set_search_restriction(self, s):
|
||||
self.search_restriction = s
|
||||
|
||||
@ -655,7 +638,7 @@ class TagsModel(QAbstractItemModel): # {{{
|
||||
return ans
|
||||
|
||||
def supportedDropActions(self):
|
||||
return Qt.CopyAction|Qt.MoveAction
|
||||
return Qt.CopyAction
|
||||
|
||||
def path_for_index(self, index):
|
||||
ans = []
|
||||
|
@ -18,7 +18,7 @@ from calibre.constants import __appname__
|
||||
from calibre.ebooks.metadata import fmt_sidx
|
||||
from calibre.library.comments import comments_to_html
|
||||
from calibre.library.server import custom_fields_to_display
|
||||
from calibre.library.server.utils import format_tag_string
|
||||
from calibre.library.server.utils import format_tag_string, Offsets
|
||||
from calibre import guess_type
|
||||
from calibre.utils.ordered_dict import OrderedDict
|
||||
|
||||
@ -321,26 +321,6 @@ class CategoryGroupFeed(NavFeed):
|
||||
self.root.append(CATALOG_GROUP_ENTRY(item, which, base_href, version, updated))
|
||||
|
||||
|
||||
class OPDSOffsets(object):
|
||||
|
||||
def __init__(self, offset, delta, total):
|
||||
if offset < 0:
|
||||
offset = 0
|
||||
if offset >= total:
|
||||
raise cherrypy.HTTPError(404, 'Invalid offset: %r'%offset)
|
||||
last_allowed_index = total - 1
|
||||
last_current_index = offset + delta - 1
|
||||
self.offset = offset
|
||||
self.next_offset = last_current_index + 1
|
||||
if self.next_offset > last_allowed_index:
|
||||
self.next_offset = -1
|
||||
self.previous_offset = self.offset - delta
|
||||
if self.previous_offset < 0:
|
||||
self.previous_offset = 0
|
||||
self.last_offset = last_allowed_index - delta
|
||||
if self.last_offset < 0:
|
||||
self.last_offset = 0
|
||||
|
||||
|
||||
class OPDSServer(object):
|
||||
|
||||
@ -374,7 +354,7 @@ class OPDSServer(object):
|
||||
items = [x for x in self.db.data.iterall() if x[idx] in ids]
|
||||
self.sort(items, sort_by, ascending)
|
||||
max_items = self.opts.max_opds_items
|
||||
offsets = OPDSOffsets(offset, max_items, len(items))
|
||||
offsets = Offsets(offset, max_items, len(items))
|
||||
items = items[offsets.offset:offsets.offset+max_items]
|
||||
updated = self.db.last_modified()
|
||||
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
|
||||
@ -448,7 +428,7 @@ class OPDSServer(object):
|
||||
id_ = 'calibre-category-group-feed:'+category+':'+which
|
||||
|
||||
max_items = self.opts.max_opds_items
|
||||
offsets = OPDSOffsets(offset, max_items, len(items))
|
||||
offsets = Offsets(offset, max_items, len(items))
|
||||
items = list(items)[offsets.offset:offsets.offset+max_items]
|
||||
|
||||
cherrypy.response.headers['Last-Modified'] = self.last_modified(updated)
|
||||
@ -495,7 +475,7 @@ class OPDSServer(object):
|
||||
|
||||
if len(items) <= MAX_ITEMS:
|
||||
max_items = self.opts.max_opds_items
|
||||
offsets = OPDSOffsets(offset, max_items, len(items))
|
||||
offsets = Offsets(offset, max_items, len(items))
|
||||
items = list(items)[offsets.offset:offsets.offset+max_items]
|
||||
ans = CategoryFeed(items, which, id_, updated, version, offsets,
|
||||
page_url, up_url, self.db)
|
||||
@ -516,7 +496,7 @@ class OPDSServer(object):
|
||||
getattr(y, 'sort', y.name).startswith(x)])
|
||||
items = [Group(x, y) for x, y in category_groups.items()]
|
||||
max_items = self.opts.max_opds_items
|
||||
offsets = OPDSOffsets(offset, max_items, len(items))
|
||||
offsets = Offsets(offset, max_items, len(items))
|
||||
items = items[offsets.offset:offsets.offset+max_items]
|
||||
ans = CategoryGroupFeed(items, which, id_, updated, version, offsets,
|
||||
page_url, up_url)
|
||||
|
@ -13,6 +13,27 @@ from calibre import strftime as _strftime, prints
|
||||
from calibre.utils.date import now as nowf
|
||||
from calibre.utils.config import tweaks
|
||||
|
||||
class Offsets(object):
|
||||
'Calculate offsets for a paginated view'
|
||||
|
||||
def __init__(self, offset, delta, total):
|
||||
if offset < 0:
|
||||
offset = 0
|
||||
if offset >= total:
|
||||
raise cherrypy.HTTPError(404, 'Invalid offset: %r'%offset)
|
||||
last_allowed_index = total - 1
|
||||
last_current_index = offset + delta - 1
|
||||
self.offset = offset
|
||||
self.next_offset = last_current_index + 1
|
||||
if self.next_offset > last_allowed_index:
|
||||
self.next_offset = -1
|
||||
self.previous_offset = self.offset - delta
|
||||
if self.previous_offset < 0:
|
||||
self.previous_offset = 0
|
||||
self.last_offset = last_allowed_index - delta
|
||||
if self.last_offset < 0:
|
||||
self.last_offset = 0
|
||||
|
||||
|
||||
def expose(func):
|
||||
|
||||
|
@ -14,7 +14,10 @@ from calibre.ptempfile import PersistentTemporaryFile, base_dir
|
||||
|
||||
if iswindows:
|
||||
import win32process
|
||||
try:
|
||||
_windows_null_file = open(os.devnull, 'wb')
|
||||
except:
|
||||
raise RuntimeError('NUL %r file missing in windows'%os.devnull)
|
||||
|
||||
class Worker(object):
|
||||
'''
|
||||
|
@ -11,6 +11,7 @@ This module implements a simple commandline SMTP client that supports:
|
||||
|
||||
import sys, traceback, os
|
||||
from email import encoders
|
||||
from calibre import isbytestring
|
||||
|
||||
def create_mail(from_, to, subject, text=None, attachment_data=None,
|
||||
attachment_type=None, attachment_name=None):
|
||||
@ -26,6 +27,9 @@ def create_mail(from_, to, subject, text=None, attachment_data=None,
|
||||
|
||||
if text is not None:
|
||||
from email.mime.text import MIMEText
|
||||
if isbytestring(text):
|
||||
msg = MIMEText(text)
|
||||
else:
|
||||
msg = MIMEText(text, 'plain', 'utf-8')
|
||||
outer.attach(msg)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user