Implement linux binary installer

This commit is contained in:
Kovid Goyal 2008-06-07 14:46:51 -07:00
parent f77c2ade9d
commit 075ada2488
15 changed files with 466 additions and 57 deletions

View File

@ -27,14 +27,13 @@ pictureflow :
mkdir -p src/calibre/plugins && rm -f src/calibre/plugins/*pictureflow* && \ mkdir -p src/calibre/plugins && rm -f src/calibre/plugins/*pictureflow* && \
cd src/calibre/gui2/pictureflow && rm -f *.o && \ cd src/calibre/gui2/pictureflow && rm -f *.o && \
mkdir -p .build && cd .build && rm -f * && \ mkdir -p .build && cd .build && rm -f * && \
qmake ../pictureflow.pro && make && \ qmake ../pictureflow.pro && make staticlib && \
cd ../PyQt && \ cd ../PyQt && \
mkdir -p .build && \ mkdir -p .build && \
cd .build && rm -f * && \ cd .build && rm -f * && \
python ../configure.py && make && \ python ../configure.py && make && \
cd ../../../../../.. && \ cd ../../../../../.. && \
cp src/calibre/gui2/pictureflow/.build/libpictureflow.so.?.?.? src/calibre/gui2/pictureflow/PyQt/.build/pictureflow.so src/calibre/plugins/ && \ cp 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])" && \
rm -rf src/calibre/gui2/pictureflow/.build rm -rf src/calibre/gui2/pictureflow/PyQt/.build rm -rf src/calibre/gui2/pictureflow/.build rm -rf src/calibre/gui2/pictureflow/PyQt/.build

View File

@ -550,15 +550,11 @@ def strftime(fmt, t=time.localtime()):
except: except:
return unicode(result, 'utf-8', 'replace') return unicode(result, 'utf-8', 'replace')
if islinux: if islinux and not getattr(sys, 'frozen', False):
import pkg_resources import pkg_resources
if not os.environ.has_key('LD_LIBRARY_PATH'):
os.environ['LD_LIBRARY_PATH'] = ''
plugins = pkg_resources.resource_filename(__appname__, 'plugins') plugins = pkg_resources.resource_filename(__appname__, 'plugins')
os.environ['LD_LIBRARY_PATH'] = plugins + ':' + os.environ['LD_LIBRARY_PATH']
sys.path.insert(1, plugins) sys.path.insert(1, plugins)
cwd = os.getcwd()
os.chdir(plugins)
if iswindows and hasattr(sys, 'frozen'): if iswindows and hasattr(sys, 'frozen'):
sys.path.insert(1, os.path.dirname(sys.executable)) sys.path.insert(1, os.path.dirname(sys.executable))
@ -569,8 +565,6 @@ except Exception, err:
pictureflow = None pictureflow = None
pictureflowerror = str(err) pictureflowerror = str(err)
if islinux:
os.chdir(cwd)
def entity_to_unicode(match, exceptions=[], encoding='cp1252'): def entity_to_unicode(match, exceptions=[], encoding='cp1252'):
''' '''

View File

@ -109,7 +109,7 @@ class PRS505(Device):
devname = re.search(r'BSD Name.*=\s+"(\S+)"', src).group(1) 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 self._main_prefix = re.search('/dev/%s(\w*)\s+on\s+([^\(]+)\s+'%(devname,), mount).group(2) + os.sep
except: 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: try:
src = subprocess.Popen('ioreg -n "%s"'%(self.OSX_SD_NAME,), src = subprocess.Popen('ioreg -n "%s"'%(self.OSX_SD_NAME,),
shell=True, stdout=subprocess.PIPE).stdout.read() shell=True, stdout=subprocess.PIPE).stdout.read()
@ -143,7 +143,7 @@ class PRS505(Device):
if not drives: 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])) drives.sort(cmp=lambda a, b: cmp(a[0], b[0]))
self._main_prefix = drives[0][1] self._main_prefix = drives[0][1]
@ -171,7 +171,7 @@ class PRS505(Device):
mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__) mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__)
if not mm: 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 self._main_prefix = None
for dev in mm: for dev in mm:
try: try:

View File

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

View File

@ -5,7 +5,7 @@ import sys, logging, os
from calibre import setup_cli_handlers, OptionParser from calibre import setup_cli_handlers, OptionParser
from calibre.ebooks import ConversionError from calibre.ebooks import ConversionError
from calibre.ebooks.lrf.meta import get_metadata 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.metadata.opf import OPFCreator
from calibre.ebooks.lrf.objects import PageAttr, BlockAttr, TextAttr from calibre.ebooks.lrf.objects import PageAttr, BlockAttr, TextAttr

View File

@ -8,11 +8,13 @@ from calibre.ebooks.lrf import option_parser as lrf_option_parser
from calibre.ebooks import ConversionError from calibre.ebooks import ConversionError
from calibre.ebooks.lrf.html.convert_from import process_file as html_process_file from calibre.ebooks.lrf.html.convert_from import process_file as html_process_file
from calibre.ebooks.metadata.opf import OPFReader 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' CLIT = 'clit'
if isosx and hasattr(sys, 'frameworks_dir'): if isosx and hasattr(sys, 'frameworks_dir'):
CLIT = os.path.join(getattr(sys, 'frameworks_dir'), CLIT) 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(): def option_parser():
return lrf_option_parser( return lrf_option_parser(

View File

@ -4,7 +4,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import sys, os, subprocess, logging import sys, os, subprocess, logging
from functools import partial 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.ebooks import ConversionError
from calibre.ptempfile import PersistentTemporaryDirectory from calibre.ptempfile import PersistentTemporaryDirectory
from calibre.ebooks.lrf import option_parser as lrf_option_parser from calibre.ebooks.lrf import option_parser as lrf_option_parser
@ -15,9 +15,10 @@ popen = subprocess.Popen
if isosx and hasattr(sys, 'frameworks_dir'): if isosx and hasattr(sys, 'frameworks_dir'):
PDFTOHTML = os.path.join(getattr(sys, 'frameworks_dir'), PDFTOHTML) PDFTOHTML = os.path.join(getattr(sys, 'frameworks_dir'), PDFTOHTML)
if iswindows and hasattr(sys, 'frozen'): if iswindows and hasattr(sys, 'frozen'):
PDFTOHTML = os.path.join(os.path.dirname(sys.executable), 'pdftohtml.exe') 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 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): def generate_html(pathtopdf, logger):
''' '''

View File

@ -8,7 +8,7 @@ from PyQt4.QtCore import Qt, QObject, SIGNAL, QCoreApplication, QThread, \
QVariant QVariant
from calibre import __appname__, __version__, __author__, setup_cli_handlers, islinux, Settings 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 import ORG_NAME, APP_UID, error_dialog, choose_files, Application
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog

View File

@ -107,7 +107,6 @@ class Main(MainWindow, Ui_MainWindow):
QObject.connect(self.job_manager, SIGNAL('job_done(int)'), self.status_bar.job_done, QObject.connect(self.job_manager, SIGNAL('job_done(int)'), self.status_bar.job_done,
Qt.QueuedConnection) Qt.QueuedConnection)
QObject.connect(self.status_bar, SIGNAL('show_book_info()'), self.show_book_info) QObject.connect(self.status_bar, SIGNAL('show_book_info()'), self.show_book_info)
####################### Setup Toolbar ##################### ####################### Setup Toolbar #####################
sm = QMenu() sm = QMenu()
sm.addAction(QIcon(':/images/reader.svg'), _('Send to main memory')) sm.addAction(QIcon(':/images/reader.svg'), _('Send to main memory'))
@ -198,7 +197,6 @@ class Main(MainWindow, Ui_MainWindow):
self.library_view.resizeColumnsToContents() self.library_view.resizeColumnsToContents()
self.library_view.resizeRowsToContents() self.library_view.resizeRowsToContents()
self.search.setFocus(Qt.OtherFocusReason) self.search.setFocus(Qt.OtherFocusReason)
########################### Cover Flow ################################ ########################### Cover Flow ################################
self.cover_flow = None self.cover_flow = None
if CoverFlow is not None: if CoverFlow is not None:
@ -219,7 +217,6 @@ class Main(MainWindow, Ui_MainWindow):
self.setMaximumHeight(available_height()) self.setMaximumHeight(available_height())
####################### Setup device detection ######################## ####################### Setup device detection ########################
self.detector = DeviceDetector(sleep_time=2000) self.detector = DeviceDetector(sleep_time=2000)
QObject.connect(self.detector, SIGNAL('connected(PyQt_PyObject, PyQt_PyObject)'), QObject.connect(self.detector, SIGNAL('connected(PyQt_PyObject, PyQt_PyObject)'),

View File

@ -1,5 +1,8 @@
import os, sys import os, sys
import sipconfig import sipconfig
if os.environ.get('PYQT4PATH', None):
print os.environ['PYQT4PATH']
sys.path.insert(0, os.environ['PYQT4PATH'])
from PyQt4 import pyqtconfig from PyQt4 import pyqtconfig
# The name of the SIP build file generated by SIP and used by the build # The name of the SIP build file generated by SIP and used by the build

View File

@ -39,7 +39,7 @@ entry_points = {
'fb22lrf = calibre.ebooks.lrf.fb2.convert_from:main', 'fb22lrf = calibre.ebooks.lrf.fb2.convert_from:main',
'fb2-meta = calibre.ebooks.metadata.fb2:main', 'fb2-meta = calibre.ebooks.metadata.fb2:main',
'any2lrf = calibre.ebooks.lrf.any.convert_from: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', 'lrs2lrf = calibre.ebooks.lrf.lrs.convert_from:main',
'pdfreflow = calibre.ebooks.lrf.pdf.reflow:main', 'pdfreflow = calibre.ebooks.lrf.pdf.reflow:main',
'isbndb = calibre.ebooks.metadata.isbndb:main', 'isbndb = calibre.ebooks.metadata.isbndb:main',
@ -154,7 +154,7 @@ def setup_completion(fatal_errors):
from calibre.ebooks.lrf.html.convert_from import option_parser as htmlop 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.txt.convert_from import option_parser as txtop
from calibre.ebooks.lrf.meta import option_parser as metaop 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.gui2.lrf_renderer.main import option_parser as lrfviewerop
from calibre.ebooks.lrf.pdf.reflow import option_parser as pdfhtmlop from calibre.ebooks.lrf.pdf.reflow import option_parser as pdfhtmlop
from calibre.ebooks.mobi.reader import option_parser as mobioeb from calibre.ebooks.mobi.reader import option_parser as mobioeb
@ -313,14 +313,17 @@ def setup_udev_rules(group_file, reload, fatal_errors):
call(('/etc/rc.d/rc.hald', 'restart')) call(('/etc/rc.d/rc.hald', 'restart'))
try: try:
check_call('udevcontrol reload_rules', shell=True) check_call('udevadm control --reload_rules', shell=True)
except: except:
try: try:
check_call('/etc/init.d/udev reload', shell=True) check_call('udevcontrol reload_rules', shell=True)
except: except:
if fatal_errors: try:
raise Exception("Couldn't reload udev, you may have to reboot") check_call('/etc/init.d/udev reload', shell=True)
print >>sys.stderr, "Couldn't reload udev, you may have to reboot" except:
if fatal_errors:
raise Exception("Couldn't reload udev, you may have to reboot")
print >>sys.stderr, "Couldn't reload udev, you may have to reboot"
def option_parser(): def option_parser():
from optparse import OptionParser from optparse import OptionParser
@ -428,6 +431,19 @@ MIME = '''\
</mime-info> </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): def setup_desktop_integration(fatal_errors):
try: try:
from PyQt4.QtCore import QFile from PyQt4.QtCore import QFile
@ -438,25 +454,16 @@ def setup_desktop_integration(fatal_errors):
tdir = mkdtemp() tdir = mkdtemp()
rsvg = 'rsvg --dpi-x 600 --dpi-y 600 -w 128 -h 128 -f png '
cwd = os.getcwdu() cwd = os.getcwdu()
try: try:
os.chdir(tdir) os.chdir(tdir)
if QFile(':/images/mimetypes/lrf.svg').copy(os.path.join(tdir, 'calibre-lrf.svg')): render_svg(QFile(':/images/mimetypes/lrf.svg'), os.path.join(tdir, 'calibre-lrf.png'))
check_call(rsvg + 'calibre-lrf.svg calibre-lrf.png', shell=True) 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 application-lrf', shell=True) check_call('xdg-icon-resource install --context mimetypes --size 128 calibre-lrf.png text-lrs', shell=True)
check_call('xdg-icon-resource install --context mimetypes --size 128 calibre-lrf.png text-lrs', shell=True) QFile(':library').copy(os.path.join(tdir, 'calibre-gui.png'))
else: check_call('xdg-icon-resource install --size 128 calibre-gui.png calibre-gui', shell=True)
raise Exception('Could not create LRF mimetype icon') render_svg(QFile(':/images/viewer.svg'), os.path.join(tdir, 'calibre-viewer.png'))
if QFile(':library').copy(os.path.join(tdir, 'calibre-gui.png')): check_call('xdg-icon-resource install --size 128 calibre-viewer.png calibre-viewer', shell=True)
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)
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 = open('calibre-lrfviewer.desktop', 'wb')
f.write(VIEWER) f.write(VIEWER)

View File

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

View File

@ -11,7 +11,7 @@ from trac.util import Markup
__appname__ = 'calibre' __appname__ = 'calibre'
DOWNLOAD_DIR = '/var/www/calibre.kovidgoyal.net/htdocs/downloads' 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): class OS(dict):
@ -38,7 +38,6 @@ class Distribution(object):
('dbus-python', '0.82.2', 'dbus-python', 'python-dbus', 'dbus-python'), ('dbus-python', '0.82.2', 'dbus-python', 'python-dbus', 'dbus-python'),
('convertlit', '1.8', 'convertlit', None, None), ('convertlit', '1.8', 'convertlit', None, None),
('lxml', '1.3.3', 'lxml', 'python-lxml', 'python-lxml'), ('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'), ('genshi', '0.4.4', 'genshi', 'python-genshi', 'python-genshi'),
('help2man', '1.36.4', 'help2man', 'help2man', 'help2man'), ('help2man', '1.36.4', 'help2man', 'help2man', 'help2man'),
] ]
@ -120,6 +119,8 @@ class Download(Component):
add_stylesheet(req, 'dl/css/download.css') add_stylesheet(req, 'dl/css/download.css')
if req.path_info == '/download': if req.path_info == '/download':
return self.top_level(req) return self.top_level(req)
elif req.path_info == '/download_linux_binary_installer':
req.send(open(LINUX_INSTALLER).read(), 'text/x-python')
else: else:
match = re.match(r'\/download_(\S+)', req.path_info) match = re.match(r'\/download_(\S+)', req.path_info)
if match: if match:

137
upload.py
View File

@ -53,7 +53,9 @@ def build_installer(installer, vm, timeout=25):
return os.path.basename(installer) return os.path.basename(installer)
def installer_name(ext): def installer_name(ext):
return 'dist/%s-%s.%s'%(__appname__, __version__, 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(): def build_windows():
installer = installer_name('exe') installer = installer_name('exe')
@ -76,6 +78,133 @@ def build_osx():
return os.path.basename(installer) return os.path.basename(installer)
#return build_installer(installer, vm, 20) #return build_installer(installer, vm, 20)
def build_linux():
cwd = os.getcwd()
tbz2 = os.path.join(cwd, installer_name('tar.bz2'))
SPEC="""\
HOME = '%s'
PYINSTALLER = HOME+'/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')
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]))
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']
temp = ['IPython.Extensions.ipy_profile_none']
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'], [os.path.join(CALIBRESRC, 'calibre', 'linux.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'))
print 'Building tarball...'
tf = tarfile.open('%s', 'w:bz2')
os.chdir(os.path.join(HOMEPATH, 'calibre', 'dist'))
for f in os.listdir('.'):
tf.add(f)
"""%('/home/kovid', 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_installers(): def build_installers():
return build_windows(), build_osx() return build_windows(), build_osx()
@ -94,13 +223,17 @@ def upload_demo():
check_call('''scp /tmp/txt-demo.zip divok:%s/'''%(DOWNLOADS,)) check_call('''scp /tmp/txt-demo.zip divok:%s/'''%(DOWNLOADS,))
def upload_installers(): 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): if exe and os.path.exists(exe):
check_call('''ssh divok rm -f %s/calibre\*.exe'''%(DOWNLOADS,)) check_call('''ssh divok rm -f %s/calibre\*.exe'''%(DOWNLOADS,))
check_call('''scp %s divok:%s/'''%(exe, DOWNLOADS)) check_call('''scp %s divok:%s/'''%(exe, DOWNLOADS))
if dmg and os.path.exists(dmg): if dmg and os.path.exists(dmg):
check_call('''ssh divok rm -f %s/calibre\*.dmg'''%(DOWNLOADS,)) check_call('''ssh divok rm -f %s/calibre\*.dmg'''%(DOWNLOADS,))
check_call('''scp %s divok:%s/'''%(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'''%(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,)) check_call('''ssh divok chmod a+r %s/\*'''%(DOWNLOADS,))
def upload_docs(): def upload_docs():