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..c12b6bdc3a --- /dev/null +++ b/installer/osx/py2app/launcher.py @@ -0,0 +1,51 @@ +#!/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')) + 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') + 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/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.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..0c19ed13d6 --- /dev/null +++ b/installer/osx/py2app/main.py @@ -0,0 +1,479 @@ +#!/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', + QT_PLUGIN_PATH='@executable_path' + ) + +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_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() + 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) + + + def strip_files(self): + print '\nStripping files...' + strip_files(self.to_strip) + + def create_exe(self): + 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, + 'main.c'), '-o', out]) + self.to_strip.append(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 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): + 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.resources_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) + 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'): + 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.extractall(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(map(str, sys.version_info[:2])) + dest = join(self.resources_dir, 'Python', 'lib', 'python') + 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'): + 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 ''' + if not os.path.exists(destdir): + os.makedirs(destdir) + 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/usbms/device.py b/src/calibre/devices/usbms/device.py index bb8f05b9a8..ff07b36691 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) 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/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(.*?)(? 0 and \ + num >= self.opts.max_toc_links: + self.log('Maximum TOC links reached, stopping.') + return diff --git a/src/calibre/gui2/convert/txt_input.ui b/src/calibre/gui2/convert/txt_input.ui index 353144b46b..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 @@ -41,6 +41,16 @@ + + + + <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 + + + 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/gui2/images/news/hln_be.png b/src/calibre/gui2/images/news/hln_be.png new file mode 100644 index 0000000000..0cddf06420 Binary files /dev/null and b/src/calibre/gui2/images/news/hln_be.png differ 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/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 "" 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/src/calibre/web/feeds/recipes/__init__.py b/src/calibre/web/feeds/recipes/__init__.py index 3979d542ed..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', + 'intelligencer', 'theoldfoodie', 'hln_be', 'honvedelem', )] 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?') diff --git a/src/calibre/web/feeds/recipes/recipe_hln_be.py b/src/calibre/web/feeds/recipes/recipe_hln_be.py new file mode 100644 index 0000000000..9ba04585e2 --- /dev/null +++ b/src/calibre/web/feeds/recipes/recipe_hln_be.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2009, Darko Miletic ' +''' +www.hln.be +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class HLN_be(BasicNewsRecipe): + title = 'HLN Belgium' + __author__ = 'Darko Miletic' + description = 'Belgium news' + publisher = 'HLN' + category = 'news, politics, Belgium' + oldest_article = 2 + max_articles_per_feed = 100 + use_embedded_content = False + no_stylesheets = True + encoding = 'utf-8' + language = _('Dutch') + + conversion_options = { + 'comments' : description + ,'tags' : category + ,'language' : 'nl-NL' + ,'publisher' : publisher + } + + remove_tags = [dict(name=['form','object','embed'])] + + keep_only_tags = [dict(name='div', attrs={'id':'art_box2'})] + + feeds = [(u'Articles', u'http://www.hln.be/rss.xml')] 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') + ] + diff --git a/src/calibre/web/feeds/recipes/recipe_theoldfoodie.py b/src/calibre/web/feeds/recipes/recipe_theoldfoodie.py new file mode 100644 index 0000000000..dacff26c15 --- /dev/null +++ b/src/calibre/web/feeds/recipes/recipe_theoldfoodie.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2009, Darko Miletic ' +''' +www.theoldfoodie.com +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class TheOldFoodie(BasicNewsRecipe): + title = 'The Old Foodie' + __author__ = 'Darko Miletic' + description = 'Food blog' + category = 'cuisine, food, blog' + oldest_article = 30 + max_articles_per_feed = 100 + use_embedded_content = True + no_stylesheets = True + encoding = 'utf-8' + language = _('English') + + conversion_options = { + 'comments' : description + ,'tags' : category + ,'language' : 'en' + } + + feeds = [(u'Articles', u'http://www.theoldfoodie.com/feeds/posts/default?alt=rss')] 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', '') 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):