diff --git a/calibre-plugin/config.py b/calibre-plugin/config.py index 28238481..c87bf0cc 100644 --- a/calibre-plugin/config.py +++ b/calibre-plugin/config.py @@ -55,6 +55,9 @@ try: except NameError: pass # load_translations() added in calibre 1.9 +from calibre.library.field_metadata import FieldMetadata +field_metadata = FieldMetadata() + # There are a number of things used several times that shouldn't be # translated. This is just a way to make that easier by keeping them # out of the _() strings. @@ -646,6 +649,11 @@ class PersonalIniTab(QWidget): label.setWordWrap(True) self.l.addWidget(label) + self.showcalcols = QPushButton(_('Show Calibre Column Names'), self) + self.showcalcols.setToolTip(_("FanFicFare passes the Calibre columns into the download/update process. This will show you the columns available by name.")) + self.showcalcols.clicked.connect(self.show_showcalcols) + self.l.addWidget(self.showcalcols) + self.l.insertStretch(-1) # let edit box fill the space. @@ -671,6 +679,26 @@ class PersonalIniTab(QWidget): if d.result() == d.Accepted: self.personalini = d.get_plain_text() + def show_showcalcols(self): + lines=[] + for k,f in field_metadata.iteritems(): + if f['name']: # only if it has a human readable name. + lines.append(('calibre_std_'+k,f['name'])) + + for k, column in self.plugin_action.gui.library_view.model().custom_columns.iteritems(): + # custom always have name. + lines.append(('calibre_cust_'+k[1:],column['name'])) + + lines.sort() # sort by key. + + EditTextDialog(self, + '\n'.join(['%s (%s)'%(l,k) for (k,l) in lines]), + icon=self.windowIcon(), + title=_('Calibre Column Entry Names'), + label=_('Label (entry_name)'), + read_only=True, + save_size_name='fff:showcalcols').exec_() + class ReadingListTab(QWidget): def __init__(self, parent_dialog, plugin_action): diff --git a/calibre-plugin/dialogs.py b/calibre-plugin/dialogs.py index b94758ae..49431055 100644 --- a/calibre-plugin/dialogs.py +++ b/calibre-plugin/dialogs.py @@ -1132,6 +1132,7 @@ class EditTextDialog(SizePersistedDialog): def __init__(self, parent, text, icon=None, title=None, label=None, tooltip=None, + read_only=False, rejectreasons=[],reasonslabel=None, save_size_name='fff:edit text dialog', ): @@ -1148,6 +1149,7 @@ class EditTextDialog(SizePersistedDialog): self.textedit = QTextEdit(self) self.textedit.setLineWrapMode(QTextEdit.NoWrap) + self.textedit.setReadOnly(read_only) self.textedit.setText(text) self.l.addWidget(self.textedit) diff --git a/calibre-plugin/fff_plugin.py b/calibre-plugin/fff_plugin.py index dad7ccb4..80f04d2a 100644 --- a/calibre-plugin/fff_plugin.py +++ b/calibre-plugin/fff_plugin.py @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__) import time, os, copy, threading, re, platform, sys from StringIO import StringIO from functools import partial -from datetime import datetime, time +from datetime import datetime, time, date from string import Template import urllib import email @@ -55,6 +55,9 @@ try: except: HAS_CALGC=False +from calibre.library.field_metadata import FieldMetadata +field_metadata = FieldMetadata() + from calibre_plugins.fanficfare_plugin.common_utils import ( set_plugin_icon_resources, get_icon, create_menu_action_unique, get_library_uuid) @@ -64,7 +67,7 @@ from calibre_plugins.fanficfare_plugin.fanficfare import ( from calibre_plugins.fanficfare_plugin.fanficfare.epubutils import ( get_dcsource, get_dcsource_chaptercount, get_story_url_from_html, - get_epub_metadata) + get_epub_metadatas) from calibre_plugins.fanficfare_plugin.fanficfare.geturls import ( get_urls_from_page, get_urls_from_html,get_urls_from_text, @@ -904,8 +907,8 @@ class FanFicFarePlugin(InterfaceAction): if 1==0 and collision in (CALIBREONLY) and \ fileform == 'epub' and \ db.has_format(book['calibre_id'],'EPUB',index_is_id=True): - adapter.setStoryMetadata(get_epub_metadata(StringIO(db.format(book['calibre_id'],'EPUB', - index_is_id=True)))) + adapter.setStoryMetadata(get_epub_metadatas(StringIO(db.format(book['calibre_id'],'EPUB', + index_is_id=True)))) # let other exceptions percolate up. story = adapter.getStoryMetadataOnly(get_cover=False) else: @@ -970,18 +973,40 @@ class FanFicFarePlugin(InterfaceAction): book['status'] = _('Skipped') return - ## if existing book, populate existing custom column values in + ## if existing book, populate existing calibre column values in ## metadata. if 'calibre_id' in book: book['calibre_columns']={} - for key, column in self.gui.library_view.model().custom_columns.iteritems(): - val = db.get_custom(book['calibre_id'], + # std columns + mi = db.get_metadata(book['calibre_id'],index_is_id=True) + # book['calibre_columns']['calibre_std_identifiers']=\ + # {'val':', '.join(["%s:%s"%(k,v) for (k,v) in mi.get_identifiers().iteritems()]), + # 'label':_('Ids')} + for k in mi.standard_field_keys(): + # for k in mi: + (label,value,v,fmd) = mi.format_field_extended(k) + if not label and k in field_metadata: + label=field_metadata[k]['name'] + key='calibre_std_'+k + + if k == 'user_categories': + value=u', '.join(mi.get(k)) + label=_('User Categories') + + if label: # only if it has a human readable name. + book['calibre_columns'][key]={'val':value,'label':label} + logger.debug("%s(%s): %s"%(label,key,value)) + + # custom columns + for k, column in self.gui.library_view.model().custom_columns.iteritems(): + key='calibre_cust_'+k[1:] + label=column['name'] + value=db.get_custom(book['calibre_id'], label=column['label'], index_is_id=True) - if val: - #print("(%s)->(%s)"%('calibre.'+key,val)) - # name: calibre.cust.namewithouthash - book['calibre_columns']['calibre_cust_'+key[1:]]={'val':val,'label':column['name']} + # custom always have name. + book['calibre_columns'][key]={'val':value,'label':label} + logger.debug("%s(%s): %s"%(label,key,value)) ################################################################################################################################################33 diff --git a/fanficfare/adapters/base_adapter.py b/fanficfare/adapters/base_adapter.py index edcb4559..9968ce6e 100644 --- a/fanficfare/adapters/base_adapter.py +++ b/fanficfare/adapters/base_adapter.py @@ -404,11 +404,12 @@ class BaseSiteAdapter(Configurable): self.metadataDone = True return self.story - def setStoryMetadata(self,metadata): - self.story.metadata = metadata - self.metadataDone = True - if not self.story.getMetadataRaw('dateUpdated'): - self.story.setMetadata('dateUpdated',self.story.getMetadataRaw('datePublished')) + def setStoryMetadata(self,metadatas): + if metadatas: + self.story.loads_metadata(metadatas) + self.metadataDone = True + if not self.story.getMetadataRaw('dateUpdated'): + self.story.setMetadata('dateUpdated',self.story.getMetadataRaw('datePublished')) def hookForUpdates(self,chaptercount): "Usually not needed." diff --git a/fanficfare/epubutils.py b/fanficfare/epubutils.py index c5560a8e..e87c63de 100644 --- a/fanficfare/epubutils.py +++ b/fanficfare/epubutils.py @@ -20,7 +20,7 @@ import bs4 as bs UpdateData = namedtuple('UpdateData', 'source filecount soups images oldcover ' - 'calibrebookmark logfile metadata') + 'calibrebookmark logfile metadatas') def get_dcsource(inputio): return get_update_data(inputio,getfilecount=False,getsoups=False).source @@ -29,8 +29,8 @@ def get_dcsource_chaptercount(inputio): nt = get_update_data(inputio,getfilecount=True,getsoups=False) return (nt.source,nt.filecount) -def get_epub_metadata(inputio): - return get_update_data(inputio,getfilecount=False,getsoups=False).metadata +def get_epub_metadatas(inputio): + return get_update_data(inputio,getfilecount=False,getsoups=False).metadatas def get_update_data(inputio, getfilecount=True, @@ -156,13 +156,12 @@ def get_update_data(inputio, except: pass - - metadata = {} + metadatas = None try: for meta in firstmetadom.getElementsByTagName("meta"): if meta.getAttribute("name")=="fanficfare:story_metadata": #print("meta.getAttribute(content):%s"%meta.getAttribute("content")) - metadata=jsonloads(meta.getAttribute("content")) + metadatas=meta.getAttribute("content") except Exception as e: pass # logger.info("metadata %s not found") @@ -172,7 +171,7 @@ def get_update_data(inputio, #for k in images.keys(): #print("\tlongdesc:%s\n\tData len:%s\n"%(k,len(images[k]))) return UpdateData(source,filecount,soups,images,oldcover, - calibrebookmark,logfile,metadata) + calibrebookmark,logfile,metadatas) def get_path_part(n): relpath = os.path.dirname(n) @@ -217,36 +216,3 @@ def get_story_url_from_html(inputio,_is_good_url=None): return ahref return None -## why json doesn't define a date/time format is beyond me... -def datetime_decoder(d): - if isinstance(d, list): - pairs = enumerate(d) - elif isinstance(d, dict): - pairs = d.items() - result = [] - for k,v in pairs: - if isinstance(v, basestring): - try: - # The %f format code is only supported in Python >= 2.6. - # For Python <= 2.5 strip off microseconds - # v = datetime.datetime.strptime(v.rsplit('.', 1)[0], - # '%Y-%m-%dT%H:%M:%S') - v = datetime.datetime.strptime(v, '%Y-%m-%dT%H:%M:%S.%f') - except ValueError: - try: - v = datetime.datetime.strptime(v, '%Y-%m-%dT%H:%M:%S') - except ValueError: - try: - v = datetime.datetime.strptime(v, '%Y-%m-%d') - except ValueError: - pass - elif isinstance(v, (dict, list)): - v = datetime_decoder(v) - result.append((k, v)) - if isinstance(d, list): - return [x[1] for x in result] - elif isinstance(d, dict): - return dict(result) - -def jsonloads(obj): - return json.loads(obj, object_hook=datetime_decoder) diff --git a/fanficfare/story.py b/fanficfare/story.py index 48821289..1967686c 100644 --- a/fanficfare/story.py +++ b/fanficfare/story.py @@ -18,6 +18,8 @@ import os, re import urlparse import string +import json +import datetime from math import floor from functools import partial import logging @@ -544,6 +546,18 @@ class Story(Configurable): else: return self.join_list(key,retlist) + # for saving a string-ified copy of metadata. + def dumps_metadata(self): + md = {} + for k,v in self.metadata.iteritems(): + if not k.startswith('calibre_'): # don't include items passed in for calibre cols. + md[k]=v + return json.dumps(md, default=datetime_encoder) + + # for loading a string-ified copy of metadata. + def loads_metadata(self,s): + self.metadata = json.loads(s, object_hook=datetime_decoder) + def getMetadataRaw(self,key): if self.isValidMetaEntry(key) and self.metadata.has_key(key): return self.metadata[key] @@ -930,3 +944,39 @@ def commaGroups(s): s = s[:-3] return s + ','.join(reversed(groups)) +## why json doesn't define a date/time format is beyond me... +## Also need to decode when reading back in. +def datetime_encoder(o): + if isinstance(o, (datetime.date, datetime.datetime, datetime.time)): + return o.isoformat() + +## why json doesn't define a date/time format is beyond me... +def datetime_decoder(d): + if isinstance(d, list): + pairs = enumerate(d) + elif isinstance(d, dict): + pairs = d.items() + result = [] + for k,v in pairs: + if isinstance(v, basestring): + try: + # The %f format code is only supported in Python >= 2.6. + # For Python <= 2.5 strip off microseconds + # v = datetime.datetime.strptime(v.rsplit('.', 1)[0], + # '%Y-%m-%dT%H:%M:%S') + v = datetime.datetime.strptime(v, '%Y-%m-%dT%H:%M:%S.%f') + except ValueError: + try: + v = datetime.datetime.strptime(v, '%Y-%m-%dT%H:%M:%S') + except ValueError: + try: + v = datetime.datetime.strptime(v, '%Y-%m-%d') + except ValueError: + pass + elif isinstance(v, (dict, list)): + v = datetime_decoder(v) + result.append((k, v)) + if isinstance(d, list): + return [x[1] for x in result] + elif isinstance(d, dict): + return dict(result) diff --git a/fanficfare/writers/writer_epub.py b/fanficfare/writers/writer_epub.py index e6df3655..b0b8315b 100644 --- a/fanficfare/writers/writer_epub.py +++ b/fanficfare/writers/writer_epub.py @@ -22,8 +22,6 @@ import zipfile from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED import urllib import re -import json -import datetime ## XML isn't as forgiving as HTML, so rather than generate as strings, ## use DOM to generate the XML files. @@ -494,7 +492,7 @@ div { margin: 0pt; padding: 0pt; } ## save a copy of *all*raw* metadata for future use metadata.appendChild(newTag(contentdom,"meta", attrs={"name":"fanficfare:story_metadata", - "content":jsondumps(self.story.metadata)})) + "content":self.story.dumps_metadata()})) if self.getConfig("include_titlepage"): items.append(("title_page","OEBPS/title_page.xhtml","application/xhtml+xml","Title Page")) @@ -697,12 +695,3 @@ def newTag(dom,name,attrs=None,text=None): if( text is not None ): tag.appendChild(dom.createTextNode(text)) return tag - -## why json doesn't define a date/time format is beyond me... -## Also need to decode when reading back in. -def datetime_encoder(o): - if isinstance(o, (datetime.date, datetime.datetime, datetime.time)): - return o.isoformat() - -def jsondumps(o): - return json.dumps(o, default=datetime_encoder)