Fixed broken font rationalization

This commit is contained in:
Marshall T. Vandegrift 2008-06-13 17:05:57 -04:00
commit 5dda09d098
55 changed files with 6019 additions and 5092 deletions

View File

@ -27,20 +27,21 @@ pictureflow :
mkdir -p src/calibre/plugins && rm -f src/calibre/plugins/*pictureflow* && \
cd src/calibre/gui2/pictureflow && rm -f *.o && \
mkdir -p .build && cd .build && rm -f * && \
qmake ../pictureflow.pro && make && \
qmake ../pictureflow.pro && make staticlib && \
cd ../PyQt && \
mkdir -p .build && \
cd .build && rm -f * && \
python ../configure.py && make && \
cd ../../../../../.. && \
cp src/calibre/gui2/pictureflow/.build/libpictureflow.so.?.?.? src/calibre/gui2/pictureflow/PyQt/.build/pictureflow.so src/calibre/plugins/ && \
python -c "import os, glob; lp = glob.glob('src/calibre/plugins/libpictureflow.so.*')[0]; os.rename(lp, lp[:-4])" && \
cp src/calibre/gui2/pictureflow/PyQt/.build/pictureflow.so src/calibre/plugins/ && \
rm -rf src/calibre/gui2/pictureflow/.build rm -rf src/calibre/gui2/pictureflow/PyQt/.build
pot :
cd src/calibre/translations && ${PYTHON} __init__.py pot
egg : all
${PYTHON} setup.py register bdist_egg --exclude-source-files upload
egg :
${PYTHON} setup.py bdist_egg --exclude-source-files
linux_binary:
${PYTHON} -c "import upload; upload._build_linux()"

View File

@ -240,6 +240,9 @@ _check_symlinks_prescript()
os.link(os.path.expanduser('~/pdftohtml'), os.path.join(frameworks_dir, 'pdftohtml'))
print 'Adding plugins'
module_dir = os.path.join(resource_dir, 'lib', 'python2.5', 'lib-dynload')
print 'Adding fontconfig'
for f in glob.glob(os.path.expanduser('~/fontconfig/*')):
os.link(f, os.path.join(frameworks_dir, os.path.basename(f)))
for src, dest in plugin_files:
if 'dylib' in dest:
os.link(src, os.path.join(frameworks_dir, dest))

View File

@ -1,7 +1,7 @@
''' E-book management software'''
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
__version__ = '0.4.68'
__version__ = '0.4.70'
__docformat__ = "epytext"
__author__ = "Kovid Goyal <kovid at kovidgoyal.net>"
__appname__ = 'calibre'
@ -15,14 +15,14 @@ from optparse import OptionParser as _OptionParser
from optparse import IndentedHelpFormatter
from logging import Formatter
from ttfquery import findsystem, describe
from PyQt4.QtCore import QSettings, QVariant
from PyQt4.QtCore import QSettings, QVariant, QUrl
from PyQt4.QtGui import QDesktopServices
from calibre.translations.msgfmt import make
from calibre.ebooks.chardet import detect
from calibre.terminfo import TerminalController
terminal_controller = TerminalController(sys.stdout)
terminal_controller = TerminalController(sys.stdout)
iswindows = 'win32' in sys.platform.lower() or 'win64' in sys.platform.lower()
isosx = 'darwin' in sys.platform.lower()
islinux = not(iswindows or isosx)
@ -305,44 +305,6 @@ def set_translator():
set_translator()
font_families = {}
def get_font_families(cached=None):
global font_families
if cached is not None:
font_families = cached
if not font_families:
try:
ffiles = findsystem.findFonts()
except Exception, err:
print 'WARNING: Could not find fonts on your system.'
print err
else:
zlist = []
for ff in ffiles:
try:
if 'Optane' in str(ff):
font = describe.openFont(ff)
wt, italic = describe.modifiers(font)
except:
pass
try:
font = describe.openFont(ff)
except: # Some font files cause ttfquery to raise an exception, in which case they are ignored
continue
try:
wt, italic = describe.modifiers(font)
except:
wt, italic = 0, 0
if wt == 400 and italic == 0:
try:
family = describe.shortName(font)[1].strip()
except: # Windows strikes again!
continue
zlist.append((family, ff))
font_families = dict(zlist)
return font_families
def sanitize_file_name(name):
'''
Remove characters that are illegal in filenames from name.
@ -389,13 +351,9 @@ def detect_ncpus():
def launch(path_or_url):
if islinux:
subprocess.Popen(('xdg-open', path_or_url))
elif isosx:
subprocess.Popen(('open', path_or_url))
elif iswindows:
win32api = __import__('win32api', globals(), locals(), [], -1)
win32api.ShellExecute(0, 'open', path_or_url, None, os.getcwd(), 1)
if os.path.exists(path_or_url):
path_or_url = 'file:'+path_or_url
QDesktopServices.openUrl(QUrl(path_or_url))
def relpath(target, base=os.curdir):
"""
@ -550,15 +508,11 @@ def strftime(fmt, t=time.localtime()):
except:
return unicode(result, 'utf-8', 'replace')
if islinux:
if islinux and not getattr(sys, 'frozen', False):
import pkg_resources
if not os.environ.has_key('LD_LIBRARY_PATH'):
os.environ['LD_LIBRARY_PATH'] = ''
plugins = pkg_resources.resource_filename(__appname__, 'plugins')
os.environ['LD_LIBRARY_PATH'] = plugins + ':' + os.environ['LD_LIBRARY_PATH']
sys.path.insert(1, plugins)
cwd = os.getcwd()
os.chdir(plugins)
if iswindows and hasattr(sys, 'frozen'):
sys.path.insert(1, os.path.dirname(sys.executable))
@ -569,8 +523,6 @@ except Exception, err:
pictureflow = None
pictureflowerror = str(err)
if islinux:
os.chdir(cwd)
def entity_to_unicode(match, exceptions=[], encoding='cp1252'):
'''
@ -605,3 +557,12 @@ def entity_to_unicode(match, exceptions=[], encoding='cp1252'):
except KeyError:
return '&'+ent+';'
if isosx:
fdir = os.path.expanduser('~/.fonts')
if not os.path.exists(fdir):
os.makedirs(fdir)
if not os.path.exists(os.path.join(fdir, 'LiberationSans_Regular.ttf')):
from calibre.ebooks.lrf.fonts.liberation import __all__ as fonts
for font in fonts:
exec 'from calibre.ebooks.lrf.fonts.liberation.'+font+' import font_data'
open(os.path.join(fdir, font+'.ttf'), 'wb').write(font_data)

View File

@ -109,7 +109,7 @@ class PRS505(Device):
devname = re.search(r'BSD Name.*=\s+"(\S+)"', src).group(1)
self._main_prefix = re.search('/dev/%s(\w*)\s+on\s+([^\(]+)\s+'%(devname,), mount).group(2) + os.sep
except:
raise DeviceError('Unable to find %s. Is it connected?'%(self.__class__.__name__,))
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__)
try:
src = subprocess.Popen('ioreg -n "%s"'%(self.OSX_SD_NAME,),
shell=True, stdout=subprocess.PIPE).stdout.read()
@ -143,7 +143,7 @@ class PRS505(Device):
if not drives:
raise DeviceError('Unable to find %s. Is it connected?'%(self.__class__.__name__,))
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__)
drives.sort(cmp=lambda a, b: cmp(a[0], b[0]))
self._main_prefix = drives[0][1]
@ -171,7 +171,7 @@ class PRS505(Device):
mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__)
if not mm:
raise DeviceError('Unable to find %s. Is it connected?'%(self.__class__.__name__,))
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,))
self._main_prefix = None
for dev in mm:
try:

View File

@ -17,4 +17,4 @@ class UnknownFormatError(Exception):
BOOK_EXTENSIONS = ['lrf', 'lrx', 'rar', 'zip', 'rtf', 'lit', 'txt', 'htm', 'xhtm',
'html', 'xhtml', 'epub', 'pdf', 'prc', 'mobi', 'azw',
'epub', 'fb2']
'epub', 'fb2', 'djvu']

View File

@ -9,7 +9,6 @@ from optparse import OptionValueError
from htmlentitydefs import name2codepoint
from uuid import uuid4
from ttfquery import describe, findsystem
from fontTools.ttLib import TTLibError
from calibre.ebooks.lrf.pylrs.pylrs import Book as _Book
@ -67,20 +66,6 @@ def profile_from_string(option, opt_str, value, parser):
except KeyError:
raise OptionValueError('Profile: '+value+' is not implemented. Implemented profiles: %s'%(profile_map.keys()))
def font_family(option, opt_str, value, parser):
if value:
value = value.split(',')
if len(value) != 2:
raise OptionValueError('Font family specification must be of the form'+\
' "path to font directory, font family"')
path, family = tuple(value)
if not os.path.isdir(path) or not os.access(path, os.R_OK|os.X_OK):
raise OptionValueError('Cannot read from ' + path)
setattr(parser.values, option.dest, (path, family))
else:
setattr(parser.values, option.dest, tuple())
def option_parser(usage, gui_mode=False):
parser = OptionParser(usage=usage, gui_mode=gui_mode)
metadata = parser.add_option_group('METADATA OPTIONS')
@ -203,18 +188,17 @@ def option_parser(usage, gui_mode=False):
fonts = parser.add_option_group('FONT FAMILIES',
_('''Specify trutype font families for serif, sans-serif and monospace fonts. '''
'''These fonts will be embedded in the LRF file. Note that custom fonts lead to '''
'''slower page turns. Each family specification is of the form: '''
'''"path to fonts directory, family" '''
'''slower page turns. '''
'''For example: '''
'''--serif-family "%s, Times New Roman"
''') % ('C:\Windows\Fonts' if iswindows else '/usr/share/fonts/corefonts'))
fonts.add_option('--serif-family', action='callback', callback=font_family,
'''--serif-family "Times New Roman"
'''))
fonts.add_option('--serif-family',
default=None, dest='serif_family', type='string',
help=_('The serif family of fonts to embed'))
fonts.add_option('--sans-family', action='callback', callback=font_family,
fonts.add_option('--sans-family',
default=None, dest='sans_family', type='string',
help=_('The sans-serif family of fonts to embed'))
fonts.add_option('--mono-family', action='callback', callback=font_family,
fonts.add_option('--mono-family',
default=None, dest='mono_family', type='string',
help=_('The monospace family of fonts to embed'))
@ -231,45 +215,25 @@ def option_parser(usage, gui_mode=False):
return parser
def find_custom_fonts(options, logger):
from calibre.utils.fontconfig import files_for_family
fonts = {'serif' : None, 'sans' : None, 'mono' : None}
def find_family(option):
path, family = option
paths = findsystem.findFonts([path])
results = {}
for path in paths:
if len(results.keys()) == 4:
break
f = describe.openFont(path)
name, cfamily = describe.shortName(f)
if cfamily.lower().strip() != family.lower().strip():
continue
try:
wt, italic = describe.modifiers(f)
except TTLibError:
logger.exception('Could not process fonts in %s', path)
wt, italic = 0, 0
result = (path, name)
if wt == 400 and italic == 0:
results['normal'] = result
elif wt == 400 and italic > 0:
results['italic'] = result
elif wt >= 700 and italic == 0:
results['bold'] = result
elif wt >= 700 and italic > 0:
results['bi'] = result
return results
def family(cmd):
return cmd.split(',')[-1].strip()
if options.serif_family:
fonts['serif'] = find_family(options.serif_family)
f = family(options.serif_family)
fonts['serif'] = files_for_family(f)
if not fonts['serif']:
logger.warn('Unable to find serif family %s in %s'%(options.serif_family[1].strip(), options.serif_family[0]))
logger.warn('Unable to find serif family %s'%f)
if options.sans_family:
fonts['sans'] = find_family(options.sans_family)
f = family(options.sans_family)
fonts['sans'] = files_for_family(f)
if not fonts['sans']:
logger.warn('Unable to find sans family %s in %s'%(options.sans_family[1].strip(), options.sans_family[0]))
logger.warn('Unable to find sans family %s'%f)
if options.mono_family:
fonts['mono'] = find_family(options.mono_family)
f = family(options.mono_family)
fonts['mono'] = files_for_family(f)
if not fonts['mono']:
logger.warn('Unable to find mono family %s in %s'%(options.mono_family[1].strip(), options.mono_family[0]))
logger.warn('Unable to find mono family %s'%f)
return fonts

View File

@ -13,6 +13,8 @@ from calibre import sanitize_file_name
import sys, os, time
import parser
def option_parser():
parser = feeds_option_parser()
parser.remove_option('--output-dir')

View File

@ -0,0 +1,5 @@
__all__ = ['LiberationMono_Bold', 'LiberationMono_Regular', 'LiberationSans_Bold',
'LiberationSans_Regular', 'LiberationSerif_Bold', 'LiberationSerif_Regular',
'LiberationMono_BoldItalic', 'LiberationMono_Italic',
'LiberationSans_BoldItalic', 'LiberationSans_Italic',
'LiberationSerif_BoldItalic', 'LiberationSerif_Italic']

View File

@ -5,7 +5,7 @@ import sys, logging, os
from calibre import setup_cli_handlers, OptionParser
from calibre.ebooks import ConversionError
from calibre.ebooks.lrf.meta import get_metadata
from calibre.ebooks.lrf.parser import LRFDocument
from calibre.ebooks.lrf.lrfparser import LRFDocument
from calibre.ebooks.metadata.opf import OPFCreator
from calibre.ebooks.lrf.objects import PageAttr, BlockAttr, TextAttr

View File

@ -8,11 +8,13 @@ from calibre.ebooks.lrf import option_parser as lrf_option_parser
from calibre.ebooks import ConversionError
from calibre.ebooks.lrf.html.convert_from import process_file as html_process_file
from calibre.ebooks.metadata.opf import OPFReader
from calibre import isosx, __appname__, setup_cli_handlers, iswindows
from calibre import isosx, __appname__, setup_cli_handlers, iswindows, islinux
CLIT = 'clit'
if isosx and hasattr(sys, 'frameworks_dir'):
CLIT = os.path.join(getattr(sys, 'frameworks_dir'), CLIT)
if islinux and getattr(sys, 'frozen_path', False):
CLIT = os.path.join(getattr(sys, 'frozen_path'), 'clit')
def option_parser():
return lrf_option_parser(

View File

@ -4,7 +4,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import sys, os, subprocess, logging
from functools import partial
from calibre import isosx, setup_cli_handlers, filename_to_utf8, iswindows
from calibre import isosx, setup_cli_handlers, filename_to_utf8, iswindows, islinux
from calibre.ebooks import ConversionError
from calibre.ptempfile import PersistentTemporaryDirectory
from calibre.ebooks.lrf import option_parser as lrf_option_parser
@ -17,7 +17,8 @@ if isosx and hasattr(sys, 'frameworks_dir'):
if iswindows and hasattr(sys, 'frozen'):
PDFTOHTML = os.path.join(os.path.dirname(sys.executable), 'pdftohtml.exe')
popen = partial(subprocess.Popen, creationflags=0x08) # CREATE_NO_WINDOW=0x08 so that no ugly console is popped up
if islinux and getattr(sys, 'frozen_path', False):
PDFTOHTML = os.path.join(getattr(sys, 'frozen_path'), 'pdftohtml')
def generate_html(pathtopdf, logger):
'''

View File

@ -556,47 +556,26 @@ class Book(Delegator):
pages = [obj for obj in main.contents if isinstance(obj, Page)]
text_blocks = []
for p in pages:
for obj in p.contents:
if isinstance(obj, TextBlock):
text_blocks.append(obj)
elif isinstance(obj, Canvas):
for o in obj.contents:
if isinstance(o.content, TextBlock):
text_blocks.append(o.content)
text_styles = set([t.textStyle for t in text_blocks])
important_text_styles = []
for ts in text_styles:
temp = [len(tb.contents) for tb in text_blocks if tb.textStyle == ts]
avg_content_length = 0
if len(temp) > 0:
avg_content_length = sum(temp)/len(temp)
if avg_content_length > 4:
important_text_styles.append(ts)
for page in pages:
text_blocks.extend(
page.get_all(lambda x: isinstance(x, TextBlock)))
fonts = {}
if not important_text_styles:
important_text_styles = text_styles
for ts in important_text_styles:
fs = int(ts.attrs['fontsize'])
if fonts.has_key(fs):
fonts[fs] += 1
else:
fonts[fs] = 1
for tb in text_blocks:
fs = int(tb.textStyle.attrs['fontsize'])
text = tb.get_all(lambda x: isinstance(x, Text))
length = sum(len(t.text) for t in text)
fonts[fs] = fonts.get(fs, 0) + length
if not fonts:
print 'WARNING: LRF seems to have no textual content. Cannot rationalize font sizes.'
return
old_base_font_size = float(max(zip(fonts.keys(), fonts.values()), key=operator.itemgetter(1))[0])
factor = base_font_size/old_base_font_size
old_base_font_size = float(max(fonts.items(), key=operator.itemgetter(1))[0])
factor = base_font_size / old_base_font_size
def rescale(old):
return str(int(int(old) * factor))
text_styles = set(t.textStyle for t in text_blocks)
for ts in text_styles:
ts.attrs['fontsize'] = rescale(ts.attrs['fontsize'])
ts.attrs['baselineskip'] = rescale(ts.attrs['baselineskip'])

View File

@ -100,12 +100,21 @@ class MetaInformation(object):
def __str__(self):
ans = u''
ans += u'Title : ' + unicode(self.title) + u'\n'
if self.authors:
ans += u'Author : ' + (', '.join(self.authors) if self.authors is not None else u'None')
ans += ((' (' + self.author_sort + ')') if self.author_sort else '') + u'\n'
if self.publisher:
ans += u'Publisher: '+ unicode(self.publisher) + u'\n'
if self.category:
ans += u'Category : ' + unicode(self.category) + u'\n'
if self.comments:
ans += u'Comments : ' + unicode(self.comments) + u'\n'
if self.isbn:
ans += u'ISBN : ' + unicode(self.isbn) + u'\n'
if self.tags:
ans += u'Tags : ' +unicode(self.tags) + '\n'
if self.series:
ans += u'Series : '+unicode(self.series) + '(%d)'%self.series_index
return ans.strip()
def __nonzero__(self):

View File

@ -133,6 +133,11 @@ def metadata_from_filename(name, pat=None):
mi.series_index = int(si)
except IndexError, ValueError:
pass
try:
si = match.group('isbn')
mi.isbn = si
except IndexError, ValueError:
pass
if not mi.title:
mi.title = name
return mi

View File

@ -290,6 +290,8 @@ class FileDialog(QObject):
def get_files(self):
if islinux and self.fd.result() != self.fd.Accepted:
return tuple()
if self.selected_files is None:
return tuple(os.path.abspath(qstring_to_unicode(i)) for i in self.fd.selectedFiles())
return tuple(self.selected_files)

View File

@ -52,10 +52,15 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
QDialog.__init__(self, window)
Ui_LRFSingleDialog.__init__(self)
self.setupUi(self)
self.item1 = QListWidgetItem(QIcon(':/images/dialog_information.svg'), _("Metadata"), self.categoryList)
self.item2 = QListWidgetItem(QIcon(':/images/lookfeel.svg'), _('Look & Feel'), self.categoryList)
self.item3 = QListWidgetItem(QIcon(':/images/page.svg'), _('Page Setup'), self.categoryList)
self.item4 = QListWidgetItem(QIcon(':/images/chapters.svg'), _('Chapter Detection'), self.categoryList)
self.__w = []
self.__w.append(QIcon(':/images/dialog_information.svg'))
self.item1 = QListWidgetItem(self.__w[-1], _("Metadata"), self.categoryList)
self.__w.append(QIcon(':/images/lookfeel.svg'))
self.item2 = QListWidgetItem(self.__w[-1], _('Look & Feel'), self.categoryList)
self.__w.append(QIcon(':/images/page.svg'))
self.item3 = QListWidgetItem(self.__w[-1], _('Page Setup'), self.categoryList)
self.__w.append(QIcon(':/images/chapters.svg'))
self.item4 = QListWidgetItem(self.__w[-1], _('Chapter Detection'), self.categoryList)
self.categoryList.setCurrentRow(0)
QObject.connect(self.categoryList, SIGNAL('itemEntered(QListWidgetItem *)'),
self.show_category_help)
@ -70,7 +75,6 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
self.cpixmap = None
self.changed = False
if db:
self.id = self.db.id(self.row)
self.read_saved_options()
@ -143,7 +147,7 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
for opt in ('--serif-family', '--sans-family', '--mono-family'):
if opt in cmdline:
print 'in'
family = cmdline[cmdline.index(opt)+1].split(',')[1].strip()
family = cmdline[cmdline.index(opt)+1].split(',')[-1].strip()
obj = getattr(self, 'gui_'+opt[2:].replace('-', '_'))
try:
obj.setCurrentIndex(self.font_family_model.index_of(family))
@ -332,12 +336,8 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
for opt in ('--serif-family', '--sans-family', '--mono-family'):
obj = getattr(self, 'gui_'+opt[2:].replace('-', '_'))
family = qstring_to_unicode(obj.itemText(obj.currentIndex())).strip()
try:
path = self.font_family_model.path_of(family)
except KeyError:
continue
if path:
cmd.extend([opt, os.path.dirname(path)+', '+family])
if family != 'None':
cmd.extend([opt, family])
return cmd

View File

@ -127,7 +127,6 @@ class MetadataSingleDialog(QDialog, Ui_MetadataSingleDialog):
self.formats_changed = False
self.cover_changed = False
self.cpixmap = None
self.changed = False
self.cover.setAcceptDrops(True)
self.connect(self.cover, SIGNAL('cover_changed()'), self.cover_dropped)
QObject.connect(self.cover_button, SIGNAL("clicked(bool)"), \
@ -331,6 +330,4 @@ class MetadataSingleDialog(QDialog, Ui_MetadataSingleDialog):
self.db.set_comment(self.id, qstring_to_unicode(self.comments.toPlainText()))
if self.cover_changed:
self.db.set_cover(self.id, pixmap_to_data(self.cover.pixmap()))
self.changed = True
QDialog.accept(self)

View File

@ -1,17 +1,15 @@
from calibre.gui2 import choose_files
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import time, os
from PyQt4.QtCore import SIGNAL
from PyQt4.QtGui import QDialog, QMessageBox
from PyQt4.QtCore import SIGNAL, QUrl
from PyQt4.QtGui import QDialog, QMessageBox, QDesktopServices
from calibre.web.feeds.recipes import compile_recipe
from calibre.web.feeds.news import AutomaticNewsRecipe
from calibre.gui2.dialogs.user_profiles_ui import Ui_Dialog
from calibre.gui2 import qstring_to_unicode, error_dialog, question_dialog
from calibre.gui2 import qstring_to_unicode, error_dialog, question_dialog, choose_files
from calibre.gui2.widgets import PythonHighlighter
from calibre.utils import sendmail
from calibre.ptempfile import PersistentTemporaryFile
from calibre import isosx
@ -68,10 +66,13 @@ class UserProfiles(QDialog, Ui_Dialog):
pt = PersistentTemporaryFile(suffix='.py')
pt.write(src.encode('utf-8'))
pt.close()
sendmail(subject='Recipe for '+title,
attachments=[pt.name],
body=_('Save the text below into a file named recipe.py and send the file to your friends, to allow them to use this recipe.') if isosx else _('The attached file: %s is a recipe to download %s.')%(os.path.basename(pt.name), title))
body = _('The attached file: %s is a recipe to download %s.')%(os.path.basename(pt.name), title)
subject = _('Recipe for ')+title
url = QUrl('mailto:')
url.addQueryItem('subject', subject)
url.addQueryItem('body', body)
url.addQueryItem('attachment', pt.name)
QDesktopServices.openUrl(url)
def edit_profile(self, current, previous):

View File

@ -5,8 +5,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>349</width>
<height>441</height>
<width>335</width>
<height>487</height>
</rect>
</property>
<property name="windowTitle" >
@ -39,6 +39,8 @@
<widget class="QLineEdit" name="re" />
</item>
</layout>
<zorder>re</zorder>
<zorder>label</zorder>
</widget>
</item>
<item>
@ -46,7 +48,7 @@
<property name="title" >
<string>&amp;Test</string>
</property>
<layout class="QVBoxLayout" >
<layout class="QVBoxLayout" name="verticalLayout" >
<item>
<layout class="QHBoxLayout" >
<item>
@ -76,7 +78,7 @@
</layout>
</item>
<item>
<layout class="QGridLayout" >
<layout class="QGridLayout" name="gridLayout" >
<item row="0" column="0" >
<widget class="QLabel" name="label_3" >
<property name="text" >
@ -84,7 +86,7 @@
</property>
</widget>
</item>
<item row="0" column="1" >
<item row="0" column="1" colspan="2" >
<widget class="QLineEdit" name="title" >
<property name="toolTip" >
<string>Regular expression group name (?P&lt;title>)</string>
@ -104,7 +106,7 @@
</property>
</widget>
</item>
<item row="1" column="1" >
<item row="1" column="1" colspan="2" >
<widget class="QLineEdit" name="authors" >
<property name="toolTip" >
<string>Regular expression group name (?P&lt;authors>)</string>
@ -124,7 +126,7 @@
</property>
</widget>
</item>
<item row="2" column="1" >
<item row="2" column="1" colspan="2" >
<widget class="QLineEdit" name="series" >
<property name="toolTip" >
<string>Regular expression group name (?P&lt;series>)</string>
@ -137,14 +139,14 @@
</property>
</widget>
</item>
<item row="3" column="0" >
<item rowspan="2" row="3" column="0" >
<widget class="QLabel" name="label_6" >
<property name="text" >
<string>Series index:</string>
</property>
</widget>
</item>
<item row="3" column="1" >
<item rowspan="2" row="3" column="1" colspan="2" >
<widget class="QLineEdit" name="series_index" >
<property name="toolTip" >
<string>Regular expression group name (?P&lt;series_index>)</string>
@ -157,6 +159,26 @@
</property>
</widget>
</item>
<item row="5" column="0" >
<widget class="QLabel" name="label_7" >
<property name="text" >
<string>ISBN:</string>
</property>
</widget>
</item>
<item row="5" column="1" >
<widget class="QLineEdit" name="isbn" >
<property name="toolTip" >
<string>Regular expression group name (?P&lt;series_index>)</string>
</property>
<property name="text" >
<string>No match</string>
</property>
<property name="readOnly" >
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>

View File

@ -163,7 +163,7 @@ class JobManager(QAbstractTableModel):
job = self.add_queue.pop()
self.waiting_jobs.append(job)
self.emit(SIGNAL('job_added(int)'), job.id, Qt.QueuedConnection)
refresh = True
for job in [job for job in self.running_jobs if job.isFinished()]:
self.running_jobs.remove(job)

View File

@ -125,6 +125,15 @@ class BooksModel(QAbstractTableModel):
db = LibraryDatabase(os.path.expanduser(db))
self.db = db
def refresh_ids(self, ids, current_row=-1):
rows = self.db.refresh_ids(ids)
for row in rows:
if row == current_row:
self.emit(SIGNAL('new_bookdisplay_data(PyQt_PyObject)'),
self.get_book_display_info(row))
self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'),
self.index(row, 0), self.index(row, self.columnCount(None)-1))
def close(self):
self.db.close()
self.db = None
@ -207,17 +216,9 @@ class BooksModel(QAbstractTableModel):
img = self.default_image
self.buffer[index] = img
def current_changed(self, current, previous, emit_signal=True):
def get_book_display_info(self, idx):
data = {}
idx = current.row()
cdata = self.cover(idx)
for key in self.buffer.keys():
if abs(key - idx) > self.buffer_size:
self.buffer.pop(key)
for i in range(max(0, idx-self.buffer_size), min(self.count(), idx+self.buffer_size)):
if not self.buffer.has_key(i):
self.load_queue.append(i)
if cdata:
data['cover'] = cdata
tags = self.db.tags(idx)
@ -241,6 +242,21 @@ class BooksModel(QAbstractTableModel):
sidx = self.db.series_index(idx)
sidx = self.__class__.roman(sidx) if self.use_roman_numbers else str(sidx)
data[_('Series')] = _('Book <font face="serif">%s</font> of %s.')%(sidx, series)
return data
def current_changed(self, current, previous, emit_signal=True):
idx = current.row()
for key in self.buffer.keys():
if abs(key - idx) > self.buffer_size:
self.buffer.pop(key)
for i in range(max(0, idx-self.buffer_size), min(self.count(), idx+self.buffer_size)):
if not self.buffer.has_key(i):
self.load_queue.append(i)
data = self.get_book_display_info(idx)
if emit_signal:
self.emit(SIGNAL('new_bookdisplay_data(PyQt_PyObject)'), data)
else:
@ -806,7 +822,7 @@ class SearchBox(QLineEdit):
QLineEdit.mouseReleaseEvent(self, event)
def text_edited_slot(self, text):
text = str(text)
text = qstring_to_unicode(text) if isinstance(text, QString) else unicode(text)
self.prev_text = text
self.timer = self.startTimer(self.__class__.INTERVAL)

View File

@ -8,7 +8,7 @@ from PyQt4.QtCore import Qt, QObject, SIGNAL, QCoreApplication, QThread, \
QVariant
from calibre import __appname__, __version__, __author__, setup_cli_handlers, islinux, Settings
from calibre.ebooks.lrf.parser import LRFDocument
from calibre.ebooks.lrf.lrfparser import LRFDocument
from calibre.gui2 import ORG_NAME, APP_UID, error_dialog, choose_files, Application
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog

View File

@ -2,13 +2,14 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, sys, textwrap, collections, traceback, shutil, time
from xml.parsers.expat import ExpatError
from functools import partial
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
QVariant, QThread, QString, QSize
QVariant, QThread, QString, QSize, QUrl
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \
QToolButton, QDialog, QSizePolicy
QToolButton, QDialog, QDesktopServices
from PyQt4.QtSvg import QSvgRenderer
from calibre import __version__, __appname__, islinux, sanitize_file_name, launch, \
from calibre import __version__, __appname__, islinux, sanitize_file_name, \
Settings, pictureflowerror, iswindows, isosx
from calibre.ptempfile import PersistentTemporaryFile
from calibre.ebooks.metadata.meta import get_metadata, get_filename_pat, set_filename_pat
@ -75,6 +76,7 @@ class Main(MainWindow, Ui_MainWindow):
self.delete_memory = {}
self.conversion_jobs = {}
self.persistent_files = []
self.metadata_dialogs = []
self.viewer_job_id = 1
self.default_thumbnail = None
self.device_error_dialog = ConversionErrorDialog(self, _('Error communicating with device'), ' ')
@ -107,7 +109,6 @@ class Main(MainWindow, Ui_MainWindow):
QObject.connect(self.job_manager, SIGNAL('job_done(int)'), self.status_bar.job_done,
Qt.QueuedConnection)
QObject.connect(self.status_bar, SIGNAL('show_book_info()'), self.show_book_info)
####################### Setup Toolbar #####################
sm = QMenu()
sm.addAction(QIcon(':/images/reader.svg'), _('Send to main memory'))
@ -198,7 +199,6 @@ class Main(MainWindow, Ui_MainWindow):
self.library_view.resizeColumnsToContents()
self.library_view.resizeRowsToContents()
self.search.setFocus(Qt.OtherFocusReason)
########################### Cover Flow ################################
self.cover_flow = None
if CoverFlow is not None:
@ -219,7 +219,6 @@ class Main(MainWindow, Ui_MainWindow):
self.setMaximumHeight(available_height())
####################### Setup device detection ########################
self.detector = DeviceDetector(sleep_time=2000)
QObject.connect(self.detector, SIGNAL('connected(PyQt_PyObject, PyQt_PyObject)'),
@ -326,7 +325,7 @@ class Main(MainWindow, Ui_MainWindow):
return
info, cp, fs = result
self.location_view.model().update_devices(cp, fs)
self.device_info = 'Connected '+' '.join(info[:-1])
self.device_info = _('Connected ')+' '.join(info[:-1])
self.vanity.setText(self.vanity_template%dict(version=self.latest_version, device=self.device_info))
func = self.device_manager.books_func()
self.job_manager.run_device_job(self.metadata_downloaded, func)
@ -571,15 +570,13 @@ class Main(MainWindow, Ui_MainWindow):
d = error_dialog(self, _('Cannot edit metadata'), _('No books selected'))
d.exec_()
return
changed = False
for row in rows:
if MetadataSingleDialog(self, row.row(),
self.library_view.model().db).changed:
changed = True
d = MetadataSingleDialog(self, row.row(),
self.library_view.model().db)
self.connect(d, SIGNAL('accepted()'), partial(self.metadata_edited, d.id), Qt.QueuedConnection)
if changed:
self.library_view.model().resort(reset=False)
self.library_view.model().research()
def metadata_edited(self, id):
self.library_view.model().refresh_ids([id], self.library_view.currentIndex().row())
def edit_bulk_metadata(self, checked):
'''
@ -861,7 +858,7 @@ class Main(MainWindow, Ui_MainWindow):
monitor=False)
self.viewer_job_id += 1
else:
launch(name)
QDesktopServices.openUrl(QUrl('file:'+name))#launch(name)
time.sleep(2) # User feedback
def view_specific_format(self, triggered):
@ -1153,6 +1150,13 @@ class Main(MainWindow, Ui_MainWindow):
self.vanity.setText(self.vanity_template%(dict(version=self.latest_version,
device=self.device_info)))
self.vanity.update()
s = Settings()
if s.get('update to version %s'%version, True):
d = question_dialog(self, _('Update available'), _('%s has been updated to version %s. See the <a href="http://calibre.kovidgoyal.net/wiki/Changelog">new features</a>. Visit the download page?')%(__appname__, version))
if d.exec_() == QMessageBox.Yes:
url = 'http://calibre.kovidgoyal.net/download_'+('windows' if iswindows else 'osx' if isosx else 'linux')
QDesktopServices.openUrl(QUrl(url))
s.set('update to version %s'%version, False)
def main(args=sys.argv):

View File

@ -27,9 +27,9 @@
<property name="geometry" >
<rect>
<x>0</x>
<y>74</y>
<y>86</y>
<width>865</width>
<height>723</height>
<height>712</height>
</rect>
</property>
<layout class="QGridLayout" >
@ -332,8 +332,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>857</width>
<height>571</height>
<width>847</width>
<height>553</height>
</rect>
</property>
<layout class="QGridLayout" >
@ -380,7 +380,7 @@
<x>0</x>
<y>0</y>
<width>865</width>
<height>74</height>
<height>86</height>
</rect>
</property>
<property name="minimumSize" >
@ -425,9 +425,9 @@
<property name="geometry" >
<rect>
<x>0</x>
<y>797</y>
<y>798</y>
<width>865</width>
<height>25</height>
<height>24</height>
</rect>
</property>
<property name="mouseTracking" >
@ -499,6 +499,9 @@
<property name="text" >
<string>Save to disk</string>
</property>
<property name="shortcut" >
<string>S</string>
</property>
</action>
<action name="action_news" >
<property name="icon" >
@ -508,6 +511,9 @@
<property name="text" >
<string>Fetch news</string>
</property>
<property name="shortcut" >
<string>F</string>
</property>
</action>
<action name="action_convert" >
<property name="icon" >
@ -517,6 +523,9 @@
<property name="text" >
<string>Convert E-books</string>
</property>
<property name="shortcut" >
<string>C</string>
</property>
</action>
<action name="action_view" >
<property name="icon" >
@ -526,6 +535,9 @@
<property name="text" >
<string>View</string>
</property>
<property name="shortcut" >
<string>V</string>
</property>
</action>
</widget>
<customwidgets>

View File

@ -1,5 +1,8 @@
import os, sys
import os, sys, glob
import sipconfig
if os.environ.get('PYQT4PATH', None):
print os.environ['PYQT4PATH']
sys.path.insert(0, os.environ['PYQT4PATH'])
from PyQt4 import pyqtconfig
# The name of the SIP build file generated by SIP and used by the build
@ -32,12 +35,15 @@ makefile = pyqtconfig.QtGuiModuleMakefile (
# Add the library we are wrapping. The name doesn't include any platform
# specific prefixes or extensions (e.g. the "lib" prefix on UNIX, or the
# ".dll" extension on Windows).
makefile.extra_lib_dirs = ['..\\..\\.build\\release', '../../.build']
if 'linux' in sys.platform:
for f in glob.glob('../../.build/libpictureflow.a'):
os.link(f, './'+os.path.basename(f))
makefile.extra_lib_dirs = ['.']
else:
makefile.extra_lib_dirs = ['..\\..\\.build\\release', '../../.build', '.']
makefile.extra_libs = ['pictureflow0' if 'win' in sys.platform and 'darwin' not in sys.platform else "pictureflow"]
makefile.extra_cflags = ['-arch i386', '-arch ppc'] if 'darwin' in sys.platform else []
makefile.extra_cxxflags = makefile.extra_cflags
if 'linux' in sys.platform:
makefile.extra_lflags = ['-Wl,--rpath=.']
# Generate the Makefile itself.
makefile.generate()

View File

@ -14,7 +14,8 @@ from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, QRect, SIGNAL, \
from calibre.gui2.jobs import DetailView
from calibre.gui2 import human_readable, NONE, TableView, qstring_to_unicode, error_dialog
from calibre.gui2.filename_pattern_ui import Ui_Form
from calibre import fit_image, get_font_families, Settings
from calibre import fit_image, Settings
from calibre.utils.fontconfig import find_font_families
from calibre.ebooks.metadata.meta import get_filename_pat, metadata_from_filename, \
set_filename_pat
@ -57,6 +58,9 @@ class FilenamePattern(QWidget, Ui_Form):
else:
self.series_index.setText(_('No match'))
self.isbn.setText(_('No match') if mi.isbn is None else str(mi.isbn))
def pattern(self):
pat = qstring_to_unicode(self.re.text())
return re.compile(pat)
@ -236,8 +240,7 @@ class FontFamilyModel(QAbstractListModel):
def __init__(self, *args):
QAbstractListModel.__init__(self, *args)
self.family_map = get_font_families()
self.families = self.family_map.keys()
self.families = find_font_families()
self.families.sort()
self.families[:0] = ['None']
@ -257,11 +260,6 @@ class FontFamilyModel(QAbstractListModel):
return QVariant(QFont(family))
return NONE
def path_of(self, family):
if family != None:
return self.family_map[family]
return None
def index_of(self, family):
return self.families.index(family.strip())

View File

@ -870,6 +870,13 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
self.data = self.cache
self.conn.commit()
def refresh_ids(self, ids):
indices = map(self.index, ids)
for id, idx in zip(ids, indices):
row = self.conn.execute('SELECT * from meta WHERE id=?', (id,)).fetchone()
self.data[idx] = row
return indices
def filter(self, filters, refilter=False, OR=False):
'''
Filter data based on filters. All the filters must match for an item to
@ -1447,8 +1454,13 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
stream.seek(0, 2)
usize = stream.tell()
stream.seek(0)
data = sqlite.Binary(compress(stream.read()))
try:
self.conn.execute('INSERT INTO data(book, format, uncompressed_size, data) VALUES (?,?,?,?)',
(id, ext, usize, sqlite.Binary(compress(stream.read()))))
(id, ext, usize, data))
except sqlite.IntegrityError:
self.conn.execute('UPDATE data SET uncompressed_size=?, data=? WHERE book=? AND format=?',
(usize, data, id, ext))
self.conn.commit()
def import_book_directory_multiple(self, dirpath):

View File

@ -39,7 +39,7 @@ entry_points = {
'fb22lrf = calibre.ebooks.lrf.fb2.convert_from:main',
'fb2-meta = calibre.ebooks.metadata.fb2:main',
'any2lrf = calibre.ebooks.lrf.any.convert_from:main',
'lrf2lrs = calibre.ebooks.lrf.parser:main',
'lrf2lrs = calibre.ebooks.lrf.lrfparser:main',
'lrs2lrf = calibre.ebooks.lrf.lrs.convert_from:main',
'pdfreflow = calibre.ebooks.lrf.pdf.reflow:main',
'isbndb = calibre.ebooks.metadata.isbndb:main',
@ -48,6 +48,7 @@ entry_points = {
'lrf2html = calibre.ebooks.lrf.html.convert_to:main',
'calibre-debug = calibre.debug:main',
'calibredb = calibre.library.cli:main',
'calibre-fontconfig = calibre.utils.fontconfig:main',
],
'gui_scripts' : [
__appname__+' = calibre.gui2.main:main',
@ -154,7 +155,7 @@ def setup_completion(fatal_errors):
from calibre.ebooks.lrf.html.convert_from import option_parser as htmlop
from calibre.ebooks.lrf.txt.convert_from import option_parser as txtop
from calibre.ebooks.lrf.meta import option_parser as metaop
from calibre.ebooks.lrf.parser import option_parser as lrf2lrsop
from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop
from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop
from calibre.ebooks.lrf.pdf.reflow import option_parser as pdfhtmlop
from calibre.ebooks.mobi.reader import option_parser as mobioeb
@ -312,6 +313,9 @@ def setup_udev_rules(group_file, reload, fatal_errors):
if not called and os.access('/etc/rc.d/rc.hald', os.X_OK):
call(('/etc/rc.d/rc.hald', 'restart'))
try:
check_call('udevadm control --reload_rules', shell=True)
except:
try:
check_call('udevcontrol reload_rules', shell=True)
except:
@ -348,7 +352,8 @@ def install_man_pages(fatal_errors):
for src in entry_points['console_scripts']:
prog = src[:src.index('=')].strip()
if prog in ('prs500', 'pdf-meta', 'epub-meta', 'lit-meta',
'markdown-calibre', 'calibre-debug', 'fb2-meta'):
'markdown-calibre', 'calibre-debug', 'fb2-meta',
'calibre-fontconfig'):
continue
help2man = ('help2man', prog, '--name', 'part of %s'%__appname__,
'--section', '1', '--no-info', '--include',
@ -428,6 +433,19 @@ MIME = '''\
</mime-info>
'''
def render_svg(image, dest):
from PyQt4.QtGui import QPainter, QImage
from PyQt4.QtSvg import QSvgRenderer
svg = QSvgRenderer(image.readAll())
painter = QPainter()
image = QImage(128,128,QImage.Format_ARGB32_Premultiplied)
painter.begin(image)
painter.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform|QPainter.HighQualityAntialiasing)
painter.setCompositionMode(QPainter.CompositionMode_SourceOver)
svg.render(painter)
painter.end()
image.save(dest)
def setup_desktop_integration(fatal_errors):
try:
from PyQt4.QtCore import QFile
@ -438,25 +456,16 @@ def setup_desktop_integration(fatal_errors):
tdir = mkdtemp()
rsvg = 'rsvg --dpi-x 600 --dpi-y 600 -w 128 -h 128 -f png '
cwd = os.getcwdu()
try:
os.chdir(tdir)
if QFile(':/images/mimetypes/lrf.svg').copy(os.path.join(tdir, 'calibre-lrf.svg')):
check_call(rsvg + 'calibre-lrf.svg calibre-lrf.png', shell=True)
render_svg(QFile(':/images/mimetypes/lrf.svg'), os.path.join(tdir, 'calibre-lrf.png'))
check_call('xdg-icon-resource install --context mimetypes --size 128 calibre-lrf.png application-lrf', shell=True)
check_call('xdg-icon-resource install --context mimetypes --size 128 calibre-lrf.png text-lrs', shell=True)
else:
raise Exception('Could not create LRF mimetype icon')
if QFile(':library').copy(os.path.join(tdir, 'calibre-gui.png')):
QFile(':library').copy(os.path.join(tdir, 'calibre-gui.png'))
check_call('xdg-icon-resource install --size 128 calibre-gui.png calibre-gui', shell=True)
else:
raise Exception('Could not creaet GUI icon')
if QFile(':/images/viewer.svg').copy(os.path.join(tdir, 'calibre-viewer.svg')):
check_call(rsvg + 'calibre-viewer.svg calibre-viewer.png', shell=True)
render_svg(QFile(':/images/viewer.svg'), os.path.join(tdir, 'calibre-viewer.png'))
check_call('xdg-icon-resource install --size 128 calibre-viewer.png calibre-viewer', shell=True)
else:
raise Exception('Could not creaet viewer icon')
f = open('calibre-lrfviewer.desktop', 'wb')
f.write(VIEWER)

View File

@ -0,0 +1,270 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
'''
Download and install the linux binary.
'''
import sys, os, shutil, tarfile, subprocess, tempfile, urllib2, re, stat
class TerminalController:
"""
A class that can be used to portably generate formatted output to
a terminal.
`TerminalController` defines a set of instance variables whose
values are initialized to the control sequence necessary to
perform a given action. These can be simply included in normal
output to the terminal:
>>> term = TerminalController()
>>> print 'This is '+term.GREEN+'green'+term.NORMAL
Alternatively, the `render()` method can used, which replaces
'${action}' with the string required to perform 'action':
>>> term = TerminalController()
>>> print term.render('This is ${GREEN}green${NORMAL}')
If the terminal doesn't support a given action, then the value of
the corresponding instance variable will be set to ''. As a
result, the above code will still work on terminals that do not
support color, except that their output will not be colored.
Also, this means that you can test whether the terminal supports a
given action by simply testing the truth value of the
corresponding instance variable:
>>> term = TerminalController()
>>> if term.CLEAR_SCREEN:
... print 'This terminal supports clearning the screen.'
Finally, if the width and height of the terminal are known, then
they will be stored in the `COLS` and `LINES` attributes.
"""
# Cursor movement:
BOL = '' #: Move the cursor to the beginning of the line
UP = '' #: Move the cursor up one line
DOWN = '' #: Move the cursor down one line
LEFT = '' #: Move the cursor left one char
RIGHT = '' #: Move the cursor right one char
# Deletion:
CLEAR_SCREEN = '' #: Clear the screen and move to home position
CLEAR_EOL = '' #: Clear to the end of the line.
CLEAR_BOL = '' #: Clear to the beginning of the line.
CLEAR_EOS = '' #: Clear to the end of the screen
# Output modes:
BOLD = '' #: Turn on bold mode
BLINK = '' #: Turn on blink mode
DIM = '' #: Turn on half-bright mode
REVERSE = '' #: Turn on reverse-video mode
NORMAL = '' #: Turn off all modes
# Cursor display:
HIDE_CURSOR = '' #: Make the cursor invisible
SHOW_CURSOR = '' #: Make the cursor visible
# Terminal size:
COLS = None #: Width of the terminal (None for unknown)
LINES = None #: Height of the terminal (None for unknown)
# Foreground colors:
BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = ''
# Background colors:
BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = ''
BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = ''
_STRING_CAPABILITIES = """
BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold
BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0
HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split()
_COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
_ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
def __init__(self, term_stream=sys.stdout):
"""
Create a `TerminalController` and initialize its attributes
with appropriate values for the current terminal.
`term_stream` is the stream that will be used for terminal
output; if this stream is not a tty, then the terminal is
assumed to be a dumb terminal (i.e., have no capabilities).
"""
# Curses isn't available on all platforms
try: import curses
except: return
# If the stream isn't a tty, then assume it has no capabilities.
if not hasattr(term_stream, 'isatty') or not term_stream.isatty(): return
# Check the terminal type. If we fail, then assume that the
# terminal has no capabilities.
try: curses.setupterm()
except: return
# Look up numeric capabilities.
self.COLS = curses.tigetnum('cols')
self.LINES = curses.tigetnum('lines')
# Look up string capabilities.
for capability in self._STRING_CAPABILITIES:
(attrib, cap_name) = capability.split('=')
setattr(self, attrib, self._tigetstr(cap_name) or '')
# Colors
set_fg = self._tigetstr('setf')
if set_fg:
for i,color in zip(range(len(self._COLORS)), self._COLORS):
setattr(self, color, curses.tparm(set_fg, i) or '')
set_fg_ansi = self._tigetstr('setaf')
if set_fg_ansi:
for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
setattr(self, color, curses.tparm(set_fg_ansi, i) or '')
set_bg = self._tigetstr('setb')
if set_bg:
for i,color in zip(range(len(self._COLORS)), self._COLORS):
setattr(self, 'BG_'+color, curses.tparm(set_bg, i) or '')
set_bg_ansi = self._tigetstr('setab')
if set_bg_ansi:
for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '')
def _tigetstr(self, cap_name):
# String capabilities can include "delays" of the form "$<2>".
# For any modern terminal, we should be able to just ignore
# these, so strip them out.
import curses
cap = curses.tigetstr(cap_name) or ''
return re.sub(r'\$<\d+>[/*]?', '', cap)
def render(self, template):
"""
Replace each $-substitutions in the given template string with
the corresponding terminal control string (if it's defined) or
'' (if it's not).
"""
return re.sub(r'\$\$|\${\w+}', self._render_sub, template)
def _render_sub(self, match):
s = match.group()
if s == '$$': return s
else: return getattr(self, s[2:-1])
#######################################################################
# Example use case: progress bar
#######################################################################
class ProgressBar:
"""
A 3-line progress bar, which looks like::
Header
20% [===========----------------------------------]
progress message
The progress bar is colored, if the terminal supports color
output; and adjusts to the width of the terminal.
"""
BAR = '%3d%% ${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}\n'
HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n'
def __init__(self, term, header):
self.term = term
if not (self.term.CLEAR_EOL and self.term.UP and self.term.BOL):
raise ValueError("Terminal isn't capable enough -- you "
"should use a simpler progress dispaly.")
self.width = self.term.COLS or 75
self.bar = term.render(self.BAR)
self.header = self.term.render(self.HEADER % header.center(self.width))
self.cleared = 1 #: true if we haven't drawn the bar yet.
def update(self, percent, message=''):
if isinstance(message, unicode):
message = message.encode('utf-8', 'ignore')
if self.cleared:
sys.stdout.write(self.header)
self.cleared = 0
n = int((self.width-10)*percent)
msg = message.center(self.width)
sys.stdout.write(
self.term.BOL + self.term.UP + self.term.CLEAR_EOL +
(self.bar % (100*percent, '='*n, '-'*(self.width-10-n))) +
self.term.CLEAR_EOL + msg)
sys.stdout.flush()
def clear(self):
if not self.cleared:
sys.stdout.write(self.term.BOL + self.term.CLEAR_EOL +
self.term.UP + self.term.CLEAR_EOL +
self.term.UP + self.term.CLEAR_EOL)
self.cleared = 1
LAUNCHER='''\
#!/bin/bash
frozen_path=%s
export ORIGWD=`pwd`
export LD_LIBRARY_PATH=$frozen_path:$LD_LIBRARY_PATH
cd $frozen_path
./%s "$@"
'''
def extract_tarball(tar, destdir):
if hasattr(tar, 'read'):
tarfile.open(fileobj=tar, mode='r').extractall(destdir)
else:
tarfile.open(tar, 'r').extractall(destdir)
def create_launchers(destdir, bindir='/usr/bin'):
for launcher in open(os.path.join(destdir, 'manifest')).readlines():
if 'postinstall' in launcher:
continue
launcher = launcher.strip()
lp = os.path.join(bindir, launcher)
print 'Creating', lp
open(lp, 'wb').write(LAUNCHER%(destdir, launcher))
os.chmod(lp, stat.S_IXUSR|stat.S_IXOTH|stat.S_IXGRP|stat.S_IREAD|stat.S_IWRITE|stat.S_IRGRP|stat.S_IROTH)
def do_postinstall(destdir):
cwd = os.getcwd()
try:
os.chdir(destdir)
os.environ['LD_LIBRARY_PATH'] = destdir+':'+os.environ.get('LD_LIBRARY_PATH', '')
subprocess.call((os.path.join(destdir, 'calibre_postinstall'),))
finally:
os.chdir(cwd)
def download_tarball():
pb = ProgressBar(TerminalController(sys.stdout), 'Downloading calibre...')
src = urllib2.urlopen('http://calibre.kovidgoyal.net/downloads/latest-linux-binary.tar.bz2')
size = int(src.info()['content-length'])
f = tempfile.NamedTemporaryFile()
while f.tell() < size:
f.write(src.read(4*1024))
pb.update(f.tell()/float(size))
f.seek(0)
return f
def main(args=sys.argv):
defdir = '/opt/calibre'
destdir = raw_input('Enter the installation directory for calibre [%s]: '%defdir)
if not destdir:
destdir = defdir
if os.path.exists(destdir):
shutil.rmtree(destdir)
os.makedirs(destdir)
f = download_tarball()
print 'Extracting...'
extract_tarball(f, destdir)
create_launchers(destdir)
do_postinstall(destdir)
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -11,7 +11,7 @@ from calibre.ebooks.lrf.any.convert_from import main as any2lrf
from calibre.ebooks.lrf.web.convert_from import main as web2lrf
from calibre.ebooks.lrf.feeds.convert_from import main as feeds2lrf
from calibre.gui2.lrf_renderer.main import main as lrfviewer
from calibre import iswindows, __appname__
from calibre import iswindows, __appname__, islinux
try:
from calibre.utils.single_qt_application import SingleApplication
except:
@ -43,6 +43,10 @@ if iswindows:
python = os.path.join(os.path.dirname(python), 'Scripts\\parallel.exe')
popen = partial(subprocess.Popen, creationflags=0x08) # CREATE_NO_WINDOW=0x08 so that no ugly console is popped up
if islinux and hasattr(sys, 'frozen_path'):
python = os.path.join(getattr(sys, 'frozen_path'), 'parallel')
popen = partial(subprocess.Popen, cwd=getattr(sys, 'frozen_path'))
def cleanup(tdir):
try:
import shutil

View File

@ -11,7 +11,7 @@ from trac.util import Markup
__appname__ = 'calibre'
DOWNLOAD_DIR = '/var/www/calibre.kovidgoyal.net/htdocs/downloads'
LINUX_INSTALLER = '/var/www/calibre.kovidgoyal.net/calibre/src/calibre/linux_installer.py'
class OS(dict):
@ -31,14 +31,12 @@ class Distribution(object):
('libusb', '0.1.12', None, None, None),
('Qt', '4.4.0', 'qt', 'libqt4-core libqt4-gui', 'qt4'),
('PyQt', '4.4.2', 'PyQt4', 'python-qt4', 'PyQt4'),
('fonttools', '2.0-beta1', 'fonttools', 'fonttools', 'fonttools'),
('mechanize for python', '0.1.7b', 'dev-python/mechanize', 'python-mechanize', 'python-mechanize'),
('ImageMagick', '6.3.5', 'imagemagick', 'imagemagick', 'ImageMagick'),
('xdg-utils', '1.0.2', 'xdg-utils', 'xdg-utils', 'xdg-utils'),
('dbus-python', '0.82.2', 'dbus-python', 'python-dbus', 'dbus-python'),
('convertlit', '1.8', 'convertlit', None, None),
('lxml', '1.3.3', 'lxml', 'python-lxml', 'python-lxml'),
('librsvg', '2.0.0', 'librsvg', 'librsvg2-bin', 'librsvg2'),
('genshi', '0.4.4', 'genshi', 'python-genshi', 'python-genshi'),
('help2man', '1.36.4', 'help2man', 'help2man', 'help2man'),
]
@ -120,6 +118,8 @@ class Download(Component):
add_stylesheet(req, 'dl/css/download.css')
if req.path_info == '/download':
return self.top_level(req)
elif req.path_info == '/download_linux_binary_installer':
req.send(open(LINUX_INSTALLER).read(), 'text/x-python')
else:
match = re.match(r'\/download_(\S+)', req.path_info)
if match:
@ -130,6 +130,8 @@ class Download(Component):
return self.osx(req)
elif os == 'linux':
return self.linux(req)
elif 'binary' in os:
return self.linux_binary(req)
else:
return self.linux_distro(req, os)
@ -190,6 +192,10 @@ You can uninstall a driver by right clicking on it and selecting uninstall.
'''%dict(appname=__appname__)))
return 'binary.html', data, None
def linux_binary(self, req):
version = self.version_from_filename()
return 'pyinstaller.html', {'app':__appname__, 'version':version}, None
def osx(self, req):
version = self.version_from_filename()
file = 'calibre-%s.dmg'%(version,)
@ -226,6 +232,7 @@ If not, head over to <a href="http://calibre.kovidgoyal.net/wiki/Development#Tra
def linux(self, req):
operating_systems = [
OS({'name' : 'binary', 'title': 'All distros'}),
OS({'name' : 'gentoo', 'title': 'Gentoo'}),
OS({'name' : 'ubuntu', 'title': 'Ubuntu'}),
OS({'name' : 'fedora', 'title': 'Fedora'}),

View File

@ -34,7 +34,7 @@
<div py:if="distro.is_generic">
<ol>
<li>Make sure that your system has <code>python &gt;= 2.5</code></li>
<li>Install the various dependencies listed below: Make sure that any python packages are installed into python2.5 (e.g. setuptools, python-imaging, PyQt4, fonttools, etc)</li>
<li>Install the various dependencies listed below: Make sure that any python packages are installed into python2.5 (e.g. setuptools, python-imaging, PyQt4, etc)</li>
<li>As root run the command <pre class="wiki">easy_install-2.5 -U TTFQuery calibre &amp;&amp; calibre_postinstall</pre></li>
</ol>
<h2>Dependencies</h2>

View File

@ -0,0 +1,47 @@
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="layout.html" />
<head>
<title>Download $app for Linux</title>
</head>
<body>
<div id="ctxtnav" class="nav"></div>
<div id="content" class="binary">
<h1>Download $app for Linux</h1>
<p>This binary package is compatible with most recent linux distributions running on Intel 32 bit CPUs.</p>
<p>
<img width="50" height="50" style="border:1px red solid" src="${href.chrome('/dl/images/binary_logo.png')}" />
(Version: $version <a href="/wiki/Changelog">Changelog</a>)
</p>
<p>To install, copy paste the following command into a terminal and press Enter:
</p>
<pre class="wiki">sudo python -c "import urllib2; exec urllib2.urlopen('http://calibre.kovidgoyal.net/download_linux_binary_installer').read(); main()"</pre>
<p>
While you wait for the download to complete, please consider donating to support the development
of ${app}.</p>
<div>
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_s-xclick" />
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!" />
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHbwYJKoZIhvcNAQcEoIIHYDCCB1wCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYBn7jneGiSLVO8rcDrBtOUXL+HftY+CiC47hTntwICio6qqpLKezIryyG8tKcjY58Rcocur/kDwljEutIafVG7XRA7BJL9eZdHAZsZdX04f4dApzkWwR9w6GQhj0kwmO2ZNE878UcgGZBve4qQKWM8bf2pMY7vJwCNoo6ozpIi3VTELMAkGBSsOAwIaBQAwgewGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIBTALt7s1gJmAgcjEAwUMRYeIdIOE/yi0Y5vrVKBFxOUCbqTx/lu3Rk4EHsODZXLHT+BDA5WSWYO3AXfv2Lmlv1kJ7jWrjUVirYoQ5M4qdIhY9DtvPioIMMRoTJmYM9JKH8n2TWcjJ1XIzIuDP4zn8/Ya9hap3RHOrj2RBj89g7iSuFRsjoA0PYZgtWAKwR7g3LLpjRachn041JO55BEd3YWUgorNQeo3WEHgowLFfTWgFFePkm8OoWA1klWkYp4S07IhX5NaRc8OegkdshpkiIHGAKCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA4MDQzMDE1MzkyMlowIwYJKoZIhvcNAQkEMRYEFJSI9/zWx7TUlKPY7kLjnvzB1h6sMA0GCSqGSIb3DQEBAQUABIGAikZNCmQdkWPdfmYnGqOb1f65ViaK0zjHf50azvsigWQLlhHqJ3PgB+jEJH3JU9Pm9M4wgiK23Bg2oIGuIsAfQkYO9mw/HjtDtOQHqXyZZbrM32YGtNWUD4ynakLYnaz7OnPl40aTPD4iDApgsGcj1oMdmw7KA2E9J0l2J9iJXF4=-----END PKCS7-----" />
</form>
</div>
<h2>Note</h2>
<div class="note">
<ul>
<li>This installer is very new and has only been tested ona couple of systems, so if you encounter
problems, please report them.</li>
<li>You shoud have help2man and xdg-utils installed on your system.</li>
</ul>
</div>
</div>
</body>
</html>

View File

@ -3,7 +3,6 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''
Manage translation of user visible strings.
'''
import sys, os, cStringIO, tempfile, subprocess, functools, tarfile, re, time, \
glob, urllib2, shutil
check_call = functools.partial(subprocess.check_call, shell=True)
@ -12,7 +11,8 @@ try:
from calibre.translations.pygettext import main as pygettext
from calibre.translations.msgfmt import main as msgfmt
except ImportError:
sys.path.insert(1, os.path.abspath('..'))
cwd = os.getcwd()
sys.path.insert(0, os.path.dirname(os.path.dirname(cwd)))
from calibre.translations.pygettext import main as pygettext
from calibre.translations.msgfmt import main as msgfmt
@ -83,4 +83,8 @@ def main(args=sys.argv):
return 0
if __name__ == '__main__':
cwd = os.getcwd()
sys.path.insert(0, os.path.dirname(os.path.dirname(cwd)))
sys.exit(main())

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6,43 +6,4 @@ __docformat__ = 'restructuredtext en'
'''
Miscelleaneous utilities.
'''
import subprocess
from calibre import iswindows, isosx
from calibre.ptempfile import PersistentTemporaryFile
def sendmail(recipient='', subject='', attachments=[], body=''):
if not recipient:
recipient = 'someone@somewhere.net'
if isinstance(recipient, unicode):
recipient = recipient.encode('utf-8')
if isinstance(subject, unicode):
subject = subject.encode('utf-8')
if isinstance(body, unicode):
body = body.encode('utf-8')
for i, src in enumerate(attachments):
if isinstance(src, unicode):
attachments[i] = src.encode('utf-8')
if iswindows:
from calibre.utils.simplemapi import SendMail
SendMail(recipient, subject=subject, body=body, attachfiles=';'.join(attachments))
elif isosx:
pt = PersistentTemporaryFile(suffix='.txt')
pt.write(body)
if attachments:
pt.write('\n\n----\n\n')
pt.write(open(attachments[0], 'rb').read())
pt.close()
subprocess.Popen(('open', '-t', pt.name))
else:
body = '"' + body.replace('"', '\\"') + '"'
subject = '"' + subject.replace('"', '\\"') + '"'
attach = ''
if attachments:
attach = attachments[0]
attach = '"' + attach.replace('"', '\\"') + '"'
attach = '--attach '+attach
subprocess.check_call('xdg-email --utf8 --subject %s --body %s %s %s'%(subject, body, attach, recipient), shell=True)

View File

@ -0,0 +1,322 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
'''
:mod:`fontconfig` -- Query system fonts
=============================================
.. module:: fontconfig
:platform: Unix, Windows, OS X
:synopsis: Query system fonts
.. moduleauthor:: Kovid Goyal <kovid@kovidgoyal.net>
A ctypes based wrapper around the `fontconfig <http://fontconfig.org>`_ library.
It can be used to find all fonts available on the system as well as the closest
match to a given font specification. The main functions in this module are:
.. autofunction:: find_font_families
.. autofunction:: files_for_family
.. autofunction:: match
'''
import sys, os, locale, codecs
from ctypes import cdll, c_void_p, Structure, c_int, POINTER, c_ubyte, c_char, \
pointer, byref, create_string_buffer, Union, c_char_p, c_double
try:
preferred_encoding = locale.getpreferredencoding()
codecs.lookup(preferred_encoding)
except:
preferred_encoding = 'utf-8'
iswindows = 'win32' in sys.platform or 'win64' in sys.platform
isosx = 'darwin' in sys.platform
def load_library():
if isosx:
lib = 'libfontconfig.1.dylib'
if hasattr(sys, 'frameworks_dir'):
lib = os.path.join(getattr(sys, 'frameworks_dir'), lib)
return cdll.LoadLibrary(lib)
elif iswindows:
return cdll.LoadLibrary('libfontconfig-1')
else:
try:
return cdll.LoadLibrary('libfontconfig.so')
except:
return cdll.LoadLibrary('libfontconfig.so.1')
class FcPattern(Structure):
_fields_ = [
('num', c_int),
('size', c_int),
('elts_offset', c_void_p),
('ref', c_int)
]
class FcFontSet(Structure):
_fields_ = [
('nfont', c_int),
('sfont', c_int),
('fonts', POINTER(POINTER(FcPattern)))
]
(
FcTypeVoid,
FcTypeInteger,
FcTypeDouble,
FcTypeString,
FcTypeBool,
FcTypeMatrix,
FcTypeCharSet,
FcTypeFTFace,
FcTypeLangSet
) = map(c_int, range(9))
(FcMatchPattern, FcMatchFont, FcMatchScan) = map(c_int, range(3))
(
FcResultMatch, FcResultNoMatch, FcResultTypeMismatch, FcResultNoId,
FcResultOutOfMemory
) = map(c_int, range(5))
FcFalse, FcTrue = c_int(0), c_int(1)
class _FcValue(Union):
_fields_ = [
('s', c_char_p),
('i', c_int),
('b', c_int),
('d', c_double),
]
class FcValue(Structure):
_fields_ = [
('type', c_int),
('u', _FcValue)
]
lib = load_library()
lib.FcPatternCreate.restype = c_void_p
lib.FcObjectSetCreate.restype = c_void_p
lib.FcFontSetDestroy.argtypes = [POINTER(FcFontSet)]
lib.FcFontList.restype = POINTER(FcFontSet)
lib.FcNameUnparse.argtypes = [POINTER(FcPattern)]
lib.FcNameUnparse.restype = POINTER(c_ubyte)
lib.FcPatternGetString.argtypes = [POINTER(FcPattern), POINTER(c_char), c_int, c_void_p]
lib.FcPatternGetString.restype = c_int
lib.FcPatternAdd.argtypes = [c_void_p, POINTER(c_char), FcValue, c_int]
lib.FcPatternGetInteger.argtypes = [POINTER(FcPattern), POINTER(c_char), c_int, POINTER(c_int)]
lib.FcPatternGetInteger.restype = c_int
lib.FcNameParse.argtypes = [c_char_p]
lib.FcNameParse.restype = POINTER(FcPattern)
lib.FcDefaultSubstitute.argtypes = [POINTER(FcPattern)]
lib.FcConfigSubstitute.argtypes = [c_void_p, POINTER(FcPattern), c_int]
lib.FcFontSetCreate.restype = POINTER(FcFontSet)
lib.FcFontMatch.argtypes = [c_void_p, POINTER(FcPattern), POINTER(c_int)]
lib.FcFontMatch.restype = POINTER(FcPattern)
lib.FcFontSetAdd.argtypes = [POINTER(FcFontSet), POINTER(FcPattern)]
lib.FcFontSort.argtypes = [c_void_p, POINTER(FcPattern), c_int, c_void_p, POINTER(c_int)]
lib.FcFontSort.restype = POINTER(FcFontSet)
lib.FcFontRenderPrepare.argtypes = [c_void_p, POINTER(FcPattern), POINTER(FcPattern)]
lib.FcFontRenderPrepare.restype = POINTER(FcPattern)
if not lib.FcInit():
raise RuntimeError(_('Could not initialize the fontconfig library'))
def find_font_families(allowed_extensions=['ttf']):
'''
Return an alphabetically sorted list of font families available on the system.
`allowed_extensions`: A list of allowed extensions for font file types. Defaults to
`['ttf']`. If it is empty, it is ignored.
'''
allowed_extensions = [i.lower() for i in allowed_extensions]
empty_pattern = lib.FcPatternCreate()
oset = lib.FcObjectSetCreate()
if not lib.FcObjectSetAdd(oset, 'file'):
raise RuntimeError('Allocation failure')
if not lib.FcObjectSetAdd(oset, 'family'):
raise RuntimeError('Allocation failure')
fs = lib.FcFontList(0, empty_pattern, oset)
font_set = fs.contents
file = pointer(create_string_buffer(chr(0), 5000))
family = pointer(create_string_buffer(chr(0), 200))
font_families = []
for i in range(font_set.nfont):
pat = font_set.fonts[i]
if lib.FcPatternGetString(pat, 'file', 0, byref(file)) != FcResultMatch.value:
raise RuntimeError('Error processing pattern')
path = str(file.contents.value)
ext = os.path.splitext(path)[1]
if ext:
ext = ext[1:].lower()
if allowed_extensions and ext in allowed_extensions:
if lib.FcPatternGetString(pat, 'family', 0, byref(family)) != FcResultMatch.value:
raise RuntimeError('Error processing pattern')
font_families.append(str(family.contents.value))
lib.FcObjectSetDestroy(oset)
lib.FcPatternDestroy(empty_pattern)
lib.FcFontSetDestroy(fs)
font_families = list(set(font_families))
font_families.sort()
return font_families
def files_for_family(family, normalize=True):
'''
Find all the variants in the font family `family`.
Returns a dictionary of tuples. Each tuple is of the form (Full font name, path to font file).
The keys of the dictionary depend on `normalize`. If `normalize` is `False`,
they are a tuple (slant, weight) otherwise they are strings from the set
`('normal', 'bold', 'italic', 'bi', 'light', 'li')`
'''
if isinstance(family, unicode):
family = family.encode(preferred_encoding)
family_pattern = lib.FcPatternBuild(0, 'family', FcTypeString, family, 0)
if not family_pattern:
raise RuntimeError('Allocation failure')
#lib.FcPatternPrint(family_pattern)
oset = lib.FcObjectSetCreate()
if not lib.FcObjectSetAdd(oset, 'file'):
raise RuntimeError('Allocation failure')
if not lib.FcObjectSetAdd(oset, 'weight'):
raise RuntimeError('Allocation failure')
if not lib.FcObjectSetAdd(oset, 'fullname'):
raise RuntimeError('Allocation failure')
if not lib.FcObjectSetAdd(oset, 'slant'):
raise RuntimeError('Allocation failure')
if not lib.FcObjectSetAdd(oset, 'style'):
raise RuntimeError('Allocation failure')
fonts = {}
fs = lib.FcFontList(0, family_pattern, oset)
font_set = fs.contents
file = pointer(create_string_buffer(chr(0), 5000))
full_name = pointer(create_string_buffer(chr(0), 200))
weight = c_int(0)
slant = c_int(0)
fname = ''
for i in range(font_set.nfont):
pat = font_set.fonts[i]
#lib.FcPatternPrint(pat)
pat = font_set.fonts[i]
if lib.FcPatternGetString(pat, 'file', 0, byref(file)) != FcResultMatch.value:
raise RuntimeError('Error processing pattern')
if lib.FcPatternGetInteger(pat, 'weight', 0, byref(weight)) != FcResultMatch.value:
raise RuntimeError('Error processing pattern')
if lib.FcPatternGetString(pat, 'fullname', 0, byref(full_name)) != FcResultMatch.value:
if lib.FcPatternGetString(pat, 'fullname', 0, byref(full_name)) == FcResultNoMatch.value:
if lib.FcPatternGetString(pat, 'style', 0, byref(full_name)) != FcResultMatch.value:
raise RuntimeError('Error processing pattern')
fname = family + ' ' + full_name.contents.value
else:
raise RuntimeError('Error processing pattern')
else:
fname = full_name.contents.value
if lib.FcPatternGetInteger(pat, 'slant', 0, byref(slant)) != FcResultMatch.value:
raise RuntimeError('Error processing pattern')
style = (slant.value, weight.value)
if normalize:
italic = slant.value > 0
normal = weight.value == 80
bold = weight.value > 80
if italic:
style = 'italic' if normal else 'bi' if bold else 'li'
else:
style = 'normal' if normal else 'bold' if bold else 'light'
fonts[style] = (file.contents.value, fname)
lib.FcObjectSetDestroy(oset)
lib.FcPatternDestroy(family_pattern)
if not iswindows:
lib.FcFontSetDestroy(fs)
return fonts
def match(name, sort=False, verbose=False):
'''
Find the system font that most closely matches `name`, where `name` is a specification
of the form::
familyname-<pointsize>:<property1=value1>:<property2=value2>...
For example, `verdana:weight=bold:slant=italic`
Returns a list of dictionaries. Each dictionary has the keys: 'weight', 'slant', 'family', 'file'
`sort`: If `True` return a sorted list of matching fonts, where the sort id in order of
decreasing closeness of matching.
`verbose`: If `True` print debugging information to stdout
'''
if isinstance(name, unicode):
name = name.encode(preferred_encoding)
pat = lib.FcNameParse(name)
if not pat:
raise ValueError('Could not parse font name')
if verbose:
print 'Searching for pattern'
lib.FcPatternPrint(pat)
if not lib.FcConfigSubstitute(0, pat, FcMatchPattern):
raise RuntimeError('Allocation failure')
lib.FcDefaultSubstitute(pat)
fs = lib.FcFontSetCreate()
result = c_int(0)
matches = []
if sort:
font_patterns = lib.FcFontSort(0, pat, FcFalse, 0, byref(result))
if not font_patterns:
raise RuntimeError('Allocation failed')
fps = font_patterns.contents
for j in range(fps.nfont):
fpat = fps.fonts[j]
fp = lib.FcFontRenderPrepare(0, pat, fpat)
if fp:
lib.FcFontSetAdd(fs, fp)
lib.FcFontSetDestroy(font_patterns)
else:
match_pat = lib.FcFontMatch(0, pat, byref(result))
if pat:
lib.FcFontSetAdd(fs, match_pat)
if result.value != FcResultMatch.value:
lib.FcPatternDestroy(pat)
return matches
font_set = fs.contents
file = pointer(create_string_buffer(chr(0), 5000))
family = pointer(create_string_buffer(chr(0), 200))
weight = c_int(0)
slant = c_int(0)
for j in range(font_set.nfont):
fpat = font_set.fonts[j]
#lib.FcPatternPrint(fpat)
if lib.FcPatternGetString(fpat, 'file', 0, byref(file)) != FcResultMatch.value:
raise RuntimeError('Error processing pattern')
if lib.FcPatternGetString(fpat, 'family', 0, byref(family)) != FcResultMatch.value:
raise RuntimeError('Error processing pattern')
if lib.FcPatternGetInteger(fpat, 'weight', 0, byref(weight)) != FcResultMatch.value:
raise RuntimeError('Error processing pattern')
if lib.FcPatternGetInteger(fpat, 'slant', 0, byref(slant)) != FcResultMatch.value:
raise RuntimeError('Error processing pattern')
matches.append({
'file' : file.contents.value,
'family' : family.contents.value,
'weight' : weight.value,
'slant' : slant.value,
}
)
lib.FcPatternDestroy(pat)
lib.FcFontSetDestroy(fs)
return matches
def main(args=sys.argv):
print find_font_families()
if len(args) > 1:
print
print files_for_family(args[1])
print
print match(args[1], verbose=True)
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -1,262 +0,0 @@
"""
Copyright (c) 2007 Ian Cook and John Popplewell
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Date : 13 August 2007
Version : 1.0.2
Contact : John Popplewell
Email : john@johnnypops.demon.co.uk
Web : http://www.johnnypops.demon.co.uk/python/
Origin : Based on the original script by Ian Cook
http://www.kirbyfooty.com/simplemapi.py
Comments: Works (and tested) with:
Outlook Express, Outlook 97 and 2000,
Eudora, Incredimail and Mozilla Thunderbird (1.5.0.2)
Thanks : Werner F. Bruhin and Michele Petrazzo on the ctypes list.
Lukas Lalinsky for patches and encouragement.
If you have any bug-fixes, enhancements or suggestions regarding this
software, please contact me at the above email address.
"""
import os
from ctypes import *
FLAGS = c_ulong
LHANDLE = c_ulong
LPLHANDLE = POINTER(LHANDLE)
# Return codes
SUCCESS_SUCCESS = 0
MAPI_USER_ABORT = 1
MAPI_E_USER_ABORT = MAPI_USER_ABORT
MAPI_E_FAILURE = 2
MAPI_E_LOGON_FAILURE = 3
MAPI_E_LOGIN_FAILURE = MAPI_E_LOGON_FAILURE
MAPI_E_DISK_FULL = 4
MAPI_E_INSUFFICIENT_MEMORY = 5
MAPI_E_ACCESS_DENIED = 6
MAPI_E_TOO_MANY_SESSIONS = 8
MAPI_E_TOO_MANY_FILES = 9
MAPI_E_TOO_MANY_RECIPIENTS = 10
MAPI_E_ATTACHMENT_NOT_FOUND = 11
MAPI_E_ATTACHMENT_OPEN_FAILURE = 12
MAPI_E_ATTACHMENT_WRITE_FAILURE = 13
MAPI_E_UNKNOWN_RECIPIENT = 14
MAPI_E_BAD_RECIPTYPE = 15
MAPI_E_NO_MESSAGES = 16
MAPI_E_INVALID_MESSAGE = 17
MAPI_E_TEXT_TOO_LARGE = 18
MAPI_E_INVALID_SESSION = 19
MAPI_E_TYPE_NOT_SUPPORTED = 20
MAPI_E_AMBIGUOUS_RECIPIENT = 21
MAPI_E_AMBIG_RECIP = MAPI_E_AMBIGUOUS_RECIPIENT
MAPI_E_MESSAGE_IN_USE = 22
MAPI_E_NETWORK_FAILURE = 23
MAPI_E_INVALID_EDITFIELDS = 24
MAPI_E_INVALID_RECIPS = 25
MAPI_E_NOT_SUPPORTED = 26
# Recipient class
MAPI_ORIG = 0
MAPI_TO = 1
# Send flags
MAPI_LOGON_UI = 1
MAPI_DIALOG = 8
class MapiRecipDesc(Structure):
_fields_ = [
('ulReserved', c_ulong),
('ulRecipClass', c_ulong),
('lpszName', c_char_p),
('lpszAddress', c_char_p),
('ulEIDSize', c_ulong),
('lpEntryID', c_void_p),
]
lpMapiRecipDesc = POINTER(MapiRecipDesc)
lppMapiRecipDesc = POINTER(lpMapiRecipDesc)
class MapiFileDesc(Structure):
_fields_ = [
('ulReserved', c_ulong),
('flFlags', c_ulong),
('nPosition', c_ulong),
('lpszPathName', c_char_p),
('lpszFileName', c_char_p),
('lpFileType', c_void_p),
]
lpMapiFileDesc = POINTER(MapiFileDesc)
class MapiMessage(Structure):
_fields_ = [
('ulReserved', c_ulong),
('lpszSubject', c_char_p),
('lpszNoteText', c_char_p),
('lpszMessageType', c_char_p),
('lpszDateReceived', c_char_p),
('lpszConversationID', c_char_p),
('flFlags', FLAGS),
('lpOriginator', lpMapiRecipDesc),
('nRecipCount', c_ulong),
('lpRecips', lpMapiRecipDesc),
('nFileCount', c_ulong),
('lpFiles', lpMapiFileDesc),
]
lpMapiMessage = POINTER(MapiMessage)
MAPI = windll.mapi32
MAPISendMail = MAPI.MAPISendMail
MAPISendMail.restype = c_ulong
MAPISendMail.argtypes = (LHANDLE, c_ulong, lpMapiMessage, FLAGS, c_ulong)
MAPIResolveName = MAPI.MAPIResolveName
MAPIResolveName.restype = c_ulong
MAPIResolveName.argtypes= (LHANDLE, c_ulong, c_char_p, FLAGS, c_ulong, lppMapiRecipDesc)
MAPIFreeBuffer = MAPI.MAPIFreeBuffer
MAPIFreeBuffer.restype = c_ulong
MAPIFreeBuffer.argtypes = (c_void_p, )
MAPILogon = MAPI.MAPILogon
MAPILogon.restype = c_ulong
MAPILogon.argtypes = (LHANDLE, c_char_p, c_char_p, FLAGS, c_ulong, LPLHANDLE)
MAPILogoff = MAPI.MAPILogoff
MAPILogoff.restype = c_ulong
MAPILogoff.argtypes = (LHANDLE, c_ulong, FLAGS, c_ulong)
class MAPIError(WindowsError):
def __init__(self, code):
WindowsError.__init__(self)
self.code = code
def __str__(self):
return 'MAPI error %d' % (self.code,)
def _logon(profileName=None, password=None):
pSession = LHANDLE()
rc = MAPILogon(0, profileName, password, MAPI_LOGON_UI, 0, byref(pSession))
if rc != SUCCESS_SUCCESS:
raise MAPIError, rc
return pSession
def _logoff(session):
rc = MAPILogoff(session, 0, 0, 0)
if rc != SUCCESS_SUCCESS:
raise MAPIError, rc
def _resolveName(session, name):
pRecipDesc = lpMapiRecipDesc()
rc = MAPIResolveName(session, 0, name, 0, 0, byref(pRecipDesc))
if rc != SUCCESS_SUCCESS:
raise MAPIError, rc
rd = pRecipDesc.contents
name, address = rd.lpszName, rd.lpszAddress
rc = MAPIFreeBuffer(pRecipDesc)
if rc != SUCCESS_SUCCESS:
raise MAPIError, rc
return name, address
def _sendMail(session, recipient, subject, body, attach, preview):
nFileCount = len(attach)
if attach:
MapiFileDesc_A = MapiFileDesc * len(attach)
fda = MapiFileDesc_A()
for fd, fa in zip(fda, attach):
fd.ulReserved = 0
fd.flFlags = 0
fd.nPosition = -1
fd.lpszPathName = fa
fd.lpszFileName = None
fd.lpFileType = None
lpFiles = fda
else:
lpFiles = lpMapiFileDesc()
RecipWork = recipient.split(';')
RecipCnt = len(RecipWork)
MapiRecipDesc_A = MapiRecipDesc * len(RecipWork)
rda = MapiRecipDesc_A()
for rd, ra in zip(rda, RecipWork):
rd.ulReserved = 0
rd.ulRecipClass = MAPI_TO
try:
rd.lpszName, rd.lpszAddress = _resolveName(session, ra)
except WindowsError:
# work-round for Mozilla Thunderbird
rd.lpszName, rd.lpszAddress = None, ra
rd.ulEIDSize = 0
rd.lpEntryID = None
recip = rda
msg = MapiMessage(0, subject, body, None, None, None, 0, lpMapiRecipDesc(),
RecipCnt, recip,
nFileCount, lpFiles)
flags = 0
if preview:
flags = MAPI_DIALOG
rc = MAPISendMail(session, 0, byref(msg), flags, 0)
if rc != SUCCESS_SUCCESS:
raise MAPIError, rc
def SendMail(recipient, subject="", body="", attachfiles="", preview=1):
"""Post an e-mail message using Simple MAPI
recipient - string: address to send to (multiple addresses separated with a semicolon)
subject - string: subject header
body - string: message text
attach - string: files to attach (multiple attachments separated with a semicolon)
preview - bool : if false, minimise user interaction. Default:true
"""
attach = []
AttachWork = attachfiles.split(';')
for filename in AttachWork:
if os.path.exists(filename):
attach.append(filename)
attach = map(os.path.abspath, attach)
restore = os.getcwd()
try:
session = _logon()
try:
_sendMail(session, recipient, subject, body, attach, preview)
finally:
_logoff(session)
finally:
os.chdir(restore)
if __name__ == '__main__':
import sys
recipient = "test@johnnypops.demon.co.uk"
subject = "Test Message Subject"
body = "Hi,\r\n\r\nthis is a quick test message,\r\n\r\ncheers,\r\nJohn."
attachment = sys.argv[0]
SendMail(recipient, subject, body, attachment)

View File

@ -435,7 +435,7 @@ class BasicNewsRecipe(object, LoggingInterface):
self.simultaneous_downloads = 1
self.navbar = templates.NavBarTemplate()
self.html2lrf_options.extend(['--page-break-before', '$', '--use-spine', '--header'])
self.html2lrf_options.extend(['--page-break-before', '$', '--use-spine', '--header', '--encoding', 'utf-8'])
if '--base-font-size' not in self.html2lrf_options:
self.html2lrf_options.extend(['--base-font-size', '12'])
self.failed_downloads = []
@ -645,9 +645,9 @@ class BasicNewsRecipe(object, LoggingInterface):
html = self.feed2index(feed)
feed_dir = os.path.join(self.output_dir, 'feed_%d'%f)
open(os.path.join(feed_dir, 'index.html'), 'wb').write(html)
self.create_opf(feeds)
self.report_progress(1, _('Feeds downloaded to %s')%index)
return index
def download_cover(self):
@ -704,7 +704,7 @@ class BasicNewsRecipe(object, LoggingInterface):
entries.append(relp.replace(os.sep, '/'))
last = sp
src = open(last, 'rb').read()
src = open(last, 'rb').read().decode('utf-8')
soup = BeautifulSoup(src)
body = soup.find('body')
if body is not None:

View File

@ -8,7 +8,7 @@ recipes = [
'newsweek', 'atlantic', 'economist', 'portfolio',
'nytimes', 'usatoday', 'outlook_india', 'bbc', 'greader', 'wsj',
'wired', 'globe_and_mail', 'smh', 'espn', 'business_week',
'ars_technica',
'ars_technica', 'upi',
]
import re, imp, inspect, time

View File

@ -0,0 +1,40 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
import re
from calibre.web.feeds.news import BasicNewsRecipe
class UnitedPressInternational(BasicNewsRecipe):
title = 'United Press International'
max_articles_per_feed = 15
html2lrf_options = ['--override-css= "H1 {font-family: Arial; font-weight: bold; color: #000000; size: 10pt;}"']
preprocess_regexps = [ (re.compile(i[0], re.IGNORECASE | re.DOTALL), i[1]) for i in
[
(r'<HEAD>.*?</HEAD>' , lambda match : '<HEAD></HEAD>'),
(r'<div id="apple-rss-sidebar-background">.*?<!-- start Entries -->', lambda match : ''),
(r'<!-- end apple-rss-content-area -->.*?</body>', lambda match : '</body>'),
(r'<script.*?>.*?</script>', lambda match : ''),
(r'<body onload=.*?>.*?<a href="http://www.upi.com">', lambda match : '<body style="font: 8pt arial;">'),
##(r'<div class=\'headerDIV\'><h2><a style="color: #990000;" href="http://www.upi.com/NewsTrack/Top_News/">Top News</a></h2></div>.*?<br clear="all">', lambda match : ''),
(r'<script src="http://www.g.*?>.*?</body>', lambda match : ''),
(r'<span style="font: 16pt arial', lambda match : '<span style="font: 12pt arial'),
]
]
def get_feeds(self):
return [ ('Top Stories', 'http://www.upi.com/rss/NewsTrack/Top_News/'),
('Science', 'http://www.upi.com/rss/NewsTrack/Science/'),
('Heatlth', 'http://www.upi.com/rss/NewsTrack/Health/'),
('Quirks', 'http://www.upi.com/rss/NewsTrack/Quirks/'),
]
def print_version(self, url):
return url + 'print/'

View File

@ -7,7 +7,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
Fetch a webpage and its links recursively. The webpages are saved to disk in
UTF-8 encoding with any charset declarations removed.
'''
import sys, socket, os, urlparse, codecs, logging, re, time, copy, urllib2, threading, traceback
import sys, socket, os, urlparse, logging, re, time, copy, urllib2, threading, traceback
from urllib import url2pathname
from httplib import responses
@ -43,8 +43,9 @@ def save_soup(soup, target):
if path and os.path.isfile(path) and os.path.exists(path) and os.path.isabs(path):
tag[key] = relpath(path, selfdir).replace(os.sep, '/')
f = codecs.open(target, 'w', 'utf-8')
f.write(unicode(soup))
f = open(target, 'wb')
html = unicode(soup)
f.write(html.encode('utf-8'))
f.close()

168
upload.py
View File

@ -53,7 +53,9 @@ def build_installer(installer, vm, timeout=25):
return os.path.basename(installer)
def installer_name(ext):
if ext in ('exe', 'dmg'):
return 'dist/%s-%s.%s'%(__appname__, __version__, ext)
return 'dist/%s-%s-i686.%s'%(__appname__, __version__, ext)
def build_windows():
installer = installer_name('exe')
@ -76,9 +78,154 @@ def build_osx():
return os.path.basename(installer)
#return build_installer(installer, vm, 20)
def _build_linux():
cwd = os.getcwd()
tbz2 = os.path.join(cwd, installer_name('tar.bz2'))
SPEC="""\
import os
HOME = '%s'
PYINSTALLER = os.path.expanduser('~/build/pyinstaller')
CALIBREPREFIX = HOME+'/work/calibre'
CLIT = '/usr/bin/clit'
PDFTOHTML = '/usr/bin/pdftohtml'
LIBUNRAR = '/usr/lib/libunrar.so'
QTDIR = '/usr/lib/qt4'
QTDLLS = ('QtCore', 'QtGui', 'QtNetwork', 'QtSvg', 'QtXml')
EXTRAS = ('/usr/lib/python2.5/site-packages/PIL', os.path.expanduser('~/ipython/IPython'))
import glob, sys, subprocess, tarfile
CALIBRESRC = os.path.join(CALIBREPREFIX, 'src')
CALIBREPLUGINS = os.path.join(CALIBRESRC, 'calibre', 'plugins')
subprocess.check_call(('/usr/bin/sudo', 'chown', '-R', 'kovid:users', glob.glob('/usr/lib/python*/site-packages/')[-1]))
subprocess.check_call('rm -rf %%(py)s/dist/* %%(py)s/build/*'%%dict(py=PYINSTALLER), shell=True)
loader = os.path.join('/tmp', 'calibre_installer_loader.py')
if not os.path.exists(loader):
open(loader, 'wb').write('''
import sys, os
sys.frozen_path = os.getcwd()
os.chdir(os.environ.get("ORIGWD", "."))
sys.path.insert(0, os.path.join(sys.frozen_path, "library.pyz"))
sys.path.insert(0, sys.frozen_path)
from PyQt4.QtCore import QCoreApplication
QCoreApplication.setLibraryPaths([sys.frozen_path, os.path.join(sys.frozen_path, "plugins")])
''')
excludes = ['gtk._gtk', 'gtk.glade', 'qt', 'matplotlib.nxutils', 'matplotlib._cntr',
'matplotlib.ttconv', 'matplotlib._image', 'matplotlib.ft2font',
'matplotlib._transforms', 'matplotlib._agg', 'matplotlib.backends._backend_agg',
'matplotlib.axes', 'matplotlib', 'matplotlib.pyparsing',
'TKinter', 'atk', 'gobject._gobject', 'pango', 'PIL', 'Image', 'IPython']
temp = ['keyword', 'codeop']
recipes = ['calibre', 'web', 'feeds', 'recipes']
prefix = '.'.join(recipes)+'.'
for f in glob.glob(os.path.join(CALIBRESRC, *(recipes+['*.py']))):
temp.append(prefix + os.path.basename(f).partition('.')[0])
hook = '/tmp/hook-calibre.py'
open(hook, 'wb').write('hiddenimports = %%s'%%repr(temp) + '\\n')
sys.path.insert(0, CALIBRESRC)
from calibre.linux import entry_points
executables, scripts = ['calibre_postinstall', 'parallel'], \
[os.path.join(CALIBRESRC, 'calibre', 'linux.py'), os.path.join(CALIBRESRC, 'calibre', 'parallel.py')]
for entry in entry_points['console_scripts'] + entry_points['gui_scripts']:
fields = entry.split('=')
executables.append(fields[0].strip())
scripts.append(os.path.join(CALIBRESRC, *map(lambda x: x.strip(), fields[1].split(':')[0].split('.')))+'.py')
recipes = Analysis(glob.glob(os.path.join(CALIBRESRC, 'calibre', 'web', 'feeds', 'recipes', '*.py')),
pathex=[CALIBRESRC], hookspath=[os.path.dirname(hook)], excludes=excludes)
analyses = [Analysis([os.path.join(HOMEPATH,'support/_mountzlib.py'), os.path.join(HOMEPATH,'support/useUnicode.py'), loader, script],
pathex=[PYINSTALLER, CALIBRESRC, CALIBREPLUGINS], excludes=excludes) for script in scripts]
pyz = TOC()
binaries = TOC()
for a in analyses:
pyz = a.pure + pyz
binaries = a.binaries + binaries
pyz = PYZ(pyz + recipes.pure, name='library.pyz')
built_executables = []
for script, exe, a in zip(scripts, executables, analyses):
built_executables.append(EXE(PYZ(TOC()),
a.scripts+[('O','','OPTION'),],
exclude_binaries=1,
name=os.path.join('buildcalibre', exe),
debug=False,
strip=True,
upx=False,
excludes=excludes,
console=1))
print 'Adding plugins...'
for f in glob.glob(os.path.join(CALIBREPLUGINS, '*.so')):
binaries += [(os.path.basename(f), f, 'BINARY')]
print 'Adding external programs...'
binaries += [('clit', CLIT, 'BINARY'), ('pdftohtml', PDFTOHTML, 'BINARY'),
('libunrar.so', LIBUNRAR, 'BINARY')]
qt = []
for dll in QTDLLS:
path = os.path.join(QTDIR, 'lib'+dll+'.so.4')
qt.append((os.path.basename(path), path, 'BINARY'))
binaries += qt
plugins = []
plugdir = os.path.join(QTDIR, 'plugins')
for dirpath, dirnames, filenames in os.walk(plugdir):
for f in filenames:
if not f.endswith('.so') or 'designer' in dirpath or 'codcs' in dirpath or 'sqldrivers' in dirpath : continue
f = os.path.join(dirpath, f)
plugins.append(('plugins/'+f.replace(plugdir, ''), f, 'BINARY'))
binaries += plugins
manifest = '/tmp/manifest'
open(manifest, 'wb').write('\\n'.join(executables))
from calibre import __version__
version = '/tmp/version'
open(version, 'wb').write(__version__)
coll = COLLECT(binaries, pyz, [('manifest', manifest, 'DATA'), ('version', version, 'DATA')],
*built_executables,
**dict(strip=True,
upx=False,
excludes=excludes,
name='dist'))
os.chdir(os.path.join(HOMEPATH, 'calibre', 'dist'))
for folder in EXTRAS:
subprocess.check_call('cp -rf %%s .'%%folder, shell=True)
print 'Building tarball...'
tf = tarfile.open('%s', 'w:bz2')
for f in os.listdir('.'):
tf.add(f)
"""%('/mnt/hgfs/giskard/', tbz2)
os.chdir(os.path.expanduser('~/build/pyinstaller'))
open('calibre/calibre.spec', 'wb').write(SPEC)
try:
subprocess.check_call(('/usr/bin/python', '-O', 'Build.py', 'calibre/calibre.spec'))
finally:
os.chdir(cwd)
return os.path.basename(tbz2)
def build_linux():
vm = '/vmware/linux/libprs500-gentoo.vmx'
vmware = ('vmware', '-q', '-x', '-n', vm)
subprocess.Popen(vmware)
print 'Waiting for linux to boot up...'
time.sleep(75)
check_call('ssh linux make -C /mnt/hgfs/giskard/work/calibre all egg linux_binary')
check_call('ssh linux sudo poweroff')
def build_installers():
return build_windows(), build_osx()
return build_linux(), build_windows(), build_osx()
def upload_demo():
check_call('''html2lrf --title='Demonstration of html2lrf' --author='Kovid Goyal' '''
@ -94,13 +241,17 @@ def upload_demo():
check_call('''scp /tmp/txt-demo.zip divok:%s/'''%(DOWNLOADS,))
def upload_installers():
exe, dmg = installer_name('exe'), installer_name('dmg')
exe, dmg, tbz2 = installer_name('exe'), installer_name('dmg'), installer_name('tar.bz2')
if exe and os.path.exists(exe):
check_call('''ssh divok rm -f %s/calibre\*.exe'''%(DOWNLOADS,))
check_call('''scp %s divok:%s/'''%(exe, DOWNLOADS))
if dmg and os.path.exists(dmg):
check_call('''ssh divok rm -f %s/calibre\*.dmg'''%(DOWNLOADS,))
check_call('''scp %s divok:%s/'''%(dmg, DOWNLOADS))
if tbz2 and os.path.exists(tbz2):
check_call('''ssh divok rm -f %s/calibre-\*-i686.tar.bz2 %s/latest-linux-binary.tar.bz2'''%(DOWNLOADS,DOWNLOADS))
check_call('''scp %s divok:%s/'''%(tbz2, DOWNLOADS))
check_call('''ssh divok ln -s %s/calibre-\*-i686.tar.bz2 %s/latest-linux-binary.tar.bz2'''%(DOWNLOADS,DOWNLOADS))
check_call('''ssh divok chmod a+r %s/\*'''%(DOWNLOADS,))
def upload_docs():
@ -127,16 +278,7 @@ def upload_tarball():
check_call('ssh divok rm -f %s/calibre-\*.tar.bz2'%DOWNLOADS)
check_call('scp dist/calibre-*.tar.bz2 divok:%s/'%DOWNLOADS)
def pypi():
vm = '/vmware/linux/libprs500-gentoo.vmx'
vmware = ('vmware', '-q', '-x', '-n', vm)
subprocess.Popen(vmware)
print 'Waiting for linux to boot up...'
time.sleep(60)
check_call('scp ~/.pypirc linux:')
check_call('ssh linux make -C /mnt/hgfs/giskard/work/calibre egg')
check_call('ssh linux rm -f ~/.pypirc')
check_call('ssh linux sudo poweroff')
def main():
upload = len(sys.argv) < 2
@ -155,10 +297,10 @@ def main():
print 'Uploading installers...'
upload_installers()
print 'Uploading to PyPI'
pypi()
upload_tarball()
upload_docs()
upload_user_manual()
check_call('python setup.py register bdist_egg --exclude-source-files upload')
check_call('''rm -rf dist/* build/*''')
if __name__ == '__main__':

View File

@ -46,6 +46,7 @@ BrandingText "${PRODUCT_NAME} created by Kovid Goyal"
!define CLIT "C:\clit\clit.exe"
!define PDFTOHTML "C:\pdftohtml\pdftohtml.exe"
!define IMAGEMAGICK "C:\ImageMagick"
!DEFINE FONTCONFIG "C:\fontconfig"
; ---------------PATH manipulation -----------------------------------------------------------------
@ -283,6 +284,7 @@ Section "Main" "secmain"
File /r "${PY2EXE_DIR}\*"
File "${CLIT}"
File "${PDFTOHTML}"
File /r "${FONTCONFIG}\*"
SetOutPath "$INSTDIR\ImageMagick"
File /r "${IMAGEMAGICK}\*"