mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Merge from trunk
This commit is contained in:
commit
197e16e62a
BIN
resources/images/news/boortz.png
Normal file
BIN
resources/images/news/boortz.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 652 B |
43
resources/recipes/boortz.recipe
Normal file
43
resources/recipes/boortz.recipe
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
class AdvancedUserRecipe1282101454(BasicNewsRecipe):
|
||||||
|
title = 'Nealz Nuze'
|
||||||
|
language = 'en'
|
||||||
|
__author__ = 'TonytheBookworm'
|
||||||
|
description = 'Neal Boortz Show Radio Notes'
|
||||||
|
publisher = 'Neal Boortz'
|
||||||
|
category = 'news, politics, USA, talkshow'
|
||||||
|
oldest_article = 1
|
||||||
|
max_articles_per_feed = 100
|
||||||
|
linearize_tables = True
|
||||||
|
no_stylesheets = True
|
||||||
|
remove_javascript = True
|
||||||
|
|
||||||
|
masthead_url = 'http://boortz.com/images/nuze_logo.gif'
|
||||||
|
keep_only_tags = [
|
||||||
|
dict(name='div', attrs={'id':['SiteContent']})
|
||||||
|
#,dict(attrs={'id':['cxArticleText']})
|
||||||
|
]
|
||||||
|
remove_tags = [
|
||||||
|
dict(name='a', attrs={'class':['blogPermalink']}),
|
||||||
|
dict(name='span', attrs={'class':['blogBylineSeparator']}),
|
||||||
|
dict(name='td', attrs={'id':['nealztitle']}),
|
||||||
|
]
|
||||||
|
remove_tags_after = [dict(name='div', attrs={'class':'blogEntryBody'}),]
|
||||||
|
feeds = [
|
||||||
|
('NUZE', 'http://boortz.com/nealz_nuze_rss/rss.xml')
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -19,6 +19,10 @@ class Book(Book_):
|
|||||||
self.authors = ['']
|
self.authors = ['']
|
||||||
else:
|
else:
|
||||||
self.authors = [authors]
|
self.authors = [authors]
|
||||||
|
|
||||||
|
if not title:
|
||||||
|
self.title = _('Unknown')
|
||||||
|
|
||||||
self.mime = mime
|
self.mime = mime
|
||||||
|
|
||||||
self.size = size # will be set later if None
|
self.size = size # will be set later if None
|
||||||
|
@ -5,7 +5,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import copy, re, traceback
|
import copy, traceback
|
||||||
|
|
||||||
from calibre import prints
|
from calibre import prints
|
||||||
from calibre.ebooks.metadata.book import SC_COPYABLE_FIELDS
|
from calibre.ebooks.metadata.book import SC_COPYABLE_FIELDS
|
||||||
|
@ -1696,11 +1696,12 @@ class MobiWriter(object):
|
|||||||
header.write(pack('>I', 1))
|
header.write(pack('>I', 1))
|
||||||
|
|
||||||
# 0x1c - 0x1f : Text encoding ?
|
# 0x1c - 0x1f : Text encoding ?
|
||||||
# GR: Language encoding for NCX entries (latin_1)
|
# header.write(pack('>I', 650001))
|
||||||
header.write(pack('>I', 0x4e4))
|
# GR: This needs to be either 0xFDE9 or 0x4E4
|
||||||
|
header.write(pack('>I', 0xFDE9))
|
||||||
|
|
||||||
# 0x20 - 0x23 : Mimicking kindleGen
|
# 0x20 - 0x23 : Language code?
|
||||||
header.write(pack('>I', 0xFFFFFFFF))
|
header.write(iana2mobi(str(self._oeb.metadata.language[0])))
|
||||||
|
|
||||||
# 0x24 - 0x27 : Number of TOC entries in INDX1
|
# 0x24 - 0x27 : Number of TOC entries in INDX1
|
||||||
header.write(pack('>I', indxt_count + 1))
|
header.write(pack('>I', indxt_count + 1))
|
||||||
@ -1800,7 +1801,7 @@ class MobiWriter(object):
|
|||||||
text = text.strip()
|
text = text.strip()
|
||||||
if not isinstance(text, unicode):
|
if not isinstance(text, unicode):
|
||||||
text = text.decode('utf-8', 'replace')
|
text = text.decode('utf-8', 'replace')
|
||||||
text = text.encode('cp1252','replace')
|
text = text.encode('ascii','replace')
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def _add_to_ctoc(self, ctoc_str, record_offset):
|
def _add_to_ctoc(self, ctoc_str, record_offset):
|
||||||
@ -2150,26 +2151,6 @@ class MobiWriter(object):
|
|||||||
indxt.write(decint(self._ctoc_map[index]['titleOffset'], DECINT_FORWARD)) # vwi title offset in CNCX
|
indxt.write(decint(self._ctoc_map[index]['titleOffset'], DECINT_FORWARD)) # vwi title offset in CNCX
|
||||||
indxt.write(decint(0, DECINT_FORWARD)) # unknown byte
|
indxt.write(decint(0, DECINT_FORWARD)) # unknown byte
|
||||||
|
|
||||||
def _write_subchapter_node(self, indxt, indices, index, offset, length, count):
|
|
||||||
# This style works without a parent chapter, mimicking what KindleGen does,
|
|
||||||
# using a value of 0x0B for parentIndex
|
|
||||||
# Writes an INDX1 NCXEntry of entryType 0x1F - subchapter
|
|
||||||
if self.opts.verbose > 2:
|
|
||||||
# *** GR: Turn this off while I'm developing my code
|
|
||||||
#self._oeb.log.debug('Writing TOC node to IDXT:', node.title, 'href:', node.href)
|
|
||||||
pass
|
|
||||||
|
|
||||||
pos = 0xc0 + indxt.tell()
|
|
||||||
indices.write(pack('>H', pos)) # Save the offset for IDXTIndices
|
|
||||||
name = "%04X"%count
|
|
||||||
indxt.write(chr(len(name)) + name) # Write the name
|
|
||||||
indxt.write(INDXT['subchapter']) # entryType [0x0F | 0xDF | 0xFF | 0x3F]
|
|
||||||
indxt.write(decint(offset, DECINT_FORWARD)) # offset
|
|
||||||
indxt.write(decint(length, DECINT_FORWARD)) # length
|
|
||||||
indxt.write(decint(self._ctoc_map[index]['titleOffset'], DECINT_FORWARD)) # vwi title offset in CNCX
|
|
||||||
indxt.write(decint(0, DECINT_FORWARD)) # unknown byte
|
|
||||||
indxt.write(decint(0xb, DECINT_FORWARD)) # parentIndex - null
|
|
||||||
|
|
||||||
def _compute_offset_length(self, i, node, entries) :
|
def _compute_offset_length(self, i, node, entries) :
|
||||||
h = node.href
|
h = node.href
|
||||||
if h not in self._id_offsets:
|
if h not in self._id_offsets:
|
||||||
|
@ -81,7 +81,6 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
|
|||||||
return self.opt_use_author_sort.isChecked()
|
return self.opt_use_author_sort.isChecked()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
print 'here in validate'
|
|
||||||
tmpl = unicode(self.opt_save_template.text())
|
tmpl = unicode(self.opt_save_template.text())
|
||||||
try:
|
try:
|
||||||
validation_formatter.validate(tmpl)
|
validation_formatter.validate(tmpl)
|
||||||
@ -89,6 +88,6 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
|
|||||||
except Exception, err:
|
except Exception, err:
|
||||||
error_dialog(self, _('Invalid template'),
|
error_dialog(self, _('Invalid template'),
|
||||||
'<p>'+_('The template %s is invalid:')%tmpl + \
|
'<p>'+_('The template %s is invalid:')%tmpl + \
|
||||||
'<br>'+str(err), show=True)
|
'<br>'+unicode(err), show=True)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
@ -304,6 +304,8 @@ class CcBoolDelegate(QStyledItemDelegate): # {{{
|
|||||||
val = 2 if val is None else 1 if not val else 0
|
val = 2 if val is None else 1 if not val else 0
|
||||||
editor.setCurrentIndex(val)
|
editor.setCurrentIndex(val)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
class CcTemplateDelegate(QStyledItemDelegate): # {{{
|
class CcTemplateDelegate(QStyledItemDelegate): # {{{
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
'''
|
'''
|
||||||
|
@ -21,7 +21,7 @@ from calibre.utils.date import dt_factory, qt_to_dt, isoformat
|
|||||||
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
|
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
|
||||||
from calibre.utils.search_query_parser import SearchQueryParser
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
|
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
|
||||||
REGEXP_MATCH, CoverCache
|
REGEXP_MATCH, CoverCache, MetadataBackup
|
||||||
from calibre.library.cli import parse_series_string
|
from calibre.library.cli import parse_series_string
|
||||||
from calibre import strftime, isbytestring, prepare_string_for_xml
|
from calibre import strftime, isbytestring, prepare_string_for_xml
|
||||||
from calibre.constants import filesystem_encoding
|
from calibre.constants import filesystem_encoding
|
||||||
@ -153,6 +153,9 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
self.cover_cache.stop()
|
self.cover_cache.stop()
|
||||||
self.cover_cache = CoverCache(db, FunctionDispatcher(self.db.cover))
|
self.cover_cache = CoverCache(db, FunctionDispatcher(self.db.cover))
|
||||||
self.cover_cache.start()
|
self.cover_cache.start()
|
||||||
|
self.metadata_backup = MetadataBackup(db,
|
||||||
|
FunctionDispatcher(self.db.dump_metadata))
|
||||||
|
self.metadata_backup.start()
|
||||||
def refresh_cover(event, ids):
|
def refresh_cover(event, ids):
|
||||||
if event == 'cover' and self.cover_cache is not None:
|
if event == 'cover' and self.cover_cache is not None:
|
||||||
self.cover_cache.refresh(ids)
|
self.cover_cache.refresh(ids)
|
||||||
|
@ -6,8 +6,6 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import string
|
|
||||||
|
|
||||||
from PyQt4.Qt import QWidget, pyqtSignal
|
from PyQt4.Qt import QWidget, pyqtSignal
|
||||||
|
|
||||||
from calibre.gui2 import error_dialog
|
from calibre.gui2 import error_dialog
|
||||||
|
@ -551,6 +551,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
|
|||||||
cc = self.library_view.model().cover_cache
|
cc = self.library_view.model().cover_cache
|
||||||
if cc is not None:
|
if cc is not None:
|
||||||
cc.stop()
|
cc.stop()
|
||||||
|
mb = self.library_view.model().metadata_backup
|
||||||
|
if mb is not None:
|
||||||
|
mb.stop()
|
||||||
|
|
||||||
self.hide_windows()
|
self.hide_windows()
|
||||||
self.emailer.stop()
|
self.emailer.stop()
|
||||||
try:
|
try:
|
||||||
|
@ -19,9 +19,39 @@ from calibre.utils.date import parse_date, now, UNDEFINED_DATE
|
|||||||
from calibre.utils.search_query_parser import SearchQueryParser
|
from calibre.utils.search_query_parser import SearchQueryParser
|
||||||
from calibre.utils.pyparsing import ParseException
|
from calibre.utils.pyparsing import ParseException
|
||||||
from calibre.ebooks.metadata import title_sort
|
from calibre.ebooks.metadata import title_sort
|
||||||
from calibre import fit_image
|
from calibre import fit_image, prints
|
||||||
|
|
||||||
class CoverCache(Thread):
|
class MetadataBackup(Thread): # {{{
|
||||||
|
|
||||||
|
def __init__(self, db, dump_func):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.daemon = True
|
||||||
|
self.db = db
|
||||||
|
self.dump_func = dump_func
|
||||||
|
self.keep_running = True
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.keep_running = False
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while self.keep_running:
|
||||||
|
try:
|
||||||
|
id_ = self.db.dirtied_queue.get(True, 5)
|
||||||
|
except Empty:
|
||||||
|
continue
|
||||||
|
except:
|
||||||
|
# Happens during interpreter shutdown
|
||||||
|
break
|
||||||
|
if self.dump_func([id_]) is None:
|
||||||
|
# An exception occured in dump_func, retry once
|
||||||
|
prints('Failed to backup metadata for id:', id_, 'once')
|
||||||
|
time.sleep(2)
|
||||||
|
if not self.dump_func([id_]):
|
||||||
|
prints('Failed to backup metadata for id:', id_, 'again, giving up')
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
class CoverCache(Thread): # {{{
|
||||||
|
|
||||||
def __init__(self, db, cover_func):
|
def __init__(self, db, cover_func):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
@ -90,6 +120,7 @@ class CoverCache(Thread):
|
|||||||
for id_ in ids:
|
for id_ in ids:
|
||||||
self.cache.pop(id_, None)
|
self.cache.pop(id_, None)
|
||||||
self.load_queue.put(id_)
|
self.load_queue.put(id_)
|
||||||
|
# }}}
|
||||||
|
|
||||||
### Global utility function for get_match here and in gui2/library.py
|
### Global utility function for get_match here and in gui2/library.py
|
||||||
CONTAINS_MATCH = 0
|
CONTAINS_MATCH = 0
|
||||||
@ -107,7 +138,7 @@ def _match(query, value, matchkind):
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class ResultCache(SearchQueryParser):
|
class ResultCache(SearchQueryParser): # {{{
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Stores sorted and filtered metadata in memory.
|
Stores sorted and filtered metadata in memory.
|
||||||
@ -694,4 +725,5 @@ class SortKeyGenerator(object):
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
@ -382,6 +382,7 @@ class CustomColumns(object):
|
|||||||
)
|
)
|
||||||
# get rid of the temp tables
|
# get rid of the temp tables
|
||||||
self.conn.executescript(drops)
|
self.conn.executescript(drops)
|
||||||
|
self.dirtied(ids, commit=False)
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
|
||||||
# set the in-memory copies of the tags
|
# set the in-memory copies of the tags
|
||||||
@ -402,19 +403,21 @@ class CustomColumns(object):
|
|||||||
same length as ids.
|
same length as ids.
|
||||||
'''
|
'''
|
||||||
if extras is not None and len(extras) != len(ids):
|
if extras is not None and len(extras) != len(ids):
|
||||||
raise ValueError('Lentgh of ids and extras is not the same')
|
raise ValueError('Length of ids and extras is not the same')
|
||||||
ev = None
|
ev = None
|
||||||
for idx,id in enumerate(ids):
|
for idx,id in enumerate(ids):
|
||||||
if extras is not None:
|
if extras is not None:
|
||||||
ev = extras[idx]
|
ev = extras[idx]
|
||||||
self._set_custom(id, val, label=label, num=num, append=append,
|
self._set_custom(id, val, label=label, num=num, append=append,
|
||||||
notify=notify, extra=ev)
|
notify=notify, extra=ev)
|
||||||
|
self.dirtied(ids, commit=False)
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
|
||||||
def set_custom(self, id, val, label=None, num=None,
|
def set_custom(self, id, val, label=None, num=None,
|
||||||
append=False, notify=True, extra=None, commit=True):
|
append=False, notify=True, extra=None, commit=True):
|
||||||
self._set_custom(id, val, label=label, num=num, append=append,
|
self._set_custom(id, val, label=label, num=num, append=append,
|
||||||
notify=notify, extra=extra)
|
notify=notify, extra=extra)
|
||||||
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
|
||||||
|
@ -9,10 +9,12 @@ The database used to store ebook metadata
|
|||||||
import os, sys, shutil, cStringIO, glob, time, functools, traceback, re
|
import os, sys, shutil, cStringIO, glob, time, functools, traceback, re
|
||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
from math import floor
|
from math import floor
|
||||||
|
from Queue import Queue
|
||||||
|
|
||||||
from PyQt4.QtGui import QImage
|
from PyQt4.QtGui import QImage
|
||||||
|
|
||||||
from calibre.ebooks.metadata import title_sort, author_to_author_sort
|
from calibre.ebooks.metadata import title_sort, author_to_author_sort
|
||||||
|
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||||
from calibre.library.database import LibraryDatabase
|
from calibre.library.database import LibraryDatabase
|
||||||
from calibre.library.field_metadata import FieldMetadata, TagsIcons
|
from calibre.library.field_metadata import FieldMetadata, TagsIcons
|
||||||
from calibre.library.schema_upgrades import SchemaUpgrade
|
from calibre.library.schema_upgrades import SchemaUpgrade
|
||||||
@ -126,6 +128,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
|
|
||||||
def __init__(self, library_path, row_factory=False):
|
def __init__(self, library_path, row_factory=False):
|
||||||
self.field_metadata = FieldMetadata()
|
self.field_metadata = FieldMetadata()
|
||||||
|
self.dirtied_queue = Queue()
|
||||||
if not os.path.exists(library_path):
|
if not os.path.exists(library_path):
|
||||||
os.makedirs(library_path)
|
os.makedirs(library_path)
|
||||||
self.listeners = set([])
|
self.listeners = set([])
|
||||||
@ -337,6 +340,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
setattr(self, 'title_sort', functools.partial(self.get_property,
|
setattr(self, 'title_sort', functools.partial(self.get_property,
|
||||||
loc=self.FIELD_MAP['sort']))
|
loc=self.FIELD_MAP['sort']))
|
||||||
|
|
||||||
|
d = self.conn.get('SELECT book FROM metadata_dirtied', all=True)
|
||||||
|
for x in d:
|
||||||
|
self.dirtied_queue.put(x[0])
|
||||||
|
|
||||||
self.refresh_ondevice = functools.partial(self.data.refresh_ondevice, self)
|
self.refresh_ondevice = functools.partial(self.data.refresh_ondevice, self)
|
||||||
self.refresh()
|
self.refresh()
|
||||||
self.last_update_check = self.last_modified()
|
self.last_update_check = self.last_modified()
|
||||||
@ -363,10 +370,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
return row[self.FIELD_MAP['path']].replace('/', os.sep)
|
return row[self.FIELD_MAP['path']].replace('/', os.sep)
|
||||||
|
|
||||||
|
|
||||||
def abspath(self, index, index_is_id=False):
|
def abspath(self, index, index_is_id=False, create_dirs=True):
|
||||||
'Return the absolute path to the directory containing this books files as a unicode string.'
|
'Return the absolute path to the directory containing this books files as a unicode string.'
|
||||||
path = os.path.join(self.library_path, self.path(index, index_is_id=index_is_id))
|
path = os.path.join(self.library_path, self.path(index, index_is_id=index_is_id))
|
||||||
if not os.path.exists(path):
|
if create_dirs and not os.path.exists(path):
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
@ -550,6 +557,35 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
def metadata_for_field(self, key):
|
def metadata_for_field(self, key):
|
||||||
return self.field_metadata[key]
|
return self.field_metadata[key]
|
||||||
|
|
||||||
|
def dump_metadata(self, book_ids, remove_from_dirtied=True, commit=True):
|
||||||
|
for book_id in book_ids:
|
||||||
|
if not self.data.has_id(book_id):
|
||||||
|
continue
|
||||||
|
mi = self.get_metadata(book_id, index_is_id=True, get_cover=True)
|
||||||
|
# Always set cover to cover.jpg. Even if cover doesn't exist,
|
||||||
|
# no harm done. This way no need to call dirtied when
|
||||||
|
# cover is set/removed
|
||||||
|
mi.cover = 'cover.jpg'
|
||||||
|
raw = metadata_to_opf(mi)
|
||||||
|
path = self.abspath(book_id, index_is_id=True)
|
||||||
|
with open(os.path.join(path, 'metadata.opf'), 'wb') as f:
|
||||||
|
f.write(raw)
|
||||||
|
if remove_from_dirtied:
|
||||||
|
self.conn.execute('DELETE FROM metadata_dirtied WHERE book=?',
|
||||||
|
(book_id,))
|
||||||
|
if commit:
|
||||||
|
self.conn.commit()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def dirtied(self, book_ids, commit=True):
|
||||||
|
self.conn.executemany(
|
||||||
|
'INSERT OR REPLACE INTO metadata_dirtied (book) VALUES (?)',
|
||||||
|
[(x,) for x in book_ids])
|
||||||
|
if commit:
|
||||||
|
self.conn.commit()
|
||||||
|
for x in book_ids:
|
||||||
|
self.dirtied_queue.put(x)
|
||||||
|
|
||||||
def get_metadata(self, idx, index_is_id=False, get_cover=False):
|
def get_metadata(self, idx, index_is_id=False, get_cover=False):
|
||||||
'''
|
'''
|
||||||
Convenience method to return metadata as a :class:`Metadata` object.
|
Convenience method to return metadata as a :class:`Metadata` object.
|
||||||
@ -657,7 +693,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
def has_cover(self, index, index_is_id=False):
|
def has_cover(self, index, index_is_id=False):
|
||||||
id = index if index_is_id else self.id(index)
|
id = index if index_is_id else self.id(index)
|
||||||
try:
|
try:
|
||||||
path = os.path.join(self.abspath(id, index_is_id=True), 'cover.jpg')
|
path = os.path.join(self.abspath(id, index_is_id=True,
|
||||||
|
create_dirs=False), 'cover.jpg')
|
||||||
except:
|
except:
|
||||||
# Can happen if path has not yet been set
|
# Can happen if path has not yet been set
|
||||||
return False
|
return False
|
||||||
@ -1241,6 +1278,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
ss = self.author_sort_from_book(id, index_is_id=True)
|
ss = self.author_sort_from_book(id, index_is_id=True)
|
||||||
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
|
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
|
||||||
(ss, id))
|
(ss, id))
|
||||||
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.set(id, self.FIELD_MAP['authors'],
|
self.data.set(id, self.FIELD_MAP['authors'],
|
||||||
@ -1267,6 +1305,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
else:
|
else:
|
||||||
self.data.set(id, self.FIELD_MAP['sort'], title, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['sort'], title, row_is_id=True)
|
||||||
self.set_path(id, index_is_id=True)
|
self.set_path(id, index_is_id=True)
|
||||||
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
if notify:
|
if notify:
|
||||||
@ -1276,6 +1315,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
if dt:
|
if dt:
|
||||||
self.conn.execute('UPDATE books SET timestamp=? WHERE id=?', (dt, id))
|
self.conn.execute('UPDATE books SET timestamp=? WHERE id=?', (dt, id))
|
||||||
self.data.set(id, self.FIELD_MAP['timestamp'], dt, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['timestamp'], dt, row_is_id=True)
|
||||||
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
if notify:
|
if notify:
|
||||||
@ -1285,6 +1325,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
if dt:
|
if dt:
|
||||||
self.conn.execute('UPDATE books SET pubdate=? WHERE id=?', (dt, id))
|
self.conn.execute('UPDATE books SET pubdate=? WHERE id=?', (dt, id))
|
||||||
self.data.set(id, self.FIELD_MAP['pubdate'], dt, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['pubdate'], dt, row_is_id=True)
|
||||||
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
if notify:
|
if notify:
|
||||||
@ -1303,6 +1344,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
else:
|
else:
|
||||||
aid = self.conn.execute('INSERT INTO publishers(name) VALUES (?)', (publisher,)).lastrowid
|
aid = self.conn.execute('INSERT INTO publishers(name) VALUES (?)', (publisher,)).lastrowid
|
||||||
self.conn.execute('INSERT INTO books_publishers_link(book, publisher) VALUES (?,?)', (id, aid))
|
self.conn.execute('INSERT INTO books_publishers_link(book, publisher) VALUES (?,?)', (id, aid))
|
||||||
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.set(id, self.FIELD_MAP['publisher'], publisher, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['publisher'], publisher, row_is_id=True)
|
||||||
@ -1593,6 +1635,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
'''.format(tables[0], tables[1])
|
'''.format(tables[0], tables[1])
|
||||||
)
|
)
|
||||||
self.conn.executescript(drops)
|
self.conn.executescript(drops)
|
||||||
|
self.dirtied(ids, commit=False)
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
|
|
||||||
for x in ids:
|
for x in ids:
|
||||||
@ -1638,6 +1681,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
(id, tid), all=False):
|
(id, tid), all=False):
|
||||||
self.conn.execute('INSERT INTO books_tags_link(book, tag) VALUES (?,?)',
|
self.conn.execute('INSERT INTO books_tags_link(book, tag) VALUES (?,?)',
|
||||||
(id, tid))
|
(id, tid))
|
||||||
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
tags = u','.join(self.get_tags(id))
|
tags = u','.join(self.get_tags(id))
|
||||||
@ -1692,6 +1736,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
else:
|
else:
|
||||||
aid = self.conn.execute('INSERT INTO series(name) VALUES (?)', (series,)).lastrowid
|
aid = self.conn.execute('INSERT INTO series(name) VALUES (?)', (series,)).lastrowid
|
||||||
self.conn.execute('INSERT INTO books_series_link(book, series) VALUES (?,?)', (id, aid))
|
self.conn.execute('INSERT INTO books_series_link(book, series) VALUES (?,?)', (id, aid))
|
||||||
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.set(id, self.FIELD_MAP['series'], series, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['series'], series, row_is_id=True)
|
||||||
@ -1706,6 +1751,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
except:
|
except:
|
||||||
idx = 1.0
|
idx = 1.0
|
||||||
self.conn.execute('UPDATE books SET series_index=? WHERE id=?', (idx, id))
|
self.conn.execute('UPDATE books SET series_index=? WHERE id=?', (idx, id))
|
||||||
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.set(id, self.FIELD_MAP['series_index'], idx, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['series_index'], idx, row_is_id=True)
|
||||||
@ -1718,6 +1764,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
rat = self.conn.get('SELECT id FROM ratings WHERE rating=?', (rating,), all=False)
|
rat = self.conn.get('SELECT id FROM ratings WHERE rating=?', (rating,), all=False)
|
||||||
rat = rat if rat else self.conn.execute('INSERT INTO ratings(rating) VALUES (?)', (rating,)).lastrowid
|
rat = rat if rat else self.conn.execute('INSERT INTO ratings(rating) VALUES (?)', (rating,)).lastrowid
|
||||||
self.conn.execute('INSERT INTO books_ratings_link(book, rating) VALUES (?,?)', (id, rat))
|
self.conn.execute('INSERT INTO books_ratings_link(book, rating) VALUES (?,?)', (id, rat))
|
||||||
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.set(id, self.FIELD_MAP['rating'], rating, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['rating'], rating, row_is_id=True)
|
||||||
@ -1730,11 +1777,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.set(id, self.FIELD_MAP['comments'], text, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['comments'], text, row_is_id=True)
|
||||||
|
self.dirtied([id], commit=False)
|
||||||
if notify:
|
if notify:
|
||||||
self.notify('metadata', [id])
|
self.notify('metadata', [id])
|
||||||
|
|
||||||
def set_author_sort(self, id, sort, notify=True, commit=True):
|
def set_author_sort(self, id, sort, notify=True, commit=True):
|
||||||
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', (sort, id))
|
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', (sort, id))
|
||||||
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.set(id, self.FIELD_MAP['author_sort'], sort, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['author_sort'], sort, row_is_id=True)
|
||||||
@ -1743,6 +1792,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
|||||||
|
|
||||||
def set_isbn(self, id, isbn, notify=True, commit=True):
|
def set_isbn(self, id, isbn, notify=True, commit=True):
|
||||||
self.conn.execute('UPDATE books SET isbn=? WHERE id=?', (isbn, id))
|
self.conn.execute('UPDATE books SET isbn=? WHERE id=?', (isbn, id))
|
||||||
|
self.dirtied([id], commit=False)
|
||||||
if commit:
|
if commit:
|
||||||
self.conn.commit()
|
self.conn.commit()
|
||||||
self.data.set(id, self.FIELD_MAP['isbn'], isbn, row_is_id=True)
|
self.data.set(id, self.FIELD_MAP['isbn'], isbn, row_is_id=True)
|
||||||
|
@ -6,7 +6,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import os, traceback, cStringIO, re, string
|
import os, traceback, cStringIO, re
|
||||||
|
|
||||||
from calibre.utils.config import Config, StringConfig, tweaks
|
from calibre.utils.config import Config, StringConfig, tweaks
|
||||||
from calibre.utils.formatter import TemplateFormatter
|
from calibre.utils.formatter import TemplateFormatter
|
||||||
|
@ -397,3 +397,15 @@ class SchemaUpgrade(object):
|
|||||||
UNIQUE(key));
|
UNIQUE(key));
|
||||||
'''
|
'''
|
||||||
self.conn.executescript(script)
|
self.conn.executescript(script)
|
||||||
|
|
||||||
|
def upgrade_version_13(self):
|
||||||
|
'Dirtied table for OPF metadata backups'
|
||||||
|
script = '''
|
||||||
|
DROP TABLE IF EXISTS metadata_dirtied;
|
||||||
|
CREATE TABLE metadata_dirtied(id INTEGER PRIMARY KEY,
|
||||||
|
book INTEGER NOT NULL,
|
||||||
|
UNIQUE(book));
|
||||||
|
INSERT INTO metadata_dirtied (book) SELECT id FROM books;
|
||||||
|
'''
|
||||||
|
self.conn.executescript(script)
|
||||||
|
|
||||||
|
@ -47,10 +47,12 @@ class TemplateFormatter(string.Formatter):
|
|||||||
'shorten' : (3, _shorten),
|
'shorten' : (3, _shorten),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
format_string_re = re.compile(r'^(.*)\|(.*)\|(.*)$')
|
||||||
|
compress_spaces = re.compile(r'\s+')
|
||||||
|
|
||||||
def get_value(self, key, args):
|
def get_value(self, key, args):
|
||||||
raise Exception('get_value must be implemented in the subclass')
|
raise Exception('get_value must be implemented in the subclass')
|
||||||
|
|
||||||
format_string_re = re.compile(r'^(.*)\|(.*)\|(.*)$')
|
|
||||||
|
|
||||||
def _explode_format_string(self, fmt):
|
def _explode_format_string(self, fmt):
|
||||||
try:
|
try:
|
||||||
@ -64,8 +66,10 @@ class TemplateFormatter(string.Formatter):
|
|||||||
return fmt, '', ''
|
return fmt, '', ''
|
||||||
|
|
||||||
def format_field(self, val, fmt):
|
def format_field(self, val, fmt):
|
||||||
|
# Handle conditional text
|
||||||
fmt, prefix, suffix = self._explode_format_string(fmt)
|
fmt, prefix, suffix = self._explode_format_string(fmt)
|
||||||
|
|
||||||
|
# Handle functions
|
||||||
p = fmt.find('(')
|
p = fmt.find('(')
|
||||||
if p >= 0 and fmt[-1] == ')' and fmt[0:p] in self.functions:
|
if p >= 0 and fmt[-1] == ')' and fmt[0:p] in self.functions:
|
||||||
field = fmt[0:p]
|
field = fmt[0:p]
|
||||||
@ -73,7 +77,7 @@ class TemplateFormatter(string.Formatter):
|
|||||||
args = fmt[p+1:-1].split(',')
|
args = fmt[p+1:-1].split(',')
|
||||||
if (func[0] == 0 and (len(args) != 1 or args[0])) or \
|
if (func[0] == 0 and (len(args) != 1 or args[0])) or \
|
||||||
(func[0] > 0 and func[0] != len(args)):
|
(func[0] > 0 and func[0] != len(args)):
|
||||||
raise Exception ('Incorrect number of arguments for function '+ fmt[0:p])
|
raise ValueError('Incorrect number of arguments for function '+ fmt[0:p])
|
||||||
if func[0] == 0:
|
if func[0] == 0:
|
||||||
val = func[1](self, val)
|
val = func[1](self, val)
|
||||||
else:
|
else:
|
||||||
@ -84,8 +88,6 @@ class TemplateFormatter(string.Formatter):
|
|||||||
return ''
|
return ''
|
||||||
return prefix + val + suffix
|
return prefix + val + suffix
|
||||||
|
|
||||||
compress_spaces = re.compile(r'\s+')
|
|
||||||
|
|
||||||
def vformat(self, fmt, args, kwargs):
|
def vformat(self, fmt, args, kwargs):
|
||||||
ans = string.Formatter.vformat(self, fmt, args, kwargs)
|
ans = string.Formatter.vformat(self, fmt, args, kwargs)
|
||||||
return self.compress_spaces.sub(' ', ans).strip()
|
return self.compress_spaces.sub(' ', ans).strip()
|
||||||
|
@ -18,6 +18,7 @@ from calibre.utils.ipc.launch import Worker
|
|||||||
from calibre.utils.ipc.worker import PARALLEL_FUNCS
|
from calibre.utils.ipc.worker import PARALLEL_FUNCS
|
||||||
from calibre import detect_ncpus as cpu_count
|
from calibre import detect_ncpus as cpu_count
|
||||||
from calibre.constants import iswindows
|
from calibre.constants import iswindows
|
||||||
|
from calibre.ptempfile import base_dir
|
||||||
|
|
||||||
_counter = 0
|
_counter = 0
|
||||||
|
|
||||||
@ -114,8 +115,9 @@ class Server(Thread):
|
|||||||
with self._worker_launch_lock:
|
with self._worker_launch_lock:
|
||||||
self.launched_worker_count += 1
|
self.launched_worker_count += 1
|
||||||
id = self.launched_worker_count
|
id = self.launched_worker_count
|
||||||
rfile = os.path.join(tempfile.gettempdir(),
|
fd, rfile = tempfile.mkstemp(prefix='ipc_result_%d_%d_'%(self.id, id),
|
||||||
'calibre_ipc_result_%d_%d.pickle'%(self.id, id))
|
dir=base_dir(), suffix='.pickle')
|
||||||
|
os.close(fd)
|
||||||
if redirect_output is None:
|
if redirect_output is None:
|
||||||
redirect_output = not gui
|
redirect_output = not gui
|
||||||
|
|
||||||
@ -189,8 +191,12 @@ class Server(Thread):
|
|||||||
job.failed = True
|
job.failed = True
|
||||||
job.returncode = worker.returncode
|
job.returncode = worker.returncode
|
||||||
elif os.path.exists(worker.rfile):
|
elif os.path.exists(worker.rfile):
|
||||||
|
try:
|
||||||
job.result = cPickle.load(open(worker.rfile, 'rb'))
|
job.result = cPickle.load(open(worker.rfile, 'rb'))
|
||||||
os.remove(worker.rfile)
|
os.remove(worker.rfile)
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
job.duration = time.time() - job.start_time
|
job.duration = time.time() - job.start_time
|
||||||
self.changed_jobs_queue.put(job)
|
self.changed_jobs_queue.put(job)
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ def console_config():
|
|||||||
c = Config('console', desc)
|
c = Config('console', desc)
|
||||||
|
|
||||||
c.add_opt('theme', default='native', help='The color theme')
|
c.add_opt('theme', default='native', help='The color theme')
|
||||||
|
c.add_opt('scrollback', default=10000,
|
||||||
|
help='Max number of lines to keep in the scrollback buffer')
|
||||||
|
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
import sys, textwrap, traceback, StringIO
|
import sys, textwrap, traceback, StringIO
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from codeop import CommandCompiler
|
||||||
|
|
||||||
from PyQt4.Qt import QTextEdit, Qt, QTextFrameFormat, pyqtSignal, \
|
from PyQt4.Qt import QTextEdit, Qt, QTextFrameFormat, pyqtSignal, \
|
||||||
QApplication, QColor, QPalette, QMenu, QActionGroup, QTimer
|
QApplication, QColor, QPalette, QMenu, QActionGroup, QTimer
|
||||||
@ -16,8 +17,9 @@ from pygments.styles import get_all_styles
|
|||||||
|
|
||||||
from calibre.utils.pyconsole.formatter import Formatter
|
from calibre.utils.pyconsole.formatter import Formatter
|
||||||
from calibre.utils.pyconsole.controller import Controller
|
from calibre.utils.pyconsole.controller import Controller
|
||||||
|
from calibre.utils.pyconsole.history import History
|
||||||
from calibre.utils.pyconsole import prints, prefs, __appname__, \
|
from calibre.utils.pyconsole import prints, prefs, __appname__, \
|
||||||
__version__, error_dialog
|
__version__, error_dialog, dynamic
|
||||||
|
|
||||||
class EditBlock(object): # {{{
|
class EditBlock(object): # {{{
|
||||||
|
|
||||||
@ -73,6 +75,7 @@ class ThemeMenu(QMenu): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
class Console(QTextEdit):
|
class Console(QTextEdit):
|
||||||
|
|
||||||
running = pyqtSignal()
|
running = pyqtSignal()
|
||||||
@ -114,7 +117,9 @@ class Console(QTextEdit):
|
|||||||
parent=None):
|
parent=None):
|
||||||
QTextEdit.__init__(self, parent)
|
QTextEdit.__init__(self, parent)
|
||||||
self.shutting_down = False
|
self.shutting_down = False
|
||||||
|
self.compiler = CommandCompiler()
|
||||||
self.buf = self.old_buf = []
|
self.buf = self.old_buf = []
|
||||||
|
self.history = History([''], dynamic.get('console_history', []))
|
||||||
self.prompt_frame = None
|
self.prompt_frame = None
|
||||||
self.allow_output = False
|
self.allow_output = False
|
||||||
self.prompt_frame_format = QTextFrameFormat()
|
self.prompt_frame_format = QTextFrameFormat()
|
||||||
@ -122,7 +127,7 @@ class Console(QTextEdit):
|
|||||||
self.prompt_frame_format.setBorderStyle(QTextFrameFormat.BorderStyle_Solid)
|
self.prompt_frame_format.setBorderStyle(QTextFrameFormat.BorderStyle_Solid)
|
||||||
self.prompt_len = len(prompt)
|
self.prompt_len = len(prompt)
|
||||||
|
|
||||||
self.doc.setMaximumBlockCount(10000)
|
self.doc.setMaximumBlockCount(int(prefs['scrollback']))
|
||||||
self.lexer = PythonLexer(ensurenl=False)
|
self.lexer = PythonLexer(ensurenl=False)
|
||||||
self.tb_lexer = PythonTracebackLexer()
|
self.tb_lexer = PythonTracebackLexer()
|
||||||
|
|
||||||
@ -139,6 +144,8 @@ class Console(QTextEdit):
|
|||||||
self.key_dispatcher = { # {{{
|
self.key_dispatcher = { # {{{
|
||||||
Qt.Key_Enter : self.enter_pressed,
|
Qt.Key_Enter : self.enter_pressed,
|
||||||
Qt.Key_Return : self.enter_pressed,
|
Qt.Key_Return : self.enter_pressed,
|
||||||
|
Qt.Key_Up : self.up_pressed,
|
||||||
|
Qt.Key_Down : self.down_pressed,
|
||||||
Qt.Key_Home : self.home_pressed,
|
Qt.Key_Home : self.home_pressed,
|
||||||
Qt.Key_End : self.end_pressed,
|
Qt.Key_End : self.end_pressed,
|
||||||
Qt.Key_Left : self.left_pressed,
|
Qt.Key_Left : self.left_pressed,
|
||||||
@ -153,15 +160,17 @@ class Console(QTextEdit):
|
|||||||
'''.format(sys.version.splitlines()[0], __appname__,
|
'''.format(sys.version.splitlines()[0], __appname__,
|
||||||
__version__))
|
__version__))
|
||||||
|
|
||||||
|
sys.excepthook = self.unhandled_exception
|
||||||
|
|
||||||
self.controllers = []
|
self.controllers = []
|
||||||
QTimer.singleShot(0, self.launch_controller)
|
QTimer.singleShot(0, self.launch_controller)
|
||||||
|
|
||||||
sys.excepthook = self.unhandled_exception
|
|
||||||
|
|
||||||
with EditBlock(self.cursor):
|
with EditBlock(self.cursor):
|
||||||
self.render_block(motd)
|
self.render_block(motd)
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
|
dynamic.set('console_history', self.history.serialize())
|
||||||
self.shutton_down = True
|
self.shutton_down = True
|
||||||
for c in self.controllers:
|
for c in self.controllers:
|
||||||
c.kill()
|
c.kill()
|
||||||
@ -365,7 +374,7 @@ class Console(QTextEdit):
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
# Keyboard handling {{{
|
# Keyboard management {{{
|
||||||
|
|
||||||
def keyPressEvent(self, ev):
|
def keyPressEvent(self, ev):
|
||||||
text = unicode(ev.text())
|
text = unicode(ev.text())
|
||||||
@ -394,6 +403,20 @@ class Console(QTextEdit):
|
|||||||
self.setTextCursor(c)
|
self.setTextCursor(c)
|
||||||
self.ensureCursorVisible()
|
self.ensureCursorVisible()
|
||||||
|
|
||||||
|
def up_pressed(self):
|
||||||
|
lineno, pos = self.cursor_pos
|
||||||
|
if lineno < 0: return
|
||||||
|
if lineno == 0:
|
||||||
|
b = self.history.back()
|
||||||
|
if b is not None:
|
||||||
|
self.set_prompt(b)
|
||||||
|
else:
|
||||||
|
c = self.cursor
|
||||||
|
c.movePosition(c.Up)
|
||||||
|
self.setTextCursor(c)
|
||||||
|
self.ensureCursorVisible()
|
||||||
|
|
||||||
|
|
||||||
def backspace_pressed(self):
|
def backspace_pressed(self):
|
||||||
lineno, pos = self.cursor_pos
|
lineno, pos = self.cursor_pos
|
||||||
if lineno < 0: return
|
if lineno < 0: return
|
||||||
@ -414,7 +437,6 @@ class Console(QTextEdit):
|
|||||||
lineno, pos = self.cursor_pos
|
lineno, pos = self.cursor_pos
|
||||||
if lineno < 0: return
|
if lineno < 0: return
|
||||||
c = self.cursor
|
c = self.cursor
|
||||||
lineno, pos = self.cursor_pos
|
|
||||||
cp = list(self.prompt(False))
|
cp = list(self.prompt(False))
|
||||||
if pos < len(cp[lineno]):
|
if pos < len(cp[lineno]):
|
||||||
c.movePosition(c.NextCharacter)
|
c.movePosition(c.NextCharacter)
|
||||||
@ -423,6 +445,22 @@ class Console(QTextEdit):
|
|||||||
self.setTextCursor(c)
|
self.setTextCursor(c)
|
||||||
self.ensureCursorVisible()
|
self.ensureCursorVisible()
|
||||||
|
|
||||||
|
def down_pressed(self):
|
||||||
|
lineno, pos = self.cursor_pos
|
||||||
|
if lineno < 0: return
|
||||||
|
c = self.cursor
|
||||||
|
cp = list(self.prompt(False))
|
||||||
|
if lineno >= len(cp) - 1:
|
||||||
|
b = self.history.forward()
|
||||||
|
if b is not None:
|
||||||
|
self.set_prompt(b)
|
||||||
|
else:
|
||||||
|
c = self.cursor
|
||||||
|
c.movePosition(c.Down)
|
||||||
|
self.setTextCursor(c)
|
||||||
|
self.ensureCursorVisible()
|
||||||
|
|
||||||
|
|
||||||
def home_pressed(self):
|
def home_pressed(self):
|
||||||
if self.prompt_frame is not None:
|
if self.prompt_frame is not None:
|
||||||
mods = QApplication.keyboardModifiers()
|
mods = QApplication.keyboardModifiers()
|
||||||
@ -454,6 +492,19 @@ class Console(QTextEdit):
|
|||||||
return self.no_controller_error()
|
return self.no_controller_error()
|
||||||
cp = list(self.prompt())
|
cp = list(self.prompt())
|
||||||
if cp[0]:
|
if cp[0]:
|
||||||
|
try:
|
||||||
|
ret = self.compiler('\n'.join(cp))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if ret is None:
|
||||||
|
c = self.prompt_frame.lastCursorPosition()
|
||||||
|
c.insertBlock()
|
||||||
|
self.setTextCursor(c)
|
||||||
|
self.render_current_prompt()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.history.enter(cp)
|
||||||
self.execute(cp)
|
self.execute(cp)
|
||||||
|
|
||||||
def text_typed(self, text):
|
def text_typed(self, text):
|
||||||
@ -461,6 +512,7 @@ class Console(QTextEdit):
|
|||||||
self.move_cursor_to_prompt()
|
self.move_cursor_to_prompt()
|
||||||
self.cursor.insertText(text)
|
self.cursor.insertText(text)
|
||||||
self.render_current_prompt(restore_cursor=True)
|
self.render_current_prompt(restore_cursor=True)
|
||||||
|
self.history.current = list(self.prompt())
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -104,7 +104,6 @@ class Controller(QThread):
|
|||||||
def returncode(self):
|
def returncode(self):
|
||||||
return self.process.returncode
|
return self.process.returncode
|
||||||
|
|
||||||
@property
|
|
||||||
def interrupt(self):
|
def interrupt(self):
|
||||||
if hasattr(signal, 'SIGINT'):
|
if hasattr(signal, 'SIGINT'):
|
||||||
os.kill(self.process.pid, signal.SIGINT)
|
os.kill(self.process.pid, signal.SIGINT)
|
||||||
|
56
src/calibre/utils/pyconsole/history.py
Normal file
56
src/calibre/utils/pyconsole/history.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
class History(object): # {{{
|
||||||
|
|
||||||
|
def __init__(self, current, entries):
|
||||||
|
self.entries = deque(entries, maxlen=max(2000, len(entries)))
|
||||||
|
self.index = len(self.entries) - 1
|
||||||
|
self.current = self.default = current
|
||||||
|
self.last_was_back = False
|
||||||
|
|
||||||
|
def back(self, amt=1):
|
||||||
|
if self.entries:
|
||||||
|
oidx = self.index
|
||||||
|
ans = self.entries[self.index]
|
||||||
|
self.index = max(0, self.index - amt)
|
||||||
|
self.last_was_back = self.index != oidx
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def forward(self, amt=1):
|
||||||
|
if self.entries:
|
||||||
|
d = self.index
|
||||||
|
if self.last_was_back:
|
||||||
|
d += 1
|
||||||
|
if d >= len(self.entries) - 1:
|
||||||
|
self.index = len(self.entries) - 1
|
||||||
|
self.last_was_back = False
|
||||||
|
return self.current
|
||||||
|
if self.last_was_back:
|
||||||
|
amt += 1
|
||||||
|
self.index = min(len(self.entries)-1, self.index + amt)
|
||||||
|
self.last_was_back = False
|
||||||
|
return self.entries[self.index]
|
||||||
|
|
||||||
|
def enter(self, x):
|
||||||
|
try:
|
||||||
|
self.entries.remove(x)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
self.entries.append(x)
|
||||||
|
self.index = len(self.entries) - 1
|
||||||
|
self.current = self.default
|
||||||
|
self.last_was_back = False
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
return list(self.entries)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
|
@ -11,6 +11,7 @@ from Queue import Queue, Empty
|
|||||||
from threading import Thread
|
from threading import Thread
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
from multiprocessing.connection import Client
|
from multiprocessing.connection import Client
|
||||||
|
from repr import repr as safe_repr
|
||||||
|
|
||||||
from calibre.utils.pyconsole import preferred_encoding, isbytestring, \
|
from calibre.utils.pyconsole import preferred_encoding, isbytestring, \
|
||||||
POLL_TIMEOUT
|
POLL_TIMEOUT
|
||||||
@ -35,7 +36,7 @@ def tounicode(raw): # {{{
|
|||||||
try:
|
try:
|
||||||
raw = raw.decode(preferred_encoding, 'replace')
|
raw = raw.decode(preferred_encoding, 'replace')
|
||||||
except:
|
except:
|
||||||
raw = repr(raw)
|
raw = safe_repr(raw)
|
||||||
|
|
||||||
if isbytestring(raw):
|
if isbytestring(raw):
|
||||||
try:
|
try:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user