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;
|
||||
}
|
||||
|
||||
p.date_read {
|
||||
text-align:left;
|
||||
margin-top:0px;
|
||||
margin-bottom:0px;
|
||||
margin-left:6em;
|
||||
text-indent:-6em;
|
||||
}
|
||||
|
||||
hr.series_divider {
|
||||
width:50%;
|
||||
margin-left:1em;
|
||||
|
@ -789,7 +789,7 @@ class Device(DeviceConfig, DevicePlugin):
|
||||
'''
|
||||
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)
|
||||
extra_components = []
|
||||
|
||||
@ -848,7 +848,7 @@ class Device(DeviceConfig, DevicePlugin):
|
||||
filedir = os.path.dirname(filepath)
|
||||
|
||||
|
||||
if not os.path.exists(filedir):
|
||||
if create_dirs and not os.path.exists(filedir):
|
||||
os.makedirs(filedir)
|
||||
|
||||
return filepath
|
||||
|
@ -571,7 +571,9 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
self.__authorClip = opts.authorClip
|
||||
self.__authors = None
|
||||
self.__basename = opts.basename
|
||||
self.__bookmarked_books = None
|
||||
self.__booksByAuthor = None
|
||||
self.__booksByDateRead = None
|
||||
self.__booksByTitle = None
|
||||
self.__booksByTitle_noSeriesPrefix = None
|
||||
self.__catalogPath = PersistentTemporaryDirectory("_epub_mobi_catalog", prefix='')
|
||||
@ -603,11 +605,13 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
self.__useSeriesPrefixInTitlesSection = False
|
||||
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:
|
||||
self.__totalSteps += 2
|
||||
if self.opts.generate_recently_added:
|
||||
self.__totalSteps += 2
|
||||
if self.opts.connected_kindle:
|
||||
self.__totalSteps += 2
|
||||
|
||||
# Accessors
|
||||
'''
|
||||
@ -642,6 +646,13 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
self.__basename = val
|
||||
return property(fget=fget, fset=fset)
|
||||
@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 fget(self):
|
||||
return self.__booksByAuthor
|
||||
@ -649,6 +660,13 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
self.__booksByAuthor = val
|
||||
return property(fget=fget, fset=fset)
|
||||
@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 fget(self):
|
||||
return self.__booksByTitle
|
||||
@ -869,6 +887,16 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
def fget(self):
|
||||
return "☆" if self.generateForKindle else ' '
|
||||
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
|
||||
def buildSources(self):
|
||||
@ -876,12 +904,14 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
if not self.fetchBooksByTitle():
|
||||
return False
|
||||
self.fetchBooksByAuthor()
|
||||
self.fetchBookmarks()
|
||||
self.generateHTMLDescriptions()
|
||||
self.generateHTMLByAuthor()
|
||||
if self.opts.generate_titles:
|
||||
self.generateHTMLByTitle()
|
||||
if self.opts.generate_recently_added:
|
||||
self.generateHTMLByDateAdded()
|
||||
self.generateHTMLByDateRead()
|
||||
self.generateHTMLByTags()
|
||||
|
||||
from calibre.utils.PythonMagickWand import ImageMagick
|
||||
@ -896,6 +926,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
self.generateNCXByTitle("Titles")
|
||||
if self.opts.generate_recently_added:
|
||||
self.generateNCXByDateAdded("Recently Added")
|
||||
self.generateNCXByDateRead("Recently Read")
|
||||
self.generateNCXByGenre("Genres")
|
||||
self.writeNCX()
|
||||
return True
|
||||
@ -980,7 +1011,9 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
this_title['series_index'] = 0.0
|
||||
|
||||
this_title['title_sort'] = self.generateSortTitle(this_title['title'])
|
||||
if 'authors' in record and record['authors']:
|
||||
if 'authors' in record:
|
||||
this_title['authors'] = record['authors']
|
||||
if record['authors']:
|
||||
this_title['author'] = " & ".join(record['authors'])
|
||||
else:
|
||||
this_title['author'] = 'Unknown'
|
||||
@ -1119,6 +1152,159 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
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):
|
||||
# Write each title to a separate HTML file in contentdir
|
||||
self.updateProgressFullStep("'Descriptions'")
|
||||
@ -1160,13 +1346,18 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
titleTag = body.find(attrs={'class':'title'})
|
||||
titleTag.insert(0,emTag)
|
||||
|
||||
# Insert the author
|
||||
# Create the author anchor
|
||||
authorTag = body.find(attrs={'class':'author'})
|
||||
aTag = Tag(soup, "a")
|
||||
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(title['author']))
|
||||
#aTag.insert(0, escape(title['author']))
|
||||
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor",
|
||||
self.generateAuthorAnchor(title['author']))
|
||||
aTag.insert(0, title['author'])
|
||||
|
||||
# This will include the reading progress dots even if we're not generating Recently Read
|
||||
if self.opts.connected_kindle and title['id'] in self.bookmarked_books:
|
||||
authorTag.insert(0, NavigableString(title['reading_progress'] + " by "))
|
||||
authorTag.insert(1, aTag)
|
||||
else:
|
||||
# Insert READ_SYMBOL
|
||||
if title['read']:
|
||||
authorTag.insert(0, NavigableString(self.READ_SYMBOL + "by "))
|
||||
@ -1174,15 +1365,6 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
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.
|
||||
seriesTag = body.find(attrs={'class':'series'})
|
||||
@ -1789,6 +1971,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
divTag.insert(dtc,hrTag)
|
||||
dtc += 1
|
||||
|
||||
# >>>> Books by month <<<<
|
||||
# Sort titles case-insensitive for by month using series prefix
|
||||
self.booksByMonth = sorted(self.booksByTitle,
|
||||
key=lambda x:(x['timestamp'], x['timestamp']),reverse=True)
|
||||
@ -1817,6 +2000,192 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
outfile.close()
|
||||
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):
|
||||
# Generate individual HTML files for each tag, e.g. Fiction, Nonfiction ...
|
||||
# Note that special tags - ~+*[] - have already been filtered from books[]
|
||||
@ -2229,8 +2598,16 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
else:
|
||||
if self.generateForKindle:
|
||||
# Don't include Author for Kindle
|
||||
textTag.insert(0, NavigableString(self.formatNCXText('%s' % (book['title']),
|
||||
dest='title')))
|
||||
title_str = self.formatNCXText('%s' % (book['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:
|
||||
# Include Author for non-Kindle
|
||||
textTag.insert(0, NavigableString(self.formatNCXText('%s · %s' % \
|
||||
@ -2616,6 +2993,177 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
btc += 1
|
||||
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):
|
||||
# Create an NCX section for 'By Genre'
|
||||
# Add each genre as an article
|
||||
@ -2789,7 +3337,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
self.thumbWidth = int(self.thumbWidth/2)
|
||||
self.thumbHeight = int(self.thumbHeight/2)
|
||||
break
|
||||
if self.verbose:
|
||||
if False and self.verbose:
|
||||
self.opts.log(" DPI = %d; thumbnail dimensions: %d x %d" % \
|
||||
(x.dpi, self.thumbWidth, self.thumbHeight))
|
||||
|
||||
@ -3428,12 +3976,14 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
|
||||
# Add local options
|
||||
opts.creator = "calibre"
|
||||
opts.connected_kindle = False
|
||||
|
||||
# Finalize output_profile
|
||||
op = opts.output_profile
|
||||
if op is None:
|
||||
op = 'default'
|
||||
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']:
|
||||
op = "kindle_dx"
|
||||
else:
|
||||
@ -3463,13 +4013,14 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
(opts.connected_device['name'],
|
||||
opts.connected_device['serial'][0: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:
|
||||
build_log.append(u" connected_device: '%s'" % opts.connected_device['name'])
|
||||
for storage in opts.connected_device['storage']:
|
||||
if 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)
|
||||
if opts_dict['ids']:
|
||||
@ -3489,8 +4040,8 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
keys.sort()
|
||||
build_log.append(" opts:")
|
||||
for key in keys:
|
||||
if key in ['catalog_title','authorClip','descriptionClip','exclude_genre','exclude_tags',
|
||||
'note_tag','numbers_as_text','read_tag',
|
||||
if key in ['catalog_title','authorClip','connected_kindle','descriptionClip',
|
||||
'exclude_genre','exclude_tags','note_tag','numbers_as_text','read_tag',
|
||||
'search_text','sort_by','sort_descriptions_by_author','sync']:
|
||||
build_log.append(" %s: %s" % (key, opts_dict[key]))
|
||||
|
||||
@ -3509,7 +4060,7 @@ class EPUB_MOBI(CatalogPlugin):
|
||||
catalog_source_built = catalog.buildSources()
|
||||
if opts.verbose:
|
||||
if catalog_source_built:
|
||||
log.info(" Finished catalog source generation\n")
|
||||
log.info(" Completed catalog source generation\n")
|
||||
else:
|
||||
log.warn(" No database hits with supplied criteria")
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user