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'
__copyright__ = '2008-2011, Darko Miletic <darko.miletic at gmail.com>'
__copyright__ = '2008-2012, Darko Miletic <darko.miletic at gmail.com>'
'''
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

View File

@ -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

View File

@ -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')

View File

@ -8,7 +8,6 @@
#include <windows.h>
#include <Shlwapi.h>
#include <tchar.h>
#include <wchar.h>
#include <stdio.h>
@ -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;
}

View File

@ -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)

View File

@ -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)
# }}}

View File

@ -6,7 +6,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
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)

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
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)

View File

@ -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]

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.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')
# }}}

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.ebooks import BOOK_EXTENSIONS
bexts = frozenset(BOOK_EXTENSIONS)
bexts = frozenset(BOOK_EXTENSIONS) - {'mbp', 'tan', 'rar', 'zip', 'xml'}
class FileOrFolder(object):

View File

@ -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

View File

@ -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

View File

@ -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):

View File

@ -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,6 +587,9 @@ class FileDialog(QObject):
self.selected_files = None
self.fd = None
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):
@ -629,6 +633,7 @@ class FileDialog(QObject):
saved_loc = self.selected_files[0]
if os.path.isfile(saved_loc):
saved_loc = os.path.dirname(saved_loc)
if not no_save_dir:
dynamic[self.dialog_name] = saved_loc
self.accepted = bool(self.selected_files)
@ -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()

View File

@ -5,7 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__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_()

View File

@ -163,7 +163,7 @@ class IgnoredDevices(QWidget): # {{{
self.l = l = QVBoxLayout()
self.setLayout(l)
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.'''))
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):

View File

@ -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):
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'),

View File

@ -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']
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')

View File

@ -5,6 +5,7 @@
#include <QPluginLoader>
#include <QStyle>
#include <QApplication>
#include <QDebug>
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;
}

View File

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

View File

@ -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);