Merge from trunk

This commit is contained in:
Charles Haley 2012-09-18 15:47:37 +02:00
commit d488847a9d
24 changed files with 293 additions and 240 deletions

View File

@ -1,105 +0,0 @@
__license__ = 'GPL v3'
__copyright__ = '2008 Kovid Goyal kovid@kovidgoyal.net, 2010 Darko Miletic <darko.miletic at gmail.com>'
'''
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

View File

@ -1,6 +1,6 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008-2011, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2008-2012, Darko Miletic <darko.miletic at gmail.com>'
''' '''
www.nin.co.rs www.nin.co.rs
''' '''
@ -15,11 +15,11 @@ class Nin(BasicNewsRecipe):
publisher = 'NIN d.o.o. - Ringier d.o.o.' publisher = 'NIN d.o.o. - Ringier d.o.o.'
category = 'news, politics, Serbia' category = 'news, politics, Serbia'
no_stylesheets = True no_stylesheets = True
delay = 1
oldest_article = 15 oldest_article = 15
encoding = 'utf-8' encoding = 'utf-8'
needs_subscription = True needs_subscription = True
remove_empty_feeds = True remove_empty_feeds = True
auto_cleanup = False
PREFIX = 'http://www.nin.co.rs' PREFIX = 'http://www.nin.co.rs'
INDEX = PREFIX + '/?change_lang=ls' INDEX = PREFIX + '/?change_lang=ls'
use_embedded_content = False use_embedded_content = False
@ -63,7 +63,11 @@ class Nin(BasicNewsRecipe):
keep_only_tags =[dict(name='td', attrs={'width':'520'})] keep_only_tags =[dict(name='td', attrs={'width':'520'})]
remove_tags_before =dict(name='span', attrs={'class':'izjava'}) remove_tags_before =dict(name='span', attrs={'class':'izjava'})
remove_tags_after =dict(name='html') 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'] remove_attributes=['border','background','height','width','align','valign']
def get_cover_url(self): 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')] 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): def preprocess_html(self, soup):
for item in soup.findAll(style=True): for item in soup.findAll(style=True):
del item['style'] del item['style']
@ -99,4 +99,3 @@ class Nin(BasicNewsRecipe):
img.extract() img.extract()
tbl.replaceWith(img) tbl.replaceWith(img)
return soup return soup

View File

@ -14,6 +14,7 @@ let g:syntastic_cpp_include_dirs = [
let g:syntastic_c_include_dirs = g:syntastic_cpp_include_dirs let g:syntastic_c_include_dirs = g:syntastic_cpp_include_dirs
set wildignore+=resources/viewer/mathjax/** set wildignore+=resources/viewer/mathjax/**
set wildignore+=build/**
fun! CalibreLog() fun! CalibreLog()
" Setup buffers to edit the calibre changelog and version info prior to " Setup buffers to edit the calibre changelog and version info prior to

View File

@ -381,7 +381,6 @@ class Win32Freeze(Command, WixMixIn):
sys.exit(1) sys.exit(1)
def build_portable_installer(self): def build_portable_installer(self):
base = self.portable_base
zf = self.a(self.j('dist', 'calibre-portable-%s.zip.lz'%VERSION)) zf = self.a(self.j('dist', 'calibre-portable-%s.zip.lz'%VERSION))
usz = os.path.getsize(zf) usz = os.path.getsize(zf)
def cc(src, obj): def cc(src, obj):
@ -442,7 +441,7 @@ class Win32Freeze(Command, WixMixIn):
'/RELEASE', '/RELEASE',
'/ENTRY:wWinMainCRTStartup', '/ENTRY:wWinMainCRTStartup',
'/OUT:'+exe, self.embed_resources(exe), '/OUT:'+exe, self.embed_resources(exe),
obj, 'User32.lib', 'Shlwapi.lib'] obj, 'User32.lib']
self.run_builder(cmd) self.run_builder(cmd)
self.info('Creating portable installer') self.info('Creating portable installer')

View File

@ -8,7 +8,6 @@
#include <windows.h> #include <windows.h>
#include <Shlwapi.h>
#include <tchar.h> #include <tchar.h>
#include <wchar.h> #include <wchar.h>
#include <stdio.h> #include <stdio.h>
@ -90,7 +89,7 @@ LPTSTR get_app_dir() {
return buf3; return buf3;
} }
void launch_calibre(LPCTSTR exe, LPCTSTR config_dir, LPCTSTR library_dir) { void launch_calibre(LPCTSTR exe, LPCTSTR config_dir) {
DWORD dwFlags=0; DWORD dwFlags=0;
STARTUPINFO si; STARTUPINFO si;
PROCESS_INFORMATION pi; 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; dwFlags = CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_PROCESS_GROUP;
_sntprintf_s(cmdline, BUFSIZE, _TRUNCATE, _T(" \"--with-library=%s\""), library_dir);
ZeroMemory( &si, sizeof(si) ); ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si); si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) ); ZeroMemory( &pi, sizeof(pi) );
fSuccess = CreateProcess(exe, cmdline, fSuccess = CreateProcess(exe, NULL,
NULL, // Process handle not inheritable NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE 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) 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(); app_dir = get_app_dir();
config_dir = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); config_dir = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR));
library_dir = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR));
exe = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR)); exe = (LPTSTR)calloc(BUFSIZE, sizeof(TCHAR));
_sntprintf_s(config_dir, BUFSIZE, _TRUNCATE, _T("%sCalibre Settings"), app_dir); _sntprintf_s(config_dir, BUFSIZE, _TRUNCATE, _T("%sCalibre Settings"), app_dir);
_sntprintf_s(exe, BUFSIZE, _TRUNCATE, _T("%sCalibre\\calibre.exe"), 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 ) { free(app_dir); free(config_dir); free(exe);
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);
return 0; return 0;
} }

View File

@ -177,6 +177,11 @@ def get_version():
v += '*' v += '*'
return 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): def get_unicode_windows_env_var(name):
import ctypes import ctypes
name = unicode(name) name = unicode(name)

View File

@ -674,6 +674,7 @@ from calibre.devices.kobo.driver import KOBO
from calibre.devices.bambook.driver import BAMBOOK from calibre.devices.bambook.driver import BAMBOOK
from calibre.devices.boeye.driver import BOEYE_BEX, BOEYE_BDX from calibre.devices.boeye.driver import BOEYE_BEX, BOEYE_BDX
from calibre.devices.smart_device_app.driver import SMART_DEVICE_APP 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. # Order here matters. The first matched device is the one used.
plugins += [ plugins += [
@ -745,14 +746,11 @@ plugins += [
ITUNES, ITUNES,
BOEYE_BEX, BOEYE_BEX,
BOEYE_BDX, BOEYE_BDX,
MTP_DEVICE,
SMART_DEVICE_APP, SMART_DEVICE_APP,
USER_DEFINED, 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)
# }}} # }}}

View File

@ -6,7 +6,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
Embedded console for debugging. Embedded console for debugging.
''' '''
import sys, os import sys, os, functools
from calibre.utils.config import OptionParser from calibre.utils.config import OptionParser
from calibre.constants import iswindows from calibre.constants import iswindows
from calibre import prints from calibre import prints
@ -160,26 +160,32 @@ def add_simple_plugin(path_to_plugin):
os.chdir(odir) os.chdir(odir)
shutil.rmtree(tdir) shutil.rmtree(tdir)
def run_debug_gui(logpath): def print_basic_debug_info(out=None):
import time, platform if out is None: out = sys.stdout
time.sleep(3) # Give previous GUI time to shutdown fully and release locks out = functools.partial(prints, file=out)
from calibre.constants import __appname__, __version__, isosx import platform
print __appname__, _('Debug log') from calibre.constants import __appname__, get_version, isportable, isosx
print __appname__, __version__ out(__appname__, get_version(), 'Portable' if isportable else '')
print platform.platform() out(platform.platform(), platform.system())
print platform.system() out(platform.system_alias(platform.system(), platform.release(),
print platform.system_alias(platform.system(), platform.release(), platform.version()))
platform.version()) out('Python', platform.python_version())
print 'Python', platform.python_version()
try: try:
if iswindows: if iswindows:
print 'Windows:', platform.win32_ver() out('Windows:', platform.win32_ver())
elif isosx: elif isosx:
print 'OSX:', platform.mac_ver() out('OSX:', platform.mac_ver())
else: else:
print 'Linux:', platform.linux_distribution() out('Linux:', platform.linux_distribution())
except: except:
pass 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 from calibre.gui2.main import main
main(['__CALIBRE_GUI_DEBUG__', logpath]) main(['__CALIBRE_GUI_DEBUG__', logpath])
@ -206,6 +212,7 @@ def main(args=sys.argv):
opts, args = option_parser().parse_args(args) opts, args = option_parser().parse_args(args)
if opts.gui: if opts.gui:
from calibre.gui2.main import main from calibre.gui2.main import main
print_basic_debug_info()
main(['calibre']) main(['calibre'])
elif opts.gui_debug is not None: elif opts.gui_debug is not None:
run_debug_gui(opts.gui_debug) run_debug_gui(opts.gui_debug)

View File

@ -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 already have been called (for example in the main GUI), pass in the list of
device plugins as the plugins parameter. device plugins as the plugins parameter.
''' '''
import textwrap, platform import textwrap
from calibre.customize.ui import device_plugins 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.devices.scanner import DeviceScanner, win_pnp_drives
from calibre.constants import iswindows, isosx, __version__ from calibre.constants import iswindows, isosx
from calibre import prints from calibre import prints
oldo, olde = sys.stdout, sys.stderr 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) out('Startup failed for device plugin: %s'%d)
try: try:
out('Calibre Version:', __version__) print_basic_debug_info(out=buf)
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
s = DeviceScanner() s = DeviceScanner()
s.scan() s.scan()
devices = (s.devices) devices = (s.devices)

View File

@ -226,6 +226,7 @@ class TREKSTOR(USBMS):
VENDOR_ID = [0x1e68] VENDOR_ID = [0x1e68]
PRODUCT_ID = [0x0041, 0x0042, 0x0052, 0x004e, 0x0056, PRODUCT_ID = [0x0041, 0x0042, 0x0052, 0x004e, 0x0056,
0x003e, # This is for the EBOOK_PLAYER_5M https://bugs.launchpad.net/bugs/792091 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] BCD = [0x0002, 0x100]

View File

@ -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 <kovid at kovidgoyal.net>'
__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 {}

View File

@ -14,9 +14,11 @@ from itertools import izip
from calibre import prints from calibre import prints
from calibre.constants import iswindows, numeric_version from calibre.constants import iswindows, numeric_version
from calibre.devices.mtp.base import debug from calibre.devices.mtp.base import debug
from calibre.devices.mtp.defaults import DeviceDefaults
from calibre.ptempfile import SpooledTemporaryFile, PersistentTemporaryDirectory from calibre.ptempfile import SpooledTemporaryFile, PersistentTemporaryDirectory
from calibre.utils.config import from_json, to_json, JSONConfig from calibre.utils.config import from_json, to_json, JSONConfig
from calibre.utils.date import now, isoformat, utcnow 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'%( BASE = importlib.import_module('calibre.devices.mtp.%s.driver'%(
'windows' if iswindows else 'unix')).MTP_DEVICE 'windows' if iswindows else 'unix')).MTP_DEVICE
@ -41,6 +43,8 @@ class MTP_DEVICE(BASE):
BASE.__init__(self, *args, **kwargs) BASE.__init__(self, *args, **kwargs)
self.plugboards = self.plugboard_func = None self.plugboards = self.plugboard_func = None
self._prefs = None self._prefs = None
self.device_defaults = DeviceDefaults()
self.current_device_defaults = {}
@property @property
def prefs(self): def prefs(self):
@ -73,17 +77,19 @@ class MTP_DEVICE(BASE):
for x in ('format_map', 'send_template', 'send_to'): for x in ('format_map', 'send_template', 'send_to'):
del self.prefs[x] del self.prefs[x]
def open(self, devices, library_uuid): def open(self, device, library_uuid):
self.current_library_uuid = library_uuid self.current_library_uuid = library_uuid
self.location_paths = None self.location_paths = None
self.driveinfo = {} self.driveinfo = {}
BASE.open(self, devices, library_uuid) BASE.open(self, device, library_uuid)
h = self.prefs['history'] h = self.prefs['history']
if self.current_serial_num: if self.current_serial_num:
h[self.current_serial_num] = (self.current_friendly_name, h[self.current_serial_num] = (self.current_friendly_name,
isoformat(utcnow())) isoformat(utcnow()))
self.prefs['history'] = h self.prefs['history'] = h
self.current_device_defaults = self.device_defaults(device, self)
# Device information {{{ # Device information {{{
def _update_drive_info(self, storage, location_code, name=None): def _update_drive_info(self, storage, location_code, name=None):
import uuid import uuid
@ -264,7 +270,11 @@ class MTP_DEVICE(BASE):
continue continue
base = os.path.join(tdir, '%s'%f.object_id) base = os.path.join(tdir, '%s'%f.object_id)
os.mkdir(base) 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: try:
self.get_mtp_file(f, out) self.get_mtp_file(f, out)
except Exception as e: except Exception as e:
@ -434,8 +444,13 @@ class MTP_DEVICE(BASE):
# Settings {{{ # Settings {{{
def get_pref(self, key): def get_pref(self, key):
return self.prefs.get('device-%s'%self.current_serial_num, {}).get(key, ''' Get the setting named key. First looks for a device specific setting.
self.prefs[key]) 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): def config_widget(self):
from calibre.gui2.device_drivers.mtp_config import MTPConfig from calibre.gui2.device_drivers.mtp_config import MTPConfig
@ -452,7 +467,7 @@ class MTP_DEVICE(BASE):
@property @property
def save_template(self): def save_template(self):
return self.prefs['send_template'] return self.get_pref('send_template')
# }}} # }}}

View File

@ -18,7 +18,7 @@ from calibre.utils.date import local_tz, as_utc
from calibre.utils.icu import sort_key, lower from calibre.utils.icu import sort_key, lower
from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks import BOOK_EXTENSIONS
bexts = frozenset(BOOK_EXTENSIONS) bexts = frozenset(BOOK_EXTENSIONS) - {'mbp', 'tan', 'rar', 'zip', 'xml'}
class FileOrFolder(object): class FileOrFolder(object):

View File

@ -137,6 +137,10 @@ class MTP_DEVICE(MTPDeviceBase):
self.currently_connected_dev = None self.currently_connected_dev = None
self.current_serial_num = None self.current_serial_num = None
@property
def is_mtp_device_connected(self):
return self.currently_connected_dev is not None
@synchronous @synchronous
def startup(self): def startup(self):
p = plugins['libmtp'] p = plugins['libmtp']
@ -189,6 +193,7 @@ class MTP_DEVICE(MTPDeviceBase):
if not self.current_friendly_name: if not self.current_friendly_name:
self.current_friendly_name = self.dev.model_name or _('Unknown MTP device') self.current_friendly_name = self.dev.model_name or _('Unknown MTP device')
self.current_serial_num = snum self.current_serial_num = snum
self.currently_connected_dev = connected_device
@property @property
def filesystem_cache(self): def filesystem_cache(self):
@ -297,14 +302,16 @@ class MTP_DEVICE(MTPDeviceBase):
def get_mtp_file(self, f, stream=None, callback=None): def get_mtp_file(self, f, stream=None, callback=None):
if f.is_folder: if f.is_folder:
raise ValueError('%s if a folder'%(f.full_path,)) raise ValueError('%s if a folder'%(f.full_path,))
set_name = stream is None
if stream is None: if stream is None:
stream = SpooledTemporaryFile(5*1024*1024, '_wpd_receive_file.dat') stream = SpooledTemporaryFile(5*1024*1024, '_wpd_receive_file.dat')
stream.name = f.name
ok, errs = self.dev.get_file(f.object_id, stream, callback) ok, errs = self.dev.get_file(f.object_id, stream, callback)
if not ok: if not ok:
raise DeviceError('Failed to get file: %s with errors: %s'%( raise DeviceError('Failed to get file: %s with errors: %s'%(
f.full_path, self.format_errorstack(errs))) f.full_path, self.format_errorstack(errs)))
stream.seek(0) stream.seek(0)
if set_name:
stream.name = f.name
return stream return stream
@synchronous @synchronous

View File

@ -246,6 +246,10 @@ class MTP_DEVICE(MTPDeviceBase):
self.dev = self._filesystem_cache = None self.dev = self._filesystem_cache = None
self.current_serial_num = 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): def eject(self):
if self.currently_connected_pnp_id is None: return if self.currently_connected_pnp_id is None: return
self.eject_dev_on_next_scan = True 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): def get_mtp_file(self, f, stream=None, callback=None):
if f.is_folder: if f.is_folder:
raise ValueError('%s if a folder'%(f.full_path,)) raise ValueError('%s if a folder'%(f.full_path,))
set_name = stream is None
if stream is None: if stream is None:
stream = SpooledTemporaryFile(5*1024*1024, '_wpd_receive_file.dat') stream = SpooledTemporaryFile(5*1024*1024, '_wpd_receive_file.dat')
stream.name = f.name
try: try:
try: try:
self.dev.get_file(f.object_id, stream, callback) 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) self.dev.get_file(f.object_id, stream, callback)
except Exception as e: except Exception as e:
raise DeviceError('Failed to fetch the file %s with error: %s'% 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) stream.seek(0)
if set_name:
stream.name = f.name
return stream return stream
@same_thread @same_thread

View File

@ -96,7 +96,7 @@ class SVGRasterizer(object):
def dataize_manifest(self): def dataize_manifest(self):
for item in self.oeb.manifest.values(): 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) self.dataize_svg(item)
def dataize_svg(self, item, svg=None): def dataize_svg(self, item, svg=None):

View File

@ -13,7 +13,7 @@ from PyQt4.Qt import (QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt,
ORG_NAME = 'KovidsBrain' ORG_NAME = 'KovidsBrain'
APP_UID = 'libprs500' APP_UID = 'libprs500'
from calibre.constants import (islinux, iswindows, isbsd, isfrozen, isosx, 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.utils.config import Config, ConfigProxy, dynamic, JSONConfig
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
from calibre.utils.date import UNDEFINED_DATE from calibre.utils.date import UNDEFINED_DATE
@ -567,7 +567,8 @@ class FileDialog(QObject):
modal = True, modal = True,
name = '', name = '',
mode = QFileDialog.ExistingFiles, mode = QFileDialog.ExistingFiles,
default_dir='~' default_dir='~',
no_save_dir=False
): ):
QObject.__init__(self) QObject.__init__(self)
ftext = '' ftext = ''
@ -586,8 +587,11 @@ class FileDialog(QObject):
self.selected_files = None self.selected_files = None
self.fd = None self.fd = None
initial_dir = dynamic.get(self.dialog_name, if no_save_dir:
os.path.expanduser(default_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): if not isinstance(initial_dir, basestring):
initial_dir = os.path.expanduser(default_dir) initial_dir = os.path.expanduser(default_dir)
self.selected_files = [] self.selected_files = []
@ -629,7 +633,8 @@ class FileDialog(QObject):
saved_loc = self.selected_files[0] saved_loc = self.selected_files[0]
if os.path.isfile(saved_loc): if os.path.isfile(saved_loc):
saved_loc = os.path.dirname(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) self.accepted = bool(self.selected_files)
def get_files(self): def get_files(self):
@ -638,10 +643,10 @@ class FileDialog(QObject):
return tuple(self.selected_files) 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, fd = FileDialog(title=title, filters=[], add_all_files_filter=False,
parent=window, name=name, mode=QFileDialog.Directory, 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() dir = fd.get_files()
fd.setParent(None) fd.setParent(None)
if dir: if dir:
@ -759,6 +764,9 @@ class Application(QApplication):
if override_program_name: if override_program_name:
args = [override_program_name] + args[1:] args = [override_program_name] + args[1:]
qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args] 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) QApplication.__init__(self, qargs)
global gui_thread, qt_app global gui_thread, qt_app
gui_thread = QThread.currentThread() gui_thread = QThread.currentThread()
@ -769,15 +777,23 @@ class Application(QApplication):
self._file_open_lock = RLock() self._file_open_lock = RLock()
self.setup_styles(force_calibre_style) 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): def load_calibre_style(self):
# On OS X QtCurve resets the palette, so we preserve it explicitly # On OS X QtCurve resets the palette, so we preserve it explicitly
orig_pal = QPalette(self.palette()) orig_pal = QPalette(self.palette())
from calibre.constants import plugins
pi = plugins['progress_indicator'][0]
path = os.path.join(sys.extensions_location, 'calibre_style.'+( path = os.path.join(sys.extensions_location, 'calibre_style.'+(
'pyd' if iswindows else 'so')) '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 # 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): for role in (orig_pal.Button, orig_pal.Window):
c = orig_pal.brush(role).color() c = orig_pal.brush(role).color()

View File

@ -5,7 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os import os, posixpath
from functools import partial from functools import partial
from PyQt4.Qt import (QMenu, Qt, QInputDialog, QToolButton, QDialog, from PyQt4.Qt import (QMenu, Qt, QInputDialog, QToolButton, QDialog,
@ -13,7 +13,8 @@ from PyQt4.Qt import (QMenu, Qt, QInputDialog, QToolButton, QDialog,
QCoreApplication, pyqtSignal) QCoreApplication, pyqtSignal)
from calibre import isbytestring, sanitize_file_name_unicode 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.utils.config import prefs
from calibre.gui2 import (gprefs, warning_dialog, Dispatcher, error_dialog, from calibre.gui2 import (gprefs, warning_dialog, Dispatcher, error_dialog,
question_dialog, info_dialog, open_local_file, choose_dir) question_dialog, info_dialog, open_local_file, choose_dir)
@ -25,6 +26,17 @@ class LibraryUsageStats(object): # {{{
def __init__(self): def __init__(self):
self.stats = {} self.stats = {}
self.read_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): def read_stats(self):
stats = gprefs.get('library_usage_stats', {}) stats = gprefs.get('library_usage_stats', {})
@ -417,6 +429,18 @@ class ChooseLibraryAction(InterfaceAction):
finally: finally:
self.gui.status_bar.clear_message() 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): def switch_requested(self, location):
if not self.change_library_allowed(): if not self.change_library_allowed():
return return
@ -425,6 +449,12 @@ class ChooseLibraryAction(InterfaceAction):
self.view_state_map[current_lib] = self.preserve_state_on_switch.state self.view_state_map[current_lib] = self.preserve_state_on_switch.state
loc = location.replace('/', os.sep) loc = location.replace('/', os.sep)
exists = db.exists_at(loc) 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: if not exists:
d = MovedDialog(self.stats, location, self.gui) d = MovedDialog(self.stats, location, self.gui)
ret = d.exec_() ret = d.exec_()

View File

@ -163,7 +163,7 @@ class IgnoredDevices(QWidget): # {{{
self.l = l = QVBoxLayout() self.l = l = QVBoxLayout()
self.setLayout(l) self.setLayout(l)
self.la = la = QLabel('<p>'+_( self.la = la = QLabel('<p>'+_(
'''Select the devices to be <b>ignored</b>. calibre will not '''Select the devices to be <b>ignored</b>. calibre <b>will not</b>
connect to devices with a checkmark next to their names.''')) connect to devices with a checkmark next to their names.'''))
la.setWordWrap(True) la.setWordWrap(True)
l.addWidget(la) l.addWidget(la)
@ -386,7 +386,7 @@ class MTPConfig(QTabWidget):
self.device.prefs['blacklist']) self.device.prefs['blacklist'])
self.addTab(self.igntab, _('Ignored devices')) self.addTab(self.igntab, _('Ignored devices'))
self.setCurrentIndex(0) self.setCurrentIndex(1 if msg else 0)
def ignore_device(self): def ignore_device(self):
self.igntab.ignore_device(self.device.current_serial_num) 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, {}) p = self.device.prefs.get(self.current_device_key, {})
if not p: if not p:
self.device.prefs[self.current_device_key] = p self.device.prefs[self.current_device_key] = p
return p.get(key, self.device.prefs[key]) return self.device.get_pref(key)
@property @property
def device(self): def device(self):

View File

@ -11,8 +11,9 @@ from PyQt4.Qt import QDialog
from calibre.gui2.dialogs.choose_library_ui import Ui_Dialog from calibre.gui2.dialogs.choose_library_ui import Ui_Dialog
from calibre.gui2 import error_dialog, choose_dir from calibre.gui2 import error_dialog, choose_dir
from calibre.constants import filesystem_encoding, iswindows from calibre.constants import (filesystem_encoding, iswindows,
from calibre import isbytestring, patheq get_portable_base)
from calibre import isbytestring, patheq, force_unicode
from calibre.gui2.wizard import move_library from calibre.gui2.wizard import move_library
from calibre.library.database2 import LibraryDatabase2 from calibre.library.database2 import LibraryDatabase2
@ -39,18 +40,45 @@ class ChooseLibrary(QDialog, Ui_Dialog):
self.copy_structure.setEnabled(to_what) self.copy_structure.setEnabled(to_what)
def choose_loc(self, *args): def choose_loc(self, *args):
loc = choose_dir(self, 'choose library location', base = get_portable_base()
_('Choose location for calibre library')) 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: if loc is not None:
self.location.setText(loc) self.location.setText(loc)
def check_action(self, ac, loc): def check_action(self, ac, loc):
exists = self.db.exists_at(loc) exists = self.db.exists_at(loc)
base = get_portable_base()
if patheq(loc, self.db.library_path): if patheq(loc, self.db.library_path):
error_dialog(self, _('Same as current'), error_dialog(self, _('Same as current'),
_('The location %s contains the current calibre' _('The location %s contains the current calibre'
' library')%loc, show=True) ' library')%loc, show=True)
return False 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) empty = not os.listdir(loc)
if ac == 'existing' and not exists: if ac == 'existing' and not exists:
error_dialog(self, _('No existing library found'), error_dialog(self, _('No existing library found'),

View File

@ -9,7 +9,7 @@ from PyQt4.Qt import (QCoreApplication, QIcon, QObject, QTimer,
from calibre import prints, plugins, force_unicode from calibre import prints, plugins, force_unicode
from calibre.constants import (iswindows, __appname__, isosx, DEBUG, islinux, 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.utils.ipc import gui_socket_address, RC
from calibre.gui2 import (ORG_NAME, APP_UID, initialize_file_icon_provider, from calibre.gui2 import (ORG_NAME, APP_UID, initialize_file_icon_provider,
Application, choose_dir, error_dialog, question_dialog, gprefs) Application, choose_dir, error_dialog, question_dialog, gprefs)
@ -21,6 +21,9 @@ from calibre.library.sqlite import sqlite, DatabaseException
if iswindows: if iswindows:
winutil = plugins['winutil'][0] winutil = plugins['winutil'][0]
class AbortInit(Exception):
pass
def option_parser(): def option_parser():
parser = _option_parser('''\ parser = _option_parser('''\
%prog [opts] [path_to_ebook] %prog [opts] [path_to_ebook]
@ -46,10 +49,43 @@ path_to_ebook to the database.
'will be silently aborted, so use with care.')) 'will be silently aborted, so use with care.'))
return parser 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): def init_qt(args):
from calibre.gui2.ui import Main from calibre.gui2.ui import Main
parser = option_parser() parser = option_parser()
opts, args = parser.parse_args(args) opts, args = parser.parse_args(args)
find_portable_library()
if opts.with_library is not None: if opts.with_library is not None:
if not os.path.exists(opts.with_library): if not os.path.exists(opts.with_library):
os.makedirs(opts.with_library) os.makedirs(opts.with_library)
@ -360,7 +396,10 @@ def main(args=sys.argv):
gui_debug = args[1] gui_debug = args[1]
args = ['calibre'] 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 calibre.utils.lock import singleinstance
from multiprocessing.connection import Listener from multiprocessing.connection import Listener
si = singleinstance('calibre GUI') si = singleinstance('calibre GUI')

View File

@ -5,6 +5,7 @@
#include <QPluginLoader> #include <QPluginLoader>
#include <QStyle> #include <QStyle>
#include <QApplication> #include <QApplication>
#include <QDebug>
QProgressIndicator::QProgressIndicator(QWidget* parent, int size) QProgressIndicator::QProgressIndicator(QWidget* parent, int size)
: QWidget(parent), : QWidget(parent),
@ -145,3 +146,16 @@ int load_style(QString &path, QString &name) {
} }
return ret; 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;
}

View File

@ -100,3 +100,5 @@ private:
*/ */
int load_style(QString &path, QString &name); int load_style(QString &path, QString &name);
bool do_notify(QObject *receiver, QEvent *event);

View File

@ -8,6 +8,7 @@
%ModuleHeaderCode %ModuleHeaderCode
int load_style(QString &path, QString &name); int load_style(QString &path, QString &name);
bool do_notify(QObject *receiver, QEvent *event);
%End %End
class QProgressIndicator : QWidget { class QProgressIndicator : QWidget {
@ -57,3 +58,5 @@ protected:
int load_style(QString &path, QString &name); int load_style(QString &path, QString &name);
bool do_notify(QObject *receiver, QEvent *event);