mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Fixed broken font rationalization
This commit is contained in:
commit
5dda09d098
11
Makefile
11
Makefile
@ -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()"
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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']
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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']
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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):
|
||||
'''
|
||||
|
@ -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'])
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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>&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<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<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<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<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<series_index>)</string>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>No match</string>
|
||||
</property>
|
||||
<property name="readOnly" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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>
|
||||
|
@ -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()
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
270
src/calibre/linux_installer.py
Normal file
270
src/calibre/linux_installer.py
Normal 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())
|
@ -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
|
||||
|
@ -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'}),
|
||||
|
@ -34,7 +34,7 @@
|
||||
<div py:if="distro.is_generic">
|
||||
<ol>
|
||||
<li>Make sure that your system has <code>python >= 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 && calibre_postinstall</pre></li>
|
||||
</ol>
|
||||
<h2>Dependencies</h2>
|
||||
|
47
src/calibre/trac/plugins/templates/pyinstaller.html
Normal file
47
src/calibre/trac/plugins/templates/pyinstaller.html
Normal 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>
|
@ -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
@ -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)
|
322
src/calibre/utils/fontconfig.py
Normal file
322
src/calibre/utils/fontconfig.py
Normal 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())
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
40
src/calibre/web/feeds/recipes/upi.py
Normal file
40
src/calibre/web/feeds/recipes/upi.py
Normal 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/'
|
@ -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
168
upload.py
@ -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__':
|
||||
|
@ -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}\*"
|
||||
|
Loading…
x
Reference in New Issue
Block a user