mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
KG updates Qt 4.7
This commit is contained in:
commit
179f8853dc
BIN
resources/images/hotmail.png
Normal file
BIN
resources/images/hotmail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
resources/images/news/moscow_times.png
Normal file
BIN
resources/images/news/moscow_times.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__copyright__ = '2010, Szing'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
@ -10,49 +10,52 @@ globeandmail.com
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class GlobeAndMail(BasicNewsRecipe):
|
||||
title = u'Globe and Mail'
|
||||
language = 'en_CA'
|
||||
|
||||
__author__ = 'Kovid Goyal'
|
||||
class AdvancedUserRecipe1287083651(BasicNewsRecipe):
|
||||
title = u'Globe & Mail'
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = 'Szing'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 10
|
||||
no_stylesheets = True
|
||||
extra_css = '''
|
||||
h3 {font-size: 22pt; font-weight:bold; margin:0px; padding:0px 0px 8pt 0px;}
|
||||
h4 {margin-top: 0px;}
|
||||
#byline { font-family: monospace; font-weight:bold; }
|
||||
#placeline {font-weight:bold;}
|
||||
#credit {margin-top:0px;}
|
||||
.tag {font-size: 22pt;}'''
|
||||
description = 'Canada\'s national newspaper'
|
||||
keep_only_tags = [dict(name='article')]
|
||||
remove_tags = [dict(name='aside'),
|
||||
dict(name='footer'),
|
||||
dict(name='div', attrs={'class':(lambda x: isinstance(x, (str,unicode)) and 'articlecommentcountholder' in x.split(' '))}),
|
||||
dict(name='ul', attrs={'class':(lambda x: isinstance(x, (str,unicode)) and 'articletoolbar' in x.split(' '))}),
|
||||
]
|
||||
max_articles_per_feed = 100
|
||||
encoding = 'utf8'
|
||||
publisher = 'Globe & Mail'
|
||||
language = 'en_CA'
|
||||
extra_css = 'p.meta {font-size:75%}\n .redtext {color: red;}\n .byline {font-size: 70%}'
|
||||
|
||||
feeds = [
|
||||
(u'Latest headlines', u'http://www.theglobeandmail.com/?service=rss'),
|
||||
(u'Top stories', u'http://www.theglobeandmail.com/?service=rss&feed=topstories'),
|
||||
(u'National', u'http://www.theglobeandmail.com/news/national/?service=rss'),
|
||||
(u'Politics', u'http://www.theglobeandmail.com/news/politics/?service=rss'),
|
||||
(u'World', u'http://www.theglobeandmail.com/news/world/?service=rss'),
|
||||
(u'Top National Stories', u'http://www.theglobeandmail.com/news/national/?service=rss'),
|
||||
(u'Business', u'http://www.theglobeandmail.com/report-on-business/?service=rss'),
|
||||
(u'Opinions', u'http://www.theglobeandmail.com/news/opinions/?service=rss'),
|
||||
(u'Columnists', u'http://www.theglobeandmail.com/news/opinions/columnists/?service=rss'),
|
||||
(u'Globe Investor', u'http://www.theglobeandmail.com/globe-investor/?service=rss'),
|
||||
(u'Sports', u'http://www.theglobeandmail.com/sports/?service=rss'),
|
||||
(u'Commentary', u'http://www.theglobeandmail.com/report-on-business/commentary/?service=rss'),
|
||||
(u'Blogs', u'http://www.theglobeandmail.com/blogs/?service=rss'),
|
||||
(u'Facts & Arguments', u'http://www.theglobeandmail.com/life/facts-and-arguments/?service=rss'),
|
||||
(u'Technology', u'http://www.theglobeandmail.com/news/technology/?service=rss'),
|
||||
(u'Investing', u'http://www.theglobeandmail.com/globe-investor/?service=rss'),
|
||||
(u'Top Polical Stories', u'http://www.theglobeandmail.com/news/politics/?service=rss'),
|
||||
(u'Arts', u'http://www.theglobeandmail.com/news/arts/?service=rss'),
|
||||
(u'Life', u'http://www.theglobeandmail.com/life/?service=rss'),
|
||||
(u'Blogs', u'http://www.theglobeandmail.com/blogs/?service=rss'),
|
||||
(u'Real Estate', u'http://www.theglobeandmail.com/real-estate/?service=rss'),
|
||||
(u'Auto', u'http://www.theglobeandmail.com/auto/?service=rss')
|
||||
(u'Auto', u'http://www.theglobeandmail.com/sports/?service=rss'),
|
||||
(u'Sports', u'http://www.theglobeandmail.com/auto/?service=rss')
|
||||
]
|
||||
|
||||
def get_article_url(self, article):
|
||||
url = BasicNewsRecipe.get_article_url(self, article)
|
||||
if '/video/' not in url:
|
||||
return url
|
||||
keep_only_tags = [
|
||||
dict(name='h1'),
|
||||
dict(name='h2', attrs={'id':'articletitle'}),
|
||||
dict(name='p', attrs={'class':['leadText', 'meta', 'leadImage', 'redtext byline', 'bodyText']}),
|
||||
dict(name='div', attrs={'class':['news','articlemeta','articlecopy']}),
|
||||
dict(name='id', attrs={'class':'article'}),
|
||||
dict(name='table', attrs={'class':'todays-market'}),
|
||||
dict(name='header', attrs={'id':'leadheader'})
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'id':['tabInside', 'ShareArticles', 'topStories']})
|
||||
]
|
||||
|
||||
#this has to be here or the text in the article appears twice.
|
||||
remove_tags_after = [dict(id='article')]
|
||||
|
||||
#Use the mobile version rather than the web version
|
||||
def print_version(self, url):
|
||||
return url + '&service=mobile'
|
||||
|
||||
|
@ -1,30 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
|
||||
__copyright__ = '2008-2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
moscowtimes.ru
|
||||
www.themoscowtimes.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Moscowtimes(BasicNewsRecipe):
|
||||
title = u'The Moscow Times'
|
||||
title = 'The Moscow Times'
|
||||
__author__ = 'Darko Miletic and Sujata Raman'
|
||||
description = 'News from Russia'
|
||||
description = 'The Moscow Times is a daily English-language newspaper featuring objective, reliable news on business, politics, sports and culture in Moscow, in Russia and the former Soviet Union (CIS).'
|
||||
category = 'Russia, Moscow, Russian news, Moscow news, Russian newspaper, daily news, independent news, reliable news, USSR, Soviet Union, CIS, Russian politics, Russian business, Russian culture, Russian opinion, St Petersburg, Saint Petersburg'
|
||||
publisher = 'The Moscow Times'
|
||||
language = 'en'
|
||||
lang = 'en'
|
||||
oldest_article = 7
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
#encoding = 'utf-8'
|
||||
encoding = 'cp1252'
|
||||
remove_javascript = True
|
||||
remove_empty_feeds = True
|
||||
encoding = 'cp1251'
|
||||
masthead_url = 'http://www.themoscowtimes.com/bitrix/templates/tmt/img/logo.gif'
|
||||
publication_type = 'newspaper'
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'language' : lang
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
extra_css = '''
|
||||
@ -35,39 +37,37 @@ class Moscowtimes(BasicNewsRecipe):
|
||||
.text{font-family:Arial,Tahoma,Verdana,Helvetica,sans-serif ; font-size:75%; }
|
||||
'''
|
||||
feeds = [
|
||||
(u'The Moscow Times Top Stories' , u'http://www.themoscowtimes.com/rss/top'),
|
||||
(u'The Moscow Times Current Issue' , u'http://www.themoscowtimes.com/rss/issue'),
|
||||
(u'The Moscow Times News' , u'http://www.themoscowtimes.com/rss/news'),
|
||||
(u'The Moscow Times Business' , u'http://www.themoscowtimes.com/rss/business'),
|
||||
(u'The Moscow Times Art and Ideas' , u'http://www.themoscowtimes.com/rss/art'),
|
||||
(u'The Moscow Times Opinion' , u'http://www.themoscowtimes.com/rss/opinion')
|
||||
]
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':['newstextblock']})
|
||||
(u'Top Stories' , u'http://www.themoscowtimes.com/rss/top' )
|
||||
,(u'Current Issue' , u'http://www.themoscowtimes.com/rss/issue' )
|
||||
,(u'News' , u'http://www.themoscowtimes.com/rss/news' )
|
||||
,(u'Business' , u'http://www.themoscowtimes.com/rss/business')
|
||||
,(u'Art and Ideas' , u'http://www.themoscowtimes.com/rss/art' )
|
||||
,(u'Opinion' , u'http://www.themoscowtimes.com/rss/opinion' )
|
||||
]
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'content'})]
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':['photo_nav']})
|
||||
dict(name='div', attrs={'class':['photo_nav','phototext']})
|
||||
,dict(name=['iframe','meta','base','link','embed','object'])
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
soup.html['xml:lang'] = self.lang
|
||||
soup.html['lang'] = self.lang
|
||||
mtag = '<meta http-equiv="Content-Type" content="text/html; charset=' + self.encoding + '">'
|
||||
soup.head.insert(0,mtag)
|
||||
|
||||
return self.adeify_images(soup)
|
||||
for lnk in soup.findAll('a'):
|
||||
if lnk.string is not None:
|
||||
ind = self.tag_to_string(lnk)
|
||||
lnk.replaceWith(ind)
|
||||
return soup
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('.themoscowtimes.com/','.themoscowtimes.com/print/')
|
||||
|
||||
def get_cover_url(self):
|
||||
|
||||
cover_url = None
|
||||
href = 'http://www.themoscowtimes.com/pdf/'
|
||||
|
||||
soup = self.index_to_soup(href)
|
||||
div = soup.find('div',attrs={'class':'left'})
|
||||
if div:
|
||||
a = div.find('a')
|
||||
print a
|
||||
if a :
|
||||
cover_url = a.img['src']
|
||||
cover_url = 'http://www.themoscowtimes.com' + a.img['src']
|
||||
return cover_url
|
||||
|
35
resources/recipes/now_toronto.recipe
Normal file
35
resources/recipes/now_toronto.recipe
Normal file
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#Based on Lars Jacob's Taz Digiabo recipe
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Starson17'
|
||||
|
||||
import os, urllib2, zipfile
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
|
||||
class NowToronto(BasicNewsRecipe):
|
||||
title = u'Now Toronto'
|
||||
description = u'Now Toronto'
|
||||
__author__ = 'Starson17'
|
||||
conversion_options = {
|
||||
'no_default_epub_cover' : True
|
||||
}
|
||||
|
||||
def build_index(self):
|
||||
epub_feed = "http://feeds.feedburner.com/NowEpubEditions"
|
||||
soup = self.index_to_soup(epub_feed)
|
||||
url = soup.find(name = 'feedburner:origlink').string
|
||||
f = urllib2.urlopen(url)
|
||||
tmp = PersistentTemporaryFile(suffix='.epub')
|
||||
self.report_progress(0,_('downloading epub'))
|
||||
tmp.write(f.read())
|
||||
tmp.close()
|
||||
zfile = zipfile.ZipFile(tmp.name, 'r')
|
||||
self.report_progress(0,_('extracting epub'))
|
||||
zfile.extractall(self.output_dir)
|
||||
tmp.close()
|
||||
index = os.path.join(self.output_dir, 'content.opf')
|
||||
self.report_progress(1,_('epub downloaded and extracted'))
|
||||
return index
|
70
resources/recipes/pc_lab.recipe
Normal file
70
resources/recipes/pc_lab.recipe
Normal file
@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
|
||||
class PCLab(BasicNewsRecipe):
|
||||
cover_url = 'http://pclab.pl/img/logo.png'
|
||||
title = u"PC Lab"
|
||||
__author__ = 'ravcio - rlelusz[at]gmail.com'
|
||||
description = u"Articles from PC Lab website"
|
||||
language = 'pl'
|
||||
oldest_article = 30.0
|
||||
max_articles_per_feed = 100
|
||||
recursions = 0
|
||||
encoding = 'iso-8859-2'
|
||||
no_stylesheets = True
|
||||
remove_javascript = True
|
||||
use_embedded_content = False
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':['substance']})
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':['chapters']})
|
||||
,dict(name='div', attrs={'id':['script_bxad_slot_display_list_bxad_slot']})
|
||||
]
|
||||
|
||||
remove_tags_after = [
|
||||
dict(name='div', attrs={'class':['navigation']})
|
||||
]
|
||||
|
||||
#links to RSS feeds
|
||||
feeds = [ ('PCLab', u'http://pclab.pl/xml/artykuly.xml') ]
|
||||
|
||||
#load second and subsequent page content
|
||||
# in: soup - full page with 'next' button
|
||||
# out: appendtag - tag to which new page is to be added
|
||||
def append_page(self, soup, appendtag):
|
||||
# find the 'Next' button
|
||||
pager = soup.find('div', attrs={'class':'next'})
|
||||
|
||||
if pager:
|
||||
#search for 'a' element with link to next page (exit if not found)
|
||||
a = pager.find('a')
|
||||
if a:
|
||||
nexturl = a['href']
|
||||
|
||||
soup2 = self.index_to_soup('http://pclab.pl/' + nexturl)
|
||||
|
||||
pagetext_substance = soup2.find('div', attrs={'class':'substance'})
|
||||
pagetext = pagetext_substance.find('div', attrs={'class':'data'})
|
||||
pagetext.extract()
|
||||
|
||||
pos = len(appendtag.contents)
|
||||
appendtag.insert(pos, pagetext)
|
||||
pos = len(appendtag.contents)
|
||||
|
||||
self.append_page(soup2, appendtag)
|
||||
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
|
||||
# soup.body contains no title and no navigator, they are in soup
|
||||
self.append_page(soup, soup.body)
|
||||
|
||||
# finally remove some tags
|
||||
tags = soup.findAll('div',attrs={'class':['tags', 'index', 'script_bxad_slot_display_list_bxad_slot', 'index first', 'zumi', 'navigation']})
|
||||
[tag.extract() for tag in tags]
|
||||
|
||||
return soup
|
@ -7,7 +7,7 @@ class AdvancedUserRecipe1284927619(BasicNewsRecipe):
|
||||
__author__ = 'noxxx'
|
||||
max_articles_per_feed = 100
|
||||
description = 'tagesanzeiger.ch: Nichts verpassen'
|
||||
category = 'News, Politik, Nachrichten, Schweiz, Zürich'
|
||||
category = 'News, Politik, Nachrichten, Schweiz, Zuerich'
|
||||
language = 'de'
|
||||
|
||||
conversion_options = {
|
||||
|
@ -13,9 +13,9 @@ from PyQt4 import pyqtconfig
|
||||
|
||||
from setup import isosx, iswindows, islinux
|
||||
|
||||
OSX_SDK = '/Developer/SDKs/MacOSX10.4u.sdk'
|
||||
OSX_SDK = '/Developer/SDKs/MacOSX10.5.sdk'
|
||||
|
||||
os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.4'
|
||||
os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.5'
|
||||
|
||||
NMAKE = RC = msvc = MT = win_inc = win_lib = win_ddk = None
|
||||
if iswindows:
|
||||
@ -124,7 +124,7 @@ elif isosx:
|
||||
fc_inc = '/sw/include/fontconfig'
|
||||
fc_lib = '/sw/lib'
|
||||
poppler_inc_dirs = consolidate('POPPLER_INC_DIR',
|
||||
'/sw/build/poppler-0.12.2/poppler:/sw/build/poppler-0.12.2')
|
||||
'/sw/build/poppler-0.14.5/poppler:/sw/build/poppler-0.14.5')
|
||||
popplerqt4_inc_dirs = poppler_inc_dirs + [poppler_inc_dirs[0]+'/qt4']
|
||||
poppler_lib_dirs = consolidate('POPPLER_LIB_DIR',
|
||||
'/sw/lib')
|
||||
|
@ -19,7 +19,7 @@ __all__ = [
|
||||
'upload_user_manual', 'upload_to_mobileread', 'upload_demo',
|
||||
'upload_to_sourceforge', 'upload_to_google_code',
|
||||
'linux32', 'linux64', 'linux', 'linux_freeze', 'linux_freeze2',
|
||||
'osx32_freeze', 'osx32', 'osx', 'rsync', 'push',
|
||||
'osx32_freeze', 'osx', 'rsync', 'push',
|
||||
'win32_freeze', 'win32', 'win',
|
||||
'stage1', 'stage2', 'stage3', 'stage4', 'publish'
|
||||
]
|
||||
@ -84,9 +84,8 @@ linux_freeze = LinuxFreeze()
|
||||
from setup.installer.linux.freeze2 import LinuxFreeze2
|
||||
linux_freeze2 = LinuxFreeze2()
|
||||
|
||||
from setup.installer.osx import OSX, OSX32
|
||||
from setup.installer.osx import OSX
|
||||
osx = OSX()
|
||||
osx32 = OSX32()
|
||||
from setup.installer.osx.app.main import OSX32_Freeze
|
||||
osx32_freeze = OSX32_Freeze()
|
||||
|
||||
|
@ -186,7 +186,7 @@ if isfreebsd:
|
||||
|
||||
|
||||
if isosx:
|
||||
x, p = ('i386', 'ppc')
|
||||
x, p = ('i386', 'x86_64')
|
||||
archs = ['-arch', x, '-arch', p, '-isysroot',
|
||||
OSX_SDK]
|
||||
cflags.append('-D_OSX')
|
||||
@ -339,7 +339,7 @@ class Build(Command):
|
||||
obj_pat = 'release\\*.obj' if iswindows else '*.o'
|
||||
objects = glob.glob(obj_pat)
|
||||
if not objects or self.newer(objects, ext.sources+ext.headers):
|
||||
archs = 'x86 ppc'
|
||||
archs = 'x86 x86_64'
|
||||
pro = textwrap.dedent('''\
|
||||
TARGET = %s
|
||||
TEMPLATE = lib
|
||||
|
@ -7,25 +7,14 @@ __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
from setup import Command
|
||||
from setup.installer import VMInstaller
|
||||
|
||||
class OSX(Command):
|
||||
class OSX(VMInstaller):
|
||||
|
||||
description = 'Build OS X binary installers'
|
||||
|
||||
sub_commands = ['osx32']
|
||||
|
||||
def run(self, opts):
|
||||
pass
|
||||
|
||||
|
||||
class OSX32(VMInstaller):
|
||||
|
||||
description = 'Build 32 bit OS X binary installer'
|
||||
description = 'Build OS X binary installer'
|
||||
|
||||
INSTALLER_EXT = 'dmg'
|
||||
VM_NAME = 'leopard_build'
|
||||
VM_NAME = 'osx_build'
|
||||
VM = '/vmware/bin/%s'%VM_NAME
|
||||
FREEZE_TEMPLATE = 'python -OO setup.py {freeze_command}'
|
||||
FREEZE_COMMAND = 'osx32_freeze'
|
||||
|
@ -48,14 +48,14 @@ def compile_launcher_lib(contents_dir, gcc, base):
|
||||
fd = join(contents_dir, 'Frameworks')
|
||||
dest = join(fd, 'calibre-launcher.dylib')
|
||||
src = join(base, 'util.c')
|
||||
cmd = [gcc] + '-Wall -arch i386 -arch ppc -dynamiclib -std=gnu99'.split() + [src] + \
|
||||
cmd = [gcc] + '-Wall -arch i386 -arch x86_64 -dynamiclib -std=gnu99'.split() + [src] + \
|
||||
['-I'+base] + \
|
||||
['-I/Library/Frameworks/Python.framework/Versions/Current/Headers'] + \
|
||||
['-I/sw/python/Python.framework/Versions/Current/Headers'] + \
|
||||
'-current_version 1.0 -compatibility_version 1.0'.split() + \
|
||||
'-fvisibility=hidden -o'.split() + [dest] + \
|
||||
['-install_name',
|
||||
'@executable_path/../Frameworks/'+os.path.basename(dest)] + \
|
||||
['-framework', 'Python', '-framework', 'CoreFoundation', '-headerpad_max_install_names']
|
||||
['-F/sw/python', '-framework', 'Python', '-framework', 'CoreFoundation', '-headerpad_max_install_names']
|
||||
info('\t'+' '.join(cmd))
|
||||
sys.stdout.flush()
|
||||
subprocess.check_call(cmd)
|
||||
@ -88,7 +88,7 @@ def compile_launchers(contents_dir, xprograms, pyver):
|
||||
fsrc = '/tmp/%s.c'%program
|
||||
with open(fsrc, 'wb') as f:
|
||||
f.write(psrc)
|
||||
cmd = [gcc, '-Wall', '-arch', 'ppc', '-arch', 'i386',
|
||||
cmd = [gcc, '-Wall', '-arch', 'x86_64', '-arch', 'i386',
|
||||
'-I'+base, fsrc, lib, '-o', out,
|
||||
'-headerpad_max_install_names']
|
||||
info('\t'+' '.join(cmd))
|
||||
@ -108,14 +108,6 @@ def flipwritable(fn, mode=None):
|
||||
os.chmod(fn, stat.S_IWRITE | old_mode)
|
||||
return old_mode
|
||||
|
||||
def thin(path):
|
||||
try:
|
||||
subprocess.check_call(['lipo', path, '-verify_arch', 'ppc64'])
|
||||
info('\tThinning', path)
|
||||
except:
|
||||
return
|
||||
else:
|
||||
subprocess.check_call(['lipo', path, '-thin', 'x86_64', '-output', path])
|
||||
|
||||
STRIPCMD = ['/usr/bin/strip', '-x', '-S', '-']
|
||||
def strip_files(files, argv_max=(256 * 1024)):
|
||||
@ -200,7 +192,6 @@ class Py2App(object):
|
||||
self.copy_site()
|
||||
self.create_exe()
|
||||
if not test_launchers:
|
||||
#self.thin_to_x86_64()
|
||||
self.strip_files()
|
||||
|
||||
ret = self.makedmg(self.build_dir, APPNAME+'-'+VERSION)
|
||||
@ -212,19 +203,6 @@ class Py2App(object):
|
||||
shutil.copytree('resources', os.path.join(self.resources_dir,
|
||||
'resources'))
|
||||
|
||||
@flush
|
||||
def thin_to_x86_64(self):
|
||||
info('\nThinning to x86_64')
|
||||
for y in (self.frameworks_dir, join(self.resources_dir, 'Python')):
|
||||
for x in os.walk(y):
|
||||
for f in x[-1]:
|
||||
f = join(x[0], f)
|
||||
if not os.path.isfile(f): continue
|
||||
for t in ('.so', '.dylib', '/Python'):
|
||||
if f.endswith(t):
|
||||
thin(f)
|
||||
break
|
||||
|
||||
@flush
|
||||
def strip_files(self):
|
||||
info('\nStripping files...')
|
||||
@ -270,10 +248,10 @@ class Py2App(object):
|
||||
continue
|
||||
for y in (SW+'/lib/', '/usr/local/lib/', SW+'/qt/lib/',
|
||||
'/opt/local/lib/',
|
||||
'/Library/Frameworks/Python.framework/', SW+'/freetype/lib/'):
|
||||
SW+'/python/Python.framework/', SW+'/freetype/lib/'):
|
||||
if x.startswith(y):
|
||||
if y == '/Library/Frameworks/Python.framework/':
|
||||
y = '/Library/Frameworks/'
|
||||
if y == SW+'/python/Python.framework/':
|
||||
y = SW+'/python/'
|
||||
yield x, x[len(y):]
|
||||
break
|
||||
|
||||
@ -299,7 +277,7 @@ class Py2App(object):
|
||||
@flush
|
||||
def add_python_framework(self):
|
||||
info('\nAdding Python framework')
|
||||
src = join('/Library/Frameworks', 'Python.framework')
|
||||
src = join('/sw/python', 'Python.framework')
|
||||
x = join(self.frameworks_dir, 'Python.framework')
|
||||
curr = os.path.realpath(join(src, 'Versions', 'Current'))
|
||||
currd = join(x, 'Versions', basename(curr))
|
||||
@ -314,7 +292,7 @@ class Py2App(object):
|
||||
def add_qt_frameworks(self):
|
||||
info('\nAdding Qt Framework')
|
||||
for f in ('QtCore', 'QtGui', 'QtXml', 'QtNetwork', 'QtSvg', 'QtWebKit',
|
||||
'QtXmlPatterns', 'phonon'):
|
||||
'QtXmlPatterns'):
|
||||
self.add_qt_framework(f)
|
||||
for d in glob.glob(join(SW, 'qt', 'plugins', '*')):
|
||||
shutil.copytree(d, join(self.contents_dir, 'MacOS', basename(d)))
|
||||
@ -353,8 +331,8 @@ class Py2App(object):
|
||||
shutil.copy2(f, dest)
|
||||
self.fix_dependencies_in_lib(join(dest, basename(f)))
|
||||
if 'podofo' in f:
|
||||
self.change_dep('libpodofo.0.6.99.dylib',
|
||||
self.FID+'/'+'libpodofo.0.6.99.dylib', join(dest, basename(f)))
|
||||
self.change_dep('libpodofo.0.8.4.dylib',
|
||||
self.FID+'/'+'libpodofo.0.8.4.dylib', join(dest, basename(f)))
|
||||
|
||||
|
||||
@flush
|
||||
@ -401,25 +379,27 @@ class Py2App(object):
|
||||
@flush
|
||||
def add_podofo(self):
|
||||
info('\nAdding PoDoFo')
|
||||
pdf = join(SW, 'lib', 'libpodofo.0.8.2.dylib')
|
||||
pdf = join(SW, 'lib', 'libpodofo.0.8.4.dylib')
|
||||
self.install_dylib(pdf)
|
||||
|
||||
@flush
|
||||
def add_poppler(self):
|
||||
info('\nAdding poppler')
|
||||
for x in ('libpoppler.5.dylib', 'libpoppler-qt4.3.dylib'):
|
||||
for x in ('libpoppler.7.dylib',):
|
||||
self.install_dylib(os.path.join(SW, 'lib', x))
|
||||
self.install_dylib(os.path.join(SW, 'bin', 'pdftohtml'), False)
|
||||
|
||||
@flush
|
||||
def add_libjpeg(self):
|
||||
info('\nAdding libjpeg')
|
||||
self.install_dylib(os.path.join(SW, 'lib', 'libjpeg.7.dylib'))
|
||||
self.install_dylib(os.path.join(SW, 'lib', 'libjpeg.8.dylib'))
|
||||
|
||||
@flush
|
||||
def add_libpng(self):
|
||||
info('\nAdding libpng')
|
||||
self.install_dylib(os.path.join(SW, 'lib', 'libpng12.0.dylib'))
|
||||
self.install_dylib(os.path.join(SW, 'lib', 'libpng.3.dylib'))
|
||||
|
||||
|
||||
@flush
|
||||
def add_fontconfig(self):
|
||||
@ -449,7 +429,7 @@ class Py2App(object):
|
||||
def add_imagemagick(self):
|
||||
info('\nAdding ImageMagick')
|
||||
for x in ('Wand', 'Core'):
|
||||
self.install_dylib(os.path.join(SW, 'lib', 'libMagick%s.2.dylib'%x))
|
||||
self.install_dylib(os.path.join(SW, 'lib', 'libMagick%s.4.dylib'%x))
|
||||
idir = glob.glob(os.path.join(SW, 'lib', 'ImageMagick-*'))[-1]
|
||||
dest = os.path.join(self.frameworks_dir, 'ImageMagick')
|
||||
if os.path.exists(dest):
|
||||
@ -463,7 +443,8 @@ class Py2App(object):
|
||||
|
||||
@flush
|
||||
def add_misc_libraries(self):
|
||||
for x in ('usb', 'unrar', 'readline.6.0', 'wmflite-0.2.7', 'chm.0'):
|
||||
for x in ('usb', 'unrar', 'readline.6.1', 'wmflite-0.2.7', 'chm.0',
|
||||
'sqlite3.0'):
|
||||
info('\nAdding', x)
|
||||
x = 'lib%s.dylib'%x
|
||||
shutil.copy2(join(SW, 'lib', x), self.frameworks_dir)
|
||||
@ -551,7 +532,7 @@ class Py2App(object):
|
||||
@flush
|
||||
def add_stdlib(self):
|
||||
info('\nAdding python stdlib')
|
||||
src = '/Library/Frameworks/Python.framework/Versions/Current/lib/python'
|
||||
src = '/sw/python/Python.framework/Versions/Current/lib/python'
|
||||
src += self.version_info
|
||||
dest = join(self.resources_dir, 'Python', 'lib', 'python')
|
||||
dest += self.version_info
|
||||
|
@ -28,7 +28,7 @@ If there are no windows binaries already compiled for the version of python you
|
||||
|
||||
Run the following command to install python dependencies::
|
||||
|
||||
easy_install --always-unzip -U ipython mechanize pyreadline python-dateutil dnspython
|
||||
easy_install --always-unzip -U ipython mechanize pyreadline python-dateutil dnspython cssutils clientform
|
||||
|
||||
Install BeautifulSoup 3.0.x manually into site-packages (3.1.x parses broken HTML very poorly)
|
||||
|
||||
@ -37,7 +37,7 @@ Qt
|
||||
|
||||
Extract Qt sourcecode to C:\Qt\4.x.x. Run configure and make::
|
||||
|
||||
configure -opensource -release -qt-zlib -qt-gif -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license && nmake
|
||||
configure -opensource -release -qt-zlib -qt-gif -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs && nmake
|
||||
|
||||
SIP
|
||||
-----
|
||||
|
@ -503,7 +503,11 @@ class KOBO(USBMS):
|
||||
ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(book.path)
|
||||
|
||||
ContentID = self.contentid_from_path(book.path, ContentType)
|
||||
datelastread = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime())
|
||||
|
||||
t = (ContentID,)
|
||||
cursor.execute('select DateLastRead from Content where BookID is Null and ContentID = ?', t)
|
||||
result = cursor.fetchone()
|
||||
datelastread = result[0] if result[0] is not None else '1970-01-01T00:00:00'
|
||||
|
||||
t = (datelastread,ContentID,)
|
||||
|
||||
|
@ -199,6 +199,8 @@ class PRS505(USBMS):
|
||||
thumbnail_dir = os.path.join(prefix, *thumbnail_dir.split('/'))
|
||||
|
||||
relpath = os.path.relpath(filepath, prefix)
|
||||
if relpath.startswith('..\\'):
|
||||
relpath = relpath[3:]
|
||||
thumbnail_dir = os.path.join(thumbnail_dir, relpath)
|
||||
if not os.path.exists(thumbnail_dir):
|
||||
os.makedirs(thumbnail_dir)
|
||||
|
@ -53,8 +53,8 @@
|
||||
|
||||
#define NUKE(x) Py_XDECREF(x); x = NULL;
|
||||
|
||||
/* This function only works on 10.5 and later
|
||||
static PyObject* send2trash(PyObject *self, PyObject *args)
|
||||
/* This function only works on 10.5 and later. Pass in a unicode object as path */
|
||||
static PyObject* usbobserver_send2trash(PyObject *self, PyObject *args)
|
||||
{
|
||||
UInt8 *utf8_chars;
|
||||
FSRef fp;
|
||||
@ -73,7 +73,7 @@ static PyObject* send2trash(PyObject *self, PyObject *args)
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
static PyObject*
|
||||
usbobserver_get_iokit_string_property(io_service_t dev, CFStringRef prop) {
|
||||
@ -323,6 +323,9 @@ static PyMethodDef usbobserver_methods[] = {
|
||||
{"get_mounted_filesystems", usbobserver_get_mounted_filesystems, METH_VARARGS,
|
||||
"Get mapping of mounted filesystems. Mapping is from BSD name to mount point."
|
||||
},
|
||||
{"send2trash", usbobserver_send2trash, METH_VARARGS,
|
||||
"send2trash(unicode object) -> Send specified file/dir to trash"
|
||||
},
|
||||
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
@ -363,11 +363,15 @@ class MobiMLizer(object):
|
||||
if value == getattr(self.profile, prop):
|
||||
result = '100%'
|
||||
else:
|
||||
# Amazon's renderer does not support
|
||||
# img sizes in units other than px
|
||||
# See #7520 for test case
|
||||
try:
|
||||
ems = int(round(float(value) / self.profile.fbase))
|
||||
pixs = int(round(float(value) / \
|
||||
(72./self.profile.dpi)))
|
||||
except:
|
||||
continue
|
||||
result = "%dem" % ems
|
||||
result = "%d"%pixs
|
||||
istate.attrib[prop] = result
|
||||
elif tag == 'hr' and asfloat(style['width']) > 0:
|
||||
prop = style['width'] / self.profile.width
|
||||
|
@ -620,6 +620,7 @@ static string get_link_dest(LinkAction *link, PDFDoc *doc) {
|
||||
case actionSound: break;
|
||||
case actionJavaScript: break;
|
||||
case actionUnknown: break;
|
||||
default: break;
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
@ -223,7 +223,6 @@ class MessageBox(QMessageBox):
|
||||
if default_button is not None:
|
||||
self.setDefaultButton(default_button)
|
||||
|
||||
|
||||
def copy_to_clipboard(self):
|
||||
QApplication.clipboard().setText('%s: %s\n\n%s' %
|
||||
(self.title, self.msg, self.det_msg))
|
||||
|
@ -212,9 +212,9 @@ class BookInfo(QWebView):
|
||||
def _show_data(self, rows, comments):
|
||||
f = QFontInfo(QApplication.font(self.parent())).pixelSize()
|
||||
p = unicode(QApplication.palette().color(QPalette.Normal,
|
||||
QPalette.Base).name())
|
||||
QPalette.Window).name())
|
||||
c = unicode(QApplication.palette().color(QPalette.Normal,
|
||||
QPalette.Text).name())
|
||||
QPalette.WindowText).name())
|
||||
templ = u'''\
|
||||
<html>
|
||||
<head>
|
||||
|
@ -14,7 +14,7 @@ from PyQt4.QtGui import QDialog, QItemSelectionModel
|
||||
from calibre.gui2.dialogs.fetch_metadata_ui import Ui_FetchMetadata
|
||||
from calibre.gui2 import error_dialog, NONE, info_dialog, config
|
||||
from calibre.gui2.widgets import ProgressIndicator
|
||||
from calibre import strftime
|
||||
from calibre import strftime, force_unicode
|
||||
from calibre.customize.ui import get_isbndb_key, set_isbndb_key
|
||||
|
||||
_hung_fetchers = set([])
|
||||
@ -179,7 +179,7 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
|
||||
self.terminate()
|
||||
return self.queue_reject.emit()
|
||||
self.model = Matches(self.fetcher.results)
|
||||
warnings = [(x[0], unicode(x[1])) for x in \
|
||||
warnings = [(x[0], force_unicode(x[1])) for x in \
|
||||
self.fetcher.exceptions if x[1] is not None]
|
||||
if warnings:
|
||||
warnings='<br>'.join(['<b>%s</b>: %s'%(name, exc) for name,exc in warnings])
|
||||
|
@ -98,7 +98,7 @@ class MyBlockingBusy(QDialog):
|
||||
return self.accept()
|
||||
|
||||
def do_one(self, id):
|
||||
remove, add, au, aus, do_aus, rating, pub, do_series, \
|
||||
remove_all, remove, add, au, aus, do_aus, rating, pub, do_series, \
|
||||
do_autonumber, do_remove_format, remove_format, do_swap_ta, \
|
||||
do_remove_conv, do_auto_author, series, do_series_restart, \
|
||||
series_start_value, do_title_case, clear_series = self.args
|
||||
@ -168,6 +168,8 @@ class MyBlockingBusy(QDialog):
|
||||
# both of these are fast enough to just do them all
|
||||
for w in self.cc_widgets:
|
||||
w.commit(self.ids)
|
||||
if remove_all:
|
||||
self.db.remove_all_tags(self.ids)
|
||||
self.db.bulk_modify_tags(self.ids, add=add, remove=remove,
|
||||
notify=False)
|
||||
self.current_index = len(self.ids)
|
||||
@ -640,9 +642,9 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
||||
for w in getattr(self, 'custom_column_widgets', []):
|
||||
w.gui_val
|
||||
|
||||
if self.remove_all_tags.isChecked():
|
||||
remove = self.db.all_tags()
|
||||
else:
|
||||
remove_all = self.remove_all_tags.isChecked()
|
||||
remove = []
|
||||
if not remove_all:
|
||||
remove = unicode(self.remove_tags.text()).strip().split(',')
|
||||
add = unicode(self.tags.text()).strip().split(',')
|
||||
au = unicode(self.authors.text())
|
||||
@ -663,7 +665,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
|
||||
do_auto_author = self.auto_author_sort.isChecked()
|
||||
do_title_case = self.change_title_to_title_case.isChecked()
|
||||
|
||||
args = (remove, add, au, aus, do_aus, rating, pub, do_series,
|
||||
args = (remove_all, remove, add, au, aus, do_aus, rating, pub, do_series,
|
||||
do_autonumber, do_remove_format, remove_format, do_swap_ta,
|
||||
do_remove_conv, do_auto_author, series, do_series_restart,
|
||||
series_start_value, do_title_case, clear_series)
|
||||
|
@ -308,6 +308,9 @@ from the value in the box</string>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>990000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1</number>
|
||||
</property>
|
||||
@ -660,8 +663,8 @@ nothing should be put between the original text and the inserted text</string>
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>726</width>
|
||||
<height>334</height>
|
||||
<width>122</width>
|
||||
<height>38</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="testgrid">
|
||||
|
@ -8,13 +8,15 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
import cStringIO, sys
|
||||
from binascii import hexlify, unhexlify
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import QWidget, pyqtSignal, QDialog, Qt
|
||||
from PyQt4.Qt import QWidget, pyqtSignal, QDialog, Qt, QLabel, \
|
||||
QLineEdit, QDialogButtonBox, QGridLayout, QCheckBox
|
||||
|
||||
from calibre.gui2.wizard.send_email_ui import Ui_Form
|
||||
from calibre.utils.smtp import config as smtp_prefs
|
||||
from calibre.gui2.dialogs.test_email_ui import Ui_Dialog as TE_Dialog
|
||||
from calibre.gui2 import error_dialog, info_dialog
|
||||
from calibre.gui2 import error_dialog
|
||||
|
||||
class TestEmail(QDialog, TE_Dialog):
|
||||
|
||||
@ -74,8 +76,9 @@ class SendEmail(QWidget, Ui_Form):
|
||||
(self.relay_tls if opts.encryption == 'TLS' else self.relay_ssl).setChecked(True)
|
||||
self.relay_tls.toggled.connect(self.changed)
|
||||
|
||||
self.relay_use_gmail.clicked.connect(
|
||||
self.create_gmail_relay)
|
||||
for x in ('gmail', 'hotmail'):
|
||||
button = getattr(self, 'relay_use_'+x)
|
||||
button.clicked.connect(partial(self.create_service_relay, x))
|
||||
self.relay_show_password.stateChanged.connect(
|
||||
lambda state : self.relay_password.setEchoMode(
|
||||
self.relay_password.Password if
|
||||
@ -114,19 +117,79 @@ class SendEmail(QWidget, Ui_Form):
|
||||
sys.stdout, sys.stderr = oout, oerr
|
||||
return tb
|
||||
|
||||
def create_gmail_relay(self, *args):
|
||||
self.relay_username.setText('@gmail.com')
|
||||
self.relay_password.setText('')
|
||||
self.relay_host.setText('smtp.gmail.com')
|
||||
self.relay_port.setValue(587)
|
||||
def create_service_relay(self, service, *args):
|
||||
service = {
|
||||
'gmail': {
|
||||
'name': 'Gmail',
|
||||
'relay': 'smtp.gmail.com',
|
||||
'port': 587,
|
||||
'username': '@gmail.com',
|
||||
'url': 'www.gmail.com',
|
||||
'extra': ''
|
||||
},
|
||||
'hotmail': {
|
||||
'name': 'Hotmail',
|
||||
'relay': 'smtp.live.com',
|
||||
'port': 587,
|
||||
'username': '',
|
||||
'url': 'www.hotmail.com',
|
||||
'extra': _('If you are setting up a new'
|
||||
' hotmail account, you must log in to it '
|
||||
' once before you will be able to send mails.'),
|
||||
}
|
||||
}[service]
|
||||
d = QDialog(self)
|
||||
l = QGridLayout()
|
||||
d.setLayout(l)
|
||||
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
|
||||
bb.accepted.connect(d.accept)
|
||||
bb.rejected.connect(d.reject)
|
||||
d.tl = QLabel('<p>'+_('You can sign up for a free {name} email '
|
||||
'account at <a href="http://{url}">http://{url}</a>. {extra}').format(
|
||||
**service))
|
||||
l.addWidget(d.tl, 0, 0, 3, 0)
|
||||
d.tl.setWordWrap(True)
|
||||
d.tl.setOpenExternalLinks(True)
|
||||
for name, label in (
|
||||
['from_', _('Your %s &email address:')],
|
||||
['username', _('Your %s &username:')],
|
||||
['password', _('Your %s &password:')],
|
||||
):
|
||||
la = QLabel(label%service['name'])
|
||||
le = QLineEdit(d)
|
||||
setattr(d, name, le)
|
||||
setattr(d, name+'_label', la)
|
||||
r = l.rowCount()
|
||||
l.addWidget(la, r, 0)
|
||||
l.addWidget(le, r, 1)
|
||||
la.setBuddy(le)
|
||||
if name == 'password':
|
||||
d.ptoggle = QCheckBox(_('&Show password'), d)
|
||||
l.addWidget(d.ptoggle, r, 2)
|
||||
d.ptoggle.stateChanged.connect(
|
||||
lambda s: d.password.setEchoMode(d.password.Normal if s
|
||||
== Qt.Checked else d.password.Password))
|
||||
d.username.setText(service['username'])
|
||||
d.password.setEchoMode(d.password.Password)
|
||||
d.bl = QLabel('<p>' + _(
|
||||
'If you plan to use email to send books to your Kindle, remember to'
|
||||
' add the your %s email address to the allowed email addresses in your '
|
||||
'Amazon.com Kindle management page.')%service['name'])
|
||||
d.bl.setWordWrap(True)
|
||||
l.addWidget(d.bl, l.rowCount(), 0, 3, 0)
|
||||
l.addWidget(bb, l.rowCount(), 0, 3, 0)
|
||||
d.setWindowTitle(_('Setup') + ' ' + service['name'])
|
||||
d.resize(d.sizeHint())
|
||||
bb.setVisible(True)
|
||||
if d.exec_() != d.Accepted:
|
||||
return
|
||||
self.relay_username.setText(d.username.text())
|
||||
self.relay_password.setText(d.password.text())
|
||||
self.email_from.setText(d.from_.text())
|
||||
self.relay_host.setText(service['relay'])
|
||||
self.relay_port.setValue(service['port'])
|
||||
self.relay_tls.setChecked(True)
|
||||
|
||||
info_dialog(self, _('Finish gmail setup'),
|
||||
_('Dont forget to enter your gmail username and password. '
|
||||
'You can sign up for a free gmail account at http://gmail.com')).exec_()
|
||||
self.relay_username.setFocus(Qt.OtherFocusReason)
|
||||
self.relay_username.setCursorPosition(0)
|
||||
|
||||
def set_email_settings(self, to_set):
|
||||
from_ = unicode(self.email_from.text()).strip()
|
||||
if to_set and not from_:
|
||||
|
@ -216,6 +216,26 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="relay_use_hotmail">
|
||||
<property name="text">
|
||||
<string>Use Hotmail</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/hotmail.png</normaloff>:/images/hotmail.png</iconset>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>48</width>
|
||||
<height>48</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonTextUnderIcon</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="test_email_button">
|
||||
<property name="text">
|
||||
|
@ -36,33 +36,8 @@ from calibre.utils.config import prefs, tweaks
|
||||
from calibre.utils.search_query_parser import saved_searches, set_saved_searches
|
||||
from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format
|
||||
from calibre.utils.magick.draw import save_cover_data_to
|
||||
from calibre.utils.recycle_bin import delete_file, delete_tree
|
||||
|
||||
if iswindows:
|
||||
import calibre.utils.winshell as winshell
|
||||
|
||||
def delete_file(path):
|
||||
try:
|
||||
winshell.delete_file(path, silent=True, no_confirm=True)
|
||||
except:
|
||||
os.remove(path)
|
||||
|
||||
def delete_tree(path, permanent=False):
|
||||
if permanent:
|
||||
try:
|
||||
# For completely mysterious reasons, sometimes a file is left open
|
||||
# leading to access errors. If we get an exception, wait and hope
|
||||
# that whatever has the file (the O/S?) lets go of it.
|
||||
shutil.rmtree(path)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
time.sleep(1)
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
try:
|
||||
if not permanent:
|
||||
winshell.delete_file(path, silent=True, no_confirm=True)
|
||||
except:
|
||||
delete_tree(path, permanent=True)
|
||||
|
||||
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
|
||||
|
||||
@ -983,9 +958,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
path = None
|
||||
self.data.remove(id)
|
||||
if path and os.path.exists(path):
|
||||
try:
|
||||
winshell.delete_file(path, no_confirm=True, silent=True)
|
||||
except:
|
||||
self.rmtree(path)
|
||||
parent = os.path.dirname(path)
|
||||
if len(os.listdir(parent)) == 0:
|
||||
@ -1759,6 +1731,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
ans.append(tag)
|
||||
return ans
|
||||
|
||||
def remove_all_tags(self, ids, notify=False, commit=True):
|
||||
self.conn.executemany(
|
||||
'DELETE FROM books_tags_link WHERE book=?', [(x,) for x in ids])
|
||||
self.dirtied(ids, commit=False)
|
||||
if commit:
|
||||
self.conn.commit()
|
||||
|
||||
for x in ids:
|
||||
self.data.set(x, self.FIELD_MAP['tags'], '', row_is_id=True)
|
||||
if notify:
|
||||
self.notify('metadata', ids)
|
||||
|
||||
def bulk_modify_tags(self, ids, add=[], remove=[], notify=False):
|
||||
add = self.cleanup_tags(add)
|
||||
remove = self.cleanup_tags(remove)
|
||||
|
59
src/calibre/utils/recycle_bin.py
Normal file
59
src/calibre/utils/recycle_bin.py
Normal file
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, shutil, time
|
||||
from functools import partial
|
||||
|
||||
from calibre import isbytestring
|
||||
from calibre.constants import iswindows, isosx, plugins, filesystem_encoding
|
||||
|
||||
recycle = None
|
||||
|
||||
if iswindows:
|
||||
import calibre.utils.winshell as winshell
|
||||
recycle = partial(winshell.delete_file, silent=True, no_confirm=True)
|
||||
elif isosx:
|
||||
u = plugins['usbobserver'][0]
|
||||
if hasattr(u, 'send2trash'):
|
||||
def recycle(path):
|
||||
if isbytestring(path):
|
||||
path = path.decode(filesystem_encoding)
|
||||
u.send2trash(path)
|
||||
|
||||
|
||||
def delete_file(path):
|
||||
if callable(recycle):
|
||||
try:
|
||||
recycle(path)
|
||||
return
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
os.remove(path)
|
||||
|
||||
def delete_tree(path, permanent=False):
|
||||
if permanent:
|
||||
try:
|
||||
# For completely mysterious reasons, sometimes a file is left open
|
||||
# leading to access errors. If we get an exception, wait and hope
|
||||
# that whatever has the file (the O/S?) lets go of it.
|
||||
shutil.rmtree(path)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
time.sleep(1)
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
if callable(recycle):
|
||||
try:
|
||||
recycle(path)
|
||||
return
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
delete_tree(path, permanent=True)
|
||||
|
@ -101,8 +101,12 @@ def sendmail(msg, from_, to, localhost=None, verbose=0, timeout=30,
|
||||
if encryption == 'SSL':
|
||||
s.sock = s.file.sslobj
|
||||
s.login(username, password)
|
||||
ret = None
|
||||
try:
|
||||
s.sendmail(from_, to, msg)
|
||||
return s.quit()
|
||||
finally:
|
||||
ret = s.quit()
|
||||
return ret
|
||||
|
||||
def option_parser():
|
||||
try:
|
||||
|
Loading…
x
Reference in New Issue
Block a user