mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
Add "Recently Read" category to catalog if Kindle is connected when catalog is generated
This commit is contained in:
commit
432612764a
@ -79,6 +79,14 @@ p.unread_book {
|
|||||||
text-indent:-2em;
|
text-indent:-2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.date_read {
|
||||||
|
text-align:left;
|
||||||
|
margin-top:0px;
|
||||||
|
margin-bottom:0px;
|
||||||
|
margin-left:6em;
|
||||||
|
text-indent:-6em;
|
||||||
|
}
|
||||||
|
|
||||||
hr.series_divider {
|
hr.series_divider {
|
||||||
width:50%;
|
width:50%;
|
||||||
margin-left:1em;
|
margin-left:1em;
|
||||||
|
@ -789,7 +789,7 @@ class Device(DeviceConfig, DevicePlugin):
|
|||||||
'''
|
'''
|
||||||
return components
|
return components
|
||||||
|
|
||||||
def create_upload_path(self, path, mdata, fname):
|
def create_upload_path(self, path, mdata, fname, create_dirs=True):
|
||||||
path = os.path.abspath(path)
|
path = os.path.abspath(path)
|
||||||
extra_components = []
|
extra_components = []
|
||||||
|
|
||||||
@ -848,7 +848,7 @@ class Device(DeviceConfig, DevicePlugin):
|
|||||||
filedir = os.path.dirname(filepath)
|
filedir = os.path.dirname(filepath)
|
||||||
|
|
||||||
|
|
||||||
if not os.path.exists(filedir):
|
if create_dirs and not os.path.exists(filedir):
|
||||||
os.makedirs(filedir)
|
os.makedirs(filedir)
|
||||||
|
|
||||||
return filepath
|
return filepath
|
||||||
|
@ -571,7 +571,9 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
self.__authorClip = opts.authorClip
|
self.__authorClip = opts.authorClip
|
||||||
self.__authors = None
|
self.__authors = None
|
||||||
self.__basename = opts.basename
|
self.__basename = opts.basename
|
||||||
|
self.__bookmarked_books = None
|
||||||
self.__booksByAuthor = None
|
self.__booksByAuthor = None
|
||||||
|
self.__booksByDateRead = None
|
||||||
self.__booksByTitle = None
|
self.__booksByTitle = None
|
||||||
self.__booksByTitle_noSeriesPrefix = None
|
self.__booksByTitle_noSeriesPrefix = None
|
||||||
self.__catalogPath = PersistentTemporaryDirectory("_epub_mobi_catalog", prefix='')
|
self.__catalogPath = PersistentTemporaryDirectory("_epub_mobi_catalog", prefix='')
|
||||||
@ -603,11 +605,13 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
self.__useSeriesPrefixInTitlesSection = False
|
self.__useSeriesPrefixInTitlesSection = False
|
||||||
self.__verbose = opts.verbose
|
self.__verbose = opts.verbose
|
||||||
|
|
||||||
# Tweak build steps based on optional sections. 1 call for HTML, 1 for NCX
|
# Tweak build steps based on optional sections: 1 call for HTML, 1 for NCX
|
||||||
if self.opts.generate_titles:
|
if self.opts.generate_titles:
|
||||||
self.__totalSteps += 2
|
self.__totalSteps += 2
|
||||||
if self.opts.generate_recently_added:
|
if self.opts.generate_recently_added:
|
||||||
self.__totalSteps += 2
|
self.__totalSteps += 2
|
||||||
|
if self.opts.connected_kindle:
|
||||||
|
self.__totalSteps += 2
|
||||||
|
|
||||||
# Accessors
|
# Accessors
|
||||||
'''
|
'''
|
||||||
@ -642,6 +646,13 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
self.__basename = val
|
self.__basename = val
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
@dynamic_property
|
@dynamic_property
|
||||||
|
def bookmarked_books(self):
|
||||||
|
def fget(self):
|
||||||
|
return self.__bookmarked_books
|
||||||
|
def fset(self, val):
|
||||||
|
self.__bookmarked_books = val
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
@dynamic_property
|
||||||
def booksByAuthor(self):
|
def booksByAuthor(self):
|
||||||
def fget(self):
|
def fget(self):
|
||||||
return self.__booksByAuthor
|
return self.__booksByAuthor
|
||||||
@ -649,6 +660,13 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
self.__booksByAuthor = val
|
self.__booksByAuthor = val
|
||||||
return property(fget=fget, fset=fset)
|
return property(fget=fget, fset=fset)
|
||||||
@dynamic_property
|
@dynamic_property
|
||||||
|
def booksByDateRead(self):
|
||||||
|
def fget(self):
|
||||||
|
return self.__booksByDateRead
|
||||||
|
def fset(self, val):
|
||||||
|
self.__booksByDateRead = val
|
||||||
|
return property(fget=fget, fset=fset)
|
||||||
|
@dynamic_property
|
||||||
def booksByTitle(self):
|
def booksByTitle(self):
|
||||||
def fget(self):
|
def fget(self):
|
||||||
return self.__booksByTitle
|
return self.__booksByTitle
|
||||||
@ -869,6 +887,16 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
def fget(self):
|
def fget(self):
|
||||||
return "☆" if self.generateForKindle else ' '
|
return "☆" if self.generateForKindle else ' '
|
||||||
return property(fget=fget)
|
return property(fget=fget)
|
||||||
|
@dynamic_property
|
||||||
|
def READ_PROGRESS_SYMBOL(self):
|
||||||
|
def fget(self):
|
||||||
|
return "▪" if self.generateForKindle else '+'
|
||||||
|
return property(fget=fget)
|
||||||
|
@dynamic_property
|
||||||
|
def UNREAD_PROGRESS_SYMBOL(self):
|
||||||
|
def fget(self):
|
||||||
|
return "▫" if self.generateForKindle else '-'
|
||||||
|
return property(fget=fget)
|
||||||
|
|
||||||
# Methods
|
# Methods
|
||||||
def buildSources(self):
|
def buildSources(self):
|
||||||
@ -876,12 +904,14 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
if not self.fetchBooksByTitle():
|
if not self.fetchBooksByTitle():
|
||||||
return False
|
return False
|
||||||
self.fetchBooksByAuthor()
|
self.fetchBooksByAuthor()
|
||||||
|
self.fetchBookmarks()
|
||||||
self.generateHTMLDescriptions()
|
self.generateHTMLDescriptions()
|
||||||
self.generateHTMLByAuthor()
|
self.generateHTMLByAuthor()
|
||||||
if self.opts.generate_titles:
|
if self.opts.generate_titles:
|
||||||
self.generateHTMLByTitle()
|
self.generateHTMLByTitle()
|
||||||
if self.opts.generate_recently_added:
|
if self.opts.generate_recently_added:
|
||||||
self.generateHTMLByDateAdded()
|
self.generateHTMLByDateAdded()
|
||||||
|
self.generateHTMLByDateRead()
|
||||||
self.generateHTMLByTags()
|
self.generateHTMLByTags()
|
||||||
|
|
||||||
from calibre.utils.PythonMagickWand import ImageMagick
|
from calibre.utils.PythonMagickWand import ImageMagick
|
||||||
@ -896,6 +926,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
self.generateNCXByTitle("Titles")
|
self.generateNCXByTitle("Titles")
|
||||||
if self.opts.generate_recently_added:
|
if self.opts.generate_recently_added:
|
||||||
self.generateNCXByDateAdded("Recently Added")
|
self.generateNCXByDateAdded("Recently Added")
|
||||||
|
self.generateNCXByDateRead("Recently Read")
|
||||||
self.generateNCXByGenre("Genres")
|
self.generateNCXByGenre("Genres")
|
||||||
self.writeNCX()
|
self.writeNCX()
|
||||||
return True
|
return True
|
||||||
@ -980,10 +1011,12 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
this_title['series_index'] = 0.0
|
this_title['series_index'] = 0.0
|
||||||
|
|
||||||
this_title['title_sort'] = self.generateSortTitle(this_title['title'])
|
this_title['title_sort'] = self.generateSortTitle(this_title['title'])
|
||||||
if 'authors' in record and record['authors']:
|
if 'authors' in record:
|
||||||
this_title['author'] = " & ".join(record['authors'])
|
this_title['authors'] = record['authors']
|
||||||
else:
|
if record['authors']:
|
||||||
this_title['author'] = 'Unknown'
|
this_title['author'] = " & ".join(record['authors'])
|
||||||
|
else:
|
||||||
|
this_title['author'] = 'Unknown'
|
||||||
|
|
||||||
'''
|
'''
|
||||||
this_title['author_sort_original'] = record['author_sort']
|
this_title['author_sort_original'] = record['author_sort']
|
||||||
@ -1119,6 +1152,159 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
|
|
||||||
self.authors = unique_authors
|
self.authors = unique_authors
|
||||||
|
|
||||||
|
def fetchBookmarks(self):
|
||||||
|
'''
|
||||||
|
Collect bookmarks for catalog entries
|
||||||
|
This will use the system default save_template specified in
|
||||||
|
Preferences|Add/Save|Sending to device, not a customized one specified in
|
||||||
|
the Kindle plugin
|
||||||
|
'''
|
||||||
|
from cStringIO import StringIO
|
||||||
|
from struct import unpack
|
||||||
|
|
||||||
|
from calibre.devices.usbms.device import Device
|
||||||
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
|
from calibre.ebooks.metadata.mobi import StreamSlicer
|
||||||
|
|
||||||
|
class BookmarkDevice(Device):
|
||||||
|
def initialize(self, save_template):
|
||||||
|
self._save_template = save_template
|
||||||
|
self.SUPPORTS_SUB_DIRS = True
|
||||||
|
def save_template(self):
|
||||||
|
return self._save_template
|
||||||
|
|
||||||
|
class Bookmark():
|
||||||
|
'''
|
||||||
|
A simple class storing bookmark data
|
||||||
|
Kindle-specific
|
||||||
|
'''
|
||||||
|
def __init__(self,path, formats, id):
|
||||||
|
self.book_format = None
|
||||||
|
self.book_length = 0
|
||||||
|
self.id = id
|
||||||
|
self.last_read_location = 0
|
||||||
|
self.timestamp = 0
|
||||||
|
self.get_bookmark_data(path)
|
||||||
|
self.get_book_length(path, formats)
|
||||||
|
|
||||||
|
|
||||||
|
def get_bookmark_data(self, path):
|
||||||
|
''' Return the timestamp and last_read_location '''
|
||||||
|
with open(path) as f:
|
||||||
|
stream = StringIO(f.read())
|
||||||
|
data = StreamSlicer(stream)
|
||||||
|
self.timestamp, = unpack('>I', data[0x24:0x28])
|
||||||
|
bpar_offset, = unpack('>I', data[0x4e:0x52])
|
||||||
|
#print "bpar_offset: 0x%x" % bpar_offset
|
||||||
|
lrlo = bpar_offset + 0x0c
|
||||||
|
self.last_read_location = int(unpack('>I', data[lrlo:lrlo+4])[0])
|
||||||
|
'''
|
||||||
|
iolr = bpar_offset + 0x14
|
||||||
|
index_of_last_read, = unpack('>I', data[iolr:iolr+4])
|
||||||
|
#print "index_of_last_read: 0x%x" % index_of_last_read
|
||||||
|
bpar_len, = unpack('>I', data[bpl:bpl+4])
|
||||||
|
bpar_len += 8
|
||||||
|
#print "bpar_len: 0x%x" % bpar_len
|
||||||
|
dro = bpar_offset + bpar_len
|
||||||
|
#print "dro: 0x%x" % dro
|
||||||
|
|
||||||
|
# Walk to index_of_last_read to find last_read_location
|
||||||
|
# If BKMK - offset 8
|
||||||
|
# If DATA - offset 0x18 + 0x1c
|
||||||
|
current_entry = 1
|
||||||
|
while current_entry < index_of_last_read:
|
||||||
|
rec_len, = unpack('>I', data[dro+4:dro+8])
|
||||||
|
rec_len += 8
|
||||||
|
dro += rec_len
|
||||||
|
current_entry += 1
|
||||||
|
|
||||||
|
# Looking at the record with last_read_location
|
||||||
|
if data[dro:dro+4] == 'DATA':
|
||||||
|
lrlo = dro + 0x18 + 0x1c
|
||||||
|
elif data[dro:dro+4] == 'BKMK':
|
||||||
|
lrlo = dro + 8
|
||||||
|
else:
|
||||||
|
print "Unrecognized bookmark block type"
|
||||||
|
|
||||||
|
#print "lrlo: 0x%x" % lrlo
|
||||||
|
self.last_read_location = float(unpack('>I', data[lrlo:lrlo+4])[0])
|
||||||
|
#print "last_read_location: 0x%x" % self.last_read_location
|
||||||
|
'''
|
||||||
|
|
||||||
|
def get_book_length(self, path, formats):
|
||||||
|
# This assumes only one of the possible formats exists on the Kindle
|
||||||
|
book_fs = None
|
||||||
|
for format in formats:
|
||||||
|
fmt = format.rpartition('.')[2]
|
||||||
|
if fmt in ['mobi','prc','azw']:
|
||||||
|
book_fs = path.replace('.mbp','.%s' % fmt)
|
||||||
|
if os.path.exists(book_fs):
|
||||||
|
self.book_format = fmt
|
||||||
|
#print "%s exists on device" % book_fs
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
#print "no files matching library formats exist on device"
|
||||||
|
self.book_length = 0
|
||||||
|
return
|
||||||
|
# Read the book len from the header
|
||||||
|
with open(book_fs) as f:
|
||||||
|
self.stream = StringIO(f.read())
|
||||||
|
self.data = StreamSlicer(self.stream)
|
||||||
|
self.nrecs, = unpack('>H', self.data[76:78])
|
||||||
|
record0 = self.record(0)
|
||||||
|
#self.hexdump(record0)
|
||||||
|
self.book_length = int(unpack('>I', record0[0x04:0x08])[0])
|
||||||
|
|
||||||
|
def record(self, n):
|
||||||
|
if n >= self.nrecs:
|
||||||
|
raise ValueError('non-existent record %r' % n)
|
||||||
|
offoff = 78 + (8 * n)
|
||||||
|
start, = unpack('>I', self.data[offoff + 0:offoff + 4])
|
||||||
|
stop = None
|
||||||
|
if n < (self.nrecs - 1):
|
||||||
|
stop, = unpack('>I', self.data[offoff + 8:offoff + 12])
|
||||||
|
return StreamSlicer(self.stream, start, stop)
|
||||||
|
|
||||||
|
def hexdump(self, src, length=16):
|
||||||
|
# Diagnostic
|
||||||
|
FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)])
|
||||||
|
N=0; result=''
|
||||||
|
while src:
|
||||||
|
s,src = src[:length],src[length:]
|
||||||
|
hexa = ' '.join(["%02X"%ord(x) for x in s])
|
||||||
|
s = s.translate(FILTER)
|
||||||
|
result += "%04X %-*s %s\n" % (N, length*3, hexa, s)
|
||||||
|
N+=length
|
||||||
|
print result
|
||||||
|
|
||||||
|
if self.opts.connected_kindle:
|
||||||
|
self.opts.log.info(" Collecting Kindle bookmarks matching catalog entries")
|
||||||
|
|
||||||
|
d = BookmarkDevice(None)
|
||||||
|
d.initialize(self.opts.connected_device['save_template'])
|
||||||
|
bookmarks = {}
|
||||||
|
for book in self.booksByTitle:
|
||||||
|
myMeta = MetaInformation(book['title'],
|
||||||
|
authors=book['authors'])
|
||||||
|
myMeta.author_sort = book['author_sort']
|
||||||
|
bm_found = False
|
||||||
|
for vol in self.opts.connected_device['storage']:
|
||||||
|
bm_path = d.create_upload_path(vol, myMeta, 'x.mbp', create_dirs=False)
|
||||||
|
if os.path.exists(bm_path):
|
||||||
|
myBookmark = Bookmark(bm_path, book['formats'], book['id'])
|
||||||
|
if myBookmark.book_length:
|
||||||
|
book['percent_read'] = float(100*myBookmark.last_read_location / myBookmark.book_length)
|
||||||
|
dots = int((book['percent_read'] + 5)/10)
|
||||||
|
dot_string = self.READ_PROGRESS_SYMBOL * dots
|
||||||
|
empty_dots = self.UNREAD_PROGRESS_SYMBOL * (10 - dots)
|
||||||
|
book['reading_progress'] = '%s%s' % (dot_string,empty_dots)
|
||||||
|
bookmarks[book['id']] = ((myBookmark,book))
|
||||||
|
bm_found = True
|
||||||
|
if bm_found:
|
||||||
|
break
|
||||||
|
|
||||||
|
self.bookmarked_books = bookmarks
|
||||||
|
|
||||||
def generateHTMLDescriptions(self):
|
def generateHTMLDescriptions(self):
|
||||||
# Write each title to a separate HTML file in contentdir
|
# Write each title to a separate HTML file in contentdir
|
||||||
self.updateProgressFullStep("'Descriptions'")
|
self.updateProgressFullStep("'Descriptions'")
|
||||||
@ -1160,29 +1346,25 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
titleTag = body.find(attrs={'class':'title'})
|
titleTag = body.find(attrs={'class':'title'})
|
||||||
titleTag.insert(0,emTag)
|
titleTag.insert(0,emTag)
|
||||||
|
|
||||||
# Insert the author
|
# Create the author anchor
|
||||||
authorTag = body.find(attrs={'class':'author'})
|
authorTag = body.find(attrs={'class':'author'})
|
||||||
aTag = Tag(soup, "a")
|
aTag = Tag(soup, "a")
|
||||||
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(title['author']))
|
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor",
|
||||||
#aTag.insert(0, escape(title['author']))
|
self.generateAuthorAnchor(title['author']))
|
||||||
aTag.insert(0, title['author'])
|
aTag.insert(0, title['author'])
|
||||||
|
|
||||||
# Insert READ_SYMBOL
|
# This will include the reading progress dots even if we're not generating Recently Read
|
||||||
if title['read']:
|
if self.opts.connected_kindle and title['id'] in self.bookmarked_books:
|
||||||
authorTag.insert(0, NavigableString(self.READ_SYMBOL + "by "))
|
authorTag.insert(0, NavigableString(title['reading_progress'] + " by "))
|
||||||
|
authorTag.insert(1, aTag)
|
||||||
else:
|
else:
|
||||||
authorTag.insert(0, NavigableString(self.NOT_READ_SYMBOL + "by "))
|
# Insert READ_SYMBOL
|
||||||
authorTag.insert(1, aTag)
|
if title['read']:
|
||||||
|
authorTag.insert(0, NavigableString(self.READ_SYMBOL + "by "))
|
||||||
|
else:
|
||||||
|
authorTag.insert(0, NavigableString(self.NOT_READ_SYMBOL + "by "))
|
||||||
|
authorTag.insert(1, aTag)
|
||||||
|
|
||||||
'''
|
|
||||||
# Insert the unlinked genres.
|
|
||||||
if 'tags' in title:
|
|
||||||
tagsTag = body.find(attrs={'class':'tags'})
|
|
||||||
emTag = Tag(soup,"em")
|
|
||||||
emTag.insert(0,NavigableString(', '.join(title['tags'])))
|
|
||||||
tagsTag.insert(0,emTag)
|
|
||||||
|
|
||||||
'''
|
|
||||||
'''
|
'''
|
||||||
# Insert Series info or remove.
|
# Insert Series info or remove.
|
||||||
seriesTag = body.find(attrs={'class':'series'})
|
seriesTag = body.find(attrs={'class':'series'})
|
||||||
@ -1789,6 +1971,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
divTag.insert(dtc,hrTag)
|
divTag.insert(dtc,hrTag)
|
||||||
dtc += 1
|
dtc += 1
|
||||||
|
|
||||||
|
# >>>> Books by month <<<<
|
||||||
# Sort titles case-insensitive for by month using series prefix
|
# Sort titles case-insensitive for by month using series prefix
|
||||||
self.booksByMonth = sorted(self.booksByTitle,
|
self.booksByMonth = sorted(self.booksByTitle,
|
||||||
key=lambda x:(x['timestamp'], x['timestamp']),reverse=True)
|
key=lambda x:(x['timestamp'], x['timestamp']),reverse=True)
|
||||||
@ -1817,6 +2000,192 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
outfile.close()
|
outfile.close()
|
||||||
self.htmlFileList.append("content/ByDateAdded.html")
|
self.htmlFileList.append("content/ByDateAdded.html")
|
||||||
|
|
||||||
|
def generateHTMLByDateRead(self):
|
||||||
|
# Write books by active bookmarks
|
||||||
|
friendly_name = 'Recently Read'
|
||||||
|
self.updateProgressFullStep("'%s'" % friendly_name)
|
||||||
|
if not self.bookmarked_books:
|
||||||
|
return
|
||||||
|
|
||||||
|
def add_books_to_HTML_by_day(todays_list, dtc):
|
||||||
|
if len(todays_list):
|
||||||
|
# Create a new day anchor
|
||||||
|
date_string = strftime(u'%A, %B %d', current_date.timetuple())
|
||||||
|
pIndexTag = Tag(soup, "p")
|
||||||
|
pIndexTag['class'] = "date_index"
|
||||||
|
aTag = Tag(soup, "a")
|
||||||
|
aTag['name'] = "bdr_%s-%s-%s" % (current_date.year, current_date.month, current_date.day)
|
||||||
|
pIndexTag.insert(0,aTag)
|
||||||
|
pIndexTag.insert(1,NavigableString(date_string))
|
||||||
|
divTag.insert(dtc,pIndexTag)
|
||||||
|
dtc += 1
|
||||||
|
|
||||||
|
for new_entry in todays_list:
|
||||||
|
pBookTag = Tag(soup, "p")
|
||||||
|
pBookTag['class'] = "date_read"
|
||||||
|
ptc = 0
|
||||||
|
|
||||||
|
# Percent read
|
||||||
|
pBookTag.insert(ptc, NavigableString(new_entry['reading_progress']))
|
||||||
|
ptc += 1
|
||||||
|
|
||||||
|
aTag = Tag(soup, "a")
|
||||||
|
aTag['href'] = "book_%d.html" % (int(float(new_entry['id'])))
|
||||||
|
aTag.insert(0,escape(new_entry['title']))
|
||||||
|
pBookTag.insert(ptc, aTag)
|
||||||
|
ptc += 1
|
||||||
|
|
||||||
|
# Dot
|
||||||
|
pBookTag.insert(ptc, NavigableString(" · "))
|
||||||
|
ptc += 1
|
||||||
|
|
||||||
|
# Link to author
|
||||||
|
emTag = Tag(soup, "em")
|
||||||
|
aTag = Tag(soup, "a")
|
||||||
|
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author']))
|
||||||
|
aTag.insert(0, NavigableString(new_entry['author']))
|
||||||
|
emTag.insert(0,aTag)
|
||||||
|
pBookTag.insert(ptc, emTag)
|
||||||
|
ptc += 1
|
||||||
|
|
||||||
|
divTag.insert(dtc, pBookTag)
|
||||||
|
dtc += 1
|
||||||
|
return dtc
|
||||||
|
|
||||||
|
def add_books_to_HTML_by_date_range(date_range_list, date_range, dtc):
|
||||||
|
if len(date_range_list):
|
||||||
|
pIndexTag = Tag(soup, "p")
|
||||||
|
pIndexTag['class'] = "date_index"
|
||||||
|
aTag = Tag(soup, "a")
|
||||||
|
aTag['name'] = "bdr_%s" % date_range.replace(' ','')
|
||||||
|
pIndexTag.insert(0,aTag)
|
||||||
|
pIndexTag.insert(1,NavigableString(date_range))
|
||||||
|
divTag.insert(dtc,pIndexTag)
|
||||||
|
dtc += 1
|
||||||
|
|
||||||
|
for new_entry in date_range_list:
|
||||||
|
# Add books
|
||||||
|
pBookTag = Tag(soup, "p")
|
||||||
|
pBookTag['class'] = "date_read"
|
||||||
|
ptc = 0
|
||||||
|
|
||||||
|
# Percent read
|
||||||
|
dots = int((new_entry['percent_read'] + 5)/10)
|
||||||
|
dot_string = self.READ_PROGRESS_SYMBOL * dots
|
||||||
|
empty_dots = self.UNREAD_PROGRESS_SYMBOL * (10 - dots)
|
||||||
|
pBookTag.insert(ptc, NavigableString('%s%s' % (dot_string,empty_dots)))
|
||||||
|
ptc += 1
|
||||||
|
|
||||||
|
aTag = Tag(soup, "a")
|
||||||
|
aTag['href'] = "book_%d.html" % (int(float(new_entry['id'])))
|
||||||
|
aTag.insert(0,escape(new_entry['title']))
|
||||||
|
pBookTag.insert(ptc, aTag)
|
||||||
|
ptc += 1
|
||||||
|
|
||||||
|
# Dot
|
||||||
|
pBookTag.insert(ptc, NavigableString(" · "))
|
||||||
|
ptc += 1
|
||||||
|
|
||||||
|
# Link to author
|
||||||
|
emTag = Tag(soup, "em")
|
||||||
|
aTag = Tag(soup, "a")
|
||||||
|
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author']))
|
||||||
|
aTag.insert(0, NavigableString(new_entry['author']))
|
||||||
|
emTag.insert(0,aTag)
|
||||||
|
pBookTag.insert(ptc, emTag)
|
||||||
|
ptc += 1
|
||||||
|
|
||||||
|
divTag.insert(dtc, pBookTag)
|
||||||
|
dtc += 1
|
||||||
|
return dtc
|
||||||
|
|
||||||
|
soup = self.generateHTMLEmptyHeader(friendly_name)
|
||||||
|
body = soup.find('body')
|
||||||
|
|
||||||
|
btc = 0
|
||||||
|
|
||||||
|
# Insert section tag
|
||||||
|
aTag = Tag(soup,'a')
|
||||||
|
aTag['name'] = 'section_start'
|
||||||
|
body.insert(btc, aTag)
|
||||||
|
btc += 1
|
||||||
|
|
||||||
|
# Insert the anchor
|
||||||
|
aTag = Tag(soup, "a")
|
||||||
|
anchor_name = friendly_name.lower()
|
||||||
|
aTag['name'] = anchor_name.replace(" ","")
|
||||||
|
body.insert(btc, aTag)
|
||||||
|
btc += 1
|
||||||
|
|
||||||
|
# <p class="letter_index">
|
||||||
|
# <p class="author_index">
|
||||||
|
divTag = Tag(soup, "div")
|
||||||
|
dtc = 0
|
||||||
|
|
||||||
|
# self.bookmarked_books: (Bookmark, book)
|
||||||
|
bookmarked_books = []
|
||||||
|
for bm_book in self.bookmarked_books:
|
||||||
|
book = self.bookmarked_books[bm_book]
|
||||||
|
#print "bm_book: %s" % bm_book
|
||||||
|
book[1]['bookmark_timestamp'] = book[0].timestamp
|
||||||
|
book[1]['percent_read'] = float(100*book[0].last_read_location / book[0].book_length)
|
||||||
|
bookmarked_books.append(book[1])
|
||||||
|
|
||||||
|
self.booksByDateRead = sorted(bookmarked_books,
|
||||||
|
key=lambda x:(x['bookmark_timestamp'], x['bookmark_timestamp']),reverse=True)
|
||||||
|
|
||||||
|
'''
|
||||||
|
# >>>> Recently by date range <<<<
|
||||||
|
date_range_list = []
|
||||||
|
today_time = datetime.datetime.utcnow()
|
||||||
|
today_time.replace(hour=23, minute=59, second=59)
|
||||||
|
books_added_in_date_range = False
|
||||||
|
for (i, date) in enumerate(self.DATE_RANGE):
|
||||||
|
date_range_limit = self.DATE_RANGE[i]
|
||||||
|
if i:
|
||||||
|
date_range = '%d to %d days ago' % (self.DATE_RANGE[i-1], self.DATE_RANGE[i])
|
||||||
|
else:
|
||||||
|
date_range = 'Last %d days' % (self.DATE_RANGE[i])
|
||||||
|
|
||||||
|
for book in self.booksByDateRead:
|
||||||
|
bookmark_time = datetime.datetime.utcfromtimestamp(book['bookmark_timestamp'])
|
||||||
|
delta = today_time-bookmark_time
|
||||||
|
if delta.days <= date_range_limit:
|
||||||
|
date_range_list.append(book)
|
||||||
|
books_added_in_date_range = True
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
dtc = add_books_to_HTML_by_date_range(date_range_list, date_range, dtc)
|
||||||
|
date_range_list = [book]
|
||||||
|
'''
|
||||||
|
|
||||||
|
# >>>> Recently read by day <<<<
|
||||||
|
current_date = datetime.date.fromordinal(1)
|
||||||
|
todays_list = []
|
||||||
|
for book in self.booksByDateRead:
|
||||||
|
bookmark_time = datetime.datetime.utcfromtimestamp(book['bookmark_timestamp'])
|
||||||
|
if bookmark_time.day != current_date.day or \
|
||||||
|
bookmark_time.month != current_date.month or \
|
||||||
|
bookmark_time.year != current_date.year:
|
||||||
|
dtc = add_books_to_HTML_by_day(todays_list, dtc)
|
||||||
|
todays_list = []
|
||||||
|
current_date = datetime.datetime.utcfromtimestamp(book['bookmark_timestamp']).date()
|
||||||
|
todays_list.append(book)
|
||||||
|
|
||||||
|
# Add the last day's list
|
||||||
|
add_books_to_HTML_by_day(todays_list, dtc)
|
||||||
|
|
||||||
|
# Add the divTag to the body
|
||||||
|
body.insert(btc, divTag)
|
||||||
|
|
||||||
|
# Write the generated file to contentdir
|
||||||
|
outfile_spec = "%s/ByDateRead.html" % (self.contentDir)
|
||||||
|
outfile = open(outfile_spec, 'w')
|
||||||
|
outfile.write(soup.prettify())
|
||||||
|
outfile.close()
|
||||||
|
self.htmlFileList.append("content/ByDateRead.html")
|
||||||
|
|
||||||
def generateHTMLByTags(self):
|
def generateHTMLByTags(self):
|
||||||
# Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ...
|
# Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ...
|
||||||
# Note that special tags - ~+*[] - have already been filtered from books[]
|
# Note that special tags - ~+*[] - have already been filtered from books[]
|
||||||
@ -2229,8 +2598,16 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
else:
|
else:
|
||||||
if self.generateForKindle:
|
if self.generateForKindle:
|
||||||
# Don't include Author for Kindle
|
# Don't include Author for Kindle
|
||||||
textTag.insert(0, NavigableString(self.formatNCXText('%s' % (book['title']),
|
title_str = self.formatNCXText('%s' % (book['title']), dest='title')
|
||||||
dest='title')))
|
if self.opts.connected_kindle and book['id'] in self.bookmarked_books:
|
||||||
|
'''
|
||||||
|
dots = int((book['percent_read'] + 5)/10)
|
||||||
|
dot_string = '+' * dots
|
||||||
|
empty_dots = '-' * (10 - dots)
|
||||||
|
title_str += ' %s%s' % (dot_string,empty_dots)
|
||||||
|
'''
|
||||||
|
title_str += '*'
|
||||||
|
textTag.insert(0, NavigableString(title_str))
|
||||||
else:
|
else:
|
||||||
# Include Author for non-Kindle
|
# Include Author for non-Kindle
|
||||||
textTag.insert(0, NavigableString(self.formatNCXText('%s · %s' % \
|
textTag.insert(0, NavigableString(self.formatNCXText('%s · %s' % \
|
||||||
@ -2616,6 +2993,177 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
btc += 1
|
btc += 1
|
||||||
self.ncxSoup = soup
|
self.ncxSoup = soup
|
||||||
|
|
||||||
|
def generateNCXByDateRead(self, tocTitle):
|
||||||
|
self.updateProgressFullStep("NCX 'Recently Read'")
|
||||||
|
if not self.booksByDateRead:
|
||||||
|
return
|
||||||
|
|
||||||
|
def add_to_master_day_list(current_titles_list):
|
||||||
|
book_count = len(current_titles_list)
|
||||||
|
current_titles_list = " • ".join(current_titles_list)
|
||||||
|
current_titles_list = self.formatNCXText(current_titles_list, dest='description')
|
||||||
|
master_day_list.append((current_titles_list, current_date, book_count))
|
||||||
|
|
||||||
|
def add_to_master_date_range_list(current_titles_list):
|
||||||
|
book_count = len(current_titles_list)
|
||||||
|
current_titles_list = " • ".join(current_titles_list)
|
||||||
|
current_titles_list = self.formatNCXText(current_titles_list, dest='description')
|
||||||
|
master_date_range_list.append((current_titles_list, date_range, book_count))
|
||||||
|
|
||||||
|
soup = self.ncxSoup
|
||||||
|
HTML_file = "content/ByDateRead.html"
|
||||||
|
body = soup.find("navPoint")
|
||||||
|
btc = len(body.contents)
|
||||||
|
|
||||||
|
# --- Construct the 'Recently Read' *section* ---
|
||||||
|
navPointTag = Tag(soup, 'navPoint')
|
||||||
|
navPointTag['class'] = "section"
|
||||||
|
file_ID = "%s" % tocTitle.lower()
|
||||||
|
file_ID = file_ID.replace(" ","")
|
||||||
|
navPointTag['id'] = "%s-ID" % file_ID
|
||||||
|
navPointTag['playOrder'] = self.playOrder
|
||||||
|
self.playOrder += 1
|
||||||
|
navLabelTag = Tag(soup, 'navLabel')
|
||||||
|
textTag = Tag(soup, 'text')
|
||||||
|
textTag.insert(0, NavigableString('%s' % tocTitle))
|
||||||
|
navLabelTag.insert(0, textTag)
|
||||||
|
nptc = 0
|
||||||
|
navPointTag.insert(nptc, navLabelTag)
|
||||||
|
nptc += 1
|
||||||
|
contentTag = Tag(soup,"content")
|
||||||
|
contentTag['src'] = "%s#section_start" % HTML_file
|
||||||
|
navPointTag.insert(nptc, contentTag)
|
||||||
|
nptc += 1
|
||||||
|
|
||||||
|
# Create an NCX article entry for each date range
|
||||||
|
current_titles_list = []
|
||||||
|
master_date_range_list = []
|
||||||
|
today = datetime.datetime.now()
|
||||||
|
today_time = datetime.datetime(today.year, today.month, today.day)
|
||||||
|
for (i,date) in enumerate(self.DATE_RANGE):
|
||||||
|
if i:
|
||||||
|
date_range = '%d to %d days ago' % (self.DATE_RANGE[i-1], self.DATE_RANGE[i])
|
||||||
|
else:
|
||||||
|
date_range = 'Last %d days' % (self.DATE_RANGE[i])
|
||||||
|
date_range_limit = self.DATE_RANGE[i]
|
||||||
|
for book in self.booksByDateRead:
|
||||||
|
bookmark_time = datetime.datetime.utcfromtimestamp(book['bookmark_timestamp'])
|
||||||
|
if (today_time-bookmark_time).days <= date_range_limit:
|
||||||
|
#print "generateNCXByDateAdded: %s added %d days ago" % (book['title'], (today_time-book_time).days)
|
||||||
|
current_titles_list.append(book['title'])
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
if current_titles_list:
|
||||||
|
add_to_master_date_range_list(current_titles_list)
|
||||||
|
current_titles_list = [book['title']]
|
||||||
|
|
||||||
|
'''
|
||||||
|
# Add *article* entries for each populated date range
|
||||||
|
# master_date_range_list{}: [0]:titles list [1]:datestr
|
||||||
|
for books_by_date_range in master_date_range_list:
|
||||||
|
navPointByDateRangeTag = Tag(soup, 'navPoint')
|
||||||
|
navPointByDateRangeTag['class'] = "article"
|
||||||
|
navPointByDateRangeTag['id'] = "%s-ID" % books_by_date_range[1].replace(' ','')
|
||||||
|
navPointTag['playOrder'] = self.playOrder
|
||||||
|
self.playOrder += 1
|
||||||
|
navLabelTag = Tag(soup, 'navLabel')
|
||||||
|
textTag = Tag(soup, 'text')
|
||||||
|
textTag.insert(0, NavigableString(books_by_date_range[1]))
|
||||||
|
navLabelTag.insert(0, textTag)
|
||||||
|
navPointByDateRangeTag.insert(0,navLabelTag)
|
||||||
|
contentTag = Tag(soup, 'content')
|
||||||
|
contentTag['src'] = "%s#bdr_%s" % (HTML_file,
|
||||||
|
books_by_date_range[1].replace(' ',''))
|
||||||
|
|
||||||
|
navPointByDateRangeTag.insert(1,contentTag)
|
||||||
|
|
||||||
|
if self.generateForKindle:
|
||||||
|
cmTag = Tag(soup, '%s' % 'calibre:meta')
|
||||||
|
cmTag['name'] = "description"
|
||||||
|
cmTag.insert(0, NavigableString(books_by_date_range[0]))
|
||||||
|
navPointByDateRangeTag.insert(2, cmTag)
|
||||||
|
|
||||||
|
cmTag = Tag(soup, '%s' % 'calibre:meta')
|
||||||
|
cmTag['name'] = "author"
|
||||||
|
navStr = '%d titles' % books_by_date_range[2] if books_by_date_range[2] > 1 else \
|
||||||
|
'%d title' % books_by_date_range[2]
|
||||||
|
cmTag.insert(0, NavigableString(navStr))
|
||||||
|
navPointByDateRangeTag.insert(3, cmTag)
|
||||||
|
|
||||||
|
navPointTag.insert(nptc, navPointByDateRangeTag)
|
||||||
|
nptc += 1
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Create an NCX article entry for each populated day
|
||||||
|
# Loop over the booksByDate list, find start of each month,
|
||||||
|
# add description_preview_count titles
|
||||||
|
# master_month_list(list,date,count)
|
||||||
|
current_titles_list = []
|
||||||
|
master_day_list = []
|
||||||
|
current_date = datetime.datetime.utcfromtimestamp(self.booksByDateRead[0]['bookmark_timestamp'])
|
||||||
|
|
||||||
|
for book in self.booksByDateRead:
|
||||||
|
bookmark_time = datetime.datetime.utcfromtimestamp(book['bookmark_timestamp'])
|
||||||
|
if bookmark_time.day != current_date.day or \
|
||||||
|
bookmark_time.month != current_date.month or \
|
||||||
|
bookmark_time.year != current_date.year:
|
||||||
|
# Save the old lists
|
||||||
|
add_to_master_day_list(current_titles_list)
|
||||||
|
|
||||||
|
# Start the new list
|
||||||
|
current_date = datetime.datetime.utcfromtimestamp(book['bookmark_timestamp']).date()
|
||||||
|
current_titles_list = [book['title']]
|
||||||
|
else:
|
||||||
|
current_titles_list.append(book['title'])
|
||||||
|
|
||||||
|
# Add the last day list
|
||||||
|
add_to_master_day_list(current_titles_list)
|
||||||
|
|
||||||
|
# Add *article* entries for each populated day
|
||||||
|
# master_day_list{}: [0]:titles list [1]:date
|
||||||
|
for books_by_day in master_day_list:
|
||||||
|
datestr = strftime(u'%A, %B %d', books_by_day[1].timetuple())
|
||||||
|
navPointByDayTag = Tag(soup, 'navPoint')
|
||||||
|
navPointByDayTag['class'] = "article"
|
||||||
|
navPointByDayTag['id'] = "bdr_%s-%s-%sID" % (books_by_day[1].year,
|
||||||
|
books_by_day[1].month,
|
||||||
|
books_by_day[1].day )
|
||||||
|
navPointTag['playOrder'] = self.playOrder
|
||||||
|
self.playOrder += 1
|
||||||
|
navLabelTag = Tag(soup, 'navLabel')
|
||||||
|
textTag = Tag(soup, 'text')
|
||||||
|
textTag.insert(0, NavigableString(datestr))
|
||||||
|
navLabelTag.insert(0, textTag)
|
||||||
|
navPointByDayTag.insert(0,navLabelTag)
|
||||||
|
contentTag = Tag(soup, 'content')
|
||||||
|
contentTag['src'] = "%s#bdr_%s-%s-%s" % (HTML_file,
|
||||||
|
books_by_day[1].year,
|
||||||
|
books_by_day[1].month,
|
||||||
|
books_by_day[1].day)
|
||||||
|
|
||||||
|
navPointByDayTag.insert(1,contentTag)
|
||||||
|
|
||||||
|
if self.generateForKindle:
|
||||||
|
cmTag = Tag(soup, '%s' % 'calibre:meta')
|
||||||
|
cmTag['name'] = "description"
|
||||||
|
cmTag.insert(0, NavigableString(books_by_day[0]))
|
||||||
|
navPointByDayTag.insert(2, cmTag)
|
||||||
|
|
||||||
|
cmTag = Tag(soup, '%s' % 'calibre:meta')
|
||||||
|
cmTag['name'] = "author"
|
||||||
|
navStr = '%d titles' % books_by_day[2] if books_by_day[2] > 1 else \
|
||||||
|
'%d title' % books_by_day[2]
|
||||||
|
cmTag.insert(0, NavigableString(navStr))
|
||||||
|
navPointByDayTag.insert(3, cmTag)
|
||||||
|
|
||||||
|
navPointTag.insert(nptc, navPointByDayTag)
|
||||||
|
nptc += 1
|
||||||
|
|
||||||
|
# Add this section to the body
|
||||||
|
body.insert(btc, navPointTag)
|
||||||
|
btc += 1
|
||||||
|
self.ncxSoup = soup
|
||||||
|
|
||||||
def generateNCXByGenre(self, tocTitle):
|
def generateNCXByGenre(self, tocTitle):
|
||||||
# Create an NCX section for 'By Genre'
|
# Create an NCX section for 'By Genre'
|
||||||
# Add each genre as an article
|
# Add each genre as an article
|
||||||
@ -2789,7 +3337,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
self.thumbWidth = int(self.thumbWidth/2)
|
self.thumbWidth = int(self.thumbWidth/2)
|
||||||
self.thumbHeight = int(self.thumbHeight/2)
|
self.thumbHeight = int(self.thumbHeight/2)
|
||||||
break
|
break
|
||||||
if self.verbose:
|
if False and self.verbose:
|
||||||
self.opts.log(" DPI = %d; thumbnail dimensions: %d x %d" % \
|
self.opts.log(" DPI = %d; thumbnail dimensions: %d x %d" % \
|
||||||
(x.dpi, self.thumbWidth, self.thumbHeight))
|
(x.dpi, self.thumbWidth, self.thumbHeight))
|
||||||
|
|
||||||
@ -3428,12 +3976,14 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
|
|
||||||
# Add local options
|
# Add local options
|
||||||
opts.creator = "calibre"
|
opts.creator = "calibre"
|
||||||
|
opts.connected_kindle = False
|
||||||
|
|
||||||
# Finalize output_profile
|
# Finalize output_profile
|
||||||
op = opts.output_profile
|
op = opts.output_profile
|
||||||
if op is None:
|
if op is None:
|
||||||
op = 'default'
|
op = 'default'
|
||||||
if opts.connected_device['name'] and 'kindle' in opts.connected_device['name'].lower():
|
if opts.connected_device['name'] and 'kindle' in opts.connected_device['name'].lower():
|
||||||
|
opts.connected_kindle = True
|
||||||
if opts.connected_device['serial'] and opts.connected_device['serial'][:4] in ['B004','B005']:
|
if opts.connected_device['serial'] and opts.connected_device['serial'][:4] in ['B004','B005']:
|
||||||
op = "kindle_dx"
|
op = "kindle_dx"
|
||||||
else:
|
else:
|
||||||
@ -3463,13 +4013,14 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
(opts.connected_device['name'],
|
(opts.connected_device['name'],
|
||||||
opts.connected_device['serial'][0:4],
|
opts.connected_device['serial'][0:4],
|
||||||
'x' * (len(opts.connected_device['serial']) - 4)))
|
'x' * (len(opts.connected_device['serial']) - 4)))
|
||||||
build_log.append(u" save_template: '%s'" % opts.connected_device['save_template'])
|
for storage in opts.connected_device['storage']:
|
||||||
|
if storage:
|
||||||
|
build_log.append(u" mount point: %s" % storage)
|
||||||
else:
|
else:
|
||||||
build_log.append(u" connected_device: '%s'" % opts.connected_device['name'])
|
build_log.append(u" connected_device: '%s'" % opts.connected_device['name'])
|
||||||
for storage in opts.connected_device['storage']:
|
for storage in opts.connected_device['storage']:
|
||||||
if storage:
|
if storage:
|
||||||
build_log.append(u" mount point: %s" % storage)
|
build_log.append(u" mount point: %s" % storage)
|
||||||
build_log.append(u" save_template: '%s'" % opts.connected_device['save_template'])
|
|
||||||
|
|
||||||
opts_dict = vars(opts)
|
opts_dict = vars(opts)
|
||||||
if opts_dict['ids']:
|
if opts_dict['ids']:
|
||||||
@ -3489,8 +4040,8 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
keys.sort()
|
keys.sort()
|
||||||
build_log.append(" opts:")
|
build_log.append(" opts:")
|
||||||
for key in keys:
|
for key in keys:
|
||||||
if key in ['catalog_title','authorClip','descriptionClip','exclude_genre','exclude_tags',
|
if key in ['catalog_title','authorClip','connected_kindle','descriptionClip',
|
||||||
'note_tag','numbers_as_text','read_tag',
|
'exclude_genre','exclude_tags','note_tag','numbers_as_text','read_tag',
|
||||||
'search_text','sort_by','sort_descriptions_by_author','sync']:
|
'search_text','sort_by','sort_descriptions_by_author','sync']:
|
||||||
build_log.append(" %s: %s" % (key, opts_dict[key]))
|
build_log.append(" %s: %s" % (key, opts_dict[key]))
|
||||||
|
|
||||||
@ -3509,7 +4060,7 @@ class EPUB_MOBI(CatalogPlugin):
|
|||||||
catalog_source_built = catalog.buildSources()
|
catalog_source_built = catalog.buildSources()
|
||||||
if opts.verbose:
|
if opts.verbose:
|
||||||
if catalog_source_built:
|
if catalog_source_built:
|
||||||
log.info(" Finished catalog source generation\n")
|
log.info(" Completed catalog source generation\n")
|
||||||
else:
|
else:
|
||||||
log.warn(" No database hits with supplied criteria")
|
log.warn(" No database hits with supplied criteria")
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user