From aaa6bc4c3fd37a9ae0fec34da94d4487121a947c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 12 Jun 2007 20:48:39 +0000 Subject: [PATCH] Created OS X installer. Added automatic detection of cover page for conversion of HTML generated by CLIT. --- epydoc-pdf.conf | 2 +- epydoc.conf | 2 +- ez_setup.py | 222 -------- icons/library.icns | Bin 0 -> 45174 bytes osx_installer.py | 241 +++++++++ setup.py | 503 +++++++++--------- src/libprs500/__init__.py | 14 +- src/libprs500/devices/libusb.py | 12 +- src/libprs500/devices/prs500/books.py | 4 +- src/libprs500/ebooks/lrf/html/convert_from.py | 28 +- src/libprs500/libunrar.py | 14 +- upload | 61 --- upload.py | 108 ++++ windows_installer.py | 51 +- 14 files changed, 683 insertions(+), 579 deletions(-) delete mode 100644 ez_setup.py create mode 100644 icons/library.icns create mode 100644 osx_installer.py delete mode 100644 upload create mode 100644 upload.py diff --git a/epydoc-pdf.conf b/epydoc-pdf.conf index 2285935ed0..f251c7b5cc 100644 --- a/epydoc-pdf.conf +++ b/epydoc-pdf.conf @@ -7,7 +7,7 @@ url: http://libprs500.kovidgoyal.net # The list of modules to document. Modules can be named using # dotted names, module filenames, or package directory names. # This option may be repeated. -modules: src/libprs500/*.py, libprs500.ebooks.lrf, struct +modules: src/libprs500/*.py, struct output: pdf target: docs/pdf diff --git a/epydoc.conf b/epydoc.conf index 546556b6d5..904004aff1 100644 --- a/epydoc.conf +++ b/epydoc.conf @@ -7,7 +7,7 @@ url: http://libprs500.kovidgoyal.net # The list of modules to document. Modules can be named using # dotted names, module filenames, or package directory names. # This option may be repeated. -modules: src/libprs500/*.py, libprs500.ebooks.lrf, struct +modules: src/libprs500/*.py, struct # Write html output to the directory "docs" output: html diff --git a/ez_setup.py b/ez_setup.py deleted file mode 100644 index 3031ad0d11..0000000000 --- a/ez_setup.py +++ /dev/null @@ -1,222 +0,0 @@ -#!python -"""Bootstrap setuptools installation - -If you want to use setuptools in your package's setup.py, just include this -file in the same directory with it, and add this to the top of your setup.py:: - - from ez_setup import use_setuptools - use_setuptools() - -If you want to require a specific version of setuptools, set a download -mirror, or use an alternate download directory, you can do so by supplying -the appropriate options to ``use_setuptools()``. - -This file can also be run as a script to install or upgrade setuptools. -""" -import sys -DEFAULT_VERSION = "0.6c3" -DEFAULT_URL = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3] - -md5_data = { - 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', - 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', - 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', - 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', - 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', - 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', - 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', - 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', - 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', - 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', - 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', - 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', - 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', - 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', - 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', -} - -import sys, os - -def _validate_md5(egg_name, data): - if egg_name in md5_data: - from md5 import md5 - digest = md5(data).hexdigest() - if digest != md5_data[egg_name]: - print >>sys.stderr, ( - "md5 validation of %s failed! (Possible download problem?)" - % egg_name - ) - sys.exit(2) - return data - - -def use_setuptools( - version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, - download_delay=15 -): - """Automatically find/download setuptools and make it available on sys.path - - `version` should be a valid setuptools version number that is available - as an egg for download under the `download_base` URL (which should end with - a '/'). `to_dir` is the directory where setuptools will be downloaded, if - it is not already available. If `download_delay` is specified, it should - be the number of seconds that will be paused before initiating a download, - should one be required. If an older version of setuptools is installed, - this routine will print a message to ``sys.stderr`` and raise SystemExit in - an attempt to abort the calling script. - """ - try: - import setuptools - if setuptools.__version__ == '0.0.1': - print >>sys.stderr, ( - "You have an obsolete version of setuptools installed. Please\n" - "remove it from your system entirely before rerunning this script." - ) - sys.exit(2) - except ImportError: - egg = download_setuptools(version, download_base, to_dir, download_delay) - sys.path.insert(0, egg) - import setuptools; setuptools.bootstrap_install_from = egg - - import pkg_resources - try: - pkg_resources.require("setuptools>="+version) - - except pkg_resources.VersionConflict, e: - # XXX could we install in a subprocess here? - print >>sys.stderr, ( - "The required version of setuptools (>=%s) is not available, and\n" - "can't be installed while this script is running. Please install\n" - " a more recent version first.\n\n(Currently using %r)" - ) % (version, e.args[0]) - sys.exit(2) - -def download_setuptools( - version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, - delay = 15 -): - """Download setuptools from a specified location and return its filename - - `version` should be a valid setuptools version number that is available - as an egg for download under the `download_base` URL (which should end - with a '/'). `to_dir` is the directory where the egg will be downloaded. - `delay` is the number of seconds to pause before an actual download attempt. - """ - import urllib2, shutil - egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) - url = download_base + egg_name - saveto = os.path.join(to_dir, egg_name) - src = dst = None - if not os.path.exists(saveto): # Avoid repeated downloads - try: - from distutils import log - if delay: - log.warn(""" ---------------------------------------------------------------------------- -This script requires setuptools version %s to run (even to display -help). I will attempt to download it for you (from -%s), but -you may need to enable firewall access for this script first. -I will start the download in %d seconds. - -(Note: if this machine does not have network access, please obtain the file - - %s - -and place it in this directory before rerunning this script.) ----------------------------------------------------------------------------""", - version, download_base, delay, url - ); from time import sleep; sleep(delay) - log.warn("Downloading %s", url) - src = urllib2.urlopen(url) - # Read/write all in one block, so we don't create a corrupt file - # if the download is interrupted. - data = _validate_md5(egg_name, src.read()) - dst = open(saveto,"wb"); dst.write(data) - finally: - if src: src.close() - if dst: dst.close() - return os.path.realpath(saveto) - -def main(argv, version=DEFAULT_VERSION): - """Install or upgrade setuptools and EasyInstall""" - - try: - import setuptools - except ImportError: - egg = None - try: - egg = download_setuptools(version, delay=0) - sys.path.insert(0,egg) - from setuptools.command.easy_install import main - return main(list(argv)+[egg]) # we're done here - finally: - if egg and os.path.exists(egg): - os.unlink(egg) - else: - if setuptools.__version__ == '0.0.1': - # tell the user to uninstall obsolete version - use_setuptools(version) - - req = "setuptools>="+version - import pkg_resources - try: - pkg_resources.require(req) - except pkg_resources.VersionConflict: - try: - from setuptools.command.easy_install import main - except ImportError: - from easy_install import main - main(list(argv)+[download_setuptools(delay=0)]) - sys.exit(0) # try to force an exit - else: - if argv: - from setuptools.command.easy_install import main - main(argv) - else: - print "Setuptools version",version,"or greater has been installed." - print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' - - - -def update_md5(filenames): - """Update our built-in md5 registry""" - - import re - from md5 import md5 - - for name in filenames: - base = os.path.basename(name) - f = open(name,'rb') - md5_data[base] = md5(f.read()).hexdigest() - f.close() - - data = [" %r: %r,\n" % it for it in md5_data.items()] - data.sort() - repl = "".join(data) - - import inspect - srcfile = inspect.getsourcefile(sys.modules[__name__]) - f = open(srcfile, 'rb'); src = f.read(); f.close() - - match = re.search("\nmd5_data = {\n([^}]+)}", src) - if not match: - print >>sys.stderr, "Internal error!" - sys.exit(2) - - src = src[:match.start(1)] + repl + src[match.end(1):] - f = open(srcfile,'w') - f.write(src) - f.close() - - -if __name__=='__main__': - if len(sys.argv)>2 and sys.argv[1]=='--md5update': - update_md5(sys.argv[2:]) - else: - main(sys.argv[1:]) - - - - - diff --git a/icons/library.icns b/icons/library.icns new file mode 100644 index 0000000000000000000000000000000000000000..60ed1beda7c2cb55d606730ecb9020ba6271f735 GIT binary patch literal 45174 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 literal 0 HcmV?d00001 diff --git a/osx_installer.py b/osx_installer.py new file mode 100644 index 0000000000..71e8289d01 --- /dev/null +++ b/osx_installer.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python +## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +''' Create an OSX installer ''' + +import sys, re, os, shutil, subprocess, stat +from setup import VERSION, APPNAME, scripts, main_modules, basenames, main_functions +from setuptools import setup +sys.argv[1:2] = ['py2app'] +from py2app.build_app import py2app +from modulegraph.find_modules import find_modules + +class BuildAPP(py2app): + QT_PREFIX = '/Users/kovid/qt' + LOADER_TEMPLATE = \ +r'''#!/usr/bin/env python +import os, sys, glob +path = os.path.abspath(os.path.realpath(__file__)) +dirpath = os.path.dirname(path) +name = os.path.basename(path) +base_dir = os.path.dirname(dirpath) +frameworks_dir = os.path.join(base_dir, 'Frameworks') +base_name = os.path.splitext(name)[0] +python = os.path.join(base_dir, 'MacOS', 'python') +loader_path = os.path.join(dirpath, base_name+'.py') +loader = open(loader_path, 'w') +site_packages = glob.glob(dirpath+'/*/*/site-packages.zip')[0] +print >>loader, '#!'+python +print >>loader, 'import sys' +print >>loader, 'sys.path.append(', repr(site_packages), ')' +print >>loader, 'sys.frozen = "macosx_app"' +print >>loader, 'sys.frameworks_dir =', repr(frameworks_dir) +print >>loader, 'import os' +print >>loader, 'base =', repr(dirpath) +print >>loader, 'from %(module)s import %(function)s' +print >>loader, '%(function)s()' +loader.close() +os.chmod(loader_path, 0700) +os.environ['PYTHONHOME'] = dirpath +os.execv(loader_path, sys.argv) + ''' + CHECK_SYMLINKS_PRESCRIPT = \ +r''' +def _check_symlinks_prescript(): + import os, tempfile, traceback, sys + from Authorization import Authorization, kAuthorizationFlagDestroyRights + + AUTHTOOL="""#!%s +import os +scripts = %(sp)s +links = %(sp)s +os.setuid(0) +for s, l in zip(scripts, links): + if os.path.lexists(l): + os.remove(l) + print 'Creating link:', l, '->', s + os.symlink(s, l) +""" + + dest_path = %(dest_path)s + resources_path = os.environ['RESOURCEPATH'] + scripts = %(scripts)s + links = [os.path.join(dest_path, i) for i in scripts] + scripts = [os.path.join(resources_path, i) for i in scripts] + + bad = False + for s, l in zip(scripts, links): + if os.path.exists(l) and os.path.exists(os.path.realpath(l)): + continue + bad = True + break + if bad: + auth = Authorization(destroyflags=(kAuthorizationFlagDestroyRights,)) + fd, name = tempfile.mkstemp('.py') + os.write(fd, AUTHTOOL %(pp)s (sys.executable, repr(scripts), repr(links))) + os.close(fd) + os.chmod(name, 0700) + try: + pipe = auth.executeWithPrivileges(name) + sys.stdout.write(pipe.read()) + pipe.close() + except: + traceback.print_exc() + finally: + os.unlink(name) +_check_symlinks_prescript() +''' + def get_modulefinder(self): + if self.debug_modulegraph: + debug = 4 + else: + debug = 0 + return find_modules( + scripts=scripts['console'] + scripts['gui'], + includes=list(self.includes) + main_modules['console'], + packages=self.packages, + excludes=self.excludes, + debug=debug, + ) + + @classmethod + def makedmg(cls, d, volname, + destdir='dist', + internet_enable=True, + format='UDBZ'): + ''' Copy a directory d into a dmg named volname ''' + dmg = os.path.join(destdir, volname+'.dmg') + if os.path.exists(dmg): + os.unlink(dmg) + subprocess.check_call(['hdiutil', 'create', '-srcfolder', os.path.abspath(d), + '-volname', volname, '-format', format, dmg]) + if internet_enable: + subprocess.check_call(['hdiutil', 'internet-enable', '-yes', dmg]) + return dmg + + @classmethod + def qt_dependencies(cls, path): + pipe = subprocess.Popen('otool -L '+path, shell=True, stdout=subprocess.PIPE).stdout + deps = [] + for l in pipe.readlines(): + match = re.search(r'(.*)\(', l) + if not match: + continue + lib = match.group(1).strip() + if lib.startswith(BuildAPP.QT_PREFIX): + deps.append(lib) + return deps + + @classmethod + def fix_qt_dependencies(cls, path, deps): + fp = '@executable_path/../Frameworks/' + print 'Fixing qt dependencies for:', os.path.basename(path) + for dep in deps: + module = re.search(r'(Qt\w+?)\.framework', dep).group(1) + newpath = fp + '%s.framework/Versions/Current/%s'%(module, module) + cmd = ' '.join(['install_name_tool', '-change', dep, newpath, path]) + subprocess.check_call(cmd, shell=True) + + + def add_qt_plugins(self): + macos_dir = os.path.join(self.dist_dir, APPNAME + '.app', 'Contents', 'MacOS') + for root, dirs, files in os.walk(BuildAPP.QT_PREFIX+'/plugins'): + for name in files: + if name.endswith('.dylib'): + path = os.path.join(root, name) + dir = os.path.basename(root) + dest_dir = os.path.join(macos_dir, dir) + if not os.path.exists(dest_dir): + os.mkdir(dest_dir) + target = os.path.join(dest_dir, name) + shutil.copyfile(path, target) + shutil.copymode(path, target) + deps = BuildAPP.qt_dependencies(target) + BuildAPP.fix_qt_dependencies(target, deps) + + + #deps = BuildAPP.qt_dependencies(path) + + + def run(self): + py2app.run(self) + self.add_qt_plugins() + resource_dir = os.path.join(self.dist_dir, + APPNAME + '.app', 'Contents', 'Resources') + all_scripts = scripts['console'] + scripts['gui'] + all_names = basenames['console'] + basenames['gui'] + all_modules = main_modules['console'] + main_modules['gui'] + all_functions = main_functions['console'] + main_functions['gui'] + print + for name, module, function in zip(all_names, all_modules, all_functions): + path = os.path.join(resource_dir, name) + print 'Creating loader:', path + f = open(path, 'w') + f.write(BuildAPP.LOADER_TEMPLATE % dict(module=module, + function=function)) + f.close() + os.chmod(path, stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH|stat.S_IREAD\ + |stat.S_IWUSR|stat.S_IROTH|stat.S_IRGRP) + + print + print 'Installing prescipt' + sf = [os.path.basename(s) for s in all_names] + cs = BuildAPP.CHECK_SYMLINKS_PRESCRIPT % dict(dest_path=repr('/usr/bin'), + scripts=repr(sf), + sp='%s', pp='%') + launcher_path = os.path.join(resource_dir, '__boot__.py') + f = open(launcher_path, 'r') + src = f.read() + f.close() + src = re.sub('(_run\s*\(.*?.py.*?\))', cs+'%s'%( +''' +sys.frameworks_dir = os.path.join(os.path.dirname(os.environ['RESOURCEPATH']), 'Frameworks') +''') + r'\n\1', src) + f = open(launcher_path, 'w') + print >>f, 'import sys, os' + f.write(src) + f.close() + print + print 'Building disk image' + BuildAPP.makedmg(os.path.join(self.dist_dir, APPNAME+'.app'), APPNAME+'-'+VERSION) + + +setup( + name = APPNAME, + app = [scripts['gui'][0]], + cmdclass = { 'py2app' : BuildAPP }, + options = { 'py2app' : + { + 'optimize' : 2, + 'dist_dir' : 'build/py2app', + 'argv_emulation' : True, + 'iconfile' : 'icons/library.icns', + 'frameworks': ['libusb.dylib', 'libunrar.dylib'], + 'includes' : ['sip', 'pkg_resources', 'PyQt4.QtSvg'], + 'packages' : ['PIL', 'Authorization',], + 'excludes' : ['pydoc'], + 'plist' : { 'CFBundleGetInfoString' : '''libprs500, an E-book management application.''' + ''' Visit http://libprs500.kovidgoyal.net for details.''', + 'CFBundleIdentifier':'net.kovidgoyal.librs500', + 'CFBundleShortVersionString':VERSION, + 'CFBundleVersion':APPNAME + ' ' + VERSION, + 'LSMinimumSystemVersion':'10.4.3', + 'LSMultipleInstancesProhibited':'true', + 'NSHumanReadableCopyright':'Copyright 2006, Kovid Goyal', + }, + }, + }, + setup_requires = ['py2app'], + ) diff --git a/setup.py b/setup.py index 2f0f55eab2..fd50aa9550 100644 --- a/setup.py +++ b/setup.py @@ -16,38 +16,10 @@ import sys, re, os, shutil sys.path.append('src') from libprs500 import __version__ as VERSION - -import ez_setup -ez_setup.use_setuptools() -from setuptools import setup, find_packages +from libprs500 import __appname__ as APPNAME -if sys.hexversion < 0x2050000: - print >> sys.stderr, "You must use python >= 2.5 Try invoking this script as python2.5 setup.py." - print >> sys.stderr, "If you are using easy_install, try easy_install-2.5" - sys.exit(1) - -try: - from PIL import Image -except ImportError: - import Image - print >>sys.stderr, "You do not have the Python Imaging Library correctly installed." - sys.exit(1) - -setup( - name='libprs500', - packages = find_packages('src'), - package_dir = { '' : 'src' }, - version=VERSION, - author='Kovid Goyal', - author_email='kovid@kovidgoyal.net', - url = 'http://libprs500.kovidgoyal.net', - package_data = { - 'libprs500.ebooks' : ['*.jpg', '*.pl'], - 'libpre500.ebooks.lrf.fonts' : ['*.ttf'], - }, - include_package_data = True, - entry_points = { +entry_points = { 'console_scripts': [ \ 'prs500 = libprs500.devices.prs500.cli.main:main', \ 'lrf-meta = libprs500.ebooks.lrf.meta:main', \ @@ -55,222 +27,279 @@ setup( 'txt2lrf = libprs500.ebooks.lrf.txt.convert_from:main', \ 'html2lrf = libprs500.ebooks.lrf.html.convert_from:main',\ ], - 'gui_scripts' : [ 'libprs500 = libprs500.gui.main:main'] - }, - zip_safe = True, - description = - """ - Ebook management application. - """, - long_description = - """ - libprs500 is a ebook management application. It maintains an ebook library - and allows for easy transfer of books from the library to an ebook reader. - At the moment, it supports the `SONY Portable Reader`_. - - It can also convert various popular ebook formats into LRF, the native - ebook format of the SONY Reader. - - For screenshots: https://libprs500.kovidgoyal.net/wiki/Screenshots - - For installation/usage instructions please see - https://libprs500.kovidgoyal.net/wiki/WikiStart#Installation - - For SVN access: svn co https://svn.kovidgoyal.net/code/libprs500 - - .. _SONY Portable Reader: http://Sony.com/reader - .. _USB: http://www.usb.org - """, - license = 'GPL', - classifiers = [ - 'Development Status :: 3 - Alpha', - 'Environment :: Console', - 'Environment :: X11 Applications :: Qt', - 'Intended Audience :: Developers', - 'Intended Audience :: End Users/Desktop', - 'License :: OSI Approved :: GNU General Public License (GPL)', - 'Natural Language :: English', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: System :: Hardware :: Hardware Drivers' - ] - ) + 'gui_scripts' : [ APPNAME+' = libprs500.gui.main:main'] + } -if '--uninstall' in ' '.join(sys.argv[1:]): - sys.exit(0) - -try: - import PyQt4 -except ImportError: - print "You do not have PyQt4 installed. The GUI will not work.", \ - "You can obtain PyQt4 from http://www.riverbankcomputing.co.uk/pyqt/download.php" -else: - import PyQt4.QtCore - if PyQt4.QtCore.PYQT_VERSION < 0x40101: - print "WARNING: The GUI needs PyQt >= 4.1.1" - -import os -def options(parse_options): - options, args, parser = parse_options(['dummy'], cli=False) - options = parser.option_list - for group in parser.option_groups: - options += group.option_list - opts = [] - for opt in options: - opts.extend(opt._short_opts) - opts.extend(opt._long_opts) - return opts - -def opts_and_exts(name, op, exts): - opts = ' '.join(options(op)) - exts.extend([i.upper() for i in exts]) - exts='|'.join(exts) - return '_'+name+'()'+\ -''' -{ - local cur prev opts - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - opts="%s" - pics="@(jpg|jpeg|png|gif|bmp|JPG|JPEG|PNG|GIF|BMP)" - - case "${prev}" in - --cover ) - _filedir "${pics}" - return 0 - ;; - esac - - case "${cur}" in - --cover ) - _filedir "${pics}" - return 0 - ;; - -* ) - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - return 0 - ;; - * ) - _filedir '@(%s)' - return 0 - ;; - esac - -} -complete -o filenames -F _'''%(opts,exts) + name + ' ' + name +"\n\n" +def _ep_to_script(ep, base='src'): + return (base+os.path.sep+re.search(r'.*=\s*(.*?):', ep).group(1).replace('.', '/')+'.py').strip() +scripts = { + 'console' : [_ep_to_script(i) for i in entry_points['console_scripts']], + 'gui' : [_ep_to_script(i) for i in entry_points['gui_scripts']], + } -if os.access('/etc/bash_completion.d', os.W_OK): +def _ep_to_basename(ep): + return re.search(r'\s*(.*?)\s*=', ep).group(1).strip() +basenames = { + 'console' : [_ep_to_basename(i) for i in entry_points['console_scripts']], + 'gui' : [_ep_to_basename(i) for i in entry_points['gui_scripts']], + } + +def _ep_to_module(ep): + return re.search(r'.*=\s*(.*?)\s*:', ep).group(1).strip() +main_modules = { + 'console' : [_ep_to_module(i) for i in entry_points['console_scripts']], + 'gui' : [_ep_to_module(i) for i in entry_points['gui_scripts']], + } + +def _ep_to_function(ep): + return ep[ep.rindex(':')+1:].strip() +main_functions = { + 'console' : [_ep_to_function(i) for i in entry_points['console_scripts']], + 'gui' : [_ep_to_function(i) for i in entry_points['gui_scripts']], + } + +if __name__ == '__main__': + from setuptools import setup, find_packages + + if sys.hexversion < 0x2050000: + print >> sys.stderr, "You must use python >= 2.5 Try invoking this script as python2.5 setup.py." + print >> sys.stderr, "If you are using easy_install, try easy_install-2.5" + sys.exit(1) + try: - print 'Setting up bash completion...', - sys.stdout.flush() - from libprs500.ebooks.lrf.html.convert_from import parse_options as htmlop - from libprs500.ebooks.lrf.txt.convert_from import parse_options as txtop - from libprs500.ebooks.lrf.meta import parse_options as metaop - f = open('/etc/bash_completion.d/libprs500', 'wb') - f.write('# libprs500 Bash Shell Completion\n') - f.write(opts_and_exts('html2lrf', htmlop, - ['htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip'])) - f.write(opts_and_exts('txt2lrf', txtop, ['txt'])) - f.write(opts_and_exts('lrf-meta', metaop, ['lrf'])) - f.write(''' -_prs500_ls() -{ - local pattern search listing prefix - pattern="$1" - search="$1" - if [[ -n "{$pattern}" ]]; then - if [[ "${pattern:(-1)}" == "/" ]]; then - pattern="" - else - pattern="$(basename ${pattern} 2> /dev/null)" - search="$(dirname ${search} 2> /dev/null)" - fi - fi + from PIL import Image + except ImportError: + import Image + print >>sys.stderr, "You do not have the Python Imaging Library correctly installed." + sys.exit(1) - if [[ "x${search}" == "x" || "x${search}" == "x." ]]; then - search="/" - fi - listing="$(prs500 ls ${search} 2>/dev/null)" - - prefix="${search}" - if [[ "x${prefix:(-1)}" != "x/" ]]; then - prefix="${prefix}/" - fi - - echo $(compgen -P "${prefix}" -W "${listing}" "${pattern}") -} - -_prs500() -{ - local cur prev - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - COMPREPLY=() - case "${prev}" in - ls|rm|mkdir|touch|cat ) - COMPREPLY=( $(_prs500_ls "${cur}") ) - return 0 - ;; - cp ) - if [[ ${cur} == prs500:* ]]; then - COMPREPLY=( $(_prs500_ls "${cur:7}") ) - return 0 - else - _filedir - return 0 - fi - ;; - prs500 ) - COMPREPLY=( $(compgen -W "cp ls rm mkdir touch cat info books df" "${cur}") ) - return 0 - ;; - * ) - if [[ ${cur} == prs500:* ]]; then - COMPREPLY=( $(_prs500_ls "${cur:7}") ) - return 0 - else - if [[ ${prev} == prs500:* ]]; then - _filedir + setup( + name='libprs500', + packages = find_packages('src'), + package_dir = { '' : 'src' }, + version=VERSION, + author='Kovid Goyal', + author_email='kovid@kovidgoyal.net', + url = 'http://libprs500.kovidgoyal.net', + include_package_data = True, + entry_points = entry_points, + zip_safe = True, + description = + """ + Ebook management application. + """, + long_description = + """ + libprs500 is a ebook management application. It maintains an ebook library + and allows for easy transfer of books from the library to an ebook reader. + At the moment, it supports the `SONY Portable Reader`_. + + It can also convert various popular ebook formats into LRF, the native + ebook format of the SONY Reader. + + For screenshots: https://libprs500.kovidgoyal.net/wiki/Screenshots + + For installation/usage instructions please see + https://libprs500.kovidgoyal.net/wiki/WikiStart#Installation + + For SVN access: svn co https://svn.kovidgoyal.net/code/libprs500 + + .. _SONY Portable Reader: http://Sony.com/reader + .. _USB: http://www.usb.org + """, + license = 'GPL', + classifiers = [ + 'Development Status :: 3 - Alpha', + 'Environment :: Console', + 'Environment :: X11 Applications :: Qt', + 'Intended Audience :: Developers', + 'Intended Audience :: End Users/Desktop', + 'License :: OSI Approved :: GNU General Public License (GPL)', + 'Natural Language :: English', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: System :: Hardware :: Hardware Drivers' + ] + ) + + if '--uninstall' in ' '.join(sys.argv[1:]): + sys.exit(0) + + try: + import PyQt4 + except ImportError: + print "You do not have PyQt4 installed. The GUI will not work.", \ + "You can obtain PyQt4 from http://www.riverbankcomputing.co.uk/pyqt/download.php" + else: + import PyQt4.QtCore + if PyQt4.QtCore.PYQT_VERSION < 0x40101: + print "WARNING: The GUI needs PyQt >= 4.1.1" + + import os + def options(parse_options): + options, args, parser = parse_options(['dummy'], cli=False) + options = parser.option_list + for group in parser.option_groups: + options += group.option_list + opts = [] + for opt in options: + opts.extend(opt._short_opts) + opts.extend(opt._long_opts) + return opts + + def opts_and_exts(name, op, exts): + opts = ' '.join(options(op)) + exts.extend([i.upper() for i in exts]) + exts='|'.join(exts) + return '_'+name+'()'+\ + ''' + { + local cur prev opts + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + opts="%s" + pics="@(jpg|jpeg|png|gif|bmp|JPG|JPEG|PNG|GIF|BMP)" + + case "${prev}" in + --cover ) + _filedir "${pics}" + return 0 + ;; + esac + + case "${cur}" in + --cover ) + _filedir "${pics}" + return 0 + ;; + -* ) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + ;; + * ) + _filedir '@(%s)' return 0 - else - COMPREPLY=( $(compgen -W "prs500:" "${cur}") ) - return 0 - fi - return 0 + ;; + esac + + } + complete -o filenames -F _'''%(opts,exts) + name + ' ' + name +"\n\n" + + + + if os.access('/etc/bash_completion.d', os.W_OK): + try: + print 'Setting up bash completion...', + sys.stdout.flush() + from libprs500.ebooks.lrf.html.convert_from import parse_options as htmlop + from libprs500.ebooks.lrf.txt.convert_from import parse_options as txtop + from libprs500.ebooks.lrf.meta import parse_options as metaop + f = open('/etc/bash_completion.d/libprs500', 'wb') + f.write('# libprs500 Bash Shell Completion\n') + f.write(opts_and_exts('html2lrf', htmlop, + ['htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip'])) + f.write(opts_and_exts('txt2lrf', txtop, ['txt'])) + f.write(opts_and_exts('lrf-meta', metaop, ['lrf'])) + f.write(''' + _prs500_ls() + { + local pattern search listing prefix + pattern="$1" + search="$1" + if [[ -n "{$pattern}" ]]; then + if [[ "${pattern:(-1)}" == "/" ]]; then + pattern="" + else + pattern="$(basename ${pattern} 2> /dev/null)" + search="$(dirname ${search} 2> /dev/null)" fi - ;; - esac -} -complete -o nospace -F _prs500 prs500 - -''') - f.close() - print 'done' - except: - print 'failed' - - -if os.access('/etc/udev/rules.d', os.W_OK): - from subprocess import check_call - print 'Trying to setup udev rules...', - sys.stdout.flush() - udev = open('/etc/udev/rules.d/95-libprs500.rules', 'w') - udev.write('''# Sony Reader PRS-500\n''' - '''BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="plugdev"\n''' - ) - udev.close() - try: - check_call('udevstart', shell=True) - print 'success' - except: + fi + + if [[ "x${search}" == "x" || "x${search}" == "x." ]]; then + search="/" + fi + + listing="$(prs500 ls ${search} 2>/dev/null)" + + prefix="${search}" + if [[ "x${prefix:(-1)}" != "x/" ]]; then + prefix="${prefix}/" + fi + + echo $(compgen -P "${prefix}" -W "${listing}" "${pattern}") + } + + _prs500() + { + local cur prev + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + COMPREPLY=() + case "${prev}" in + ls|rm|mkdir|touch|cat ) + COMPREPLY=( $(_prs500_ls "${cur}") ) + return 0 + ;; + cp ) + if [[ ${cur} == prs500:* ]]; then + COMPREPLY=( $(_prs500_ls "${cur:7}") ) + return 0 + else + _filedir + return 0 + fi + ;; + prs500 ) + COMPREPLY=( $(compgen -W "cp ls rm mkdir touch cat info books df" "${cur}") ) + return 0 + ;; + * ) + if [[ ${cur} == prs500:* ]]; then + COMPREPLY=( $(_prs500_ls "${cur:7}") ) + return 0 + else + if [[ ${prev} == prs500:* ]]; then + _filedir + return 0 + else + COMPREPLY=( $(compgen -W "prs500:" "${cur}") ) + return 0 + fi + return 0 + fi + ;; + esac + } + complete -o nospace -F _prs500 prs500 + + ''') + f.close() + print 'done' + except: + print 'failed' + + + if os.access('/etc/udev/rules.d', os.W_OK): + from subprocess import check_call + print 'Trying to setup udev rules...', + sys.stdout.flush() + udev = open('/etc/udev/rules.d/95-libprs500.rules', 'w') + udev.write('''# Sony Reader PRS-500\n''' + '''BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="plugdev"\n''' + ) + udev.close() try: - check_call('/etc/init.d/udev reload', shell=True) + check_call('udevstart', shell=True) print 'success' except: - print >>sys.stderr, "Couldn't reload udev, you may have to reboot" - + try: + check_call('/etc/init.d/udev reload', shell=True) + print 'success' + except: + print >>sys.stderr, "Couldn't reload udev, you may have to reboot" + diff --git a/src/libprs500/__init__.py b/src/libprs500/__init__.py index 2b6b300fa3..689794455b 100644 --- a/src/libprs500/__init__.py +++ b/src/libprs500/__init__.py @@ -13,15 +13,25 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ''' E-book management software''' -__version__ = "0.3.48" +__version__ = "0.3.49" __docformat__ = "epytext" __author__ = "Kovid Goyal " __appname__ = 'libprs500' -import sys +import sys, os iswindows = 'win32' in sys.platform.lower() isosx = 'darwin' in sys.platform.lower() +def load_library(name, cdll): + if iswindows: + return cdll.LoadLibrary(name) + if isosx: + name += '.dylib' + if hasattr(sys, 'frameworks_dir'): + return cdll.LoadLibrary(os.path.join(sys.frameworks_dir, name)) + return cdll.LoadLibrary(name) + return cdll.LoadLibrary(name+'.so') + def filename_to_utf8(name): '''Return C{name} encoded in utf8. Unhandled characters are replaced. ''' codec = 'cp1252' if iswindows else 'utf8' diff --git a/src/libprs500/devices/libusb.py b/src/libprs500/devices/libusb.py index c01e5708ad..4cf4c7e718 100644 --- a/src/libprs500/devices/libusb.py +++ b/src/libprs500/devices/libusb.py @@ -20,20 +20,16 @@ from ctypes import cdll, POINTER, byref, pointer, Structure, \ c_ubyte, c_ushort, c_int, c_char, c_void_p, c_byte, c_uint from errno import EBUSY, ENOMEM -from libprs500 import iswindows, isosx +from libprs500 import iswindows, isosx, load_library -_libusb_name = 'libusb.so' -PATH_MAX = 4096 +_libusb_name = 'libusb' +PATH_MAX = 511 if iswindows else 1024 if isosx else 4096 if iswindows: - PATH_MAX = 511 Structure._pack_ = 1 _libusb_name = 'libusb0' -if isosx: - PATH_MAX = 1024 - _libusb_name = 'libusb.dylib' try: - _libusb = cdll.LoadLibrary(_libusb_name) + _libusb = load_library(_libusb_name, cdll) except OSError: if iswindows or isosx: raise diff --git a/src/libprs500/devices/prs500/books.py b/src/libprs500/devices/prs500/books.py index 7826ca0208..2b5c222b6a 100644 --- a/src/libprs500/devices/prs500/books.py +++ b/src/libprs500/devices/prs500/books.py @@ -185,8 +185,8 @@ class BookList(list): cid = self.max_id()+1 sourceid = str(self[0].sourceid) if len(self) else "1" attrs = { - "title":info["title"], - "author":info["authors"] if info['authors'] else 'Unknown', \ + "title" : info["title"], + "author" : info["authors"] if info['authors'] else 'Unknown', \ "page":"0", "part":"0", "scale":"0", \ "sourceid":sourceid, "id":str(cid), "date":"", \ "mime":mime, "path":name, "size":str(size) diff --git a/src/libprs500/ebooks/lrf/html/convert_from.py b/src/libprs500/ebooks/lrf/html/convert_from.py index 1a6a03b48a..4cf107baee 100644 --- a/src/libprs500/ebooks/lrf/html/convert_from.py +++ b/src/libprs500/ebooks/lrf/html/convert_from.py @@ -1090,15 +1090,7 @@ def process_file(path, options): try: dirpath, path = get_path(path) cpath, tpath = '', '' - isbn = try_opf(path, options) - if not options.cover and isbn: - for item in isbn: - matches = glob.glob(re.sub('-', '', item[1])+'.*') - for match in matches: - if match.lower().endswith('.jpeg') or match.lower().endswith('.jpg') or \ - match.lower().endswith('.gif') or match.lower().endswith('.png'): - options.cover = match - break + try_opf(path, options) if options.cover: options.cover = os.path.abspath(os.path.expanduser(options.cover)) cpath = options.cover @@ -1204,7 +1196,23 @@ def try_opf(path, options): if not scheme: scheme = item.get('opf:scheme') isbn.append((scheme, item.string)) - return isbn + if not options.cover: + for item in isbn: + matches = glob.glob(re.sub('-', '', item[1])+'.*') + for match in matches: + if match.lower().endswith('.jpeg') or match.lower().endswith('.jpg') or \ + match.lower().endswith('.gif') or match.lower().endswith('.png'): + options.cover = match + if not options.cover: + ref = soup.package.find('reference', {'type':'other.ms-coverimage-standard'}) + if ref: + try: + options.cover = ref.get('href') + if not os.access(options.cover, os.R_OK): + options.cover = None + except: + if options.verbose: + traceback.print_exc() except Exception, err: if options.verbose: print >>sys.stderr, 'Failed to process opf file', err diff --git a/src/libprs500/libunrar.py b/src/libprs500/libunrar.py index 6f16b0046d..ae67d8f539 100644 --- a/src/libprs500/libunrar.py +++ b/src/libprs500/libunrar.py @@ -17,22 +17,20 @@ This module provides a thin ctypes based wrapper around libunrar. See ftp://ftp.rarlabs.com/rar/unrarsrc-3.7.5.tar.gz """ -import os +import os, ctypes from ctypes import Structure, c_char_p, c_uint, c_void_p, POINTER, \ byref, c_wchar_p, CFUNCTYPE, c_int, c_long, c_char, c_wchar from StringIO import StringIO -from libprs500 import iswindows +from libprs500 import iswindows, isosx, load_library -_librar_name = 'libunrar.so' +_librar_name = 'libunrar' +cdll = ctypes.cdll if iswindows: Structure._pack_ = 1 _librar_name = 'unrar' - from ctypes import windll - _libunrar = windll.LoadLibrary('unrar') -else: - from ctypes import cdll - _libunrar = cdll.LoadLibrary(_librar_name) + cdll = ctypes.windll +_libunrar = load_library(_librar_name, cdll) RAR_OM_LIST = 0 RAR_OM_EXTRACT = 1 diff --git a/upload b/upload deleted file mode 100644 index 666fb0b443..0000000000 --- a/upload +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/python -import sys, glob, os, subprocess -from subprocess import check_call as _check_call -from functools import partial -from pyvix.vix import * - -check_call = partial(_check_call, shell=True) - -check_call("sudo python setup.py develop", shell=True) - -files = glob.glob('dist/*.exe') -for file in files: - os.unlink(file) - - -h = Host(hostType=VIX_SERVICEPROVIDER_VMWARE_WORKSTATION) -vm = h.openVM('/mnt/extra/vmware/Windows Vista/Windows Vista.vmx') -vm.powerOn() -if not vm.waitForToolsInGuest(): - print >>sys.stderr, 'Windows is not booting up' - sys.exit(1) - - - -vm.loginInGuest('kovid', 'et tu brutus') -vm.loginInGuest(VIX_CONSOLE_USER_NAME, '') -vm.runProgramInGuest('C:\\Users\kovid\Desktop\libprs500.bat', '') -vm.runProgramInGuest('C:\Windows\system32\shutdown.exe', '/s') - -if not glob.glob('dist/*.exe'): - raise Exception('Windows build has failed') - - -PREFIX = "/var/www/vhosts/kovidgoyal.net/subdomains/libprs500" -DOWNLOADS = PREFIX+"/httpdocs/downloads" -DOCS = PREFIX+"/httpdocs/apidocs" -exe = os.path.basename(glob.glob('dist/*.exe')[0]) -HTML2LRF = "src/libprs500/ebooks/lrf/html/demo" - -f = open(os.path.join(HTML2LRF, 'demo_ext.html'), 'w') -f.write("

The HTML

\n")
-f.write(open(os.path.join(HTML2LRF, 'demo.html')).read())
-f.write('\n
') -f.close() - - -check_call('''html2lrf --title='Demonstration of html2lrf' --author='Kovid Goyal' --header --output=/tmp/html2lrf.lrf %s/demo.html'''%(HTML2LRF,)) -check_call('''scp /tmp/html2lrf.lrf castalia:%s/'''%(DOWNLOADS,)) - -check_call('''ssh castalia rm -f %s/libprs500\*.exe'''%(DOWNLOADS,)) -check_call('''scp dist/%s castalia:%s/'''%(exe, DOWNLOADS)) -check_call('''ssh castalia chmod a+r %s/\*'''%(DOWNLOADS,)) -check_call('''ssh castalia /root/bin/update-installer-link %s'''%(exe,)) -check_call('''python setup.py register sdist bdist_egg upload''') - -check_call('''epydoc --config epydoc.conf''') -check_call('''scp -r docs/html castalia:%s/'''%(DOCS,)) -check_call('''epydoc -v --config epydoc-pdf.conf''') -check_call('''scp docs/pdf/api.pdf castalia:%s/'''%(DOCS,)) - -check_call('''rm -rf dist/* build/*''') diff --git a/upload.py b/upload.py new file mode 100644 index 0000000000..13d5049773 --- /dev/null +++ b/upload.py @@ -0,0 +1,108 @@ +#!/usr/bin/python +import sys, glob, os, subprocess, time, shutil +sys.path.append('src') +from subprocess import check_call as _check_call +from functools import partial +from pyvix.vix import * + +PREFIX = "/var/www/vhosts/kovidgoyal.net/subdomains/libprs500" +DOWNLOADS = PREFIX+"/httpdocs/downloads" +DOCS = PREFIX+"/httpdocs/apidocs" +HTML2LRF = "src/libprs500/ebooks/lrf/html/demo" +check_call = partial(_check_call, shell=True) +h = Host(hostType=VIX_SERVICEPROVIDER_VMWARE_WORKSTATION) + +def build_windows(): + files = glob.glob('dist/*.exe') + for file in files: + os.unlink(file) + + + + vm = h.openVM('/mnt/extra/vmware/Windows Vista/Windows Vista.vmx') + vm.powerOn() + if not vm.waitForToolsInGuest(): + print >>sys.stderr, 'Windows is not booting up' + sys.exit(1) + + + + vm.loginInGuest('kovid', 'et tu brutus') + vm.loginInGuest(VIX_CONSOLE_USER_NAME, '') + vm.runProgramInGuest('C:\\Users\kovid\Desktop\libprs500.bat', '') + vm.runProgramInGuest('C:\Windows\system32\shutdown.exe', '/s') + + if not glob.glob('dist/*.exe'): + raise Exception('Windows build has failed') + return os.path.basename(glob.glob('dist/*.exe')[-1]) + +def build_osx(): + files = glob.glob('dist/*.dmg') + for file in files: + os.unlink(file) + + vm = h.openVM('/mnt/extra/vmware/Mac OSX/Mac OSX.vmx') + vm.powerOn() + c = 20 * 60 + print 'Waiting (minutes):', + while c > 0: + if glob.glob('dist/*.dmg'): + break + time.sleep(10) + c -= 10 + if c%60==0: + print c, ',', + print + + if not glob.glob('dist/*.dmg'): + raise Exception('OSX build has failed') + vm.powerOff() + return os.path.basename(glob.glob('dist/*.dmg')[-1]) + + + +def upload_demo(): + f = open(os.path.join(HTML2LRF, 'demo_ext.html'), 'w') + f.write("

The HTML

\n")
+    f.write(open(os.path.join(HTML2LRF, 'demo.html')).read())
+    f.write('\n
') + f.close() + check_call('''html2lrf --title='Demonstration of html2lrf' --author='Kovid Goyal' --header --output=/tmp/html2lrf.lrf %s/demo.html'''%(HTML2LRF,)) + check_call('''scp /tmp/html2lrf.lrf castalia:%s/'''%(DOWNLOADS,)) + +def upload_installers(exe, dmg): + check_call('''ssh castalia rm -f %s/libprs500\*.exe'''%(DOWNLOADS,)) + check_call('''scp dist/%s castalia:%s/'''%(exe, DOWNLOADS)) + check_call('''ssh castalia rm -f %s/libprs500\*.dmg'''%(DOWNLOADS,)) + check_call('''scp dist/%s castalia:%s/'''%(dmg, DOWNLOADS)) + check_call('''ssh castalia chmod a+r %s/\*'''%(DOWNLOADS,)) + check_call('''ssh castalia /root/bin/update-installer-links %s %s'''%(exe, dmg)) + +def upload_docs(): + check_call('''epydoc --config epydoc.conf''') + check_call('''scp -r docs/html castalia:%s/'''%(DOCS,)) + check_call('''epydoc -v --config epydoc-pdf.conf''') + check_call('''scp docs/pdf/api.pdf castalia:%s/'''%(DOCS,)) + + + +def main(): + shutil.rmtree('build') + os.mkdir('build') + shutil.rmtree('docs') + os.mkdir('docs') + check_call("sudo python setup.py develop", shell=True) + upload_demo() + print 'Building OSX installer...' + dmg = build_osx() + print 'Building Windows installer...' + exe = build_windows() + print 'Uploading installers...' + upload_installers(exe, dmg) + print 'Uploading to PyPI' + check_call('''python setup.py register sdist bdist_egg upload''') + upload_docs() + check_call('''rm -rf dist/* build/*''') + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/windows_installer.py b/windows_installer.py index c1630fb7c7..1de133dc53 100644 --- a/windows_installer.py +++ b/windows_installer.py @@ -1,6 +1,20 @@ +## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License along +## with this program; if not, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ''' Create a windows installer ''' import sys, re, os, shutil, subprocess -sys.path.append('src') +from setup import VERSION, APPNAME, entry_points, scripts, basenames sys.argv[1:2] = ['py2exe'] if '--verbose' not in ' '.join(sys.argv): sys.argv.append('--quiet') #py2exe produces too much output by default @@ -210,7 +224,7 @@ SectionEnd os.remove(path) -class BuildInstaller(build_exe): +class BuildEXE(build_exe): manifest_resource_id = 0 MANIFEST_TEMPLATE = ''' @@ -249,20 +263,7 @@ class BuildInstaller(build_exe): print 'Adding', qtxmldll shutil.copyfile(qtxmldll, os.path.join(self.dist_dir, os.path.basename(qtxmldll))) - print 'Copying fonts' - dest_dir = os.path.join(self.dist_dir, 'fonts') - if os.path.exists(dest_dir): - shutil.rmtree(dest_dir, True) - fl = FileList() - fl.include_pattern('^src.*\.ttf$', is_regex=True) - fl.findall() - for file in fl.files: - dir = os.path.join(dest_dir, os.path.basename(os.path.dirname(file))) - if not os.path.exists(dir): - os.makedirs(dir) - print file - shutil.copy(file, dir) print print 'Building Installer' installer = NSISInstaller(APPNAME, self.dist_dir, 'dist') @@ -274,20 +275,17 @@ class BuildInstaller(build_exe): return (24, cls.manifest_resource_id, cls.MANIFEST_TEMPLATE % dict(prog=prog, version=VERSION+'.0')) +console = [dict(dest_base=basenames['console'][i], script=scripts['console'][i]) + for i in range(len(scripts['console']))] + setup( - cmdclass = {'py2exe': BuildInstaller}, - windows = [{'script' : 'src/libprs500/gui/main.py', + cmdclass = {'py2exe': BuildEXE}, + windows = [{'script' : scripts['gui'][0], 'dest_base' : APPNAME, 'icon_resources' : [(1, 'icons/library.ico')], - 'other_resources' : [BuildInstaller.manifest(APPNAME)], + 'other_resources' : [BuildEXE.manifest(APPNAME)], },], - console = [ - {'script' : 'src/libprs500/devices/prs500/cli/main.py', 'dest_base':'prs500'}, - {'script' : 'src/libprs500/ebooks/lrf/html/convert_from.py', 'dest_base':'html2lrf'}, - {'script' : 'src/libprs500/ebooks/lrf/txt/convert_from.py', 'dest_base':'txt2lrf'}, - {'script' : 'src/libprs500/ebooks/lrf/meta.py', 'dest_base':'lrf-meta'}, - {'script' : 'src/libprs500/ebooks/metadata/rtf.py', 'dest_base':'rtf-meta'}, - ], + console = console, options = { 'py2exe' : {'compressed': 1, 'optimize' : 2, 'dist_dir' : r'build\py2exe', @@ -299,5 +297,4 @@ setup( }, }, - ) - + ) \ No newline at end of file