From 0586ab7b7ae7ecfcf528013d17e76b337827ce14 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 1 Sep 2009 10:44:21 -0600 Subject: [PATCH 01/13] New recipes for The Old Foodie and HLN Belgium by Darko Miletic --- src/calibre/gui2/images/news/hln_be.png | Bin 0 -> 359 bytes src/calibre/web/feeds/recipes/__init__.py | 2 +- .../web/feeds/recipes/recipe_hln_be.py | 35 ++++++++++++++++++ .../web/feeds/recipes/recipe_theoldfoodie.py | 29 +++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/calibre/gui2/images/news/hln_be.png create mode 100644 src/calibre/web/feeds/recipes/recipe_hln_be.py create mode 100644 src/calibre/web/feeds/recipes/recipe_theoldfoodie.py diff --git a/src/calibre/gui2/images/news/hln_be.png b/src/calibre/gui2/images/news/hln_be.png new file mode 100644 index 0000000000000000000000000000000000000000..0cddf06420070893cc1bf65e206e442625ba9503 GIT binary patch literal 359 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*F|v9*VRoC%&Tjv*GOw+0{NJ)pp0)1$m|vhmKz))(7v zX*Dg(^hzpHkSWq)O!(1LrFZt=3Y83rMS|IDbS8O<#9{8Wz9S?MyYKdz^NlIc#s#S7PDv)9@GBB{z z1tG%_14}C-ATrW5Ftai+u+lsE5Jf|7eoAIqB~*ijuCZZ=p}Cc*rIn!xL_ Date: Tue, 1 Sep 2009 17:29:46 -0600 Subject: [PATCH 02/13] IGN:Various things needed to get calibre to build in Leopard --- installer/osx/freeze.py | 52 +- installer/osx/py2app/__init__.py | 10 + installer/osx/py2app/launcher.py | 50 + installer/osx/py2app/main.c | 1002 +++++++++++++++++ installer/osx/py2app/main.py | 459 ++++++++ installer/osx/py2app/site.py | 106 ++ pyqtdistutils.py | 31 +- setup.py | 16 +- src/calibre/devices/usbobserver/usbobserver.c | 4 +- src/calibre/gui2/dialogs/config/__init__.py | 12 +- src/calibre/gui2/dialogs/config/config.ui | 60 +- src/calibre/manual/faq.rst | 7 +- src/calibre/trac/plugins/download.py | 2 +- src/calibre/utils/PythonMagickWand.py | 9 +- src/calibre/utils/fonts/fontconfig.c | 26 +- src/calibre/utils/osx_symlinks.py | 57 + src/calibre/utils/podofo/podofo.cpp | 1 - upload.py | 63 +- 18 files changed, 1837 insertions(+), 130 deletions(-) create mode 100644 installer/osx/py2app/__init__.py create mode 100644 installer/osx/py2app/launcher.py create mode 100644 installer/osx/py2app/main.c create mode 100644 installer/osx/py2app/main.py create mode 100644 installer/osx/py2app/site.py create mode 100644 src/calibre/utils/osx_symlinks.py diff --git a/installer/osx/freeze.py b/installer/osx/freeze.py index 7791668a09..a6fd365869 100644 --- a/installer/osx/freeze.py +++ b/installer/osx/freeze.py @@ -54,54 +54,7 @@ os.environ['QT_PLUGIN_PATH'] = qt_plugins args = [path, loader_path] + sys.argv[1:] os.execv(python, args) ''' - CHECK_SYMLINKS_PRESCRIPT = \ -r''' -def _check_symlinks_prescript(): - import os, tempfile, traceback, sys - from Authorization import Authorization, kAuthorizationFlagDestroyRights - AUTHTOOL="""#!%(sp)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 - omask = os.umask(022) - os.symlink(s, l) - os.umask(omask) -""" - - 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, 'loaders', 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(sys.executable, 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 @@ -286,15 +239,12 @@ _check_symlinks_prescript() 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 = src.replace('import Image', 'from PIL import Image') - src = re.sub('(_run\s*\(.*?.py.*?\))', cs+'%s'%( + src = re.sub('(_run\s*\(.*?.py.*?\))', '%s'%( ''' sys.frameworks_dir = os.path.join(os.path.dirname(os.environ['RESOURCEPATH']), 'Frameworks') ''') + r'\n\1', src) diff --git a/installer/osx/py2app/__init__.py b/installer/osx/py2app/__init__.py new file mode 100644 index 0000000000..3d1a86922e --- /dev/null +++ b/installer/osx/py2app/__init__.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + + + diff --git a/installer/osx/py2app/launcher.py b/installer/osx/py2app/launcher.py new file mode 100644 index 0000000000..dcd14668f7 --- /dev/null +++ b/installer/osx/py2app/launcher.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +def _disable_linecache(): + import linecache + def fake_getline(*args, **kwargs): + return '' + linecache.orig_getline = linecache.getline + linecache.getline = fake_getline +_disable_linecache() + +def _recipes_pil_prescript(plugins): + from PIL import Image + import sys + def init(): + if Image._initialized >= 2: + return + for plugin in plugins: + try: + __import__(plugin, globals(), locals(), []) + except ImportError: + if Image.DEBUG: + print 'Image: failed to import' + print plugin, ':', sys.exc_info()[1] + if Image.OPEN or Image.SAVE: + Image._initialized = 2 + Image.init = init + + +_recipes_pil_prescript(['Hdf5StubImagePlugin', 'FitsStubImagePlugin', 'SunImagePlugin', 'GbrImagePlugin', 'PngImagePlugin', 'MicImagePlugin', 'FpxImagePlugin', 'PcxImagePlugin', 'ImImagePlugin', 'SpiderImagePlugin', 'PsdImagePlugin', 'BufrStubImagePlugin', 'SgiImagePlugin', 'McIdasImagePlugin', 'XpmImagePlugin', 'BmpImagePlugin', 'TgaImagePlugin', 'PalmImagePlugin', 'XVThumbImagePlugin', 'GribStubImagePlugin', 'ArgImagePlugin', 'PdfImagePlugin', 'ImtImagePlugin', 'GifImagePlugin', 'CurImagePlugin', 'WmfImagePlugin', 'MpegImagePlugin', 'IcoImagePlugin', 'TiffImagePlugin', 'PpmImagePlugin', 'MspImagePlugin', 'EpsImagePlugin', 'JpegImagePlugin', 'PixarImagePlugin', 'PcdImagePlugin', 'IptcImagePlugin', 'XbmImagePlugin', 'DcxImagePlugin', 'IcnsImagePlugin', 'FliImagePlugin']) + +def _run(): + global __file__ + import os, sys, site + sys.frozen = 'macosx_app' + base = os.environ['RESOURCEPATH'] + sys.frameworks_dir = os.path.join(os.path.dirname(base, 'Frameworks')) + site.addsitedir(base) + site.addsitedir(os.path.join(base, 'Python', 'site-packages')) + exe = os.environ.get('CALIBRE_LAUNCH_MODULE', 'calibre.gui2.main') + exe = os.path.join(base, 'Python', 'site-packages', *exe.split('.')) + sys.argv[0] = __file__ = exe + execfile(exe, globals(), globals()) + +_run() diff --git a/installer/osx/py2app/main.c b/installer/osx/py2app/main.c new file mode 100644 index 0000000000..01abe6d3a7 --- /dev/null +++ b/installer/osx/py2app/main.c @@ -0,0 +1,1002 @@ +#include +#include +#include +#include +#include +#include +#include + +/* + Typedefs +*/ + +typedef int PyObject; +typedef void (*Py_DecRefPtr)(PyObject *); +typedef void (*Py_SetProgramNamePtr)(const char *); +typedef void (*Py_InitializePtr)(void); +typedef int (*PyRun_SimpleFilePtr)(FILE *, const char *); +typedef void (*Py_FinalizePtr)(void); +typedef PyObject *(*PySys_GetObjectPtr)(const char *); +typedef int *(*PySys_SetArgvPtr)(int argc, char **argv); +typedef PyObject *(*PyObject_StrPtr)(PyObject *); +typedef const char *(*PyString_AsStringPtr)(PyObject *); +typedef PyObject *(*PyObject_GetAttrStringPtr)(PyObject *, const char *); +static void DefaultDecRef(PyObject *op) { + if (op != NULL) --(*op); +} + +typedef CFTypeRef id; +typedef const char *SEL; +typedef signed char BOOL; +#define NSAlertAlternateReturn 0 + +/* + Forward declarations +*/ +static int report_error(const char *); +static CFTypeRef getKey(const char *key); + +/* + Strings +*/ +static const char *ERR_REALLYBADTITLE = "The application could not be launched."; +static const char *ERR_TITLEFORMAT = "%@ has encountered a fatal error, and will now terminate."; +static const char *ERR_NONAME = "The Info.plist file must have values for the CFBundleName or CFBundleExecutable strings."; +static const char *ERR_PYRUNTIMELOCATIONS = "The Info.plist file must have a PyRuntimeLocations array containing string values for preferred Python runtime locations. These strings should be \"otool -L\" style mach ids; \"@executable_stub\" and \"~\" prefixes will be translated accordingly."; +static const char *ERR_NOPYTHONRUNTIME = "A Python runtime could be located. You may need to install a framework build of Python, or edit the PyRuntimeLocations array in this application's Info.plist file."; +static const char *ERR_NOPYTHONSCRIPT = "A main script could not be located in the Resources folder.;"; +static const char *ERR_LINKERRFMT = "An internal error occurred while attempting to link:\r\r%s\r\r"; +static const char *ERR_UNKNOWNPYTHONEXCEPTION = "An uncaught exception was raised during execution of the main script, but its class or name could not be determined"; +static const char *ERR_PYTHONEXCEPTION = "An uncaught exception was raised during execution of the main script:\r\r%@: %@\r\rThis may mean that an unexpected error has occurred, or that you do not have all of the dependencies for this application.\r\rSee the Console for a detailed traceback."; +static const char *ERR_COLONPATH = "Python applications can not currently run from paths containing a '/' (or ':' from the Terminal)."; +static const char *ERR_DEFAULTURLTITLE = "Visit Website"; +static const char *ERR_CONSOLEAPP = "Console.app"; +static const char *ERR_CONSOLEAPPTITLE = "Open Console"; +static const char *ERR_TERMINATE = "Terminate"; + +/* + Constants +*/ + +#define PYMACAPP_DYLD_FLAGS RTLD_LAZY|RTLD_GLOBAL + +/* + Globals +*/ +static CFMutableArrayRef pool; + +#define USES(NAME) static __typeof__(&NAME) x ## NAME +/* ApplicationServices */ +USES(LSOpenFSRef); +USES(LSFindApplicationForInfo); +USES(GetCurrentProcess); +USES(SetFrontProcess); +/* CoreFoundation */ +USES(CFArrayRemoveValueAtIndex); +USES(CFStringCreateFromExternalRepresentation); +USES(CFStringAppendCString); +USES(CFStringCreateMutable); +USES(kCFTypeArrayCallBacks); +USES(CFArrayCreateMutable); +USES(CFRetain); +USES(CFRelease); +USES(CFBundleGetMainBundle); +USES(CFBundleGetValueForInfoDictionaryKey); +USES(CFArrayGetCount); +USES(CFStringCreateWithCString); +USES(CFArrayGetValueAtIndex); +USES(CFArrayAppendValue); +USES(CFStringFind); +USES(CFBundleCopyPrivateFrameworksURL); +USES(CFURLCreateWithFileSystemPathRelativeToBase); +USES(CFStringCreateWithSubstring); +USES(CFStringGetLength); +USES(CFURLGetFileSystemRepresentation); +USES(CFURLCreateWithFileSystemPath); +USES(CFShow); +USES(CFBundleCopyResourcesDirectoryURL); +USES(CFURLCreateFromFileSystemRepresentation); +USES(CFURLCreateFromFileSystemRepresentationRelativeToBase); +USES(CFStringGetCharacterAtIndex); +USES(CFURLCreateWithString); +USES(CFStringGetCString); +USES(CFStringCreateByCombiningStrings); +USES(CFDictionaryGetValue); +USES(CFBooleanGetValue); +USES(CFStringCreateArrayBySeparatingStrings); +USES(CFArrayAppendArray); +USES(CFStringCreateByCombiningStrings); +USES(CFStringCreateWithFormat); +USES(CFBundleCopyResourceURL); +USES(CFBundleCopyAuxiliaryExecutableURL); +USES(CFURLCreateCopyDeletingLastPathComponent); +USES(CFURLCreateCopyAppendingPathComponent); +USES(CFURLCopyLastPathComponent); +USES(CFStringGetMaximumSizeForEncoding); +#undef USES + +/* + objc +*/ + +#define CLS(name) xobjc_getClass(name) +#define MSG(receiver, selName, ...) \ + xobjc_msgSend(receiver, xsel_getUid(selName), ## __VA_ARGS__) +static id (*xobjc_getClass)(const char *name); +static SEL (*xsel_getUid)(const char *str); +static id (*xobjc_msgSend)(id self, SEL op, ...); + +/* + Cocoa +*/ +static void (*xNSLog)(CFStringRef format, ...); +static BOOL (*xNSApplicationLoad)(void); +static int (*xNSRunAlertPanel)(CFStringRef title, CFStringRef msg, CFStringRef defaultButton, CFStringRef alternateButton, CFStringRef otherButton, ...); + +/* + Functions +*/ + +static int bind_objc_Cocoa_ApplicationServices(void) { + static Boolean bound = false; + if (bound) return 0; + bound = true; + void *cf_dylib; + cf_dylib = dlopen("/usr/lib/libobjc.dylib", PYMACAPP_DYLD_FLAGS); + if (cf_dylib == NULL) return -1; +#define LOOKUP(NAME) do { \ + void *tmpSymbol = dlsym( \ + cf_dylib, #NAME); \ + if (tmpSymbol == NULL) return -1; \ + x ## NAME = (__typeof__(x ## NAME))(tmpSymbol); \ + } while (0) + + LOOKUP(objc_getClass); + LOOKUP(sel_getUid); + LOOKUP(objc_msgSend); + + cf_dylib = dlopen( + "/System/Library/Frameworks/Cocoa.framework/Cocoa", + PYMACAPP_DYLD_FLAGS); + if (cf_dylib == NULL) return -1; + LOOKUP(NSLog); + LOOKUP(NSApplicationLoad); + LOOKUP(NSRunAlertPanel); + +#undef LOOKUP + + cf_dylib = dlopen( + "/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices", + PYMACAPP_DYLD_FLAGS); + if (cf_dylib == NULL) return -1; +#define LOOKUP(NAME) do { \ + void *tmpSymbol = dlsym( \ + cf_dylib, #NAME); \ + if (tmpSymbol == NULL) return -1; \ + x ## NAME = (__typeof__(&NAME))(tmpSymbol); \ + } while (0) + + LOOKUP(GetCurrentProcess); + LOOKUP(SetFrontProcess); + LOOKUP(LSOpenFSRef); + LOOKUP(LSFindApplicationForInfo); +#undef LOOKUP + return 0; +} + +static int bind_CoreFoundation(void) { + static Boolean bound = false; + void *cf_dylib; + if (bound) return 0; + bound = true; + cf_dylib = dlopen( + "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", + PYMACAPP_DYLD_FLAGS); + if (cf_dylib == NULL) return -1; + +#define LOOKUP(NAME) do { \ + void *tmpSymbol = dlsym( \ + cf_dylib, #NAME); \ + if (tmpSymbol == NULL) return -1; \ + x ## NAME = (__typeof__(&NAME))(tmpSymbol); \ + } while (0) + + LOOKUP(CFArrayRemoveValueAtIndex); + LOOKUP(CFStringCreateFromExternalRepresentation); + LOOKUP(CFStringAppendCString); + LOOKUP(CFStringCreateMutable); + LOOKUP(kCFTypeArrayCallBacks); + LOOKUP(CFArrayCreateMutable); + LOOKUP(CFRetain); + LOOKUP(CFRelease); + LOOKUP(CFBundleGetMainBundle); + LOOKUP(CFBundleGetValueForInfoDictionaryKey); + LOOKUP(CFArrayGetCount); + LOOKUP(CFStringCreateWithCString); + LOOKUP(CFArrayGetValueAtIndex); + LOOKUP(CFArrayAppendValue); + LOOKUP(CFStringFind); + LOOKUP(CFBundleCopyPrivateFrameworksURL); + LOOKUP(CFURLCreateWithFileSystemPathRelativeToBase); + LOOKUP(CFStringCreateWithSubstring); + LOOKUP(CFStringGetLength); + LOOKUP(CFURLGetFileSystemRepresentation); + LOOKUP(CFURLCreateWithFileSystemPath); + LOOKUP(CFShow); + LOOKUP(CFBundleCopyResourcesDirectoryURL); + LOOKUP(CFURLCreateFromFileSystemRepresentation); + LOOKUP(CFURLCreateFromFileSystemRepresentationRelativeToBase); + LOOKUP(CFStringGetCharacterAtIndex); + LOOKUP(CFURLCreateWithString); + LOOKUP(CFStringGetCString); + LOOKUP(CFStringCreateByCombiningStrings); + LOOKUP(CFDictionaryGetValue); + LOOKUP(CFBooleanGetValue); + LOOKUP(CFStringCreateArrayBySeparatingStrings); + LOOKUP(CFArrayAppendArray); + LOOKUP(CFStringCreateByCombiningStrings); + LOOKUP(CFStringCreateWithFormat); + LOOKUP(CFBundleCopyResourceURL); + LOOKUP(CFBundleCopyAuxiliaryExecutableURL); + LOOKUP(CFURLCreateCopyDeletingLastPathComponent); + LOOKUP(CFURLCreateCopyAppendingPathComponent); + LOOKUP(CFURLCopyLastPathComponent); + LOOKUP(CFStringGetMaximumSizeForEncoding); + +#undef LOOKUP + + return 0; +} + +#define AUTORELEASE(obj) ((obj == NULL) ? NULL : ( \ + xCFArrayAppendValue(pool, (const void *)obj), \ + xCFRelease(obj), \ + obj)) + +#define xCFSTR(s) AUTORELEASE( \ + xCFStringCreateWithCString(NULL, s, kCFStringEncodingUTF8)) + +static int openConsole(void) { + OSStatus err; + FSRef consoleRef; + err = xLSFindApplicationForInfo( + kLSUnknownCreator, + NULL, + xCFSTR(ERR_CONSOLEAPP), + &consoleRef, + NULL); + if (err != noErr) return err; + return xLSOpenFSRef((const FSRef *)&consoleRef, NULL); +} + +static CFTypeRef getKey(const char *key) { + CFTypeRef rval; + CFStringRef cfKey = xCFStringCreateWithCString(NULL, + key, kCFStringEncodingUTF8); + if (!cfKey) return NULL; + rval = xCFBundleGetValueForInfoDictionaryKey( + xCFBundleGetMainBundle(), + cfKey); + xCFRelease(cfKey); + return rval; +} + +static CFStringRef getApplicationName(void) { + static CFStringRef name = NULL; + if (name) return name; + name = (CFStringRef)getKey("CFBundleName"); + if (!name) name = (CFStringRef)getKey("CFBundleExecutable"); + return AUTORELEASE(name); +} + + +static CFStringRef getErrorTitle(CFStringRef applicationName) { + CFStringRef res; + if (!applicationName) return xCFSTR(ERR_REALLYBADTITLE); + res = xCFStringCreateWithFormat( + NULL, NULL, xCFSTR(ERR_TITLEFORMAT), applicationName); + AUTORELEASE(res); + return res; +} + +static void ensureGUI(void) { + ProcessSerialNumber psn; + id app = MSG(CLS("NSApplication"), "sharedApplication"); + xNSApplicationLoad(); + MSG(app, "activateIgnoringOtherApps:", (BOOL)1); + if (xGetCurrentProcess(&psn) == noErr) { + xSetFrontProcess(&psn); + } +} + +static int report_error(const char *error) { + int choice; + id releasePool; + if (bind_objc_Cocoa_ApplicationServices()) { + fprintf(stderr, "%s\n", error); + return -1; + } + releasePool = MSG(MSG(CLS("NSAutoreleasePool"), "alloc"), "init"); + xNSLog(xCFSTR("%@"), xCFSTR(error)); + if (!xNSApplicationLoad()) { + xNSLog(xCFSTR("NSApplicationLoad() failed")); + } else { + ensureGUI(); + choice = xNSRunAlertPanel( + getErrorTitle(getApplicationName()), + xCFSTR("%@"), + xCFSTR(ERR_TERMINATE), + xCFSTR(ERR_CONSOLEAPPTITLE), + NULL, + xCFSTR(error)); + if (choice == NSAlertAlternateReturn) openConsole(); + } + MSG(releasePool, "release"); + return -1; +} + +static CFStringRef pathFromURL(CFURLRef anURL) { + UInt8 buf[PATH_MAX]; + xCFURLGetFileSystemRepresentation(anURL, true, buf, sizeof(buf)); + return xCFStringCreateWithCString(NULL, (char *)buf, kCFStringEncodingUTF8); +} + +static CFStringRef pyStandardizePath(CFStringRef pyLocation) { + CFRange foundRange; + CFURLRef fmwkURL; + CFURLRef locURL; + CFStringRef subpath; + static CFStringRef prefix = NULL; + if (!prefix) prefix = xCFSTR("@executable_path/"); + foundRange = xCFStringFind(pyLocation, prefix, 0); + if (foundRange.location == kCFNotFound || foundRange.length == 0) { + return NULL; + } + fmwkURL = xCFBundleCopyPrivateFrameworksURL(xCFBundleGetMainBundle()); + foundRange.location = foundRange.length; + foundRange.length = xCFStringGetLength(pyLocation) - foundRange.length; + subpath = xCFStringCreateWithSubstring(NULL, pyLocation, foundRange); + locURL = xCFURLCreateWithFileSystemPathRelativeToBase( + NULL, + subpath, + kCFURLPOSIXPathStyle, + false, + fmwkURL); + xCFRelease(subpath); + xCFRelease(fmwkURL); + subpath = pathFromURL(locURL); + xCFRelease(locURL); + return subpath; +} + +static Boolean doesPathExist(CFStringRef path) { + struct stat st; + CFURLRef locURL; + UInt8 buf[PATH_MAX]; + locURL = xCFURLCreateWithFileSystemPath( + NULL, path, kCFURLPOSIXPathStyle, false); + xCFURLGetFileSystemRepresentation(locURL, true, buf, sizeof(buf)); + xCFRelease(locURL); + return (stat((const char *)buf, &st) == -1 ? false : true); +} + +static CFStringRef findPyLocation(CFArrayRef pyLocations) { + int i; + int cnt = xCFArrayGetCount(pyLocations); + for (i = 0; i < cnt; i++) { + CFStringRef newLoc; + CFStringRef pyLocation = xCFArrayGetValueAtIndex(pyLocations, i); + newLoc = pyStandardizePath(pyLocation); + if (!newLoc) newLoc = pyLocation; + if (doesPathExist(newLoc)) { + if (newLoc == pyLocation) xCFRetain(newLoc); + return newLoc; + } + if (newLoc) xCFRelease(newLoc); + } + return NULL; +} + +static CFStringRef tildeExpand(CFStringRef path) { + CFURLRef pathURL; + char buf[PATH_MAX]; + CFURLRef fullPathURL; + struct passwd *pwnam; + char tmp; + char *dir = NULL; + + + xCFStringGetCString(path, buf, sizeof(buf), kCFStringEncodingUTF8); + + int i; + if (buf[0] != '~') { + return xCFStringCreateWithCString( + NULL, buf, kCFStringEncodingUTF8); + } + /* user in path */ + i = 1; + while (buf[i] != '\0' && buf[i] != '/') { + i++; + } + if (i == 1) { + dir = getenv("HOME"); + } else { + tmp = buf[i]; + buf[i] = '\0'; + pwnam = getpwnam((const char *)&buf[1]); + if (pwnam) dir = pwnam->pw_dir; + buf[i] = tmp; + } + if (!dir) { + return xCFStringCreateWithCString(NULL, buf, kCFStringEncodingUTF8); + } + pathURL = xCFURLCreateFromFileSystemRepresentation( + NULL, (const UInt8*)dir, strlen(dir), false); + fullPathURL = xCFURLCreateFromFileSystemRepresentationRelativeToBase( + NULL, (const UInt8*)&buf[i + 1], strlen(&buf[i + 1]), false, pathURL); + xCFRelease(pathURL); + path = pathFromURL(fullPathURL); + xCFRelease(fullPathURL); + return path; +} + +static void setcfenv(char *name, CFStringRef value) { + char buf[PATH_MAX]; + xCFStringGetCString(value, buf, sizeof(buf), kCFStringEncodingUTF8); + setenv(name, buf, 1); +} + +static void setPythonPath(void) { + CFMutableArrayRef paths; + CFURLRef resDir; + CFStringRef resPath; + CFArrayRef resPackages; + CFDictionaryRef options; + + paths = xCFArrayCreateMutable(NULL, 0, xkCFTypeArrayCallBacks); + + resDir = xCFBundleCopyResourcesDirectoryURL(xCFBundleGetMainBundle()); + resPath = pathFromURL(resDir); + xCFArrayAppendValue(paths, resPath); + xCFRelease(resPath); + + resPackages = getKey("PyResourcePackages"); + if (resPackages) { + int i; + int cnt = xCFArrayGetCount(resPackages); + for (i = 0; i < cnt; i++) { + resPath = tildeExpand(xCFArrayGetValueAtIndex(resPackages, i)); + if (xCFStringGetLength(resPath)) { + if (xCFStringGetCharacterAtIndex(resPath, 0) != '/') { + CFURLRef absURL = xCFURLCreateWithString( + NULL, resPath, resDir); + xCFRelease(resPath); + resPath = pathFromURL(absURL); + xCFRelease(absURL); + } + xCFArrayAppendValue(paths, resPath); + } + xCFRelease(resPath); + } + } + + xCFRelease(resDir); + + options = getKey("PyOptions"); + if (options) { + CFBooleanRef use_pythonpath; + use_pythonpath = xCFDictionaryGetValue( + options, xCFSTR("use_pythonpath")); + if (use_pythonpath && xCFBooleanGetValue(use_pythonpath)) { + char *ppath = getenv("PYTHONPATH"); + if (ppath) { + CFArrayRef oldPath; + oldPath = xCFStringCreateArrayBySeparatingStrings( + NULL, xCFSTR(ppath), xCFSTR(":")); + if (oldPath) { + CFRange rng; + rng.location = 0; + rng.length = xCFArrayGetCount(oldPath); + xCFArrayAppendArray(paths, oldPath, rng); + xCFRelease(oldPath); + } + } + } + } + + if (xCFArrayGetCount(paths)) { + resPath = xCFStringCreateByCombiningStrings(NULL, paths, xCFSTR(":")); + setcfenv("PYTHONPATH", resPath); + xCFRelease(resPath); + } + xCFRelease(paths); +} + + + +static void setResourcePath(void) { + CFURLRef resDir; + CFStringRef resPath; + resDir = xCFBundleCopyResourcesDirectoryURL(xCFBundleGetMainBundle()); + resPath = pathFromURL(resDir); + xCFRelease(resDir); + setcfenv("RESOURCEPATH", resPath); + xCFRelease(resPath); +} + +static void setExecutablePath(void) { + char executable_path[PATH_MAX]; + uint32_t bufsize = PATH_MAX; + if (!_NSGetExecutablePath(executable_path, &bufsize)) { + executable_path[bufsize] = '\0'; + setenv("EXECUTABLEPATH", executable_path, 1); + } +} + +static CFStringRef getMainScript(void) { + CFMutableArrayRef possibleMains; + CFBundleRef bndl; + CFStringRef e_py, e_pyc, e_pyo, path; + int i, cnt; + possibleMains = xCFArrayCreateMutable(NULL, 0, xkCFTypeArrayCallBacks); + CFArrayRef firstMains = getKey("PyMainFileNames"); + if (firstMains) { + CFRange rng; + rng.location = 0; + rng.length = xCFArrayGetCount(firstMains); + xCFArrayAppendArray(possibleMains, firstMains, rng); + } + xCFArrayAppendValue(possibleMains, xCFSTR("__main__")); + xCFArrayAppendValue(possibleMains, xCFSTR("__realmain__")); + xCFArrayAppendValue(possibleMains, xCFSTR("launcher")); + + e_py = xCFSTR("py"); + e_pyc = xCFSTR("pyc"); + e_pyo = xCFSTR("pyo"); + + cnt = xCFArrayGetCount(possibleMains); + bndl = xCFBundleGetMainBundle(); + path = NULL; + for (i = 0; i < cnt; i++) { + CFStringRef base; + CFURLRef resURL; + base = xCFArrayGetValueAtIndex(possibleMains, i); + resURL = xCFBundleCopyResourceURL(bndl, base, e_py, NULL); + if (resURL == NULL) { + resURL = xCFBundleCopyResourceURL(bndl, base, e_pyc, NULL); + } + if (resURL == NULL) { + resURL = xCFBundleCopyResourceURL(bndl, base, e_pyo, NULL); + } + if (resURL != NULL) { + path = pathFromURL(resURL); + xCFRelease(resURL); + break; + } + } + xCFRelease(possibleMains); + return path; +} + +static int report_linkEdit_error(void) { + CFStringRef errString; + const char *errorString; + char *buf; + errorString = dlerror(); + fprintf(stderr, errorString); + errString = xCFStringCreateWithFormat( + NULL, NULL, xCFSTR(ERR_LINKERRFMT), errString); + buf = alloca(xCFStringGetMaximumSizeForEncoding( + xCFStringGetLength(errString), kCFStringEncodingUTF8)); + xCFStringGetCString(errString, buf, sizeof(buf), kCFStringEncodingUTF8); + xCFRelease(errString); + return report_error(buf); +} + +static CFStringRef getPythonInterpreter(CFStringRef pyLocation) { + CFBundleRef bndl; + CFStringRef auxName; + CFURLRef auxURL; + CFStringRef path; + + auxName = getKey("PyExecutableName"); + if (!auxName) auxName = xCFSTR("python"); + bndl = xCFBundleGetMainBundle(); + auxURL = xCFBundleCopyAuxiliaryExecutableURL(bndl, auxName); + if (auxURL) { + path = pathFromURL(auxURL); + xCFRelease(auxURL); + return path; + } + return NULL; +} + +static CFStringRef getErrorScript(void) { + CFMutableArrayRef errorScripts; + CFBundleRef bndl; + CFStringRef path; + int i, cnt; + errorScripts = xCFArrayCreateMutable(NULL, 0, xkCFTypeArrayCallBacks); + CFArrayRef firstErrorScripts = getKey("PyErrorScripts"); + if (firstErrorScripts) { + CFRange rng; + rng.location = 0; + rng.length = xCFArrayGetCount(firstErrorScripts); + xCFArrayAppendArray(errorScripts, firstErrorScripts, rng); + } + xCFArrayAppendValue(errorScripts, xCFSTR("__error__")); + xCFArrayAppendValue(errorScripts, xCFSTR("__error__.py")); + xCFArrayAppendValue(errorScripts, xCFSTR("__error__.pyc")); + xCFArrayAppendValue(errorScripts, xCFSTR("__error__.pyo")); + xCFArrayAppendValue(errorScripts, xCFSTR("__error__.sh")); + + cnt = xCFArrayGetCount(errorScripts); + bndl = xCFBundleGetMainBundle(); + path = NULL; + for (i = 0; i < cnt; i++) { + CFStringRef base; + CFURLRef resURL; + base = xCFArrayGetValueAtIndex(errorScripts, i); + resURL = xCFBundleCopyResourceURL(bndl, base, NULL, NULL); + if (resURL) { + path = pathFromURL(resURL); + xCFRelease(resURL); + break; + } + } + xCFRelease(errorScripts); + return path; + +} + +static CFMutableArrayRef get_trimmed_lines(CFStringRef output) { + CFMutableArrayRef lines; + CFArrayRef tmp; + CFRange rng; + lines = xCFArrayCreateMutable(NULL, 0, xkCFTypeArrayCallBacks); + tmp = xCFStringCreateArrayBySeparatingStrings( + NULL, output, xCFSTR("\n")); + rng.location = 0; + rng.length = xCFArrayGetCount(tmp); + xCFArrayAppendArray(lines, tmp, rng); + while (true) { + CFIndex cnt = xCFArrayGetCount(lines); + CFStringRef last; + /* Nothing on stdout means pass silently */ + if (cnt <= 0) { + xCFRelease(lines); + return NULL; + } + last = xCFArrayGetValueAtIndex(lines, cnt - 1); + if (xCFStringGetLength(last) > 0) break; + xCFArrayRemoveValueAtIndex(lines, cnt - 1); + } + return lines; +} + +static int report_script_error(const char *msg, CFStringRef cls, CFStringRef name) { + CFStringRef errorScript; + CFMutableArrayRef lines; + CFRange foundRange; + CFStringRef lastLine; + CFStringRef output = NULL; + CFIndex lineCount; + CFURLRef buttonURL = NULL; + CFStringRef buttonString = NULL; + CFStringRef title = NULL; + CFStringRef errmsg = NULL; + id releasePool; + int errBinding; + int status = 0; + char *buf; + + + if (cls && name) { + CFStringRef errString = xCFStringCreateWithFormat( + NULL, NULL, xCFSTR(msg), cls, name); + buf = alloca(xCFStringGetMaximumSizeForEncoding( + xCFStringGetLength(errString), kCFStringEncodingUTF8)); + xCFStringGetCString( + errString, buf, sizeof(buf), kCFStringEncodingUTF8); + xCFRelease(errString); + } else { + buf = (char *)msg; + } + + errorScript = getErrorScript(); + if (!errorScript) return report_error(buf); + + errBinding = bind_objc_Cocoa_ApplicationServices(); + if (!errBinding) { + id task, stdoutPipe, taskData; + CFMutableArrayRef argv; + releasePool = MSG(MSG(CLS("NSAutoreleasePool"), "alloc"), "init"); + task = MSG(MSG(CLS("NSTask"), "alloc"), "init"); + stdoutPipe = MSG(CLS("NSPipe"), "pipe"); + MSG(task, "setLaunchPath:", xCFSTR("/bin/sh")); + MSG(task, "setStandardOutput:", stdoutPipe); + argv = xCFArrayCreateMutable(NULL, 0, xkCFTypeArrayCallBacks); + xCFArrayAppendValue(argv, errorScript); + xCFArrayAppendValue(argv, getApplicationName()); + if (cls && name) { + xCFArrayAppendValue(argv, cls); + xCFArrayAppendValue(argv, name); + } + MSG(task, "setArguments:", argv); + /* This could throw, in theory, but /bin/sh should prevent that */ + MSG(task, "launch"); + MSG(task, "waitUntilExit"); + taskData = MSG( + MSG(stdoutPipe, "fileHandleForReading"), + "readDataToEndOfFile"); + xCFRelease(argv); + + status = (int)MSG(task, "terminationStatus"); + xCFRelease(task); + if (!status && taskData) { + output = xCFStringCreateFromExternalRepresentation( + NULL, taskData, kCFStringEncodingUTF8); + } + + MSG(releasePool, "release"); + } + + xCFRelease(errorScript); + if (status || !output) return report_error(buf); + + lines = get_trimmed_lines(output); + xCFRelease(output); + /* Nothing on stdout means pass silently */ + if (!lines) return -1; + lineCount = xCFArrayGetCount(lines); + lastLine = xCFArrayGetValueAtIndex(lines, lineCount - 1); + foundRange = xCFStringFind(lastLine, xCFSTR("ERRORURL: "), 0); + if (foundRange.location != kCFNotFound && foundRange.length != 0) { + CFMutableArrayRef buttonArr; + CFArrayRef tmp; + CFRange rng; + buttonArr = xCFArrayCreateMutable(NULL, 0, xkCFTypeArrayCallBacks); + tmp = xCFStringCreateArrayBySeparatingStrings( + NULL, lastLine, xCFSTR(" ")); + lineCount -= 1; + xCFArrayRemoveValueAtIndex(lines, lineCount); + rng.location = 1; + rng.length = xCFArrayGetCount(tmp) - 1; + xCFArrayAppendArray(buttonArr, tmp, rng); + xCFRelease(tmp); + while (true) { + CFStringRef tmpstr; + if (xCFArrayGetCount(buttonArr) <= 0) break; + tmpstr = xCFArrayGetValueAtIndex(buttonArr, 0); + if (xCFStringGetLength(tmpstr) == 0) { + xCFArrayRemoveValueAtIndex(buttonArr, 0); + } else { + break; + } + } + + buttonURL = xCFURLCreateWithString( + NULL, xCFArrayGetValueAtIndex(buttonArr, 0), NULL); + if (buttonURL) { + xCFArrayRemoveValueAtIndex(buttonArr, 0); + while (true) { + CFStringRef tmpstr; + if (xCFArrayGetCount(buttonArr) <= 0) break; + tmpstr = xCFArrayGetValueAtIndex(buttonArr, 0); + if (xCFStringGetLength(tmpstr) == 0) { + xCFArrayRemoveValueAtIndex(buttonArr, 0); + } else { + break; + } + } + if (xCFArrayGetCount(buttonArr) > 0) { + buttonString = xCFStringCreateByCombiningStrings( + NULL, buttonArr, xCFSTR(" ")); + } + if (!buttonString) buttonString = xCFSTR(ERR_DEFAULTURLTITLE); + } + xCFRelease(buttonArr); + + } + if (lineCount <= 0 || errBinding) { + xCFRelease(lines); + return report_error(buf); + } + + releasePool = MSG(MSG(CLS("NSAutoreleasePool"), "alloc"), "init"); + + title = xCFArrayGetValueAtIndex(lines, 0); + xCFRetain(title); + AUTORELEASE(title); + lineCount -= 1; + xCFArrayRemoveValueAtIndex(lines, lineCount); + xNSLog(xCFSTR("%@"), title); + if (lineCount > 0) { + CFStringRef showerr; + errmsg = xCFStringCreateByCombiningStrings( + NULL, lines, xCFSTR("\r")); + AUTORELEASE(errmsg); + showerr = MSG( + MSG(errmsg, "componentsSeparatedByString:", xCFSTR("\r")), + "componentsJoinedByString:", xCFSTR("\n")); + xNSLog(xCFSTR("%@"), showerr); + } else { + errmsg = xCFSTR(""); + } + + ensureGUI(); + if (!buttonURL) { + int choice = xNSRunAlertPanel( + title, xCFSTR("%@"), xCFSTR(ERR_TERMINATE), + xCFSTR(ERR_CONSOLEAPPTITLE), NULL, errmsg); + if (choice == NSAlertAlternateReturn) openConsole(); + } else { + int choice = xNSRunAlertPanel( + title, xCFSTR("%@"), xCFSTR(ERR_TERMINATE), + buttonString, NULL, errmsg); + if (choice == NSAlertAlternateReturn) { + id ws = MSG(CLS("NSWorkspace"), "sharedWorkspace"); + MSG(ws, "openURL:", buttonURL); + } + } + MSG(releasePool, "release"); + xCFRelease(lines); + return -1; +} + +static int py2app_main(int argc, char * const *argv, char * const *envp) { + CFArrayRef pyLocations; + CFStringRef pyLocation; + CFStringRef mainScript; + CFStringRef pythonInterpreter; + char *resource_path; + char buf[PATH_MAX]; + char c_pythonInterpreter[PATH_MAX]; + char c_mainScript[PATH_MAX]; + char **argv_new; + struct stat sb; + void *py_dylib; + void *tmpSymbol; + int rval; + FILE *mainScriptFile; + + + if (!getApplicationName()) return report_error(ERR_NONAME); + pyLocations = (CFArrayRef)getKey("PyRuntimeLocations"); + if (!pyLocations) return report_error(ERR_PYRUNTIMELOCATIONS); + pyLocation = findPyLocation(pyLocations); + if (!pyLocation) return report_error(ERR_NOPYTHONRUNTIME); + + setExecutablePath(); + setResourcePath(); + /* check for ':' in path, not compatible with Python due to Py_GetPath */ + /* XXX: Could work-around by creating something in /tmp I guess */ + resource_path = getenv("RESOURCEPATH"); + if ((resource_path == NULL) || (strchr(resource_path, ':') != NULL)) { + return report_error(ERR_COLONPATH); + } + setPythonPath(); + setenv("ARGVZERO", argv[0], 1); + + mainScript = getMainScript(); + if (!mainScript) return report_error(ERR_NOPYTHONSCRIPT); + + pythonInterpreter = getPythonInterpreter(pyLocation); + xCFStringGetCString( + pythonInterpreter, c_pythonInterpreter, + sizeof(c_pythonInterpreter), kCFStringEncodingUTF8); + xCFRelease(pythonInterpreter); + if (lstat(c_pythonInterpreter, &sb) == 0) { + if (!((sb.st_mode & S_IFLNK) == S_IFLNK)) { + setenv("PYTHONHOME", resource_path, 1); + } + } + + xCFStringGetCString(pyLocation, buf, sizeof(buf), kCFStringEncodingUTF8); + py_dylib = dlopen(buf, PYMACAPP_DYLD_FLAGS); + if (py_dylib == NULL) return report_linkEdit_error(); + +#define LOOKUP_SYMBOL(NAME) \ + tmpSymbol = dlsym(py_dylib, # NAME) +#define LOOKUP_DEFINEADDRESS(NAME, ADDRESS) \ + NAME ## Ptr NAME = (NAME ## Ptr)ADDRESS +#define LOOKUP_DEFINE(NAME) \ + LOOKUP_DEFINEADDRESS(NAME, (tmpSymbol)) +#define LOOKUP(NAME) \ + LOOKUP_SYMBOL(NAME); \ + if ( tmpSymbol == NULL) \ + return report_linkEdit_error(); \ + LOOKUP_DEFINE(NAME) + + LOOKUP_SYMBOL(Py_DecRef); + LOOKUP_DEFINEADDRESS(Py_DecRef, (tmpSymbol ? (tmpSymbol) : &DefaultDecRef)); + LOOKUP(Py_SetProgramName); + LOOKUP(Py_Initialize); + LOOKUP(PyRun_SimpleFile); + LOOKUP(Py_Finalize); + LOOKUP(PySys_GetObject); + LOOKUP(PySys_SetArgv); + LOOKUP(PyObject_Str); + LOOKUP(PyString_AsString); + LOOKUP(PyObject_GetAttrString); + +#undef LOOKUP +#undef LOOKUP_DEFINE +#undef LOOKUP_DEFINEADDRESS +#undef LOOKUP_SYMBOL + + Py_SetProgramName(c_pythonInterpreter); + + Py_Initialize(); + + xCFStringGetCString( + mainScript, c_mainScript, + sizeof(c_mainScript), kCFStringEncodingUTF8); + xCFRelease(mainScript); + + argv_new = alloca((argc + 1) * sizeof(char *)); + argv_new[argc] = NULL; + argv_new[0] = c_mainScript; + memcpy(&argv_new[1], &argv[1], (argc - 1) * sizeof(char *)); + PySys_SetArgv(argc, argv_new); + + mainScriptFile = fopen(c_mainScript, "r"); + rval = PyRun_SimpleFile(mainScriptFile, c_mainScript); + fclose(mainScriptFile); + + while (rval) { + PyObject *exc, *exceptionClassName, *v, *exceptionName; + CFStringRef clsName, excName; + + exc = PySys_GetObject("last_type"); + if ( !exc ) { + rval = report_error(ERR_UNKNOWNPYTHONEXCEPTION); + break; + } + + exceptionClassName = PyObject_GetAttrString(exc, "__name__"); + if (!exceptionClassName) { + rval = report_error(ERR_UNKNOWNPYTHONEXCEPTION); + break; + } + + v = PySys_GetObject("last_value"); + exceptionName = (v ? PyObject_Str(v) : NULL); + + clsName = xCFSTR(PyString_AsString(exceptionClassName)); + Py_DecRef(exceptionClassName); + if (exceptionName) { + excName = xCFSTR(PyString_AsString(exceptionName)); + Py_DecRef(exceptionName); + } else { + excName = xCFSTR(""); + } + rval = report_script_error(ERR_PYTHONEXCEPTION, clsName, excName); + break; + } + + Py_Finalize(); + + return rval; +} + +int main(int argc, char * const *argv, char * const *envp) +{ + int rval; + if (bind_CoreFoundation()) { + fprintf(stderr, "CoreFoundation not found or functions missing\n"); + return -1; + } + if (!xCFBundleGetMainBundle()) { + fprintf(stderr, "Not bundled, exiting\n"); + return -1; + } + pool = xCFArrayCreateMutable(NULL, 0, xkCFTypeArrayCallBacks); + if (!pool) { + fprintf(stderr, "Couldn't create global pool\n"); + return -1; + } + rval = py2app_main(argc, argv, envp); + xCFRelease(pool); + return rval; +} diff --git a/installer/osx/py2app/main.py b/installer/osx/py2app/main.py new file mode 100644 index 0000000000..42f9c4361f --- /dev/null +++ b/installer/osx/py2app/main.py @@ -0,0 +1,459 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import sys, os, shutil, plistlib, subprocess, glob, zipfile, tempfile, \ + py_compile, stat, operator +abspath, join, basename = os.path.abspath, os.path.join, os.path.basename + +l = {} +exec open('setup.py').read() in l +VERSION = l['VERSION'] +APPNAME = l['APPNAME'] +scripts = l['scripts'] +basenames = l['basenames'] +main_functions = l['main_functions'] +main_modules = l['main_modules'] +LICENSE = open('LICENSE', 'rb').read() +ENV = dict( + FC_CONFIG_DIR='@executable_path/../Resources/fonts', + MAGICK_HOME='@executable_path/../Frameworks/ImageMagick', + PYTHONDONTWRITEBYTECODE='1', + PYTHONIOENCODING='utf-8:replace', + PYTHONPATH='@executable_path/../Resources/Python/site-packages', + PYTHONHOME='@executable_path/../Resources/Python', + ) + +SW = os.environ.get('SW') + +def flipwritable(fn, mode=None): + """ + Flip the writability of a file and return the old mode. Returns None + if the file is already writable. + """ + if os.access(fn, os.W_OK): + return None + old_mode = os.stat(fn).st_mode + os.chmod(fn, stat.S_IWRITE | old_mode) + return old_mode + +STRIPCMD = ['/usr/bin/strip', '-x', '-S', '-'] +def strip_files(files, argv_max=(256 * 1024)): + """ + Strip a list of files + """ + tostrip = [(fn, flipwritable(fn)) for fn in files] + while tostrip: + cmd = list(STRIPCMD) + flips = [] + pathlen = reduce(operator.add, [len(s) + 1 for s in cmd]) + while pathlen < argv_max: + if not tostrip: + break + added, flip = tostrip.pop() + pathlen += len(added) + 1 + cmd.append(added) + flips.append((added, flip)) + else: + cmd.pop() + tostrip.append(flips.pop()) + os.spawnv(os.P_WAIT, cmd[0], cmd) + for args in flips: + flipwritable(*args) + +class Py2App(object): + + FID = '@executable_path/../Frameworks' + + def __init__(self, build_dir): + self.build_dir = build_dir + self.contents_dir = join(self.build_dir, 'Contents') + self.resources_dir = join(self.contents_dir, 'Resources') + self.frameworks_dir = join(self.contents_dir, 'Frameworks') + self.to_strip = [] + + def run(self): + self.create_skeleton() + self.create_plist() + + self.add_python_framework() + self.add_qt_frameworks() + self.add_calibre_plugins() + self.add_podofo() + self.add_poppler() + self.add_libjpeg() + self.add_libpng() + self.add_fontconfig() + self.add_imagemagick() + self.add_plugins() + self.add_qt_plugins() + self.add_ipython() + self.add_misc_libraries() + + self.add_site_packages() + self.add_stdlib() + self.compile_py_modules() + + self.create_console_app() + + self.copy_launcher_and_site() + self.create_exe() + self.strip_files() + + return self.makedmg(self.builddir, APPNAME+'-'+VERSION+'-x86_64') + + def strip_files(self): + strip_files(self.to_strip) + + def create_exe(self): + gcc = os.environ.get('GCC', 'gcc') + base = os.path.dirname(__file__) + out = join(self.contents_dir, 'MacOS', 'calibre') + subprocess.check_call([gcc, '-Wall', '-arch x86_64', join(base, + 'main.c'), '-o', out]) + self.to_strip(out) + + def set_id(self, path_to_lib, new_id): + old_mode = flipwritable(path_to_lib) + subprocess.check_call(['install_name_tool', '-id', new_id, path_to_lib]) + if old_mode is not None: + flipwritable(path_to_lib, old_mode) + + def get_dependencies(self, path_to_lib): + raw = subprocess.Popen(['otool', '-L', path_to_lib], + stdout=subprocess.PIPE).stdout.read() + for line in raw.splitlines(): + if 'compatibility' not in line: + continue + idx = line.find('(') + path = line[:idx].strip() + bname = os.path.basename(path).partition('.')[0] + if bname in path_to_lib: + continue + yield path + + def get_local_dependencies(self, path_to_lib): + for x in self.get_dependencies(path_to_lib): + for y in (SW+'/lib/', '/usr/local/lib/', SW+'/qt/lib/', + SW+'/python/'): + if x.startswith(y): + yield x, x[len(y):] + break + + def change_dep(self, old_dep, new_dep, path_to_lib): + print '\tResolving dependency %s to'%old_dep, new_dep + subprocess.check_call(['install_name_tool', '-change', old_dep, new_dep, + path_to_lib]) + + def fix_dependencies_in_lib(self, path_to_lib): + print '\nFixing dependecies in', path_to_lib + self.to_strip.append(path_to_lib) + old_mode = flipwritable(path_to_lib) + for dep, bname in self.get_local_dependencies(path_to_lib): + ndep = self.FID+'/'+bname + self.change_dep(dep, ndep, path_to_lib) + if list(self.get_local_dependencies(path_to_lib)): + raise Exception('Failed to resolve deps in: '+path_to_lib) + if old_mode is not None: + flipwritable(path_to_lib, old_mode) + + def add_python_framework(self): + src = join(SW, 'python', 'Python.framework') + x = join(self.frameworks_dir, 'Python.framework') + curr = os.path.realpath(join(src, 'Versions', 'Current')) + currd = join(x, 'Versions', basename(curr)) + rd = join(currd, 'Resources') + os.makedirs(rd) + shutil.copy2(join(curr, 'Resources', 'Info.plist'), rd) + shutil.copy2(join(curr, 'Python'), currd) + self.set_id(join(currd, 'Python'), + self.FID+'/Python.framework/Versions/%s/Python'%basename(curr)) + + def add_qt_frameworks(self): + for f in ('QtCore', 'QtGui', 'QtXml', 'QtNetwork', 'QtSvg', 'QtWebkit', + 'phonon'): + self.add_qt_framework(f) + for d in glob.glob(join(SW, 'qt', 'plugins', '*')): + shutil.copytree(d, join(self.contents_dir, 'MacOS', basename(d))) + for l in glob.glob(join(self.contents_dir, 'MacOS', '*/*.dylib')): + self.fix_dependencies_in_lib(l) + x = os.path.relpath(l, join(self.contents_dir, 'MacOS')) + self.set_id(l, '@executable_path/'+x) + + def add_qt_framework(self, f): + libname = f + f = f+'.framework' + src = join(SW, 'qt', 'lib', f) + ignore = shutil.ignore_patterns('Headers', '*.h', 'Headers/*') + dest = join(self.frameworks_dir, f) + shutil.copytree(src, dest, symlinks=True, + ignore=ignore) + lib = os.path.realpath(join(dest, libname)) + rpath = os.path.relpath(lib, self.frameworks_dir) + self.set_id(lib, self.FID+'/'+rpath) + self.fix_dependencies_in_lib(lib) + + def create_skeleton(self): + c = join(self.build_dir, 'Contents') + for x in ('Frameworks', 'MacOS', 'Resources'): + os.makedirs(join(c, x)) + x = 'library.icns' + shutil.copyfile(join('icons', x), join(self.resources_dir, x)) + + def add_calibre_plugins(self): + dest = join(self.frameworks_dir, 'plugins') + os.mkdir(dest) + for f in glob.glob('src/calibre/plugins/*.so'): + shutil.copy2(f, dest) + self.fix_dependencies_in_lib(join(dest, basename(f))) + + + def create_plist(self): + pl = dict( + CFBundleDevelopmentRegion='English', + CFBundleDisplayName=APPNAME, + CFBundleName=APPNAME, + CFBundleIdentifier='net.kovidgoyal.calibre', + CFBundleVersion=VERSION, + CFBundlePackageType='APPL', + CFBundleSignature='????', + CFBundleExecutable='calibre', + LSMinimumSystemVersion='10.5.2', + PyRuntimeLocations=[self.FID+'/Python.framework/Versions/Current/Python'], + LSRequiresNativeExecution=True, + NSAppleScriptEnabled=False, + NSHumanReadableCopyright='Copyright 2008, Kovid Goyal', + CFBundleGetInfoString=('calibre, an E-book management ' + 'application. Visit http://calibre.kovidgoyal.net for details.'), + CFBundleIconFile='library.icns', + LSMultipleInstancesProhibited=True, + LSEnvironment=ENV + ) + plistlib.writePlist(pl, join(self.contents_dir, 'Info.plist')) + + def install_dylib(self, path, set_id=True): + shutil.copy2(path, self.frameworks_dir) + if set_id: + self.set_id(join(self.frameworks_dir, basename(path)), + self.FID+'/'+basename(path)) + self.fix_dependencies_in_lib(join(self.frameworks_dir, basename(path))) + + def add_podofo(self): + print '\nAdding PoDoFo' + pdf = join(SW, 'lib', 'libpodofo.0.6.99.dylib') + self.install_dylib(pdf) + + def add_poppler(self): + print '\nAdding poppler' + for x in ('libpoppler.4.dylib', 'libpoppler-qt4.3.dylib'): + self.install_dylib(os.path.join(SW, 'lib', x)) + self.install_dylib(os.path.join(SW, 'bin', 'pdftohtml'), False) + + def add_libjpeg(self): + print '\nAdding libjpeg' + self.install_dylib(os.path.join(SW, 'lib', 'libjpeg.7.dylib')) + + def add_libpng(self): + print '\nAdding libpng' + self.install_dylib(os.path.join(SW, 'lib', 'libpng12.0.dylib')) + + def add_fontconfig(self): + print '\nAdding fontconfig' + for x in ('fontconfig.1', 'freetype.6', 'expat.1'): + src = os.path.join(SW, 'lib', 'lib'+x+'.dylib') + self.install_dylib(src) + dst = os.path.join(self.resource_dir, 'fonts') + if os.path.exists(dst): + shutil.rmtree(dst) + src = os.path.join(SW, 'etc', 'fonts') + shutil.copytree(src, dst, symlinks=False) + fc = os.path.join(dst, 'fonts.conf') + raw = open(fc, 'rb').read() + raw = raw.replace('/usr/share/fonts', '''\ + /Library/Fonts + /Network/Library/Fonts + /System/Library/Fonts + /usr/X11R6/lib/X11/fonts + /usr/share/fonts + /var/root/Library/Fonts + /usr/share/fonts + ''') + open(fc, 'wb').write(raw) + + def add_imagemagick(self): + print '\nAdding ImageMagick' + for x in ('Wand', 'Core'): + self.install_dylib(os.path.join(SW, 'lib', 'libMagick%s.2.dylib'%x)) + idir = glob.glob(os.path.join(SW, 'lib', 'ImageMagick-*'))[-1] + dest = os.path.join(self.frameworks_dir, 'ImageMagick', 'lib') + if not os.path.exists(dest): + os.makedirs(dest) + dest = os.path.join(dest, os.path.basename(idir)) + if os.path.exists(dest): + shutil.rmtree(dest) + shutil.copytree(idir, dest, True) + + def add_misc_libraries(self): + for x in ('usb', 'unrar'): + print '\nAdding', x + shutil.copy2(join(SW, 'lib', 'lib%s.dylib'%x), self.frameworks_dir) + + def add_site_packages(self): + print '\nAdding site-packages' + self.site_packages = join(self.resources_dir, 'Python', 'site-packages') + os.makedirs(self.site_packages) + paths = reversed(map(abspath, [x for x in sys.path if x.startswith('/')])) + upaths = [] + for x in paths: + if x not in upaths: + upaths.append(x) + for x in upaths: + if x.endswith('/PIL') or 'site-packages' not in x: + continue + tdir = None + try: + if not os.path.isdir(x): + try: + zf = zipfile.ZipFile(x) + except: + print "WARNING:", x, 'is neither a directory nor a zipfile' + continue + tdir = tempfile.mkdtemp() + zf.extract_all(tdir) + x = tdir + self.add_modules_from_dir(x) + self.add_packages_from_dir(x) + finally: + if tdir is not None: + shutil.rmtree(tdir) + self.remove_bytecode(join(self.resources_dir, 'Python', 'site-packages')) + + def add_modules_from_dir(self, src): + for x in glob.glob(join(src, '*.py'))+glob.glob(join(src, '*.so')): + dest = join(self.site_packages, basename(x)) + shutil.copy2(x, self.site_packages) + if x.endswith('.so'): + self.fix_dependencies_in_lib(x) + + def add_packages_from_dir(self, src): + for x in os.listdir(src): + x = join(src, x) + if os.path.isdir(x) and os.path.exists(join(x, '__init__.py')): + if self.filter_package(basename(x)): + continue + self.add_package_dir(x) + + def add_package_dir(self, x, dest=None): + def ignore(root, files): + ans = [] + for y in files: + if os.path.splitext(y) in ('.py', '.so'): + continue + ans.append(y) + return ans + if dest is None: + dest = self.site_packages + dest = join(dest, basename(x)) + shutil.copytree(x, dest, symlinks=True, ignore=ignore) + self.postprocess_package(x, dest) + + def filter_package(self, name): + return name in ('Cython', 'modulegraph', 'macholib', 'py2app', + 'bdist_mpkg', 'altgraph') + + def postprocess_package(self, src_path, dest_path): + pass + + def add_stdlib(self): + print '\nAdding python stdlib' + src = join(SW, '/python/Python.framework/Versions/Current/lib/python') + src += '.'.join(sys.version_info[:2]) + dest = join(self.resources_dir, 'Python', 'lib', 'python') + dest += '.'.join(sys.version_info[:2]) + for x in os.listdir(src): + if x in ('site-packages', 'config', 'test', 'lib2to3', 'lib-tk', + 'lib-old', 'idlelib', 'plat-mac', 'plat-darwin', 'site.py'): + continue + if os.path.isdir(x): + self.add_package_dir(join(src, x), dest) + elif os.path.splitext(x) in ('.so', '.py'): + shutil.copy2(join(src, x), dest) + dest = join(dest, basename(x)) + if dest.endswith('.so'): + self.fix_dependencies_in_lib(dest) + self.remove_bytecode(join(self.resources_dir, 'Python', 'lib')) + + def remove_bytecode(self, dest): + for x in os.walk(dest): + root = x[0] + for f in x[-1]: + if os.path.splitext(f) in ('.pyc', '.pyo'): + os.remove(join(root, f)) + + def compile_py_modules(self): + print '\nCompiling Python modules' + base = join(self.resources_dir, 'Python') + for x in os.walk(base): + root = x[0] + for f in x[-1]: + if f.endswith('.py'): + y = join(root, f) + rel = os.path.relpath(y, base) + try: + py_compile.compile(y, dfile=rel, doraise=True) + os.remove(y) + except: + print 'WARNING: Failed to byte-compile', y + + def create_console_app(self): + print '\nCreating console.app' + cc_dir = os.path.join(self.contents_dir, 'console.app', 'Contents') + os.makedirs(cc_dir) + for x in os.listdir(self.contents_dir): + if x == 'console.app': + continue + if x == 'Info.plist': + plist = plistlib.readPlist(join(self.contents_dir, x)) + plist['LSUIElement'] = '1' + plistlib.writePlist(plist, join(cc_dir, x)) + else: + os.symlink(join('../..', x), + join(cc_dir, x)) + + def copy_launcher_and_site(self): + base = os.path.dirname(__file__) + for x in ('launcher', 'site'): + shutil.copy2(join(base, x+'.py'), self.resources_dir) + + def makedmg(self, 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(['/usr/bin/hdiutil', 'create', '-srcfolder', os.path.abspath(d), + '-volname', volname, '-format', format, dmg]) + if internet_enable: + subprocess.check_call(['/usr/bin/hdiutil', 'internet-enable', '-yes', dmg]) + return dmg + + + +def main(): + build_dir = abspath(join('build', APPNAME+'.app')) + if os.path.exists(build_dir): + shutil.rmtree(build_dir) + os.makedirs(build_dir) + py2app = Py2App(build_dir) + py2app.run() + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/installer/osx/py2app/site.py b/installer/osx/py2app/site.py new file mode 100644 index 0000000000..4c98a871ca --- /dev/null +++ b/installer/osx/py2app/site.py @@ -0,0 +1,106 @@ +""" +Append module search paths for third-party packages to sys.path. + +This is stripped down and customized for use in py2app applications +""" + +import sys +import os + +def makepath(*paths): + dir = os.path.abspath(os.path.join(*paths)) + return dir, os.path.normcase(dir) + +for m in sys.modules.values(): + f = getattr(m, '__file__', None) + if isinstance(f, basestring) and os.path.exists(f): + m.__file__ = os.path.abspath(m.__file__) +del m + +# This ensures that the initial path provided by the interpreter contains +# only absolute pathnames, even if we're running from the build directory. +L = [] +_dirs_in_sys_path = {} +dir = dircase = None # sys.path may be empty at this point +for dir in sys.path: + # Filter out duplicate paths (on case-insensitive file systems also + # if they only differ in case); turn relative paths into absolute + # paths. + dir, dircase = makepath(dir) + if not dircase in _dirs_in_sys_path: + L.append(dir) + _dirs_in_sys_path[dircase] = 1 +sys.path[:] = L +del dir, dircase, L +_dirs_in_sys_path = None + +def _init_pathinfo(): + global _dirs_in_sys_path + _dirs_in_sys_path = d = {} + for dir in sys.path: + if dir and not os.path.isdir(dir): + continue + dir, dircase = makepath(dir) + d[dircase] = 1 + +def addsitedir(sitedir): + global _dirs_in_sys_path + if _dirs_in_sys_path is None: + _init_pathinfo() + reset = 1 + else: + reset = 0 + sitedir, sitedircase = makepath(sitedir) + if not sitedircase in _dirs_in_sys_path: + sys.path.append(sitedir) # Add path component + try: + names = os.listdir(sitedir) + except os.error: + return + names.sort() + for name in names: + if name[-4:] == os.extsep + "pth": + addpackage(sitedir, name) + if reset: + _dirs_in_sys_path = None + +def addpackage(sitedir, name): + global _dirs_in_sys_path + if _dirs_in_sys_path is None: + _init_pathinfo() + reset = 1 + else: + reset = 0 + fullname = os.path.join(sitedir, name) + try: + f = open(fullname) + except IOError: + return + while 1: + dir = f.readline() + if not dir: + break + if dir[0] == '#': + continue + if dir.startswith("import"): + exec dir + continue + if dir[-1] == '\n': + dir = dir[:-1] + dir, dircase = makepath(sitedir, dir) + if not dircase in _dirs_in_sys_path and os.path.exists(dir): + sys.path.append(dir) + _dirs_in_sys_path[dircase] = 1 + if reset: + _dirs_in_sys_path = None + + +sys.setdefaultencoding('utf-8') + +# +# Remove sys.setdefaultencoding() so that users cannot change the +# encoding after initialization. The test for presence is needed when +# this module is run as a script, because this code is executed twice. +# +if hasattr(sys, "setdefaultencoding"): + del sys.setdefaultencoding diff --git a/pyqtdistutils.py b/pyqtdistutils.py index 0150cd8684..550bf46da7 100644 --- a/pyqtdistutils.py +++ b/pyqtdistutils.py @@ -23,13 +23,24 @@ elif find_executable('qmake'): QMAKE = find_executable('qmake') QMAKE = os.environ.get('QMAKE', QMAKE) WINDOWS_PYTHON = ['C:/Python26/libs'] -OSX_SDK = '/Developer/SDKs/MacOSX10.4u.sdk' +OSX_SDK = '/Developer/SDKs/MacOSX10.5.sdk' +if not os.path.exists(OSX_SDK): + OSX_SDK = '/Developer/SDKs/MacOSX10.4u.sdk' + +leopard_build = '10.5' in OSX_SDK def replace_suffix(path, new_suffix): return os.path.splitext(path)[0] + new_suffix class Extension(_Extension): - pass + + def __init__(self, *args, **kwargs): + if leopard_build: + prev = kwargs.get('extra_compile_args', []) + prev.extend(['-arch', 'ppc64', '-arch', 'x86_64']) + kwargs['extra_compile_args'] = prev + _Extension.__init__(self, *args, **kwargs) + if iswindows: from distutils import msvc9compiler @@ -38,7 +49,6 @@ if iswindows: nmake = msvc.find_exe('nmake.exe') rc = msvc.find_exe('rc.exe') - class PyQtExtension(Extension): def __init__(self, name, sources, sip_sources, **kw): @@ -66,6 +76,7 @@ class build_ext(_build_ext): cwd = os.getcwd() sources = map(os.path.abspath, ext.sources) os.chdir(bdir) + archs = 'x86_64 ppc64' if leopard_build else 'x86 ppc' try: headers = set([f for f in sources if f.endswith('.h')]) sources = set(sources) - headers @@ -76,10 +87,13 @@ TEMPLATE = lib HEADERS = %s SOURCES = %s VERSION = 1.0.0 -CONFIG += x86 ppc -'''%(name, ' '.join(headers), ' '.join(sources)) +CONFIG += %s +'''%(name, ' '.join(headers), ' '.join(sources), archs) open(name+'.pro', 'wb').write(pro) self.spawn([QMAKE, '-o', 'Makefile.qt', name+'.pro']) + if leopard_build: + raw = open('Makefile.qt', 'rb').read() + open('Makefile.qt', 'wb').write(raw.replace('ppc64', 'x86_64')) self.make('Makefile.qt') pat = 'release\\*.obj' if iswindows else '*.o' return map(os.path.abspath, glob.glob(pat)) @@ -116,6 +130,13 @@ CONFIG += x86 ppc makefile.generate() cwd = os.getcwd() os.chdir(bdir) + if leopard_build: + mf = 'Makefile.pyqt' + raw = open(mf, 'rb').read() + raw = raw.replace('ppc64 x86_64', 'x86_64') + for x in ('ppc64', 'ppc', 'i386'): + raw = raw.replace(x, 'x86_64') + open(mf, 'wb').write(raw) try: self.make('Makefile.pyqt') finally: diff --git a/setup.py b/setup.py index ed372920de..27adc64016 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,9 @@ if __name__ == '__main__': tag_release, upload_demo, build_linux, build_windows, \ build_osx, upload_installers, upload_user_manual, \ upload_to_pypi, stage3, stage2, stage1, upload, \ - upload_rss, betas, build_linux32, build_linux64 + upload_rss, betas, build_linux32, build_linux64, \ + build_osx64 + resources.SCRIPTS = list(basenames['console']+basenames['gui']) entry_points['console_scripts'].append( 'calibre_postinstall = calibre.linux:post_install') @@ -147,14 +149,19 @@ if __name__ == '__main__': r'C:\cygwin\home\kovid\fontconfig\lib' if iswindows else \ '/Users/kovid/fontconfig/lib' - + fc_inc = os.environ.get('FC_INC_DIR', fc_inc) + fc_lib = os.environ.get('FC_LIB_DIR', fc_lib) + if not os.path.exists(os.path.join(fc_inc, 'fontconfig.h')): + print 'ERROR: fontconfig not found on your system.', + print 'Use the FC_INC_DIR and FC_LIB_DIR environment variables.' + raise SystemExit(1) ext_modules = optional + [ Extension('calibre.plugins.fontconfig', sources = ['src/calibre/utils/fonts/fontconfig.c'], - include_dirs = [os.environ.get('FC_INC_DIR', fc_inc)], + include_dirs = [fc_inc], libraries=['fontconfig'], - library_dirs=[os.environ.get('FC_LIB_DIR', fc_lib)]), + library_dirs=[fc_lib]), Extension('calibre.plugins.lzx', sources=['src/calibre/utils/lzx/lzxmodule.c', @@ -262,6 +269,7 @@ if __name__ == '__main__': 'build_linux64' : build_linux64, 'build_windows' : build_windows, 'build_osx' : build_osx, + 'build_osx64' : build_osx64, 'upload_installers': upload_installers, 'upload_user_manual': upload_user_manual, 'upload_to_pypi': upload_to_pypi, diff --git a/src/calibre/devices/usbobserver/usbobserver.c b/src/calibre/devices/usbobserver/usbobserver.c index 051901f849..a6238f1005 100644 --- a/src/calibre/devices/usbobserver/usbobserver.c +++ b/src/calibre/devices/usbobserver/usbobserver.c @@ -26,7 +26,7 @@ #include #include - +#include static PyObject * usbobserver_get_usb_devices(PyObject *self, PyObject *args) { @@ -67,7 +67,7 @@ usbobserver_get_usb_devices(PyObject *self, PyObject *args) { return NULL; } - while (usbDevice = IOIteratorNext(iter)) { + while ((usbDevice = IOIteratorNext(iter))) { plugInInterface = NULL; dev = NULL; //Create an intermediate plugin kr = IOCreatePlugInInterfaceForService(usbDevice, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score); diff --git a/src/calibre/gui2/dialogs/config/__init__.py b/src/calibre/gui2/dialogs/config/__init__.py index 6cc77725e8..df1ed65025 100644 --- a/src/calibre/gui2/dialogs/config/__init__.py +++ b/src/calibre/gui2/dialogs/config/__init__.py @@ -10,7 +10,7 @@ from PyQt4.Qt import QDialog, QListWidgetItem, QIcon, \ QDialogButtonBox, QTabWidget, QBrush, QLineEdit, \ QProgressDialog -from calibre.constants import islinux, iswindows +from calibre.constants import islinux, iswindows, isosx from calibre.gui2.dialogs.config.config_ui import Ui_Dialog from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config, \ ALL_COLUMNS, NONE, info_dialog, choose_files, \ @@ -450,6 +450,9 @@ class ConfigDialog(QDialog, Ui_Dialog): self.connect(self.remove_plugin, SIGNAL('clicked()'), lambda : self.modify_plugin(op='remove')) self.connect(self.button_plugin_browse, SIGNAL('clicked()'), self.find_plugin) self.connect(self.button_plugin_add, SIGNAL('clicked()'), self.add_plugin) + self.connect(self.button_osx_symlinks, SIGNAL('clicked()'), + self.create_symlinks) + self.button_osx_symlinks.setVisible(isosx) self.separate_cover_flow.setChecked(config['separate_cover_flow']) self.setup_email_page() self.category_view.setCurrentIndex(self.category_view.model().index(0)) @@ -458,6 +461,13 @@ class ConfigDialog(QDialog, Ui_Dialog): self.delete_news.setEnabled) self.setup_conversion_options() + def create_symlinks(self): + from calibre.utils.osx_symlinks import create_symlinks + loc, paths = create_symlinks() + info_dialog(self, _('Command line tools installed'), + _('Command line tools installed in')+' '+loc, + det_msg=paths, show=True) + def setup_conversion_options(self): self.conversion_options = ConfigTabs(self) self.stackedWidget.insertWidget(2, self.conversion_options) diff --git a/src/calibre/gui2/dialogs/config/config.ui b/src/calibre/gui2/dialogs/config/config.ui index e38b16cdf5..62979c8a5c 100644 --- a/src/calibre/gui2/dialogs/config/config.ui +++ b/src/calibre/gui2/dialogs/config/config.ui @@ -677,28 +677,27 @@ - - + - + - - - - - Qt::Horizontal - - - - 40 - 20 - - - - + + + Qt::Horizontal + + + + 154 + 20 + + + + + + @@ -710,20 +709,27 @@ - - - Qt::Horizontal + + + &Install command line tools - - - 40 - 20 - - - + + + + + Qt::Horizontal + + + + 153 + 20 + + + + diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index c3914bdb81..ea0241d46e 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -107,6 +107,11 @@ Now you should be able to access your books on your iPhone by opening Stanza and Replace ``192.168.1.2`` with the local IP address of the computer running |app|. If you have changed the port the |app| content server is running on, you will have to change ``8080`` as well to the new port. The local IP address is the IP address you computer is assigned on your home network. A quick Google search will tell you how to find out your local IP address. +I get the error message "Failed to start content server: Port 8080 not free on '0.0.0.0'"? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The most likely cause of this is your antivirus program. Try temporarily disabling it and see if it does the trick. + Why is my device not detected in linux? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -216,7 +221,7 @@ Your antivirus program is wrong. |app| is a completely open source product. You How do I use purchased EPUB books with |app|? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Most purchased EPUB books have `DRM `_. This prevents |app| from opening them. You can still use |app| to store and transfer them to your SONY Reader. First, you must authorize your reader on a windows machine with Adobe DIgital Editions. Once this is done, EPUB books transferred with |app| will work fine on your reader. Sometimes, the EPUB file itself is corrupted, in which case you should notify the e-book vendor. +Most purchased EPUB books have `DRM `_. This prevents |app| from opening them. You can still use |app| to store and transfer them to your SONY Reader. First, you must authorize your reader on a windows machine with Adobe Digital Editions. Once this is done, EPUB books transferred with |app| will work fine on your reader. Sometimes, the EPUB file itself is corrupted, in which case you should notify the e-book vendor. I want some feature added to |app|. What can I do? diff --git a/src/calibre/trac/plugins/download.py b/src/calibre/trac/plugins/download.py index 61cf330a78..28c947d06c 100644 --- a/src/calibre/trac/plugins/download.py +++ b/src/calibre/trac/plugins/download.py @@ -185,7 +185,7 @@ else: note=Markup(\ u'''
    -
  1. Before trying to use the command line tools, you must run the app at least once. This will ask you for you password and then setup the symbolic links for the command line tools.
  2. +
  3. To install the command line tools, go to Preferences->Advanced
  4. The app cannot be run from within the dmg. You must drag it to a folder on your filesystem (The Desktop, Applications, wherever).
  5. In order for localization of the user interface in your language, select your language in the preferences (by pressing u\2318+P) and select your language.
diff --git a/src/calibre/utils/PythonMagickWand.py b/src/calibre/utils/PythonMagickWand.py index f650d6dc4e..6a3fbd654c 100644 --- a/src/calibre/utils/PythonMagickWand.py +++ b/src/calibre/utils/PythonMagickWand.py @@ -73,8 +73,13 @@ isosx = 'darwin' in sys.platform isfrozen = getattr(sys, 'frozen', False) if isosx: - _lib = os.path.join(getattr(sys, 'frameworks_dir'), 'ImageMagick', 'libMagickWand.dylib') \ - if isfrozen else util.find_library('Wand') + if isfrozen: + fd = getattr(sys, 'frameworks_dir') + _lib = os.path.join(fd, 'libMagickWand.2.dylib') + if not os.path.exists(_lib): + _lib = os.path.join(fd, 'ImageMagick', 'libMagickWand.dylib') + else: + _lib = util.find_library('Wand') elif iswindows: _lib = os.path.join(os.path.dirname(sys.executable), 'CORE_RL_wand_.dll') \ if isfrozen else 'CORE_RL_wand_' diff --git a/src/calibre/utils/fonts/fontconfig.c b/src/calibre/utils/fonts/fontconfig.c index 54ff22d915..d8f7190798 100644 --- a/src/calibre/utils/fonts/fontconfig.c +++ b/src/calibre/utils/fonts/fontconfig.c @@ -18,7 +18,7 @@ static PyObject * fontconfig_initialize(PyObject *self, PyObject *args) { - char *path; + FcChar8 *path; FcBool ok; FcConfig *config; PyThreadState *_save; @@ -44,7 +44,7 @@ fontconfig_initialize(PyObject *self, PyObject *args) { Py_RETURN_FALSE; } -static +static void fontconfig_cleanup_find(FcPattern *p, FcObjectSet *oset, FcFontSet *fs) { if (p != NULL) FcPatternDestroy(p); if (oset != NULL) FcObjectSetDestroy(oset); @@ -105,13 +105,13 @@ fontconfig_find_font_families(PyObject *self, PyObject *args) { ext = PyBytes_AS_STRING(PySequence_ITEM(exts, j)); extlen = PyBytes_GET_SIZE(PySequence_ITEM(exts, j)); ok = flen > extlen && extlen > 0 && - PyOS_strnicmp(ext, v.u.s + (flen - extlen), extlen) == 0; + PyOS_strnicmp(ext, ((char *)v.u.s) + (flen - extlen), extlen) == 0; } if (ok) { if (FcPatternGet(temp, FC_FAMILY, 0, &w) != FcResultMatch) continue; if (w.type != FcTypeString) continue; - t = PyString_FromString(w.u.s); + t = PyString_FromString((char *)w.u.s); if (t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } if (PyList_Append(ans, t) != 0) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } @@ -169,16 +169,16 @@ fontconfig_files_for_family(PyObject *self, PyObject *args) { temp = PyTuple_New(6); if(temp == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } - t = PyBytes_FromString(fullname.u.s); + t = PyBytes_FromString((char *)fullname.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 0, t); - t = PyBytes_FromString(file.u.s); + t = PyBytes_FromString((char *)file.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 1, t); - t = PyBytes_FromString(style.u.s); + t = PyBytes_FromString((char *)style.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 2, t); - t = PyBytes_FromString(family2.u.s); + t = PyBytes_FromString((char *)family2.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 3, t); t = PyInt_FromLong((long)weight.u.i); @@ -197,7 +197,7 @@ fontconfig_files_for_family(PyObject *self, PyObject *args) { static PyObject * fontconfig_match(PyObject *self, PyObject *args) { - char *namespec; int i; + FcChar8 *namespec; int i; FcPattern *pat, *tp; FcObjectSet *oset; FcFontSet *fs, *fs2; @@ -255,16 +255,16 @@ fontconfig_match(PyObject *self, PyObject *args) { temp = PyTuple_New(6); if(temp == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } - t = PyBytes_FromString(fullname.u.s); + t = PyBytes_FromString((char *)fullname.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 0, t); - t = PyBytes_FromString(file.u.s); + t = PyBytes_FromString((char *)file.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 1, t); - t = PyBytes_FromString(style.u.s); + t = PyBytes_FromString((char *)style.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 2, t); - t = PyBytes_FromString(family.u.s); + t = PyBytes_FromString((char *)family.u.s); if(t == NULL) { fontconfig_cleanup_find(pat, oset, fs); return PyErr_NoMemory(); } PyTuple_SET_ITEM(temp, 3, t); t = PyInt_FromLong((long)weight.u.i); diff --git a/src/calibre/utils/osx_symlinks.py b/src/calibre/utils/osx_symlinks.py new file mode 100644 index 0000000000..36fa0e7b45 --- /dev/null +++ b/src/calibre/utils/osx_symlinks.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +AUTHTOOL="""#!%s +import os +scripts = %s +links = %s +os.setuid(0) +for s, l in zip(scripts, links): + if os.path.lexists(l): + os.remove(l) + print 'Creating link:', l, '->', s + omask = os.umask(022) + os.symlink(s, l) + os.umask(omask) +""" + +DEST_PATH = '/usr/bin' + +def create_symlinks(): + import os, tempfile, traceback, sys + from Authorization import Authorization, kAuthorizationFlagDestroyRights + from calibre.resources import scripts + + + resources_path = os.environ['RESOURCEPATH'] + links = [os.path.join(DEST_PATH, i) for i in scripts] + scripts = [os.path.join(resources_path, 'loaders', 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 % (sys.executable, repr(scripts), repr(links))) + os.close(fd) + os.chmod(name, 0700) + try: + pipe = auth.executeWithPrivileges(sys.executable, name) + sys.stdout.write(pipe.read()) + pipe.close() + except: + traceback.print_exc() + finally: + os.unlink(name) + + return DEST_PATH, links + diff --git a/src/calibre/utils/podofo/podofo.cpp b/src/calibre/utils/podofo/podofo.cpp index 188ed0c5dc..8766b709c9 100644 --- a/src/calibre/utils/podofo/podofo.cpp +++ b/src/calibre/utils/podofo/podofo.cpp @@ -144,7 +144,6 @@ podofo_PDFDoc_version_getter(podofo_PDFDoc *self, void *closure) { static PyObject * podofo_PDFDoc_extract_first_page(podofo_PDFDoc *self, PyObject *args, PyObject *kwargs) { - int i, num_pages; try { while (self->doc->GetPageCount() > 1) self->doc->GetPagesTree()->DeletePage(1); } catch(const PdfError & err) { diff --git a/upload.py b/upload.py index a7146008de..6e3c5fa099 100644 --- a/upload.py +++ b/upload.py @@ -167,7 +167,7 @@ class resources(OptionlessCommand): translations_found = True break if not translations_found: - print 'WARNING: Could not find Qt transations' + print 'WARNING: Could not find Qt translations' return data def get_static_resources(self): @@ -216,7 +216,8 @@ class resources(OptionlessCommand): static, smax = self.get_static_resources() recipes, rmax = self.get_recipes() hyphenate, hmax = self.get_hyphenate() - amax = max(rmax, smax, hmax, os.stat(__file__).st_mtime) + lmax = os.stat(os.path.join('src', 'calibre', 'linux.py')).st_mtime + amax = max(rmax, smax, hmax, lmax, os.stat(__file__).st_mtime) if newer([dest], RESOURCES.values()) or os.stat(dest).st_mtime < amax: print 'Compiling resources...' with open(dest, 'wb') as f: @@ -226,6 +227,7 @@ class resources(OptionlessCommand): f.write('server_resources = %s\n\n'%repr(static)) f.write('recipes = %s\n\n'%repr(recipes)) f.write('hyphenate = %s\n\n'%repr(hyphenate)) + f.write('scripts = %r\n\n'%self.SCRIPTS) f.write('build_time = "%s"\n\n'%time.strftime('%d %m %Y %H%M%S')) else: print 'Resources are up to date' @@ -504,6 +506,8 @@ class VMInstaller(OptionlessCommand): user_options = [('dont-shutdown', 'd', 'Dont shutdown VM after build')] boolean_options = ['dont-shutdown'] EXTRA_SLEEP = 5 + BUILD_CMD = 'ssh -t %s bash build-calibre' + INIT_CMD = '' def initialize_options(self): self.dont_shutdown = False @@ -511,6 +515,7 @@ class VMInstaller(OptionlessCommand): BUILD_SCRIPT = textwrap.dedent('''\ #!/bin/bash export CALIBRE_BUILDBOT=1 + %%s cd ~/build && \ rsync -avz --exclude src/calibre/plugins \ --exclude calibre/src/calibre.egg-info --exclude docs \ @@ -519,12 +524,13 @@ class VMInstaller(OptionlessCommand): rsync://%(host)s/work/%(project)s . && \ cd %(project)s && \ %%s && \ - rm -rf build/* dist/* && \ + rm -rf build/* dist/* src/calibre/plugins/* && \ %%s %%s '''%dict(host=HOST, project=__appname__)) def get_build_script(self, subs): - return self.BUILD_SCRIPT%subs + subs = [self.INIT_CMD]+list(subs) + return self.BUILD_SCRIPT%tuple(subs) def vmware_started(self): return 'started' in subprocess.Popen('/etc/init.d/vmware status', shell=True, stdout=subprocess.PIPE).stdout.read() @@ -562,16 +568,7 @@ class VMInstaller(OptionlessCommand): time.sleep(self.EXTRA_SLEEP) print 'Trying to SSH into VM' check_call(('scp', t.name, ssh_host+':build-calibre')) - check_call('ssh -t %s bash build-calibre'%ssh_host, shell=True) - -class KVMInstaller(VMInstaller): - - def run_vm(self): - self.stop_vmware() - check_call('sudo modprobe kvm-intel', shell=True) - self.__p = Popen(self.VM) - - + check_call(self.BUILD_CMD%ssh_host, shell=True) class build_linux32(VMInstaller): @@ -582,7 +579,7 @@ class build_linux32(VMInstaller): installer = installer_name('tar.bz2').replace('x86_64', 'i686') self.start_vm('gentoo32_build', ('sudo python setup.py develop && sudo chown -R kovid:users *', 'python', 'installer/linux/freeze.py')) - check_call(('scp', 'gentoo32_build:build/calibre/dist/*.dmg', 'dist')) + check_call(('scp', 'gentoo32_build:build/calibre/dist/*.bz2', 'dist')) if not os.path.exists(installer): raise Exception('Failed to build installer '+installer) if not self.dont_shutdown: @@ -624,22 +621,44 @@ class build_windows(VMInstaller): class build_osx(VMInstaller): description = 'Build OS X app bundle' VM = '/vmware/bin/tiger_build' + FREEZE_SCRIPT = 'installer/osx/freeze.py' + VM_NAME = 'tiger_build' + PYTHON = '/Library/Frameworks/Python.framework/Versions/Current/bin/python' + DEVELOP = 'sudo %s setup.py develop' def get_build_script(self, subs): - return (self.BUILD_SCRIPT%subs).replace('rm ', 'sudo rm ') + return VMInstaller.get_build_script(self, subs).replace('rm ', 'sudo rm ') + + def installer_name(self): + return installer_name('dmg') def run(self): - installer = installer_name('dmg') - python = '/Library/Frameworks/Python.framework/Versions/Current/bin/python' - self.start_vm('tiger_build', ('sudo %s setup.py develop'%python, python, - 'installer/osx/freeze.py')) - check_call(('scp', 'tiger_build:build/calibre/dist/*.dmg', 'dist')) + installer = self.installer_name() + python = self.PYTHON + self.start_vm(self.VM_NAME, (self.DEVELOP%python, python, + self.FREEZE_SCRIPT)) + check_call(('scp', self.VM_NAME+':build/calibre/dist/*.dmg', 'dist')) if not os.path.exists(installer): raise Exception('Failed to build installer '+installer) if not self.dont_shutdown: - Popen(('ssh', 'tiger_build', 'sudo', '/sbin/shutdown', '-h', 'now')) + Popen(('ssh', self.VM_NAME, 'sudo', '/sbin/shutdown', '-h', 'now')) return os.path.basename(installer) +class build_osx64(build_osx): + + description = 'Build OS X 64-bit app bundle' + VM = '/vmware/bin/leopard_build' + FREEZE_SCRIPT = 'installer/osx/py2app/main.py' + VM_NAME = 'leopard_build' + PYTHON = '/sw/bin/python -OO' + DEVELOP = '%s setup.py develop' + BUILD_CMD = 'ssh -t %s bash --login build-calibre' + INIT_CMD = 'source ~/.profile' + + def installer_name(self): + return installer_name('dmg').replace('.dmg', '-x86_64.dmg') + + class upload_installers(OptionlessCommand): description = 'Upload any installers present in dist/' def curl_list_dir(self, url=MOBILEREAD, listonly=1): From 5f6c33090142d8b0613e38874cf640bb521a1afd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 1 Sep 2009 17:43:10 -0600 Subject: [PATCH 03/13] Implement #3359 (Make markdown processing optional) --- src/calibre/ebooks/conversion/preprocess.py | 22 +++++------ src/calibre/ebooks/html/input.py | 2 +- src/calibre/ebooks/oeb/base.py | 4 +- src/calibre/ebooks/pdb/palmdoc/reader.py | 8 ++-- src/calibre/ebooks/pdb/ztxt/reader.py | 7 +++- src/calibre/ebooks/txt/input.py | 23 ++++++++---- src/calibre/ebooks/txt/processor.py | 41 +++++++++++++++++---- src/calibre/gui2/convert/txt_input.py | 2 +- src/calibre/gui2/convert/txt_input.ui | 19 +++++++++- 9 files changed, 92 insertions(+), 36 deletions(-) diff --git a/src/calibre/ebooks/conversion/preprocess.py b/src/calibre/ebooks/conversion/preprocess.py index cb2564ec0a..029b9752e1 100644 --- a/src/calibre/ebooks/conversion/preprocess.py +++ b/src/calibre/ebooks/conversion/preprocess.py @@ -223,16 +223,7 @@ class HTMLPreProcessor(object): elif self.is_book_designer(html): rules = self.BOOK_DESIGNER elif self.is_pdftohtml(html): - end_rules = [] - if getattr(self.extra_opts, 'unwrap_factor', None): - length = line_length(html, getattr(self.extra_opts, 'unwrap_factor')) - if length: - end_rules.append( - # Un wrap using punctuation - (re.compile(r'(?<=.{%i}[a-z\.,;:)-IA])\s*(?P)?\s*()\s*(?=(<(i|b|u)>)?\s*[\w\d(])' % length, re.UNICODE), wrap_lines), - ) - - rules = self.PDFTOHTML + end_rules + rules = self.PDFTOHTML else: rules = [] @@ -246,7 +237,16 @@ class HTMLPreProcessor(object): (re.compile(getattr(self.extra_opts, 'footer_regex')), lambda match : '') ) - for rule in self.PREPROCESS + pre_rules + rules: + end_rules = [] + if getattr(self.extra_opts, 'unwrap_factor', None): + length = line_length(html, getattr(self.extra_opts, 'unwrap_factor')) + if length: + end_rules.append( + # Un wrap using punctuation + (re.compile(r'(?<=.{%i}[a-z\.,;:)-IA])\s*(?P)?\s*()\s*(?=(<(i|b|u)>)?\s*[\w\d(])' % length, re.UNICODE), wrap_lines), + ) + + for rule in self.PREPROCESS + pre_rules + rules + end_rules: html = rule[0].sub(rule[1], html) # Handle broken XHTML w/ SVG (ugh) diff --git a/src/calibre/ebooks/html/input.py b/src/calibre/ebooks/html/input.py index 92c2df9690..7b7bfdf3aa 100644 --- a/src/calibre/ebooks/html/input.py +++ b/src/calibre/ebooks/html/input.py @@ -262,7 +262,7 @@ class HTMLInput(InputFormatPlugin): ) ), - OptionRecommendation(name='pdf_line_length', recommended_value=0.5, + OptionRecommendation(name='unwrap_factor', recommended_value=0.5, help=_('Average line length for line breaking if the HTML is from a ' 'previous partial conversion of a PDF file.')), diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index 3f9e6a4d4a..2e06fffe4e 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -934,7 +934,7 @@ class Manifest(object): self.oeb.log.debug('Converting', self.href, '...') - from calibre.ebooks.txt.processor import txt_to_markdown + from calibre.ebooks.txt.processor import convert_markdown title = self.oeb.metadata.title if title: @@ -942,7 +942,7 @@ class Manifest(object): else: title = _('Unknown') - return self._parse_xhtml(txt_to_markdown(data, title)) + return self._parse_xhtml(convert_markdown(data, title)) def _parse_css(self, data): diff --git a/src/calibre/ebooks/pdb/palmdoc/reader.py b/src/calibre/ebooks/pdb/palmdoc/reader.py index e1935db566..8992382597 100644 --- a/src/calibre/ebooks/pdb/palmdoc/reader.py +++ b/src/calibre/ebooks/pdb/palmdoc/reader.py @@ -13,8 +13,8 @@ import struct from calibre.ebooks.compression.palmdoc import decompress_doc from calibre.ebooks.pdb.formatreader import FormatReader -from calibre.ebooks.txt.processor import opf_writer -from calibre.ebooks.txt.processor import txt_to_markdown +from calibre.ebooks.txt.processor import convert_basic, separate_paragraphs, \ + opf_writer class HeaderRecord(object): ''' @@ -62,7 +62,9 @@ class Reader(FormatReader): txt += self.decompress_text(i) self.log.info('Converting text to OEB...') - html = txt_to_markdown(txt, single_line_paras=self.single_line_paras) + if self.single_line_paras: + txt = separate_paragraphs(txt) + html = convert_basic(txt) with open(os.path.join(output_dir, 'index.html'), 'wb') as index: index.write(html.encode('utf-8')) diff --git a/src/calibre/ebooks/pdb/ztxt/reader.py b/src/calibre/ebooks/pdb/ztxt/reader.py index 86c5abfe82..664f498bee 100644 --- a/src/calibre/ebooks/pdb/ztxt/reader.py +++ b/src/calibre/ebooks/pdb/ztxt/reader.py @@ -12,7 +12,8 @@ import os, struct, zlib from calibre.ebooks.pdb.formatreader import FormatReader from calibre.ebooks.pdb.ztxt import zTXTError -from calibre.ebooks.txt.processor import txt_to_markdown, opf_writer +from calibre.ebooks.txt.processor import convert_basic, separate_paragraphs, \ + opf_writer SUPPORTED_VERSION = (1, 40) @@ -77,7 +78,9 @@ class Reader(FormatReader): txt += self.decompress_text(i) self.log.info('Converting text to OEB...') - html = txt_to_markdown(txt, single_line_paras=self.single_line_paras) + if self.single_line_paras: + txt = separate_paragraphs(txt) + html = convert_basic(txt) with open(os.path.join(output_dir, 'index.html'), 'wb') as index: index.write(html.encode('utf-8')) diff --git a/src/calibre/ebooks/txt/input.py b/src/calibre/ebooks/txt/input.py index 5d84a1bde1..2b0245c98b 100644 --- a/src/calibre/ebooks/txt/input.py +++ b/src/calibre/ebooks/txt/input.py @@ -7,7 +7,8 @@ __docformat__ = 'restructuredtext en' import os from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation -from calibre.ebooks.txt.processor import txt_to_markdown +from calibre.ebooks.txt.processor import convert_basic, convert_markdown, \ + separate_paragraphs class TXTInput(InputFormatPlugin): @@ -21,6 +22,8 @@ class TXTInput(InputFormatPlugin): help=_('Normally calibre treats blank lines as paragraph markers. ' 'With this option it will assume that every line represents ' 'a paragraph instead.')), + OptionRecommendation(name='markdown', recommended_value=False, + help=_('Run the text input though the markdown processor.')), ]) def convert(self, stream, options, file_ext, log, @@ -31,12 +34,18 @@ class TXTInput(InputFormatPlugin): log.debug('Reading text from file...') txt = stream.read().decode(ienc, 'replace') - log.debug('Running text though markdown conversion...') - try: - html = txt_to_markdown(txt, single_line_paras=options.single_line_paras) - except RuntimeError: - raise ValueError('This txt file has malformed markup, it cannot be' - 'converted by calibre. See http://daringfireball.net/projects/markdown/syntax') + if options.single_line_paras: + txt = separate_paragraphs(txt) + + if options.markdown: + log.debug('Running text though markdown conversion...') + try: + html = convert_markdown(txt) + except RuntimeError: + raise ValueError('This txt file has malformed markup, it cannot be' + 'converted by calibre. See http://daringfireball.net/projects/markdown/syntax') + else: + html = convert_basic(txt) from calibre.customize.ui import plugin_for_input_format html_input = plugin_for_input_format('html') diff --git a/src/calibre/ebooks/txt/processor.py b/src/calibre/ebooks/txt/processor.py index 3005d633b8..f6503c0bc5 100644 --- a/src/calibre/ebooks/txt/processor.py +++ b/src/calibre/ebooks/txt/processor.py @@ -5,7 +5,9 @@ Read content from txt file. ''' import os +import re +from calibre import prepare_string_for_xml from calibre.ebooks.markdown import markdown from calibre.ebooks.metadata.opf2 import OPFCreator @@ -13,18 +15,41 @@ __license__ = 'GPL v3' __copyright__ = '2009, John Schember ' __docformat__ = 'restructuredtext en' -def txt_to_markdown(txt, title='', single_line_paras=False): - if single_line_paras: - txt = txt.replace('\r\n', '\n') - txt = txt.replace('\r', '\n') - txt = txt.replace('\n', '\n\n') +HTML_TEMPLATE = u'%s\n%s\n' + +def convert_basic(txt, title=''): + lines = [] + # Strip whitespace from the beginning and end of the line. Also replace + # all line breaks with \n. + for line in txt.splitlines(): + lines.append(line.strip()) + txt = '\n'.join(lines) + + # Remove blank lines from the beginning and end of the document. + txt = re.sub('^\s+(?=.)', '', txt) + txt = re.sub('(?<=.)\s+$', '', txt) + # Remove excessive line breaks. + txt = re.sub('\n{3,}', '\n\n', txt) + + lines = [] + # Split into paragraphs based on having a blank line between text. + for line in txt.split('\n\n'): + if line.strip(): + lines.append('

%s

' % prepare_string_for_xml(line.replace('\n', ' '))) + + return HTML_TEMPLATE % (title, '\n'.join(lines)) + +def convert_markdown(txt, title=''): md = markdown.Markdown( extensions=['footnotes', 'tables', 'toc'], safe_mode=False,) - html = u'%s%s' % (title, - md.convert(txt)) + return HTML_TEMPLATE % (title, md.convert(txt)) - return html +def separate_paragraphs(txt): + txt = txt.replace('\r\n', '\n') + txt = txt.replace('\r', '\n') + txt = re.sub(u'(?<=.)\n(?=.)', u'\n\n', txt) + return txt def opf_writer(path, opf_name, manifest, spine, mi): opf = OPFCreator(path, mi) diff --git a/src/calibre/gui2/convert/txt_input.py b/src/calibre/gui2/convert/txt_input.py index 71dbbe1fe2..3d17eefe0d 100644 --- a/src/calibre/gui2/convert/txt_input.py +++ b/src/calibre/gui2/convert/txt_input.py @@ -14,6 +14,6 @@ class PluginWidget(Widget, Ui_Form): def __init__(self, parent, get_option, get_help, db=None, book_id=None): Widget.__init__(self, parent, 'txt_input', - ['single_line_paras']) + ['single_line_paras', 'markdown']) self.db, self.book_id = db, book_id self.initialize_options(get_option, get_help, db, book_id) diff --git a/src/calibre/gui2/convert/txt_input.ui b/src/calibre/gui2/convert/txt_input.ui index 191e749833..8c22ff721e 100644 --- a/src/calibre/gui2/convert/txt_input.ui +++ b/src/calibre/gui2/convert/txt_input.ui @@ -14,7 +14,7 @@ Form - + Qt::Vertical @@ -34,6 +34,23 @@
+ + + + Process using markdown + + + + + + + <p>Markdown is a simple markup language for text files, that allows for advanced formatting. To learn more visit <a href="http://daringfireball.net/projects/markdown">markdown</a>. + + + true + + + From 707f0ffa0c846a4a7406df64d81c579b7f0a6d27 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 1 Sep 2009 17:54:55 -0600 Subject: [PATCH 04/13] Fix #3377 (--toc-filter only removes alternate entries) --- src/calibre/ebooks/conversion/preprocess.py | 16 ++++++++-------- src/calibre/ebooks/oeb/transforms/structure.py | 4 +++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/calibre/ebooks/conversion/preprocess.py b/src/calibre/ebooks/conversion/preprocess.py index 029b9752e1..1aae939a06 100644 --- a/src/calibre/ebooks/conversion/preprocess.py +++ b/src/calibre/ebooks/conversion/preprocess.py @@ -237,14 +237,14 @@ class HTMLPreProcessor(object): (re.compile(getattr(self.extra_opts, 'footer_regex')), lambda match : '') ) - end_rules = [] - if getattr(self.extra_opts, 'unwrap_factor', None): - length = line_length(html, getattr(self.extra_opts, 'unwrap_factor')) - if length: - end_rules.append( - # Un wrap using punctuation - (re.compile(r'(?<=.{%i}[a-z\.,;:)-IA])\s*(?P)?\s*()\s*(?=(<(i|b|u)>)?\s*[\w\d(])' % length, re.UNICODE), wrap_lines), - ) + end_rules = [] + if getattr(self.extra_opts, 'unwrap_factor', None): + length = line_length(html, getattr(self.extra_opts, 'unwrap_factor')) + if length: + end_rules.append( + # Un wrap using punctuation + (re.compile(r'(?<=.{%i}[a-z\.,;:)-IA])\s*(?P)?\s*()\s*(?=(<(i|b|u)>)?\s*[\w\d(])' % length, re.UNICODE), wrap_lines), + ) for rule in self.PREPROCESS + pre_rules + rules + end_rules: html = rule[0].sub(rule[1], html) diff --git a/src/calibre/ebooks/oeb/transforms/structure.py b/src/calibre/ebooks/oeb/transforms/structure.py index c377a8b3a8..b84222d82c 100644 --- a/src/calibre/ebooks/oeb/transforms/structure.py +++ b/src/calibre/ebooks/oeb/transforms/structure.py @@ -48,8 +48,10 @@ class DetectStructure(object): if opts.toc_filter is not None: regexp = re.compile(opts.toc_filter) - for node in self.oeb.toc.iter(): + for node in list(self.oeb.toc.iter()): if not node.title or regexp.search(node.title) is not None: + self.log('Filtering', node.title if node.title else\ + 'empty node', 'from TOC') self.oeb.toc.remove(node) if opts.page_breaks_before is not None: From 3d4e7660513d85a8db74983e974560710058844c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 1 Sep 2009 18:06:53 -0600 Subject: [PATCH 05/13] Fix #3375 (--max-toc-links option does not seem to work) --- src/calibre/ebooks/oeb/transforms/structure.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/calibre/ebooks/oeb/transforms/structure.py b/src/calibre/ebooks/oeb/transforms/structure.py index b84222d82c..2f52fde371 100644 --- a/src/calibre/ebooks/oeb/transforms/structure.py +++ b/src/calibre/ebooks/oeb/transforms/structure.py @@ -106,6 +106,7 @@ class DetectStructure(object): counter += 1 def create_toc_from_links(self): + num = 0 for item in self.oeb.spine: for a in XPath('//h:a[@href]')(item.data): href = a.get('href') @@ -120,8 +121,13 @@ class DetectStructure(object): a.xpath('descendant::text()')]) text = text[:100].strip() if not self.oeb.toc.has_text(text): + num += 1 self.oeb.toc.add(text, href, play_order=self.oeb.toc.next_play_order()) + if self.opts.max_toc_links > 0 and \ + num >= self.opts.max_toc_links: + self.log('Maximum TOC links reached, stopping.') + return From 881a08686a4f468daf38dfa4f0631b6f09e92d46 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 1 Sep 2009 18:10:42 -0600 Subject: [PATCH 06/13] New recipe for Honvedelem.hu by Devilinside --- src/calibre/web/feeds/recipes/__init__.py | 2 +- .../web/feeds/recipes/recipe_honvedelem.py | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/calibre/web/feeds/recipes/recipe_honvedelem.py diff --git a/src/calibre/web/feeds/recipes/__init__.py b/src/calibre/web/feeds/recipes/__init__.py index 613f5ba2bb..adbc69c4e1 100644 --- a/src/calibre/web/feeds/recipes/__init__.py +++ b/src/calibre/web/feeds/recipes/__init__.py @@ -56,7 +56,7 @@ recipe_modules = ['recipe_' + r for r in ( 'volksrant', 'theeconomictimes_india', 'ourdailybread', 'monitor', 'republika', 'beta', 'beta_en', 'glasjavnosti', 'esquire', 'livemint', 'thedgesingapore', 'darknet', 'rga', - 'intelligencer', 'theoldfoodie', 'hln_be', + 'intelligencer', 'theoldfoodie', 'hln_be', 'honvedelem', )] diff --git a/src/calibre/web/feeds/recipes/recipe_honvedelem.py b/src/calibre/web/feeds/recipes/recipe_honvedelem.py new file mode 100644 index 0000000000..96ca29f5e9 --- /dev/null +++ b/src/calibre/web/feeds/recipes/recipe_honvedelem.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + + +from calibre.web.feeds.news import BasicNewsRecipe + +class HunMilNews(BasicNewsRecipe): + title = u'Honvedelem.hu' + oldest_article = 3 + description = u'Katonah\xedrek' + language = _('Hungarian') + lang = 'hu' + encoding = 'windows-1250' + category = 'news, military' + + no_stylesheets = True + + + __author__ = 'Devilinside' + max_articles_per_feed = 16 + no_stylesheets = True + + + + keep_only_tags = [dict(name='div', attrs={'class':'cikkoldal_cikk_cim'}), + dict(name='div', attrs={'class':'cikkoldal_cikk_alcim'}), + dict(name='div', attrs={'class':'cikkoldal_datum'}), + dict(name='div', attrs={'class':'cikkoldal_lead'}), + dict(name='div', attrs={'class':'cikkoldal_szoveg'}), + dict(name='img', attrs={'class':'ajanlo_kep_keretes'}), + ] + + + + feeds = [(u'Misszi\xf3k', u'http://www.honvedelem.hu/rss_b?c=22'), + (u'Aktu\xe1lis hazai h\xedrek', u'http://www.honvedelem.hu/rss_b?c=3'), + (u'K\xfclf\xf6ldi h\xedrek', u'http://www.honvedelem.hu/rss_b?c=4'), + (u'A h\xf3nap t\xe9m\xe1ja', u'http://www.honvedelem.hu/rss_b?c=6'), + (u'Riport', u'http://www.honvedelem.hu/rss_b?c=5'), + (u'Portr\xe9k', u'http://www.honvedelem.hu/rss_b?c=7'), + (u'Haditechnika', u'http://www.honvedelem.hu/rss_b?c=8'), + (u'Programok, esem\xe9nyek', u'http://www.honvedelem.hu/rss_b?c=12') + ] + From 111830363fe60e4792aeb9be31c95bb6627a356f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 1 Sep 2009 18:13:57 -0600 Subject: [PATCH 07/13] Fix #3380 (Conversion to LRF ignore input text code page) --- src/calibre/ebooks/txt/input.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/ebooks/txt/input.py b/src/calibre/ebooks/txt/input.py index 2b0245c98b..47b03181f0 100644 --- a/src/calibre/ebooks/txt/input.py +++ b/src/calibre/ebooks/txt/input.py @@ -51,6 +51,7 @@ class TXTInput(InputFormatPlugin): html_input = plugin_for_input_format('html') for opt in html_input.options: setattr(options, opt.option.name, opt.recommended_value) + options.input_encoding = 'utf-8' base = os.getcwdu() if hasattr(stream, 'name'): base = os.path.dirname(stream.name) From 880ac0d3a054e94802770c5c11b693bda5ea898f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 1 Sep 2009 18:28:35 -0600 Subject: [PATCH 08/13] IGN:Increase time that calibre wait s before querying windows for drive information when it detects a device --- src/calibre/devices/usbms/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index c92c2258e9..0e83069eb4 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -262,7 +262,7 @@ class Device(DeviceConfig, DevicePlugin): return True return False - time.sleep(6) + time.sleep(8) drives = {} wmi = __import__('wmi', globals(), locals(), [], -1) c = wmi.WMI(find_classes=False) From 63e13101beb43d1ae180e45de7cd82b117906f74 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 1 Sep 2009 18:30:42 -0600 Subject: [PATCH 09/13] Fix #3345 (Please add more sections to "Die Zeit" recipe) --- src/calibre/web/feeds/recipes/recipe_zeitde.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/calibre/web/feeds/recipes/recipe_zeitde.py b/src/calibre/web/feeds/recipes/recipe_zeitde.py index 6e0f919d41..8de0809736 100644 --- a/src/calibre/web/feeds/recipes/recipe_zeitde.py +++ b/src/calibre/web/feeds/recipes/recipe_zeitde.py @@ -9,20 +9,27 @@ from calibre.web.feeds.news import BasicNewsRecipe class ZeitDe(BasicNewsRecipe): - + title = 'Die Zeit Nachrichten' description = 'Die Zeit - Online Nachrichten' language = _('German') - __author__ = 'Kovid Goyal' + __author__ = 'Kovid Goyal and Martin Pitt' use_embedded_content = False timefmt = ' [%d %b %Y]' max_articles_per_feed = 40 no_stylesheets = True encoding = 'latin1' - - feeds = [ ('Zeit.de', 'http://newsfeed.zeit.de/news/index') ] - + + feeds = [ ('Kurznachrichten', 'http://newsfeed.zeit.de/news/index'), + ('Politik', 'http://newsfeed.zeit.de/politik/index'), + ('Wirtschaft', 'http://newsfeed.zeit.de/wirtschaft/index'), + ('Meinung', 'http://newsfeed.zeit.de/meinung/index'), + ('Gesellschaft', 'http://newsfeed.zeit.de/gesellschaft/index'), + ('Kultur', 'http://newsfeed.zeit.de/kultur/index'), + ('Wissen', 'http://newsfeed.zeit.de/wissen/index'), + ] + def print_version(self,url): return url.replace('http://www.zeit.de/', 'http://images.zeit.de/text/').replace('?from=rss', '') From 48b99181d19bd3f7566610ff184885312a3f7ebf Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 1 Sep 2009 18:59:50 -0600 Subject: [PATCH 10/13] Fix #3373 (I have problems with characters in title and book author name) --- src/calibre/ebooks/metadata/rtf.py | 58 ++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/src/calibre/ebooks/metadata/rtf.py b/src/calibre/ebooks/metadata/rtf.py index b1ee453218..7f418de8d7 100644 --- a/src/calibre/ebooks/metadata/rtf.py +++ b/src/calibre/ebooks/metadata/rtf.py @@ -3,7 +3,7 @@ __copyright__ = '2008, Kovid Goyal ' """ Edit metadata in RTF files. """ -import re, cStringIO, sys +import re, cStringIO, codecs from calibre.ebooks.metadata import MetaInformation, string_to_authors @@ -13,7 +13,7 @@ comment_pat = re.compile(r'\{\\info.*?\{\\subject(.*?)(? Date: Tue, 1 Sep 2009 20:09:53 -0600 Subject: [PATCH 11/13] Fix recipe for De Standaard --- .../web/feeds/recipes/recipe_de_standaard.py | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/calibre/web/feeds/recipes/recipe_de_standaard.py b/src/calibre/web/feeds/recipes/recipe_de_standaard.py index de456b9169..cead8018a5 100644 --- a/src/calibre/web/feeds/recipes/recipe_de_standaard.py +++ b/src/calibre/web/feeds/recipes/recipe_de_standaard.py @@ -9,25 +9,21 @@ from calibre.web.feeds.news import BasicNewsRecipe class DeStandaard(BasicNewsRecipe): title = u'De Standaard' __author__ = u'Darko Miletic' - language = _('Dutch') + language = _('Dutch') description = u'News from Belgium' oldest_article = 7 max_articles_per_feed = 100 no_stylesheets = True use_embedded_content = False - - keep_only_tags = [dict(name='div' , attrs={'id':'_parts_midContainer_div'})] - remove_tags_after = dict(name='h3', attrs={'title':'Binnenland'}) - remove_tags = [ - dict(name='h3' , attrs={'title':'Binnenland' }) - ,dict(name='p' , attrs={'class':'by' }) - ,dict(name='div' , attrs={'class':'articlesright'}) - ,dict(name='a' , attrs={'class':'help' }) - ,dict(name='a' , attrs={'class':'archive' }) - ,dict(name='a' , attrs={'class':'print' }) - ,dict(name='a' , attrs={'class':'email' }) - ] + encoding = 'utf-8' + + keep_only_tags = [dict(name='div' , attrs={'id':['intro','continued']})] - feeds = [ - (u'De Standaard Online', u'http://feeds.feedburner.com/dso-front') - ] + feeds = [(u'De Standaard Online', u'http://feeds.feedburner.com/dso-front')] + + + def get_article_url(self, article): + return article.get('guid', None) + + def print_version(self, url): + return url.replace('/Detail.aspx?','/PrintArtikel.aspx?') From c33d8a09da966d042f0f8dcd745fa76bdb23935c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 1 Sep 2009 21:16:40 -0600 Subject: [PATCH 12/13] IGN:... --- installer/osx/py2app/main.py | 19 +- src/calibre/translations/calibre.pot | 433 ++++++++++++++------------- 2 files changed, 235 insertions(+), 217 deletions(-) diff --git a/installer/osx/py2app/main.py b/installer/osx/py2app/main.py index 42f9c4361f..a875d5670f 100644 --- a/installer/osx/py2app/main.py +++ b/installer/osx/py2app/main.py @@ -89,9 +89,6 @@ class Py2App(object): self.add_libpng() self.add_fontconfig() self.add_imagemagick() - self.add_plugins() - self.add_qt_plugins() - self.add_ipython() self.add_misc_libraries() self.add_site_packages() @@ -107,6 +104,7 @@ class Py2App(object): return self.makedmg(self.builddir, APPNAME+'-'+VERSION+'-x86_64') def strip_files(self): + print '\nStripping files...' strip_files(self.to_strip) def create_exe(self): @@ -150,7 +148,7 @@ class Py2App(object): path_to_lib]) def fix_dependencies_in_lib(self, path_to_lib): - print '\nFixing dependecies in', path_to_lib + print '\nFixing dependencies in', path_to_lib self.to_strip.append(path_to_lib) old_mode = flipwritable(path_to_lib) for dep, bname in self.get_local_dependencies(path_to_lib): @@ -266,7 +264,7 @@ class Py2App(object): for x in ('fontconfig.1', 'freetype.6', 'expat.1'): src = os.path.join(SW, 'lib', 'lib'+x+'.dylib') self.install_dylib(src) - dst = os.path.join(self.resource_dir, 'fonts') + dst = os.path.join(self.resources_dir, 'fonts') if os.path.exists(dst): shutil.rmtree(dst) src = os.path.join(SW, 'etc', 'fonts') @@ -296,6 +294,11 @@ class Py2App(object): if os.path.exists(dest): shutil.rmtree(dest) shutil.copytree(idir, dest, True) + for x in os.walk(dest): + for f in x[-1]: + if f.endswith('.so'): + f = join(x[0], f) + self.fix_dependencies_in_lib(f) def add_misc_libraries(self): for x in ('usb', 'unrar'): @@ -323,7 +326,7 @@ class Py2App(object): print "WARNING:", x, 'is neither a directory nor a zipfile' continue tdir = tempfile.mkdtemp() - zf.extract_all(tdir) + zf.extractall(tdir) x = tdir self.add_modules_from_dir(x) self.add_packages_from_dir(x) @@ -371,9 +374,9 @@ class Py2App(object): def add_stdlib(self): print '\nAdding python stdlib' src = join(SW, '/python/Python.framework/Versions/Current/lib/python') - src += '.'.join(sys.version_info[:2]) + src += '.'.join(map(str, sys.version_info[:2])) dest = join(self.resources_dir, 'Python', 'lib', 'python') - dest += '.'.join(sys.version_info[:2]) + dest += '.'.join(map(str, sys.version_info[:2])) for x in os.listdir(src): if x in ('site-packages', 'config', 'test', 'lib2to3', 'lib-tk', 'lib-old', 'idlelib', 'plat-mac', 'plat-darwin', 'site.py'): diff --git a/src/calibre/translations/calibre.pot b/src/calibre/translations/calibre.pot index 0989db9b32..407f713e3d 100644 --- a/src/calibre/translations/calibre.pot +++ b/src/calibre/translations/calibre.pot @@ -5,8 +5,8 @@ msgid "" msgstr "" "Project-Id-Version: calibre 0.6.10\n" -"POT-Creation-Date: 2009-08-27 20:01+MDT\n" -"PO-Revision-Date: 2009-08-27 20:01+MDT\n" +"POT-Creation-Date: 2009-09-01 21:13+MDT\n" +"PO-Revision-Date: 2009-09-01 21:13+MDT\n" "Last-Translator: Automatically generated\n" "Language-Team: LANGUAGE\n" "MIME-Version: 1.0\n" @@ -21,11 +21,11 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:44 #: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:94 -#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:52 +#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:53 #: /home/kovid/work/calibre/src/calibre/devices/prs505/books.py:58 #: /home/kovid/work/calibre/src/calibre/devices/prs505/books.py:199 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:695 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:698 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:703 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:706 #: /home/kovid/work/calibre/src/calibre/ebooks/comic/input.py:403 #: /home/kovid/work/calibre/src/calibre/ebooks/fb2/input.py:66 #: /home/kovid/work/calibre/src/calibre/ebooks/fb2/input.py:68 @@ -350,15 +350,15 @@ msgstr "" msgid "Disable the named plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:12 +#: /home/kovid/work/calibre/src/calibre/devices/android/driver.py:13 msgid "Communicate with Android phones." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/bebook/driver.py:18 +#: /home/kovid/work/calibre/src/calibre/devices/bebook/driver.py:19 msgid "Communicate with the BeBook eBook reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/bebook/driver.py:93 +#: /home/kovid/work/calibre/src/calibre/devices/bebook/driver.py:95 msgid "Communicate with the BeBook Mini eBook reader." msgstr "" @@ -373,23 +373,23 @@ msgstr "" msgid "Kovid Goyal" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:20 +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:21 msgid "Communicate with the Cybook Gen 3 eBook reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:21 -#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:84 +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:22 +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:86 #: /home/kovid/work/calibre/src/calibre/devices/iliad/driver.py:17 #: /home/kovid/work/calibre/src/calibre/devices/irexdr/driver.py:18 -#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:21 -#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:66 -#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:77 +#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:22 +#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:67 +#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:78 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:30 msgid "John Schember" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:73 -#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:75 +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:74 +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:76 #: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:76 #: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:78 #: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:129 @@ -399,7 +399,7 @@ msgstr "" msgid "Transferring books to device..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:83 +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:85 msgid "Communicate with the Cybook Opus eBook reader." msgstr "" @@ -427,12 +427,12 @@ msgstr "" msgid "James Ralston" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:20 +#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:21 msgid "Communicate with the Kindle eBook reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:65 -#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:76 +#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:66 +#: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:77 msgid "Communicate with the Kindle 2 eBook reader." msgstr "" @@ -454,11 +454,11 @@ msgstr "" msgid "Getting list of books on device..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:25 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:26 msgid "Communicate with the Sony PRS-505 eBook reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:26 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:27 #: /home/kovid/work/calibre/src/calibre/devices/prs700/driver.py:18 msgid "Kovid Goyal and John Schember" msgstr "" @@ -479,46 +479,46 @@ msgstr "" msgid "Communicate with the Sony PRS-700 eBook reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:277 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:349 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:285 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:357 msgid "Unable to detect the %s disk drive. Try rebooting." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:417 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:425 msgid "Unable to detect the %s disk drive." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:510 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:518 msgid "Could not find mount helper: %s." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:522 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:530 msgid "Unable to detect the %s disk drive. Your kernel is probably exporting a deprecated version of SYSFS." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:530 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:538 msgid "Unable to mount main memory (Error code: %d)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:635 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:637 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:643 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:645 msgid "The reader has no storage card in this slot." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:639 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:647 msgid "Selected slot: %s is not supported." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:663 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:671 msgid "There is insufficient free space in main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:665 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:667 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:673 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:675 msgid "There is insufficient free space on the storage card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:678 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:686 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:433 #: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:83 #: /home/kovid/work/calibre/src/calibre/library/database2.py:1000 @@ -1566,7 +1566,7 @@ msgstr "" msgid "Could not find reasonable point at which to split: %s Sub-tree size: %d KB" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/structure.py:66 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/structure.py:68 msgid "Unnamed" msgstr "" @@ -1582,16 +1582,16 @@ msgstr "" msgid "Generate an Adobe \"page-map\" file if pagination information is available." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/reader132.py:117 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/reader132.py:120 msgid "Footnotes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/reader132.py:126 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ereader/reader132.py:129 msgid "Sidebar" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/pdb/input.py:22 -#: /home/kovid/work/calibre/src/calibre/ebooks/txt/input.py:21 +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/input.py:22 msgid "Normally calibre treats blank lines as paragraph markers. With this option it will assume that every line represents a paragraph instead." msgstr "" @@ -1809,6 +1809,10 @@ msgstr "" msgid "This RTF file has a feature calibre does not support. Convert it to HTML first and then try it." msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/txt/input.py:26 +msgid "Run the text input though the markdown processor." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/txt/output.py:24 msgid "Type of newline to use. Options are %s. Default is 'system'. Use 'old_mac' for compatibility with Mac OS 9 and earlier. For Mac OS X use 'unix'. 'system' will default to the newline type used by this OS." msgstr "" @@ -1818,7 +1822,7 @@ msgid "Specify the character encoding of the output document. The default is utf msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:28 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:502 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:505 msgid "Frequently used directories" msgstr "" @@ -2095,11 +2099,11 @@ msgstr "" msgid "Choose debug folder" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/debug.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/debug.py:57 msgid "Invalid debug directory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/debug.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/debug.py:58 msgid "Failed to create debug directory" msgstr "" @@ -2113,14 +2117,14 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/xpath_edit_ui.py:44 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:61 #: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/configwidget_ui.py:62 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:488 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:500 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:501 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:491 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:503 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:504 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:506 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:522 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:523 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:553 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:507 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:509 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:525 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:526 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:557 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:343 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:348 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:362 @@ -2379,7 +2383,7 @@ msgid "Change the title of this book" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:173 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:140 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:135 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:344 msgid "&Author(s): " msgstr "" @@ -2393,7 +2397,7 @@ msgid "Change the author(s) of this book. Multiple authors should be separated b msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:147 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:143 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:353 msgid "&Publisher: " msgstr "" @@ -2404,21 +2408,21 @@ msgid "Ta&gs: " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:178 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:145 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:355 msgid "Tags categorize the book. This is particularly useful while searching.

They can be any words or phrases, separated by commas." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:179 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:154 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:150 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:358 msgid "&Series:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:180 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:181 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:155 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:152 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:359 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:360 msgid "List of known series. You can add new series." @@ -3095,109 +3099,117 @@ msgstr "" msgid "new email address" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:508 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:467 +msgid "Command line tools installed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:468 +msgid "Command line tools installed in" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:518 msgid "No valid plugin path" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:509 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:519 msgid "%s is not a valid plugin path" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:512 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:522 msgid "Choose plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:524 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:534 msgid "Plugin cannot be disabled" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:525 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:535 msgid "The plugin: %s cannot be disabled" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:534 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:544 msgid "Plugin not customizable" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:535 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:545 msgid "Plugin: %s does not need customization" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:559 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:569 msgid "Customize %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:569 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:579 msgid "Cannot remove builtin plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:570 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:580 msgid " cannot be removed. It is a builtin plugin. Try disabling it instead." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:603 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:613 msgid "Error log:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:610 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:620 msgid "Access log:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:635 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:645 #: /home/kovid/work/calibre/src/calibre/gui2/main.py:574 msgid "Failed to start content server" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:659 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:669 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:471 msgid "Select location for books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:676 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:686 msgid "Invalid size" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:677 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:687 msgid "The size %s is invalid. must be of the form widthxheight" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:721 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:726 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:731 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:736 msgid "Invalid database location" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:722 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:732 msgid "Invalid database location " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:723 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:733 msgid "
Must be a directory." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:727 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:737 msgid "Invalid database location.
Cannot write to " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:765 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:775 msgid "Checking database integrity" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:784 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:794 #: /home/kovid/work/calibre/src/calibre/gui2/main.py:142 #: /home/kovid/work/calibre/src/calibre/gui2/main.py:1005 #: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:52 msgid "Error" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:785 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:795 msgid "Failed to check database integrity" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:790 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:800 msgid "Some inconsistencies found" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:791 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/__init__.py:801 msgid "The following books had formats listed in the database that are not actually available. The entries for the formats have been removed. You should check them manually. This can happen if you manipulate the files in the library folder directly." msgstr "" @@ -3281,256 +3293,260 @@ msgstr "" msgid "&Saving books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:485 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:488 #: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:367 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:173 msgid "Preferences" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:486 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:489 msgid "&Location of ebooks (The ebooks are stored in folders sorted by author and metadata is stored in the file metadata.db)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:487 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:490 msgid "Browse for the new database location" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:489 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:492 msgid "Show notification when &new version is available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:490 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:493 msgid "Default network &timeout:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:491 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:494 msgid "Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:492 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:495 msgid " seconds" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:493 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:496 msgid "Choose &language (requires restart):" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:494 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:497 msgid "Normal" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:495 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:498 msgid "High" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:496 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:499 msgid "Low" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:497 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:500 msgid "Job &priority:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:498 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:501 msgid "Preferred &output format:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:499 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:502 msgid "Preferred &input format order:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:503 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:506 msgid "Add a directory to the frequently used directories list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:505 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:508 msgid "Remove a directory from the frequently used directories list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:507 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:510 msgid "Use &Roman numerals for series number" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:508 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:511 msgid "Enable system &tray icon (needs restart)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:509 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:512 msgid "Show ¬ifications in system tray" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:510 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:513 msgid "Show cover &browser in a separate window (needs restart)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:511 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:514 msgid "Search as you type" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:512 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:515 msgid "Automatically send downloaded &news to ebook reader" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:513 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:516 msgid "&Delete news from library when it is automatically sent to reader" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:517 msgid "&Number of covers to show in browse mode (needs restart):" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:518 msgid "Toolbar" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:516 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:519 msgid "Large" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:517 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:520 msgid "Medium" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:518 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:521 msgid "Small" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:519 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:522 msgid "&Button size in toolbar" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:520 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:523 msgid "Show &text in toolbar buttons" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:521 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:524 msgid "Select visible &columns in library view" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:524 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:527 msgid "Use internal &viewer for:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:525 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:528 msgid "Add an email address to which to send books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:526 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:529 msgid "&Add email" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:527 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:530 msgid "Make &default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:528 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:531 msgid "&Remove email" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:529 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:532 msgid "calibre can send your books to you (or your reader) by email" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:530 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:533 msgid "Free unused diskspace from the database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:531 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:534 msgid "&Check database integrity" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:532 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:535 +msgid "&Install command line tools" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:536 msgid "calibre contains a network server that allows you to access your book collection using a browser from anywhere in the world. Any changes to the settings will only take effect after a server restart." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:533 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:537 msgid "Server &port:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:534 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:538 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:58 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:178 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:117 msgid "&Username:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:535 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:539 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:59 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:179 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email_ui.py:119 msgid "&Password:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:536 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:540 msgid "If you leave the password blank, anyone will be able to access your book collection using the web interface." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:537 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:541 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:60 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:180 msgid "&Show password" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:538 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:542 msgid "The maximum size (widthxheight) for displayed covers. Larger covers are resized. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:539 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:543 msgid "Max. &cover size:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:540 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:544 msgid "&Start Server" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:541 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:545 msgid "St&op Server" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:542 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:546 msgid "&Test Server" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:543 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:547 msgid "Run server &automatically on startup" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:544 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:548 msgid "View &server logs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:545 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:549 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/stanza_ui.py:46 msgid "" "

Remember to leave calibre running as the server only runs as long as calibre is running.\n" "

Stanza should see your calibre collection automatically. If not, try adding the URL http://myhostname:8080 as a new catalog in the Stanza reader on your iPhone. Here myhostname should be the fully qualified hostname or the IP address of the computer calibre is running on." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:547 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:551 msgid "Here you can customize the behavior of Calibre by controlling what plugins it uses." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:548 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:552 msgid "Enable/&Disable plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:549 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:553 msgid "&Customize plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:550 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:554 msgid "&Remove plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:551 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:555 msgid "Add new plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:552 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:556 msgid "Plugin &file:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:554 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config/config_ui.py:558 msgid "&Add" msgstr "" @@ -3622,68 +3638,68 @@ msgstr "" msgid "Show job &details" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:138 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:133 msgid "Edit Meta information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:134 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:339 msgid "Meta information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:141 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:136 +msgid "A&utomatically set author sort" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:137 msgid "Author s&ort: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:142 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:138 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:346 msgid "Specify how the author(s) of this book should be sorted. For example Charles Dickens should be sorted as Dickens, Charles." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:143 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:139 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:349 msgid "&Rating:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:144 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:140 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:141 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:350 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:351 msgid "Rating of this book. 0-5 stars" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:146 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:142 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:352 msgid " stars" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:144 msgid "Add ta&gs: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:150 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:151 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:146 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:147 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:356 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:357 msgid "Open Tag Editor" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:148 msgid "&Remove tags:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:153 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:149 msgid "Comma separated list of tags to remove from the books. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:153 msgid "Remove &format:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:158 -msgid "A&utomatically set author sort" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:119 msgid "Not a valid picture" msgstr "" @@ -4567,7 +4583,7 @@ msgid "Save to disk in a single directory" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/main.py:281 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1484 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1485 msgid "Save only %s format to disk" msgstr "" @@ -4607,7 +4623,7 @@ msgid "Calibre Library" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/main.py:438 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1625 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1626 msgid "Choose a location for your ebook library." msgstr "" @@ -4787,7 +4803,7 @@ msgid "No book selected" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/main.py:1377 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1429 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1427 msgid "Cannot view" msgstr "" @@ -4807,132 +4823,132 @@ msgstr "" msgid "You are attempting to open %d books. Opening too many books at once can be slow and have a negative effect on the responsiveness of your computer. Once started the process cannot be stopped until complete. Do you wish to continue?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1430 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1428 msgid "%s has no available formats." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1468 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1469 msgid "Cannot configure" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1469 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1470 msgid "Cannot configure while there are running jobs." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1514 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1515 msgid "No detailed info available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1515 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1516 msgid "No detailed information is available for books on the device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1563 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1564 msgid "Error talking to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1564 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1565 msgid "There was a temporary error talking to the device. Please unplug and reconnect the device and or reboot." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1587 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1605 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1588 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1606 msgid "Conversion Error" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1588 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1589 msgid "

Could not convert: %s

It is a DRMed book. You must first remove the DRM using 3rd party tools." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1606 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1607 msgid "Failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1634 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1635 msgid "Invalid library location" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1635 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1636 msgid "Could not access %s. Using %s as the library." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1683 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1684 msgid "is the result of the efforts of many volunteers from all over the world. If you find it useful, please consider donating to support its development." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1707 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1708 msgid "There are active jobs. Are you sure you want to quit?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1710 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1711 msgid "" " is communicating with the device!
\n" " Quitting may cause corruption on the device.
\n" " Are you sure you want to quit?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1714 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1715 msgid "WARNING: Active jobs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1765 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1766 msgid "will keep running in the system tray. To close it, choose Quit in the context menu of the system tray." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1784 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1785 msgid "Latest version: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1792 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1793 msgid "Update available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1793 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1794 msgid "%s has been updated to version %s. See the new features. Visit the download page?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1811 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1812 msgid "Use the library located at the specified path." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1813 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1814 msgid "Start minimized to system tray." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1815 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1816 msgid "Log debugging information to console" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1863 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1864 msgid "If you are sure it is not running" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1865 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1866 msgid "Cannot Start " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1866 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1867 msgid "%s is already running." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1869 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1870 msgid "may be running in the system tray, in the" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1871 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1872 msgid "upper right region of the screen." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1873 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1874 msgid "lower right region of the screen." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1876 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1877 msgid "try rebooting your computer." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1878 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1898 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1879 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1899 msgid "try deleting the file" msgstr "" @@ -6010,11 +6026,11 @@ msgstr "" msgid "Checking SQL integrity..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1727 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1731 msgid "Checking for missing files." msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1751 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1755 msgid "Checked id" msgstr "" @@ -6106,7 +6122,7 @@ msgstr "" msgid "Replace whitespace with underscores." msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:228 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:230 msgid "Requested formats not available" msgstr "" @@ -6292,19 +6308,19 @@ msgstr "" msgid "Downloading cover from %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:926 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:930 msgid "Untitled Article" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:997 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1001 msgid "Article downloaded: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1008 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1012 msgid "Article download failed: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1023 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:1027 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_borba.py:80 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_glas_srpske.py:76 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_instapaper.py:59 @@ -6407,7 +6423,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_chr_mon.py:11 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_cincinnati_enquirer.py:10 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_climate_progress.py:23 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_cnn.py:15 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_cnn.py:14 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_coding_horror.py:17 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_common_dreams.py:8 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_corriere_della_sera_en.py:23 @@ -6438,6 +6454,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_iht.py:15 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_indy_star.py:6 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_inquirer_net.py:24 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_intelligencer.py:17 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_irish_times.py:13 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_japan_times.py:15 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_joelonsoftware.py:15 @@ -6503,6 +6520,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_thedgesingapore.py:17 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_theeconomictimes_india.py:25 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_themarketticker.py:17 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_theoldfoodie.py:21 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_theonion.py:20 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_time_magazine.py:18 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_times_online.py:25 @@ -6510,7 +6528,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_twitchfilms.py:22 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_uncrate.py:24 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_upi.py:15 -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_usatoday.py:17 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_usatoday.py:18 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_usnews.py:21 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_utne.py:20 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_wash_post.py:12 @@ -6574,6 +6592,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_demorgen_be.py:16 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_gva_be.py:22 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_hln.py:22 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_hln_be.py:22 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_tijd.py:22 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_volksrant.py:17 msgid "Dutch" @@ -6599,6 +6618,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_h1.py:15 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_h2.py:15 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_h3.py:15 +#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_honvedelem.py:16 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_huntechnet.py:15 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_index_hu.py:8 #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_pcworld_hu.py:17 @@ -6621,11 +6641,6 @@ msgstr "" msgid "Montenegrin" msgstr "" -#: -#: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_usatoday.py:18 -msgid "Kovid Goyal and Sujata Raman" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/web/feeds/recipes/recipe_zaobao.py:17 msgid "Chinese" msgstr "" From d6b76bcaf177126edebf1fe7a1bdb7c3fa89ff88 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 1 Sep 2009 22:27:29 -0600 Subject: [PATCH 13/13] IGN:More work on the leopard build --- installer/osx/py2app/launcher.py | 1 + installer/osx/py2app/loader.py | 30 ++++++++++++++++++++++++++++++ installer/osx/py2app/main.py | 27 ++++++++++++++++++++++----- 3 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 installer/osx/py2app/loader.py diff --git a/installer/osx/py2app/launcher.py b/installer/osx/py2app/launcher.py index dcd14668f7..c12b6bdc3a 100644 --- a/installer/osx/py2app/launcher.py +++ b/installer/osx/py2app/launcher.py @@ -40,6 +40,7 @@ def _run(): sys.frozen = 'macosx_app' base = os.environ['RESOURCEPATH'] sys.frameworks_dir = os.path.join(os.path.dirname(base, 'Frameworks')) + sys.new_app_bundle = True site.addsitedir(base) site.addsitedir(os.path.join(base, 'Python', 'site-packages')) exe = os.environ.get('CALIBRE_LAUNCH_MODULE', 'calibre.gui2.main') diff --git a/installer/osx/py2app/loader.py b/installer/osx/py2app/loader.py new file mode 100644 index 0000000000..533a88bbe3 --- /dev/null +++ b/installer/osx/py2app/loader.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + + +import os, sys + +ENV = {}##ENV## +MODULE = ''##MODULE## + +path = os.path.abspath(os.path.realpath(__file__)) +dirpath = os.path.dirname(path) +name = os.path.basename(path) +base_dir = os.path.dirname(os.path.dirname(dirpath)) +resources_dir = os.path.join(base_dir, 'Resources') +frameworks_dir = os.path.join(base_dir, 'Frameworks') +exe_dir = os.path.join(base_dir, 'MacOS') +base_name = os.path.splitext(name)[0] +python = os.path.join(base_dir, 'MacOS', 'calibre') + +for key, val in ENV.items(): + if val.startswith('@exec'): + ENV[key] = os.path.normpath(val.replace('@executable_path', exe_dir)) +ENV['CALIBRE_LAUNCH_MODULE'] = MODULE +os.environ.update(ENV) +args = [path] + sys.argv[1:] +os.execv(python, args) + diff --git a/installer/osx/py2app/main.py b/installer/osx/py2app/main.py index a875d5670f..0c19ed13d6 100644 --- a/installer/osx/py2app/main.py +++ b/installer/osx/py2app/main.py @@ -26,6 +26,7 @@ ENV = dict( PYTHONIOENCODING='utf-8:replace', PYTHONPATH='@executable_path/../Resources/Python/site-packages', PYTHONHOME='@executable_path/../Resources/Python', + QT_PLUGIN_PATH='@executable_path' ) SW = os.environ.get('SW') @@ -100,20 +101,34 @@ class Py2App(object): self.copy_launcher_and_site() self.create_exe() self.strip_files() + self.create_launchers() + + return self.makedmg(self.build_dir, APPNAME+'-'+VERSION+'-x86_64') + + def create_launchers(self): + launcher = join(os.path.dirname(__file__), 'launcher.py') + launcher = open(launcher, 'rb').read() + launcher = launcher.replace('{}##ENV##', repr(ENV)) + os.mkdir(join(self.resources_dir, 'loaders')) + for module, basename in zip(main_modules, basenames): + raw = launcher.replace("''##MODULE##", repr(module)) + path = join(self.resources_dir, 'loaders', basename) + open(path, 'wb').write(raw) + os.chmod(path, stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH|stat.S_IREAD\ + |stat.S_IWUSR|stat.S_IROTH|stat.S_IRGRP) - return self.makedmg(self.builddir, APPNAME+'-'+VERSION+'-x86_64') def strip_files(self): print '\nStripping files...' strip_files(self.to_strip) def create_exe(self): - gcc = os.environ.get('GCC', 'gcc') + gcc = os.environ.get('CC', 'gcc') base = os.path.dirname(__file__) out = join(self.contents_dir, 'MacOS', 'calibre') - subprocess.check_call([gcc, '-Wall', '-arch x86_64', join(base, + subprocess.check_call([gcc, '-Wall', '-arch', 'x86_64', join(base, 'main.c'), '-o', out]) - self.to_strip(out) + self.to_strip.append(out) def set_id(self, path_to_lib, new_id): old_mode = flipwritable(path_to_lib) @@ -373,7 +388,7 @@ class Py2App(object): def add_stdlib(self): print '\nAdding python stdlib' - src = join(SW, '/python/Python.framework/Versions/Current/lib/python') + src = join(SW, 'python/Python.framework/Versions/Current/lib/python') src += '.'.join(map(str, sys.version_info[:2])) dest = join(self.resources_dir, 'Python', 'lib', 'python') dest += '.'.join(map(str, sys.version_info[:2])) @@ -437,6 +452,8 @@ class Py2App(object): internet_enable=True, format='UDBZ'): ''' Copy a directory d into a dmg named volname ''' + if not os.path.exists(destdir): + os.makedirs(destdir) dmg = os.path.join(destdir, volname+'.dmg') if os.path.exists(dmg): os.unlink(dmg)