diff --git a/recipes/business_week.recipe b/recipes/business_week.recipe deleted file mode 100644 index fe98d9fa00..0000000000 --- a/recipes/business_week.recipe +++ /dev/null @@ -1,105 +0,0 @@ -__license__ = 'GPL v3' -__copyright__ = '2008 Kovid Goyal kovid@kovidgoyal.net, 2010 Darko Miletic ' -''' -www.businessweek.com -''' - -from calibre.web.feeds.news import BasicNewsRecipe - -class BusinessWeek(BasicNewsRecipe): - title = 'Business Week' - __author__ = 'Kovid Goyal and Darko Miletic' - description = 'Read the latest international business news & stock market news. Get updated company profiles, financial advice, global economy and technology news.' - publisher = 'Bloomberg L.P.' - category = 'Business, business news, stock market, stock market news, financial advice, company profiles, financial advice, global economy, technology news' - oldest_article = 7 - max_articles_per_feed = 200 - no_stylesheets = True - encoding = 'utf8' - use_embedded_content = False - language = 'en' - remove_empty_feeds = True - publication_type = 'magazine' - cover_url = 'http://images.businessweek.com/mz/covers/current_120x160.jpg' - masthead_url = 'http://assets.businessweek.com/images/bw-logo.png' - extra_css = """ - body{font-family: Helvetica,Arial,sans-serif } - img{margin-bottom: 0.4em; display:block} - .tagline{color: gray; font-style: italic} - .photoCredit{font-size: small; color: gray} - """ - - conversion_options = { - 'comment' : description - , 'tags' : category - , 'publisher' : publisher - , 'language' : language - } - - remove_tags = [ - dict(attrs={'class':'inStory'}) - ,dict(name=['meta','link','iframe','base','embed','object','table','th','tr','td']) - ,dict(attrs={'id':['inset','videoDisplay']}) - ] - keep_only_tags = [dict(name='div', attrs={'id':['story-body','storyBody','article_body','articleBody']})] - remove_attributes = ['lang'] - match_regexps = [r'http://www.businessweek.com/.*_page_[1-9].*'] - - - feeds = [ - (u'Top Stories', u'http://www.businessweek.com/topStories/rss/topStories.rss'), - (u'Top News' , u'http://www.businessweek.com/rss/bwdaily.rss' ), - (u'Asia', u'http://www.businessweek.com/rss/asia.rss'), - (u'Autos', u'http://www.businessweek.com/rss/autos/index.rss'), - (u'Classic Cars', u'http://rss.businessweek.com/bw_rss/classiccars'), - (u'Hybrids', u'http://rss.businessweek.com/bw_rss/hybrids'), - (u'Europe', u'http://www.businessweek.com/rss/europe.rss'), - (u'Auto Reviews', u'http://rss.businessweek.com/bw_rss/autoreviews'), - (u'Innovation & Design', u'http://www.businessweek.com/rss/innovate.rss'), - (u'Architecture', u'http://www.businessweek.com/rss/architecture.rss'), - (u'Brand Equity', u'http://www.businessweek.com/rss/brandequity.rss'), - (u'Auto Design', u'http://www.businessweek.com/rss/carbuff.rss'), - (u'Game Room', u'http://rss.businessweek.com/bw_rss/gameroom'), - (u'Technology', u'http://www.businessweek.com/rss/technology.rss'), - (u'Investing', u'http://rss.businessweek.com/bw_rss/investor'), - (u'Small Business', u'http://www.businessweek.com/rss/smallbiz.rss'), - (u'Careers', u'http://rss.businessweek.com/bw_rss/careers'), - (u'B-Schools', u'http://www.businessweek.com/rss/bschools.rss'), - (u'Magazine Selections', u'http://www.businessweek.com/rss/magazine.rss'), - (u'CEO Guide to Tech', u'http://www.businessweek.com/rss/ceo_guide_tech.rss'), - ] - - def get_article_url(self, article): - url = article.get('guid', None) - if 'podcasts' in url: - return None - if 'surveys' in url: - return None - if 'images' in url: - return None - if 'feedroom' in url: - return None - if '/magazine/toc/' in url: - return None - rurl, sep, rest = url.rpartition('?') - if rurl: - return rurl - return rest - - def print_version(self, url): - if '/news/' in url or '/blog/' in url: - return url - if '/magazine' in url: - rurl = url.replace('http://www.businessweek.com/','http://www.businessweek.com/printer/') - else: - rurl = url.replace('http://www.businessweek.com/','http://www.businessweek.com/print/') - return rurl.replace('/investing/','/investor/') - - def preprocess_html(self, soup): - for item in soup.findAll(style=True): - del item['style'] - for alink in soup.findAll('a'): - if alink.string is not None: - tstr = alink.string - alink.replaceWith(tstr) - return soup diff --git a/recipes/nin.recipe b/recipes/nin.recipe index ae09b3d0eb..78c9dd4324 100644 --- a/recipes/nin.recipe +++ b/recipes/nin.recipe @@ -1,6 +1,6 @@ __license__ = 'GPL v3' -__copyright__ = '2008-2011, Darko Miletic ' +__copyright__ = '2008-2012, Darko Miletic ' ''' www.nin.co.rs ''' @@ -15,11 +15,11 @@ class Nin(BasicNewsRecipe): publisher = 'NIN d.o.o. - Ringier d.o.o.' category = 'news, politics, Serbia' no_stylesheets = True - delay = 1 oldest_article = 15 encoding = 'utf-8' needs_subscription = True remove_empty_feeds = True + auto_cleanup = False PREFIX = 'http://www.nin.co.rs' INDEX = PREFIX + '/?change_lang=ls' use_embedded_content = False @@ -63,7 +63,11 @@ class Nin(BasicNewsRecipe): keep_only_tags =[dict(name='td', attrs={'width':'520'})] remove_tags_before =dict(name='span', attrs={'class':'izjava'}) remove_tags_after =dict(name='html') - remove_tags = [dict(name=['object','link','iframe','meta','base'])] + remove_tags = [ + dict(name=['object','link','iframe','meta','base']) + ,dict(attrs={'class':['fb-like','twitter-share-button']}) + ,dict(attrs={'rel':'nofollow'}) + ] remove_attributes=['border','background','height','width','align','valign'] def get_cover_url(self): @@ -78,10 +82,6 @@ class Nin(BasicNewsRecipe): feeds = [(u'NIN Online', u'http://www.nin.co.rs/misc/rss.php?feed=RSS2.0')] - def get_article_url(self, article): - url = BasicNewsRecipe.get_article_url(self, article) - return url.replace('.co.yu', '.co.rs') - def preprocess_html(self, soup): for item in soup.findAll(style=True): del item['style'] @@ -99,4 +99,3 @@ class Nin(BasicNewsRecipe): img.extract() tbl.replaceWith(img) return soup - diff --git a/session.vim b/session.vim index 4b9dcb72c1..6b8878b84d 100644 --- a/session.vim +++ b/session.vim @@ -14,6 +14,7 @@ let g:syntastic_cpp_include_dirs = [ let g:syntastic_c_include_dirs = g:syntastic_cpp_include_dirs set wildignore+=resources/viewer/mathjax/** +set wildignore+=build/** fun! CalibreLog() " Setup buffers to edit the calibre changelog and version info prior to diff --git a/setup/installer/windows/freeze.py b/setup/installer/windows/freeze.py index a640a6fcd1..72779d096f 100644 --- a/setup/installer/windows/freeze.py +++ b/setup/installer/windows/freeze.py @@ -381,7 +381,6 @@ class Win32Freeze(Command, WixMixIn): sys.exit(1) def build_portable_installer(self): - base = self.portable_base zf = self.a(self.j('dist', 'calibre-portable-%s.zip.lz'%VERSION)) usz = os.path.getsize(zf) def cc(src, obj): @@ -442,7 +441,7 @@ class Win32Freeze(Command, WixMixIn): '/RELEASE', '/ENTRY:wWinMainCRTStartup', '/OUT:'+exe, self.embed_resources(exe), - obj, 'User32.lib', 'Shlwapi.lib'] + obj, 'User32.lib'] self.run_builder(cmd) self.info('Creating portable installer') diff --git a/setup/installer/windows/portable.c b/setup/installer/windows/portable.c index 606057432f..874838c9a2 100644 --- a/setup/installer/windows/portable.c +++ b/setup/installer/windows/portable.c @@ -8,7 +8,6 @@ #include -#include #include #include #include @@ -90,7 +89,7 @@ LPTSTR get_app_dir() { return buf3; } -void launch_calibre(LPCTSTR exe, LPCTSTR config_dir, LPCTSTR library_dir) { +void launch_calibre(LPCTSTR exe, LPCTSTR config_dir) { DWORD dwFlags=0; STARTUPINFO si; PROCESS_INFORMATION pi; @@ -108,13 +107,12 @@ void launch_calibre(LPCTSTR exe, LPCTSTR config_dir, LPCTSTR library_dir) { } dwFlags = CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP; - _sntprintf_s(cmdline, BUFSIZE, _TRUNCATE, _T(" \"--with-library=%s\""), library_dir); ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); - fSuccess = CreateProcess(exe, cmdline, + fSuccess = CreateProcess(exe, NULL, NULL, // Process handle not inheritable NULL, // Thread handle not inheritable FALSE, // Set handle inheritance to FALSE @@ -135,45 +133,6 @@ void launch_calibre(LPCTSTR exe, LPCTSTR config_dir, LPCTSTR library_dir) { } -static BOOL is_dots(LPCTSTR name) { - return _tcscmp(name, _T(".")) == 0 || _tcscmp(name, _T("..")) == 0; -} - -static void find_calibre_library(LPTSTR library_dir) { - TCHAR base[BUFSIZE] = {0}, buf[BUFSIZE] = {0}; - WIN32_FIND_DATA fdFile; - HANDLE hFind = NULL; - - _sntprintf_s(buf, BUFSIZE, _TRUNCATE, _T("%s\\metadata.db"), base); - - if (PathFileExists(buf)) return; // Calibre Library/metadata.db exists, we use it - - _tcscpy(base, library_dir); - PathRemoveFileSpec(base); - - _sntprintf_s(buf, BUFSIZE, _TRUNCATE, _T("%s\\*"), base); - - // Look for some other folder that contains a metadata.db file inside the Calibre Portable folder - if((hFind = FindFirstFileEx(buf, FindExInfoStandard, &fdFile, FindExSearchLimitToDirectories, NULL, 0)) - != INVALID_HANDLE_VALUE) { - do { - if(is_dots(fdFile.cFileName)) continue; - - if(fdFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - _sntprintf_s(buf, BUFSIZE, _TRUNCATE, _T("%s\\%s\\metadata.db"), base, fdFile.cFileName); - if (PathFileExists(buf)) { - // some dir/metadata.db exists, we use it as the library - PathRemoveFileSpec(buf); - _tcscpy(library_dir, buf); - FindClose(hFind); - return; - } - } - } while(FindNextFile(hFind, &fdFile)); - FindClose(hFind); - } - -} int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) { @@ -181,26 +140,14 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine app_dir = get_app_dir(); config_dir = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); - library_dir = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); exe = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); _sntprintf_s(config_dir, BUFSIZE, _TRUNCATE, _T("%sCalibre Settings"), app_dir); _sntprintf_s(exe, BUFSIZE, _TRUNCATE, _T("%sCalibre\\calibre.exe"), app_dir); - _sntprintf_s(library_dir, BUFSIZE, _TRUNCATE, _T("%sCalibre Library"), app_dir); - find_calibre_library(library_dir); + launch_calibre(exe, config_dir); - if ( _tcscnlen(library_dir, BUFSIZE) <= 74 ) { - launch_calibre(exe, config_dir, library_dir); - } else { - too_long = (LPTSTR)calloc(BUFSIZE+300, sizeof(TCHAR)); - _sntprintf_s(too_long, BUFSIZE+300, _TRUNCATE, - _T("Path to Calibre Portable (%s) too long. Must be less than 59 characters."), app_dir); - - show_error(too_long); - } - - free(app_dir); free(config_dir); free(exe); free(library_dir); + free(app_dir); free(config_dir); free(exe); return 0; } diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 391a64027f..aafec33c3b 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -177,6 +177,11 @@ def get_version(): v += '*' return v +def get_portable_base(): + 'Return path to the directory that contains calibre-portable.exe or None' + if isportable: + return os.path.dirname(os.path.dirname(os.environ['CALIBRE_PORTABLE_BUILD'])) + def get_unicode_windows_env_var(name): import ctypes name = unicode(name) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index f46ac23772..609b05e7d0 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -674,6 +674,7 @@ from calibre.devices.kobo.driver import KOBO from calibre.devices.bambook.driver import BAMBOOK from calibre.devices.boeye.driver import BOEYE_BEX, BOEYE_BDX from calibre.devices.smart_device_app.driver import SMART_DEVICE_APP +from calibre.devices.mtp.driver import MTP_DEVICE # Order here matters. The first matched device is the one used. plugins += [ @@ -745,14 +746,11 @@ plugins += [ ITUNES, BOEYE_BEX, BOEYE_BDX, + MTP_DEVICE, SMART_DEVICE_APP, USER_DEFINED, ] -from calibre.utils.config_base import tweaks -if tweaks.get('test_mtp_driver', False): - from calibre.devices.mtp.driver import MTP_DEVICE - plugins.append(MTP_DEVICE) # }}} diff --git a/src/calibre/debug.py b/src/calibre/debug.py index 2a3cee9b15..22871cab9e 100644 --- a/src/calibre/debug.py +++ b/src/calibre/debug.py @@ -6,7 +6,7 @@ __copyright__ = '2008, Kovid Goyal ' Embedded console for debugging. ''' -import sys, os +import sys, os, functools from calibre.utils.config import OptionParser from calibre.constants import iswindows from calibre import prints @@ -160,26 +160,32 @@ def add_simple_plugin(path_to_plugin): os.chdir(odir) shutil.rmtree(tdir) -def run_debug_gui(logpath): - import time, platform - time.sleep(3) # Give previous GUI time to shutdown fully and release locks - from calibre.constants import __appname__, __version__, isosx - print __appname__, _('Debug log') - print __appname__, __version__ - print platform.platform() - print platform.system() - print platform.system_alias(platform.system(), platform.release(), - platform.version()) - print 'Python', platform.python_version() +def print_basic_debug_info(out=None): + if out is None: out = sys.stdout + out = functools.partial(prints, file=out) + import platform + from calibre.constants import __appname__, get_version, isportable, isosx + out(__appname__, get_version(), 'Portable' if isportable else '') + out(platform.platform(), platform.system()) + out(platform.system_alias(platform.system(), platform.release(), + platform.version())) + out('Python', platform.python_version()) try: if iswindows: - print 'Windows:', platform.win32_ver() + out('Windows:', platform.win32_ver()) elif isosx: - print 'OSX:', platform.mac_ver() + out('OSX:', platform.mac_ver()) else: - print 'Linux:', platform.linux_distribution() + out('Linux:', platform.linux_distribution()) except: pass + +def run_debug_gui(logpath): + import time + time.sleep(3) # Give previous GUI time to shutdown fully and release locks + from calibre.constants import __appname__ + prints(__appname__, _('Debug log')) + print_basic_debug_info() from calibre.gui2.main import main main(['__CALIBRE_GUI_DEBUG__', logpath]) @@ -206,6 +212,7 @@ def main(args=sys.argv): opts, args = option_parser().parse_args(args) if opts.gui: from calibre.gui2.main import main + print_basic_debug_info() main(['calibre']) elif opts.gui_debug is not None: run_debug_gui(opts.gui_debug) diff --git a/src/calibre/devices/__init__.py b/src/calibre/devices/__init__.py index 9fa0ab355c..69b8ddc07d 100644 --- a/src/calibre/devices/__init__.py +++ b/src/calibre/devices/__init__.py @@ -62,10 +62,11 @@ def debug(ioreg_to_tmp=False, buf=None, plugins=None): already have been called (for example in the main GUI), pass in the list of device plugins as the plugins parameter. ''' - import textwrap, platform + import textwrap from calibre.customize.ui import device_plugins + from calibre.debug import print_basic_debug_info from calibre.devices.scanner import DeviceScanner, win_pnp_drives - from calibre.constants import iswindows, isosx, __version__ + from calibre.constants import iswindows, isosx from calibre import prints oldo, olde = sys.stdout, sys.stderr @@ -85,21 +86,7 @@ def debug(ioreg_to_tmp=False, buf=None, plugins=None): out('Startup failed for device plugin: %s'%d) try: - out('Calibre Version:', __version__) - out(platform.platform(), platform.system()) - out(platform.system_alias(platform.system(), platform.release(), - platform.version())) - out('Python', platform.python_version()) - try: - if iswindows: - out('Windows:', platform.win32_ver()) - elif isosx: - out('OSX:', platform.mac_ver()) - else: - out('Linux:', platform.linux_distribution()) - except: - pass - + print_basic_debug_info(out=buf) s = DeviceScanner() s.scan() devices = (s.devices) diff --git a/src/calibre/devices/misc.py b/src/calibre/devices/misc.py index 7ec38af774..2565c24218 100644 --- a/src/calibre/devices/misc.py +++ b/src/calibre/devices/misc.py @@ -226,6 +226,7 @@ class TREKSTOR(USBMS): VENDOR_ID = [0x1e68] PRODUCT_ID = [0x0041, 0x0042, 0x0052, 0x004e, 0x0056, 0x003e, # This is for the EBOOK_PLAYER_5M https://bugs.launchpad.net/bugs/792091 + 0x5cL, # This is for the 4ink http://www.mobileread.com/forums/showthread.php?t=191318 ] BCD = [0x0002, 0x100] diff --git a/src/calibre/devices/mtp/defaults.py b/src/calibre/devices/mtp/defaults.py new file mode 100644 index 0000000000..2b72aa77cc --- /dev/null +++ b/src/calibre/devices/mtp/defaults.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2012, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import traceback, re + +from calibre.constants import iswindows + +class DeviceDefaults(object): + + def __init__(self): + self.rules = ( + # Amazon devices + ({'vendor':0x1949}, { + 'format_map': ['azw3', 'mobi', 'azw', + 'azw1', 'azw4', 'pdf'], + 'send_to': ['documents', 'books', 'kindle'], + } + ), + ) + + def __call__(self, device, driver): + if iswindows: + vid = pid = 0xffff + m = re.search(r'(?i)vid_([0-9a-fA-F]+)&pid_([0-9a-fA-F]+)', device) + if m is not None: + try: + vid, pid = int(m.group(1), 16), int(m.group(2), 16) + except: + traceback.print_exc() + else: + vid, pid = device.vendor_id, device.product_id + + for rule in self.rules: + tests = rule[0] + matches = True + for k, v in tests.iteritems(): + if k == 'vendor' and v != vid: + matches = False + break + if k == 'product' and v != pid: + matches = False + break + if matches: + return rule[1] + + return {} + + diff --git a/src/calibre/devices/mtp/driver.py b/src/calibre/devices/mtp/driver.py index c5b874a0fa..fa37d33889 100644 --- a/src/calibre/devices/mtp/driver.py +++ b/src/calibre/devices/mtp/driver.py @@ -14,9 +14,11 @@ from itertools import izip from calibre import prints from calibre.constants import iswindows, numeric_version from calibre.devices.mtp.base import debug +from calibre.devices.mtp.defaults import DeviceDefaults from calibre.ptempfile import SpooledTemporaryFile, PersistentTemporaryDirectory from calibre.utils.config import from_json, to_json, JSONConfig from calibre.utils.date import now, isoformat, utcnow +from calibre.utils.filenames import shorten_components_to BASE = importlib.import_module('calibre.devices.mtp.%s.driver'%( 'windows' if iswindows else 'unix')).MTP_DEVICE @@ -41,6 +43,8 @@ class MTP_DEVICE(BASE): BASE.__init__(self, *args, **kwargs) self.plugboards = self.plugboard_func = None self._prefs = None + self.device_defaults = DeviceDefaults() + self.current_device_defaults = {} @property def prefs(self): @@ -73,17 +77,19 @@ class MTP_DEVICE(BASE): for x in ('format_map', 'send_template', 'send_to'): del self.prefs[x] - def open(self, devices, library_uuid): + def open(self, device, library_uuid): self.current_library_uuid = library_uuid self.location_paths = None self.driveinfo = {} - BASE.open(self, devices, library_uuid) + BASE.open(self, device, library_uuid) h = self.prefs['history'] if self.current_serial_num: h[self.current_serial_num] = (self.current_friendly_name, isoformat(utcnow())) self.prefs['history'] = h + self.current_device_defaults = self.device_defaults(device, self) + # Device information {{{ def _update_drive_info(self, storage, location_code, name=None): import uuid @@ -264,7 +270,11 @@ class MTP_DEVICE(BASE): continue base = os.path.join(tdir, '%s'%f.object_id) os.mkdir(base) - with open(os.path.join(base, f.name), 'wb') as out: + name = f.name + if iswindows: + plen = len(base) + name = ''.join(shorten_components_to(245-plen, [name])) + with open(os.path.join(base, name), 'wb') as out: try: self.get_mtp_file(f, out) except Exception as e: @@ -434,8 +444,13 @@ class MTP_DEVICE(BASE): # Settings {{{ def get_pref(self, key): - return self.prefs.get('device-%s'%self.current_serial_num, {}).get(key, - self.prefs[key]) + ''' Get the setting named key. First looks for a device specific setting. + If that is not found looks for a device default and if that is not + found uses the global default.''' + dd = self.current_device_defaults if self.is_mtp_device_connected else {} + dev_settings = self.prefs.get('device-%s'%self.current_serial_num, {}) + default_value = dd.get(key, self.prefs[key]) + return dev_settings.get(key, default_value) def config_widget(self): from calibre.gui2.device_drivers.mtp_config import MTPConfig @@ -452,7 +467,7 @@ class MTP_DEVICE(BASE): @property def save_template(self): - return self.prefs['send_template'] + return self.get_pref('send_template') # }}} diff --git a/src/calibre/devices/mtp/filesystem_cache.py b/src/calibre/devices/mtp/filesystem_cache.py index dfabef06d9..3acb026b77 100644 --- a/src/calibre/devices/mtp/filesystem_cache.py +++ b/src/calibre/devices/mtp/filesystem_cache.py @@ -18,7 +18,7 @@ from calibre.utils.date import local_tz, as_utc from calibre.utils.icu import sort_key, lower from calibre.ebooks import BOOK_EXTENSIONS -bexts = frozenset(BOOK_EXTENSIONS) +bexts = frozenset(BOOK_EXTENSIONS) - {'mbp', 'tan', 'rar', 'zip', 'xml'} class FileOrFolder(object): diff --git a/src/calibre/devices/mtp/unix/driver.py b/src/calibre/devices/mtp/unix/driver.py index 4b9ed9e928..d86262c78b 100644 --- a/src/calibre/devices/mtp/unix/driver.py +++ b/src/calibre/devices/mtp/unix/driver.py @@ -137,6 +137,10 @@ class MTP_DEVICE(MTPDeviceBase): self.currently_connected_dev = None self.current_serial_num = None + @property + def is_mtp_device_connected(self): + return self.currently_connected_dev is not None + @synchronous def startup(self): p = plugins['libmtp'] @@ -189,6 +193,7 @@ class MTP_DEVICE(MTPDeviceBase): if not self.current_friendly_name: self.current_friendly_name = self.dev.model_name or _('Unknown MTP device') self.current_serial_num = snum + self.currently_connected_dev = connected_device @property def filesystem_cache(self): @@ -297,14 +302,16 @@ class MTP_DEVICE(MTPDeviceBase): def get_mtp_file(self, f, stream=None, callback=None): if f.is_folder: raise ValueError('%s if a folder'%(f.full_path,)) + set_name = stream is None if stream is None: stream = SpooledTemporaryFile(5*1024*1024, '_wpd_receive_file.dat') - stream.name = f.name ok, errs = self.dev.get_file(f.object_id, stream, callback) if not ok: raise DeviceError('Failed to get file: %s with errors: %s'%( f.full_path, self.format_errorstack(errs))) stream.seek(0) + if set_name: + stream.name = f.name return stream @synchronous diff --git a/src/calibre/devices/mtp/windows/driver.py b/src/calibre/devices/mtp/windows/driver.py index 3da81d26e2..22079c287b 100644 --- a/src/calibre/devices/mtp/windows/driver.py +++ b/src/calibre/devices/mtp/windows/driver.py @@ -246,6 +246,10 @@ class MTP_DEVICE(MTPDeviceBase): self.dev = self._filesystem_cache = None self.current_serial_num = None + @property + def is_mtp_device_connected(self): + return self.currently_connected_pnp_id is not None + def eject(self): if self.currently_connected_pnp_id is None: return self.eject_dev_on_next_scan = True @@ -321,9 +325,9 @@ class MTP_DEVICE(MTPDeviceBase): def get_mtp_file(self, f, stream=None, callback=None): if f.is_folder: raise ValueError('%s if a folder'%(f.full_path,)) + set_name = stream is None if stream is None: stream = SpooledTemporaryFile(5*1024*1024, '_wpd_receive_file.dat') - stream.name = f.name try: try: self.dev.get_file(f.object_id, stream, callback) @@ -332,8 +336,10 @@ class MTP_DEVICE(MTPDeviceBase): self.dev.get_file(f.object_id, stream, callback) except Exception as e: raise DeviceError('Failed to fetch the file %s with error: %s'% - f.full_path, as_unicode(e)) + (f.full_path, as_unicode(e))) stream.seek(0) + if set_name: + stream.name = f.name return stream @same_thread diff --git a/src/calibre/ebooks/oeb/transforms/rasterize.py b/src/calibre/ebooks/oeb/transforms/rasterize.py index 1ed5bfd25a..d5eb7c5008 100644 --- a/src/calibre/ebooks/oeb/transforms/rasterize.py +++ b/src/calibre/ebooks/oeb/transforms/rasterize.py @@ -96,7 +96,7 @@ class SVGRasterizer(object): def dataize_manifest(self): for item in self.oeb.manifest.values(): - if item.media_type == SVG_MIME: + if item.media_type == SVG_MIME and item.data is not None: self.dataize_svg(item) def dataize_svg(self, item, svg=None): diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 7a5e71e17b..3146436030 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -13,7 +13,7 @@ from PyQt4.Qt import (QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, ORG_NAME = 'KovidsBrain' APP_UID = 'libprs500' from calibre.constants import (islinux, iswindows, isbsd, isfrozen, isosx, - config_dir, filesystem_encoding) + plugins, config_dir, filesystem_encoding, DEBUG) from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig from calibre.ebooks.metadata import MetaInformation from calibre.utils.date import UNDEFINED_DATE @@ -567,7 +567,8 @@ class FileDialog(QObject): modal = True, name = '', mode = QFileDialog.ExistingFiles, - default_dir='~' + default_dir='~', + no_save_dir=False ): QObject.__init__(self) ftext = '' @@ -586,8 +587,11 @@ class FileDialog(QObject): self.selected_files = None self.fd = None - initial_dir = dynamic.get(self.dialog_name, - os.path.expanduser(default_dir)) + if no_save_dir: + initial_dir = os.path.expanduser(default_dir) + else: + initial_dir = dynamic.get(self.dialog_name, + os.path.expanduser(default_dir)) if not isinstance(initial_dir, basestring): initial_dir = os.path.expanduser(default_dir) self.selected_files = [] @@ -629,7 +633,8 @@ class FileDialog(QObject): saved_loc = self.selected_files[0] if os.path.isfile(saved_loc): saved_loc = os.path.dirname(saved_loc) - dynamic[self.dialog_name] = saved_loc + if not no_save_dir: + dynamic[self.dialog_name] = saved_loc self.accepted = bool(self.selected_files) def get_files(self): @@ -638,10 +643,10 @@ class FileDialog(QObject): return tuple(self.selected_files) -def choose_dir(window, name, title, default_dir='~'): +def choose_dir(window, name, title, default_dir='~', no_save_dir=False): fd = FileDialog(title=title, filters=[], add_all_files_filter=False, parent=window, name=name, mode=QFileDialog.Directory, - default_dir=default_dir) + default_dir=default_dir, no_save_dir=no_save_dir) dir = fd.get_files() fd.setParent(None) if dir: @@ -759,6 +764,9 @@ class Application(QApplication): if override_program_name: args = [override_program_name] + args[1:] qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args] + self.pi = plugins['progress_indicator'][0] + if DEBUG: + self.redirect_notify = True QApplication.__init__(self, qargs) global gui_thread, qt_app gui_thread = QThread.currentThread() @@ -769,15 +777,23 @@ class Application(QApplication): self._file_open_lock = RLock() self.setup_styles(force_calibre_style) + if DEBUG: + def notify(self, receiver, event): + if self.redirect_notify: + self.redirect_notify = False + return self.pi.do_notify(receiver, event) + else: + ret = QApplication.notify(self, receiver, event) + self.redirect_notify = True + return ret + def load_calibre_style(self): # On OS X QtCurve resets the palette, so we preserve it explicitly orig_pal = QPalette(self.palette()) - from calibre.constants import plugins - pi = plugins['progress_indicator'][0] path = os.path.join(sys.extensions_location, 'calibre_style.'+( 'pyd' if iswindows else 'so')) - pi.load_style(path, 'Calibre') + self.pi.load_style(path, 'Calibre') # On OSX, on some machines, colors can be invalid. See https://bugs.launchpad.net/bugs/1014900 for role in (orig_pal.Button, orig_pal.Window): c = orig_pal.brush(role).color() diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index 0a361209e6..829268adee 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -5,7 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os +import os, posixpath from functools import partial from PyQt4.Qt import (QMenu, Qt, QInputDialog, QToolButton, QDialog, @@ -13,7 +13,8 @@ from PyQt4.Qt import (QMenu, Qt, QInputDialog, QToolButton, QDialog, QCoreApplication, pyqtSignal) from calibre import isbytestring, sanitize_file_name_unicode -from calibre.constants import filesystem_encoding, iswindows +from calibre.constants import (filesystem_encoding, iswindows, + get_portable_base) from calibre.utils.config import prefs from calibre.gui2 import (gprefs, warning_dialog, Dispatcher, error_dialog, question_dialog, info_dialog, open_local_file, choose_dir) @@ -25,6 +26,17 @@ class LibraryUsageStats(object): # {{{ def __init__(self): self.stats = {} self.read_stats() + base = get_portable_base() + if base is not None: + lp = prefs['library_path'] + if lp: + # Rename the current library. Renaming of other libraries is + # handled by the switch function + q = os.path.basename(lp) + for loc in list(self.stats.iterkeys()): + bn = posixpath.basename(loc) + if bn.lower() == q.lower(): + self.rename(loc, lp) def read_stats(self): stats = gprefs.get('library_usage_stats', {}) @@ -417,6 +429,18 @@ class ChooseLibraryAction(InterfaceAction): finally: self.gui.status_bar.clear_message() + def look_for_portable_lib(self, db, location): + base = get_portable_base() + if base is None: + return False, None + loc = location.replace('/', os.sep) + candidate = os.path.join(base, os.path.basename(loc)) + if db.exists_at(candidate): + newloc = candidate.replace(os.sep, '/') + self.stats.rename(location, newloc) + return True, newloc + return False, None + def switch_requested(self, location): if not self.change_library_allowed(): return @@ -425,6 +449,12 @@ class ChooseLibraryAction(InterfaceAction): self.view_state_map[current_lib] = self.preserve_state_on_switch.state loc = location.replace('/', os.sep) exists = db.exists_at(loc) + if not exists: + exists, new_location = self.look_for_portable_lib(db, location) + if exists: + location = new_location + loc = location.replace('/', os.sep) + if not exists: d = MovedDialog(self.stats, location, self.gui) ret = d.exec_() diff --git a/src/calibre/gui2/device_drivers/mtp_config.py b/src/calibre/gui2/device_drivers/mtp_config.py index c6d2154229..cd7e495225 100644 --- a/src/calibre/gui2/device_drivers/mtp_config.py +++ b/src/calibre/gui2/device_drivers/mtp_config.py @@ -163,7 +163,7 @@ class IgnoredDevices(QWidget): # {{{ self.l = l = QVBoxLayout() self.setLayout(l) self.la = la = QLabel('

'+_( - '''Select the devices to be ignored. calibre will not + '''Select the devices to be ignored. calibre will not connect to devices with a checkmark next to their names.''')) la.setWordWrap(True) l.addWidget(la) @@ -386,7 +386,7 @@ class MTPConfig(QTabWidget): self.device.prefs['blacklist']) self.addTab(self.igntab, _('Ignored devices')) - self.setCurrentIndex(0) + self.setCurrentIndex(1 if msg else 0) def ignore_device(self): self.igntab.ignore_device(self.device.current_serial_num) @@ -400,7 +400,7 @@ class MTPConfig(QTabWidget): p = self.device.prefs.get(self.current_device_key, {}) if not p: self.device.prefs[self.current_device_key] = p - return p.get(key, self.device.prefs[key]) + return self.device.get_pref(key) @property def device(self): diff --git a/src/calibre/gui2/dialogs/choose_library.py b/src/calibre/gui2/dialogs/choose_library.py index 62d6c4c437..91048e8ff1 100644 --- a/src/calibre/gui2/dialogs/choose_library.py +++ b/src/calibre/gui2/dialogs/choose_library.py @@ -11,8 +11,9 @@ from PyQt4.Qt import QDialog from calibre.gui2.dialogs.choose_library_ui import Ui_Dialog from calibre.gui2 import error_dialog, choose_dir -from calibre.constants import filesystem_encoding, iswindows -from calibre import isbytestring, patheq +from calibre.constants import (filesystem_encoding, iswindows, + get_portable_base) +from calibre import isbytestring, patheq, force_unicode from calibre.gui2.wizard import move_library from calibre.library.database2 import LibraryDatabase2 @@ -39,18 +40,45 @@ class ChooseLibrary(QDialog, Ui_Dialog): self.copy_structure.setEnabled(to_what) def choose_loc(self, *args): - loc = choose_dir(self, 'choose library location', - _('Choose location for calibre library')) + base = get_portable_base() + if base is None: + loc = choose_dir(self, 'choose library location', + _('Choose location for calibre library')) + else: + name = force_unicode('choose library loc at' + base, + filesystem_encoding) + loc = choose_dir(self, name, + _('Choose location for calibre library'), default_dir=base, + no_save_dir=True) if loc is not None: self.location.setText(loc) def check_action(self, ac, loc): exists = self.db.exists_at(loc) + base = get_portable_base() if patheq(loc, self.db.library_path): error_dialog(self, _('Same as current'), _('The location %s contains the current calibre' ' library')%loc, show=True) return False + + if base is not None and ac in ('new', 'move'): + abase = os.path.normcase(os.path.abspath(base)) + cal = os.path.normcase(os.path.abspath(os.path.join(abase, + 'Calibre'))) + aloc = os.path.normcase(os.path.abspath(loc)) + if (aloc.startswith(cal+os.sep) or aloc == cal): + error_dialog(self, _('Bad location'), + _('You should not create a library inside the Calibre' + ' folder as this folder is automatically deleted during upgrades.'), + show=True) + return False + if aloc.startswith(abase) and os.path.dirname(aloc) != abase: + error_dialog(self, _('Bad location'), + _('You can only create libraries inside %s at the top ' + 'level, not in sub-folders')%base, show=True) + return False + empty = not os.listdir(loc) if ac == 'existing' and not exists: error_dialog(self, _('No existing library found'), diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index b52bd2cadb..0b4a755679 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -9,7 +9,7 @@ from PyQt4.Qt import (QCoreApplication, QIcon, QObject, QTimer, from calibre import prints, plugins, force_unicode from calibre.constants import (iswindows, __appname__, isosx, DEBUG, islinux, - filesystem_encoding) + filesystem_encoding, get_portable_base) from calibre.utils.ipc import gui_socket_address, RC from calibre.gui2 import (ORG_NAME, APP_UID, initialize_file_icon_provider, Application, choose_dir, error_dialog, question_dialog, gprefs) @@ -21,6 +21,9 @@ from calibre.library.sqlite import sqlite, DatabaseException if iswindows: winutil = plugins['winutil'][0] +class AbortInit(Exception): + pass + def option_parser(): parser = _option_parser('''\ %prog [opts] [path_to_ebook] @@ -46,10 +49,43 @@ path_to_ebook to the database. 'will be silently aborted, so use with care.')) return parser +def find_portable_library(): + base = get_portable_base() + if base is None: return + import glob + candidates = [os.path.basename(os.path.dirname(x)) for x in glob.glob( + os.path.join(base, u'*%smetadata.db'%os.sep))] + if not candidates: + candidates = [u'Calibre Library'] + lp = prefs['library_path'] + if not lp: + lib = os.path.join(base, candidates[0]) + else: + lib = None + q = os.path.basename(lp) + for c in candidates: + c = c + if c.lower() == q.lower(): + lib = os.path.join(base, c) + break + if lib is None: + lib = os.path.join(base, candidates[0]) + + if len(lib) > 74: + error_dialog(None, _('Path too long'), + _("Path to Calibre Portable (%s) " + 'too long. Must be less than 59 characters.')%base, show=True) + raise AbortInit() + + prefs.set('library_path', lib) + if not os.path.exists(lib): + os.mkdir(lib) + def init_qt(args): from calibre.gui2.ui import Main parser = option_parser() opts, args = parser.parse_args(args) + find_portable_library() if opts.with_library is not None: if not os.path.exists(opts.with_library): os.makedirs(opts.with_library) @@ -360,7 +396,10 @@ def main(args=sys.argv): gui_debug = args[1] args = ['calibre'] - app, opts, args, actions = init_qt(args) + try: + app, opts, args, actions = init_qt(args) + except AbortInit: + return 1 from calibre.utils.lock import singleinstance from multiprocessing.connection import Listener si = singleinstance('calibre GUI') diff --git a/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp b/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp index 64fd346674..bcd352d6be 100644 --- a/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp +++ b/src/calibre/gui2/progress_indicator/QProgressIndicator.cpp @@ -5,6 +5,7 @@ #include #include #include +#include QProgressIndicator::QProgressIndicator(QWidget* parent, int size) : QWidget(parent), @@ -145,3 +146,16 @@ int load_style(QString &path, QString &name) { } return ret; } + +bool do_notify(QObject *receiver, QEvent *event) { + try { + return QApplication::instance()->notify(receiver, event); + } catch (std::exception& e) { + qCritical() << "C++ exception thrown in slot: " << e.what(); + } catch (...) { + qCritical() << "Unknown C++ exception thrown in slot"; + } + qCritical() << "Receiver name:" << receiver->objectName() << "Receiver class:" << receiver->metaObject()->className() << "Event type: " << event->type(); + return false; +} + diff --git a/src/calibre/gui2/progress_indicator/QProgressIndicator.h b/src/calibre/gui2/progress_indicator/QProgressIndicator.h index 0fd82a99f5..e228964dab 100644 --- a/src/calibre/gui2/progress_indicator/QProgressIndicator.h +++ b/src/calibre/gui2/progress_indicator/QProgressIndicator.h @@ -100,3 +100,5 @@ private: */ int load_style(QString &path, QString &name); +bool do_notify(QObject *receiver, QEvent *event); + diff --git a/src/calibre/gui2/progress_indicator/QProgressIndicator.sip b/src/calibre/gui2/progress_indicator/QProgressIndicator.sip index 03c6dacdd6..7789f52eac 100644 --- a/src/calibre/gui2/progress_indicator/QProgressIndicator.sip +++ b/src/calibre/gui2/progress_indicator/QProgressIndicator.sip @@ -8,6 +8,7 @@ %ModuleHeaderCode int load_style(QString &path, QString &name); +bool do_notify(QObject *receiver, QEvent *event); %End class QProgressIndicator : QWidget { @@ -57,3 +58,5 @@ protected: int load_style(QString &path, QString &name); +bool do_notify(QObject *receiver, QEvent *event); +