diff --git a/Makefile b/Makefile index 6ced0750b9..e65fd0d235 100644 --- a/Makefile +++ b/Makefile @@ -27,14 +27,13 @@ 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 diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index 4b53422a05..3bfda762a0 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -549,16 +549,12 @@ def strftime(fmt, t=time.localtime()): return unicode(result, locale.getpreferredencoding(), 'replace') 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 +565,6 @@ except Exception, err: pictureflow = None pictureflowerror = str(err) -if islinux: - os.chdir(cwd) def entity_to_unicode(match, exceptions=[], encoding='cp1252'): ''' diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index ab43d17474..1a3c346210 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -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: diff --git a/src/calibre/ebooks/lrf/feeds/convert_from.py b/src/calibre/ebooks/lrf/feeds/convert_from.py index d4ea3d4c63..4425877525 100644 --- a/src/calibre/ebooks/lrf/feeds/convert_from.py +++ b/src/calibre/ebooks/lrf/feeds/convert_from.py @@ -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') diff --git a/src/calibre/ebooks/lrf/html/convert_to.py b/src/calibre/ebooks/lrf/html/convert_to.py index df36c5f7e3..2fff037abe 100644 --- a/src/calibre/ebooks/lrf/html/convert_to.py +++ b/src/calibre/ebooks/lrf/html/convert_to.py @@ -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 diff --git a/src/calibre/ebooks/lrf/lit/convert_from.py b/src/calibre/ebooks/lrf/lit/convert_from.py index efbf82f2e9..d93eaf9534 100644 --- a/src/calibre/ebooks/lrf/lit/convert_from.py +++ b/src/calibre/ebooks/lrf/lit/convert_from.py @@ -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( diff --git a/src/calibre/ebooks/lrf/parser.py b/src/calibre/ebooks/lrf/lrfparser.py similarity index 100% rename from src/calibre/ebooks/lrf/parser.py rename to src/calibre/ebooks/lrf/lrfparser.py diff --git a/src/calibre/ebooks/lrf/pdf/convert_from.py b/src/calibre/ebooks/lrf/pdf/convert_from.py index 2adb9a12a7..078359e9c3 100644 --- a/src/calibre/ebooks/lrf/pdf/convert_from.py +++ b/src/calibre/ebooks/lrf/pdf/convert_from.py @@ -4,7 +4,7 @@ __copyright__ = '2008, Kovid Goyal ' 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 @@ -15,9 +15,10 @@ popen = subprocess.Popen if isosx and hasattr(sys, 'frameworks_dir'): PDFTOHTML = os.path.join(getattr(sys, 'frameworks_dir'), PDFTOHTML) 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 - + 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): ''' @@ -93,4 +94,4 @@ def main(args=sys.argv, logger=None): return 0 if __name__ == '__main__': - sys.exit(main()) \ No newline at end of file + sys.exit(main()) diff --git a/src/calibre/gui2/lrf_renderer/main.py b/src/calibre/gui2/lrf_renderer/main.py index d60e02609e..2d927242cc 100644 --- a/src/calibre/gui2/lrf_renderer/main.py +++ b/src/calibre/gui2/lrf_renderer/main.py @@ -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 diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index c2b139c6a5..eb631398ad 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -107,7 +107,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 +197,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 +217,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)'), @@ -1161,7 +1158,7 @@ def main(args=sys.argv): pid = os.fork() if islinux else -1 if pid <= 0: app = Application(args) - app.setWindowIcon(QIcon(':/library')) + app.setWindowIcon(QIcon(':/library')) QCoreApplication.setOrganizationName(ORG_NAME) QCoreApplication.setApplicationName(APP_UID) single_instance = None if SingleApplication is None else SingleApplication('calibre GUI') @@ -1170,19 +1167,19 @@ def main(args=sys.argv): single_instance.send_message('launched:'+repr(sys.argv)): return 0 - QMessageBox.critical(None, 'Cannot Start '+__appname__, + QMessageBox.critical(None, 'Cannot Start '+__appname__, '

%s is already running.

'%__appname__) return 1 initialize_file_icon_provider() try: main = Main(single_instance) except DatabaseLocked, err: - QMessageBox.critical(None, 'Cannot Start '+__appname__, + QMessageBox.critical(None, 'Cannot Start '+__appname__, '

Another program is using the database.
Perhaps %s is already running?
If not try deleting the file %s'%(__appname__, err.lock_file_path)) return 1 sys.excepthook = main.unhandled_exception if len(sys.argv) > 1: - main.add_filesystem_book(sys.argv[1]) + main.add_filesystem_book(sys.argv[1]) return app.exec_() return 0 diff --git a/src/calibre/gui2/pictureflow/PyQt/configure.py b/src/calibre/gui2/pictureflow/PyQt/configure.py index 4474026f33..56b3112a7e 100644 --- a/src/calibre/gui2/pictureflow/PyQt/configure.py +++ b/src/calibre/gui2/pictureflow/PyQt/configure.py @@ -1,5 +1,8 @@ import os, sys 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 diff --git a/src/calibre/linux.py b/src/calibre/linux.py index bc38597481..aad0fb616c 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -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', @@ -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.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 @@ -313,14 +313,17 @@ def setup_udev_rules(group_file, reload, fatal_errors): call(('/etc/rc.d/rc.hald', 'restart')) try: - check_call('udevcontrol reload_rules', shell=True) + check_call('udevadm control --reload_rules', shell=True) except: try: - check_call('/etc/init.d/udev reload', shell=True) + check_call('udevcontrol reload_rules', shell=True) 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" + try: + check_call('/etc/init.d/udev reload', shell=True) + 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(): from optparse import OptionParser @@ -428,6 +431,19 @@ MIME = '''\ ''' +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 +454,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) - 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')): - 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') + 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) + 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) + 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) f = open('calibre-lrfviewer.desktop', 'wb') f.write(VIEWER) diff --git a/src/calibre/linux_installer.py b/src/calibre/linux_installer.py new file mode 100644 index 0000000000..58b59117d0 --- /dev/null +++ b/src/calibre/linux_installer.py @@ -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()) \ No newline at end of file diff --git a/src/calibre/trac/plugins/download.py b/src/calibre/trac/plugins/download.py index df3da00dca..443a96a41a 100644 --- a/src/calibre/trac/plugins/download.py +++ b/src/calibre/trac/plugins/download.py @@ -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): @@ -38,7 +38,6 @@ class Distribution(object): ('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 +119,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: diff --git a/upload.py b/upload.py index 8f3ae8b1a0..15362fbeba 100644 --- a/upload.py +++ b/upload.py @@ -53,7 +53,9 @@ def build_installer(installer, vm, timeout=25): return os.path.basename(installer) 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(): installer = installer_name('exe') @@ -75,7 +77,134 @@ def build_osx(): subprocess.Popen(('ssh', 'osx', 'sudo', '/sbin/shutdown', '-h', '+1')) 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="""\ +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(): return build_windows(), build_osx() @@ -94,13 +223,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'''%(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():