From 6bd56c0d5b94e342c919541b96e12f0ceab447d8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 14 Nov 2010 09:50:22 -0700 Subject: [PATCH 01/19] MOBI Output: Specify image sizes in pixels instead of em to accomodate Amazon's @#$%#@! MOBI renderer --- src/calibre/ebooks/mobi/mobiml.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/mobi/mobiml.py b/src/calibre/ebooks/mobi/mobiml.py index 7e4e2e4f57..98e7b6023c 100644 --- a/src/calibre/ebooks/mobi/mobiml.py +++ b/src/calibre/ebooks/mobi/mobiml.py @@ -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 From 66e37da7becb7bd96de246c5189120fc54fb54b2 Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Sun, 14 Nov 2010 21:32:53 -0400 Subject: [PATCH 02/19] Fix I'm Reading list issue - 7192 --- src/calibre/devices/kobo/driver.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 3562da55d2..55ae40420c 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -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,) From e9bebad70d1058214d3d1754abbe490db6baec7f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 15 Nov 2010 09:25:10 -0700 Subject: [PATCH 03/19] ... --- resources/recipes/tagesan.recipe | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/recipes/tagesan.recipe b/resources/recipes/tagesan.recipe index 8514162598..aac064645f 100644 --- a/resources/recipes/tagesan.recipe +++ b/resources/recipes/tagesan.recipe @@ -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 = { From e4d29be176977459dc41e59c4d763d103f8840a0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 15 Nov 2010 11:13:27 -0700 Subject: [PATCH 04/19] Improved Globe and Mail --- resources/recipes/globe_and_mail.recipe | 89 +++++++++++++------------ 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/resources/recipes/globe_and_mail.recipe b/resources/recipes/globe_and_mail.recipe index b6e6b5c25b..4cc76688c1 100644 --- a/resources/recipes/globe_and_mail.recipe +++ b/resources/recipes/globe_and_mail.recipe @@ -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(' '))}), - ] - 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'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'Technology', u'http://www.theglobeandmail.com/news/technology/?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') - ] + 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%}' - def get_article_url(self, article): - url = BasicNewsRecipe.get_article_url(self, article) - if '/video/' not in url: - return url + feeds = [ + (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'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'Real Estate', u'http://www.theglobeandmail.com/real-estate/?service=rss'), + (u'Auto', u'http://www.theglobeandmail.com/sports/?service=rss'), + (u'Sports', u'http://www.theglobeandmail.com/auto/?service=rss') + ] + + 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' From 52bce9f719537f0a4e44c2a706e01d7cc4d0136c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 15 Nov 2010 14:03:10 -0700 Subject: [PATCH 05/19] Add button to easy configure Hotmail as email relay. Also improve usability of easy config buttons --- resources/images/hotmail.png | Bin 0 -> 2637 bytes src/calibre/gui2/__init__.py | 1 - src/calibre/gui2/wizard/send_email.py | 89 +++++++++++++++++++++----- src/calibre/gui2/wizard/send_email.ui | 20 ++++++ 4 files changed, 94 insertions(+), 16 deletions(-) create mode 100644 resources/images/hotmail.png diff --git a/resources/images/hotmail.png b/resources/images/hotmail.png new file mode 100644 index 0000000000000000000000000000000000000000..862b59c93341989da82cbd57825f134823cd3c09 GIT binary patch literal 2637 zcmd5+c|6qX9-cwAX+$b!h8VI8MRZP!bgWa>WT|14Lf8I z;A*;YNQ#1UQA5H4xQK?ETf0a~N{&uDOo1oJDLWVJIsij#ZBcgZCqgiOmOC!T`XDQO zkE6HBbI3M=I$chs;p}q>ZrOfLnb9iAfO>x`E9b~+8fqd#f*QGPF;I2X2D-MOvdJK% zX>F45Mt1@pW;Yt}aO*IFYIV+2jGz(;r~nB&S8p|4ca^BW@`vh-H*(xjYRrBMXaMj4 z1~3JTXOpzS7*;A8yySjXTfMFgKJmv~1mlLFx{xdIOyC8^RPv}j&v z;c$Sk-)pTG55{mht-&n83r#^i0+a(PKt(VGsMx4mwx3<5wppD+LbG#G^jw8aDo_{L z0@!}Ip=TV?Q?{zYg&g6`b>U2mU?M;;8V)Sl6v7|H3O+f4qWmE|Z}=c6Ef`V-T6hDe zcoYnug5h<0Z8ZNLCw%Ml@3vj|Z9$+hZif!9Igrz4&VTJId}GCHIxA?hV86J`ZPpev z7y}bP+kCPKND#Qet##Zmrw#*b;Z)kKJn;Z3KpdQ>=+!bD(87L**^u+V6hyo!ignkG zS>&)vGGhD-xsex18<-4a@`_*#5^^QqVlmGaECY}u5Dq+K=V<-RHUzB|F^?a)-~jX1z!qnfKto2ZQ*QIF1;34}nP z5Q5W5XNcTCLhYY%;6Jdmv^)*qyMf5a$igQ8uU_?TVEn&W{9oAk1^+K#Ox0-v+k{kz zm0LIj0^7cEKtGJf3BXGw0@jt_9PCSoyb$ICF}oP-<)eDsKg=gGI4m$+)x!A~@tVOB z2xQB1YfCd1*Bd``16{NI6yr^ZWc=vJ$2(`-M@LG7$QkYuWTdwErDQx}@bsO2r`ZRZ z0koRBaKb=cuHmw5K^|;&ssoQKP)}+ZI#rt}-L5v^bBlfTh)&cw;RIFKr4lwt=NnH- zD1NlqwvOX9vsuqCeCtfVJ0pY6Id-hMDK<9Nw@(^=uBo7h%2a!7a%5nvRl>gxaln_u zRN6R=8d|DXi{S0?+c$_gn!b@DegZa6R~?` z%k9L}+6B?ww70FWUA^~IcI+S$Dkx^ZW%?O;MCtiV`l#?*hL(qI70eSALT2b9P=c_1 zcDAdpYdK5*-9-4AzL2`4$8y_458LeBD&;}gyOW1N9G{Fa)~60M%SfQ&V{H|1`fzBB z)w_EaE@&;+97Z6zN+}2U=4La+%#4hz@!w1`D~7-Be{icvjHK7wXTW9f%+d6gWZJlL zrhTEs`p&z%(2p}DVHOF4L~}g`_Z`h|@zrp3z@Am8hAAaPwh`}$?RZ^EY}JU*T<{{c zl*1g9-<{}^7k$7T(UwREWUKoS_IDaaM}#(F_4V}j?G!1xwlYv8(`mZ!hfUs4sOKD+ zKb>`2SM$O*+B+)lGJ(h#dTmRYW*XnIEjv9f(bkS-xm2oPoz$y`ZKs!lh7vwU84Z;g z-b(OapU8wr&ePfq0=}2+QdXuYCbJ${p$!Z7PRfo4#baD5Exm`V?2OibB##2VJ3Y74 zMPz~*k~}+K|GbnGR+&{EQ@SJxNB?k4ys5d@=Jm%`c$_Hopx6c+`ZYk7SO>jtNp70`yg?u|+W(VsuJ#|LgXkJG2>T9N~?z+g?Mzv?p z?(|Td3J-R|1?~GmM}3w5imMvUF6ixlrRu0dmA>`Te(KzPlY*u_go!30zaedpq= zATMTCTME+WT#-3?-fL7$@~RXgQyybDG)c+qq4f22+jwgQxydW^;ByK{;_}@udCy{! zx_78HHnv0J(j30qeGtp+eAE|j6s=*B+Uy^3`LbZdvf$E{D^KW^6=_hB*0()d>`njt zY4Qc(FjVIcc?uyr<-YY$P(U@vOqfe;hr8r?GSf50y9sN8OM*AWRgw>e#ZS&? zYk;El(xIaiBc3jDG)i-x* z=Q$8S&dlq%1C`mK!7$ zkr+{xM|tZIuPdsZo|av84ecLvTY7N%a*^i`#Jm}NI+mAtt**n%Yov62?)?+F;HT^X di*3O@DET<{{`xJ4E5IHPvBsXXd}i*M_&1v7h*kgq literal 0 HcmV?d00001 diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 2f39a548ef..f035c40cb4 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -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)) diff --git a/src/calibre/gui2/wizard/send_email.py b/src/calibre/gui2/wizard/send_email.py index 61b9c9f934..da9c21c864 100644 --- a/src/calibre/gui2/wizard/send_email.py +++ b/src/calibre/gui2/wizard/send_email.py @@ -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,75 @@ 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', + }, + 'hotmail': { + 'name': 'Hotmail', + 'relay': 'smtp.live.com', + 'port': 587, + 'username': '', + 'url': 'www.hotmail.com', + } + }[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('

'+_('You can sign up for a free {name} email ' + 'account at http://{url}.').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('

' + _( + '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_: diff --git a/src/calibre/gui2/wizard/send_email.ui b/src/calibre/gui2/wizard/send_email.ui index f248b8df89..ba6b6866e2 100644 --- a/src/calibre/gui2/wizard/send_email.ui +++ b/src/calibre/gui2/wizard/send_email.ui @@ -216,6 +216,26 @@ + + + + Use Hotmail + + + + :/images/hotmail.png:/images/hotmail.png + + + + 48 + 48 + + + + Qt::ToolButtonTextUnderIcon + + + From 1f6f28cffed146cb2e0d1085671e5bcb64ff912b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 15 Nov 2010 15:14:58 -0700 Subject: [PATCH 06/19] Now Toronto by Starson17 --- resources/recipes/now_toronto.recipe | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 resources/recipes/now_toronto.recipe diff --git a/resources/recipes/now_toronto.recipe b/resources/recipes/now_toronto.recipe new file mode 100644 index 0000000000..41741dbccb --- /dev/null +++ b/resources/recipes/now_toronto.recipe @@ -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 From a6ebb4c0401e3c535defe0c6f2b7a5b39b9fada9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 15 Nov 2010 16:13:37 -0700 Subject: [PATCH 07/19] ... --- src/calibre/gui2/book_details.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index e5711bb31a..e193fe10b2 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -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'''\ From 377da4abad29cd40b6ec6cb7943b097eb4fa8f00 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 15 Nov 2010 16:23:07 -0700 Subject: [PATCH 08/19] pclab.pl by ravcio. Fixes #7545 (recipe for http://pclab.pl) --- resources/recipes/pc_lab.recipe | 70 +++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 resources/recipes/pc_lab.recipe diff --git a/resources/recipes/pc_lab.recipe b/resources/recipes/pc_lab.recipe new file mode 100644 index 0000000000..c4b33b8416 --- /dev/null +++ b/resources/recipes/pc_lab.recipe @@ -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 From ca466d77eebc66c123244fe93ce3e803c2d7932c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 15 Nov 2010 16:56:15 -0700 Subject: [PATCH 09/19] Fix #7524 (Remove all option for tags when editing metadata hanging up system) --- src/calibre/gui2/dialogs/metadata_bulk.py | 12 +++++++----- src/calibre/library/database2.py | 12 ++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 8e9fca718e..f8177b7680 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -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) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index efe92d3c63..c4d11beb58 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1759,6 +1759,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) From fee80b5cf220678c8bae05b06e608b19c82fdd33 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 15 Nov 2010 22:09:46 -0700 Subject: [PATCH 10/19] ... --- setup/installer/windows/notes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/installer/windows/notes.rst b/setup/installer/windows/notes.rst index 46aabecfa0..98ec6dac0f 100644 --- a/setup/installer/windows/notes.rst +++ b/setup/installer/windows/notes.rst @@ -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) From a6286dcfdaa7d89e10ae8a07e02f4e018581aa48 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 16 Nov 2010 08:17:43 -0700 Subject: [PATCH 11/19] Fix #7560 (UnicodeDecodeError while Fetching Metadata (0.7.28)) --- src/calibre/gui2/dialogs/fetch_metadata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/dialogs/fetch_metadata.py b/src/calibre/gui2/dialogs/fetch_metadata.py index 6ee9cd9a96..2c64219464 100644 --- a/src/calibre/gui2/dialogs/fetch_metadata.py +++ b/src/calibre/gui2/dialogs/fetch_metadata.py @@ -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='
'.join(['%s: %s'%(name, exc) for name,exc in warnings]) From e5c8638af651b88e70227b0e94e770b7e5746c02 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 16 Nov 2010 08:22:21 -0700 Subject: [PATCH 12/19] Fix #7561 (Updated recipe for The Moscow Times) --- resources/images/news/moscow_times.png | Bin 0 -> 1034 bytes resources/recipes/moscow_times.recipe | 82 ++++++++++++------------- 2 files changed, 41 insertions(+), 41 deletions(-) create mode 100644 resources/images/news/moscow_times.png diff --git a/resources/images/news/moscow_times.png b/resources/images/news/moscow_times.png new file mode 100644 index 0000000000000000000000000000000000000000..34c31179742b7538511477edcae5b07936fb13c1 GIT binary patch literal 1034 zcmV+l1oiugP)y^$D}C=6|^OCAq6Tn2q6`Lc&MNPi>j)GC?MJ_*nk8J#G*wMgoI!L2m~mVsx}Fw zg@#rYh0qY!kMhZD;%#kLSUethmx$%)L72p8xy*AxFMm0CTCj0Rabl^w3y% z_S4I$D+Nh$4F`osyn^+wYOC|_gHbPZG#!@3I)uF(_yiUlHX4}mN25Tn0Yec;m0J3_ zcy0=SJbgV~dpn+ycJJtOu15U)=|sLhJ{aH$O%VhBw+I%2t^?52Ruk&frPLG_yLN|N znVCds|9ZIv*t;~;EgX(TJmYo?ee!{b=Sw$d0Rbl<6LTU+vId2E8?4x+t?EyFH=CKL zG!?^w&ryZ}BmG_Q%EMtWi_+LoQH@j4fbcFh$WrDD+k*n^8}dPq%gQZgrQ`Kh2PYzA zXQ&H?@NP%117ZVis5CnboH%)Ht@=#9-ga(OWpio*Zr*}G7{@oiZ@fIeUK{Q6+P9$2 zrE9Q?&}fDNRWl)5>0Xz5oMV}v^O8~-@Y#Vf;WZTv0X9E;ezfnzp|Mc0-@|=>t0L=W zlRZ%nZ0`}EV}b&(O~-R@L<4-Sg*C_n7_@8u)nsM#?mpq!n`IeXoP`YfxjiP-ODjd# zz_W9I=K&#tIc>Tk$CgED5?Lwu{T#DaZvG%jGQFCUsy$t{*YV=cBM*g+{`5}~&diG@ zFpyOZ68Xkf4lFb=_ei;+t%#fQd(@Nn^qfEy&`>ln!^rDra-gnsP10T<%)UU4H0>8(; z6*8xl*&GaTZM1vw#aRFSL8nbzD>mO5@bkY?$wK3!F3$2rv91h%el`8(+4;QG(Ts#? zn+M~XMXSzS&$y7`Mm)2*cPT4Ilokhpj?cKHg@vjjXxQTEYO6M(A(U>*zHr3@%%2i zqw!`)P?mwRh-u-4<@`1;&#E6hb=M1Mp@c_^EarpR8FGPzA{r*|G((f~+k(OzPV(c# zT-J=Pbl)9rDN~hQf268BK9VAYv?-+C?X-d)CDN}QdkZk`2FKDfIY|*wa}f!zH5Hx1 zbpG<#NO$@0-taNh9w9&)LqUGpW}(;3|6*6z4gU!+0Jq}Ici}N+P5=M^07*qoM6N<$ Ef(+L0i~s-t literal 0 HcmV?d00001 diff --git a/resources/recipes/moscow_times.recipe b/resources/recipes/moscow_times.recipe index 3105aba58e..9d178e8c53 100644 --- a/resources/recipes/moscow_times.recipe +++ b/resources/recipes/moscow_times.recipe @@ -1,31 +1,33 @@ -#!/usr/bin/env python - __license__ = 'GPL v3' -__copyright__ = '2008, Darko Miletic ' +__copyright__ = '2008-2010, Darko Miletic ' ''' -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' - language = 'en' - lang = 'en' - oldest_article = 7 + 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' + 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 - } + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : language + } extra_css = ''' h1{ color:#0066B3; font-family: Georgia,serif ; font-size: large} @@ -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') + (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={'class':['newstextblock']}) - ] - + 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 = '' - 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) + soup = self.index_to_soup(href) div = soup.find('div',attrs={'class':'left'}) - a = div.find('a') - print a - if a : - cover_url = a.img['src'] + if div: + a = div.find('a') + if a : + cover_url = 'http://www.themoscowtimes.com' + a.img['src'] return cover_url From cce8c46a8033f637a963b0e4cb91fec0cffd02ff Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 16 Nov 2010 10:09:09 -0700 Subject: [PATCH 13/19] ... --- src/calibre/ebooks/pdf/reflow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/ebooks/pdf/reflow.cpp b/src/calibre/ebooks/pdf/reflow.cpp index 43efe2c103..c08d7e5507 100644 --- a/src/calibre/ebooks/pdf/reflow.cpp +++ b/src/calibre/ebooks/pdf/reflow.cpp @@ -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(); } From ef5b44c78d1bb50714784a222500651ebd5850c9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 16 Nov 2010 12:04:54 -0700 Subject: [PATCH 14/19] Fix #7564 (Allow bigger numbers in bulk metadata series numbers) --- src/calibre/gui2/dialogs/metadata_bulk.ui | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui index c6830c5d5f..44839bbacd 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.ui +++ b/src/calibre/gui2/dialogs/metadata_bulk.ui @@ -308,6 +308,9 @@ from the value in the box 1 + + 990000 + 1 @@ -660,8 +663,8 @@ nothing should be put between the original text and the inserted text 0 0 - 726 - 334 + 122 + 38
From 740cbd6cb2dcf2e78e61562c74af20dfb29d7601 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 16 Nov 2010 12:40:23 -0700 Subject: [PATCH 15/19] ... --- src/calibre/gui2/wizard/send_email.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/wizard/send_email.py b/src/calibre/gui2/wizard/send_email.py index da9c21c864..c0830ddae4 100644 --- a/src/calibre/gui2/wizard/send_email.py +++ b/src/calibre/gui2/wizard/send_email.py @@ -125,6 +125,7 @@ class SendEmail(QWidget, Ui_Form): 'port': 587, 'username': '@gmail.com', 'url': 'www.gmail.com', + 'extra': '' }, 'hotmail': { 'name': 'Hotmail', @@ -132,6 +133,9 @@ class SendEmail(QWidget, Ui_Form): '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) @@ -141,7 +145,7 @@ class SendEmail(QWidget, Ui_Form): bb.accepted.connect(d.accept) bb.rejected.connect(d.reject) d.tl = QLabel('

'+_('You can sign up for a free {name} email ' - 'account at http://{url}.').format( + 'account at http://{url}. {extra}').format( **service)) l.addWidget(d.tl, 0, 0, 3, 0) d.tl.setWordWrap(True) From 9177bda0bc0f7ce3273cd5f0a89e3914d4055ef8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 16 Nov 2010 13:13:46 -0700 Subject: [PATCH 16/19] ... --- src/calibre/utils/smtp.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/calibre/utils/smtp.py b/src/calibre/utils/smtp.py index b8b46a96cb..8af31b5d38 100644 --- a/src/calibre/utils/smtp.py +++ b/src/calibre/utils/smtp.py @@ -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) - s.sendmail(from_, to, msg) - return s.quit() + ret = None + try: + s.sendmail(from_, to, msg) + finally: + ret = s.quit() + return ret def option_parser(): try: From ab1795de9a54dd71f1d512de1a49060cc895ac1d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 16 Nov 2010 13:15:12 -0700 Subject: [PATCH 17/19] OS X binary build: Switch to intel Leopard Qt 4.7.1 based binary build --- setup/build_environment.py | 6 +- setup/commands.py | 5 +- setup/extensions.py | 4 +- setup/installer/osx/__init__.py | 17 +----- setup/installer/osx/app/main.py | 59 +++++++------------ setup/installer/windows/notes.rst | 2 +- src/calibre/devices/usbobserver/usbobserver.c | 9 ++- 7 files changed, 37 insertions(+), 65 deletions(-) diff --git a/setup/build_environment.py b/setup/build_environment.py index b29ee88cc3..6c4cf04479 100644 --- a/setup/build_environment.py +++ b/setup/build_environment.py @@ -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') diff --git a/setup/commands.py b/setup/commands.py index 26af3d967a..06ab7b36f7 100644 --- a/setup/commands.py +++ b/setup/commands.py @@ -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() diff --git a/setup/extensions.py b/setup/extensions.py index 531107d3cb..f68a35974e 100644 --- a/setup/extensions.py +++ b/setup/extensions.py @@ -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 diff --git a/setup/installer/osx/__init__.py b/setup/installer/osx/__init__.py index f68e984ef1..dfc129eab6 100644 --- a/setup/installer/osx/__init__.py +++ b/setup/installer/osx/__init__.py @@ -7,25 +7,14 @@ __copyright__ = '2009, Kovid Goyal ' __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' diff --git a/setup/installer/osx/app/main.py b/setup/installer/osx/app/main.py index 565b5dd07d..0c46994262 100644 --- a/setup/installer/osx/app/main.py +++ b/setup/installer/osx/app/main.py @@ -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 diff --git a/setup/installer/windows/notes.rst b/setup/installer/windows/notes.rst index 98ec6dac0f..281cd8668e 100644 --- a/setup/installer/windows/notes.rst +++ b/setup/installer/windows/notes.rst @@ -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 ----- diff --git a/src/calibre/devices/usbobserver/usbobserver.c b/src/calibre/devices/usbobserver/usbobserver.c index 4b9b39d473..63923fabf4 100644 --- a/src/calibre/devices/usbobserver/usbobserver.c +++ b/src/calibre/devices/usbobserver/usbobserver.c @@ -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} }; From fe896f0c8e5fdeb4a02820c67d2a8c7bae3544c6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 16 Nov 2010 14:29:05 -0700 Subject: [PATCH 18/19] OS X: When deleting books, put the files into the recycle bin instead of deleting them permanently --- src/calibre/library/database2.py | 32 ++--------------- src/calibre/utils/recycle_bin.py | 59 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 30 deletions(-) create mode 100644 src/calibre/utils/recycle_bin.py diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index c4d11beb58..6d18a2d663 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -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,10 +958,7 @@ 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) + self.rmtree(path) parent = os.path.dirname(path) if len(os.listdir(parent)) == 0: self.rmtree(parent) diff --git a/src/calibre/utils/recycle_bin.py b/src/calibre/utils/recycle_bin.py new file mode 100644 index 0000000000..df6016d796 --- /dev/null +++ b/src/calibre/utils/recycle_bin.py @@ -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 ' +__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) + From cf4ffc4607c45eb1b505b047a76bdf3a63aa31f7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 16 Nov 2010 14:44:48 -0700 Subject: [PATCH 19/19] Fix #7541 (Sony PRS 650 Thumbnails in wrong directory) --- src/calibre/devices/prs505/driver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 3bcf7715a2..44ecd5cfd0 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -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)