From 4978a030d801814a228d302efcc547947491155d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 10 Oct 2009 14:39:10 -0600 Subject: [PATCH 01/11] IGN:Add link to sourceforge download site --- src/calibre/trac/plugins/templates/binary.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/trac/plugins/templates/binary.html b/src/calibre/trac/plugins/templates/binary.html index 7361e37014..03bfc0bbee 100644 --- a/src/calibre/trac/plugins/templates/binary.html +++ b/src/calibre/trac/plugins/templates/binary.html @@ -21,7 +21,8 @@ (Version: $version Changelog)

While you wait for the download to complete, please consider donating to support the development - of ${app}. + of ${app}. If the above link does not work, the files are also available from + sourceforge.
From 470f1aa4c944e6819470d3806518a4b41718fdf4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 11 Oct 2009 13:07:11 -0600 Subject: [PATCH 02/11] Fix very long search strings causing main window to become wider than screen --- src/calibre/gui2/main.py | 6 ++++++ src/calibre/gui2/main.ui | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index b33bf3d619..14ee79d310 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -507,6 +507,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self._calculated_available_height = min(max_available_height()-15, self.height()) self.resize(self.width(), self._calculated_available_height) + self.search.setMaximumWidth(self.width()-150) if config['autolaunch_server']: @@ -523,6 +524,11 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.scheduler.show_dialog) self.location_view.setCurrentIndex(self.location_view.model().index(0)) + def resizeEvent(self, ev): + MainWindow.resizeEvent(self, ev) + self.search.setMaximumWidth(self.width()-150) + + def create_device_menu(self): self._sync_menu = DeviceMenu(self) self.action_sync.setMenu(self._sync_menu) diff --git a/src/calibre/gui2/main.ui b/src/calibre/gui2/main.ui index 0be1df12df..8849c2fa07 100644 --- a/src/calibre/gui2/main.ui +++ b/src/calibre/gui2/main.ui @@ -192,6 +192,12 @@ 0 + + + 700 + 16777215 + + <p>Search the list of books by title, author, publisher, tags, comments, etc.<br><br>Words separated by spaces are ANDed From a8bf1d329ec311c9d938a0a99db650596e3e7379 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 11 Oct 2009 20:26:59 -0600 Subject: [PATCH 03/11] New recipe For Business World India by Darko Miletic --- resources/images/news/businessworldin.png | Bin 0 -> 444 bytes src/calibre/web/feeds/recipes/__init__.py | 2 +- .../feeds/recipes/recipe_businessworldin.py | 77 ++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 resources/images/news/businessworldin.png create mode 100644 src/calibre/web/feeds/recipes/recipe_businessworldin.py diff --git a/resources/images/news/businessworldin.png b/resources/images/news/businessworldin.png new file mode 100644 index 0000000000000000000000000000000000000000..4dd8612fd2543c53b25e9cda89d9f3206839e5cd GIT binary patch literal 444 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*F|v9*VRoTr{Hjv*GOmj*lLu^4g`fA{_;{9CO;c+W}O z35jaE?|NNUQgrI*G2{}O;PKaVk+9d2Esp|iKA$gLGV_Vuymyms^0K`PdUf|s{fj+C z^DKp4yqk0>HuD<8OmmY62BX(WdR~8)oD6N|W}c~i@;49LgRC^K^TKCx9vPgwvUrt2 zSZmh{WzHkQp;y0$GIXBKzGD1gnPZ+*#C-nv*ynvYY%FGC$u7o9w^s)+cr03U-}Z-7 zcbbBDN+R?6j+KH}_olsHZx(H_ZBg!?80|bZ!Q~C^JsE7q$L!R^o&S6{uj{2OC7#SE?>KYp88XATe8CV$^S{WPZ x8kkub80^>55<}6Do1c=IR*9^^(A>(%!phVHqG8(kWpO|a44$rjF6*2UngH;#pDzFa literal 0 HcmV?d00001 diff --git a/src/calibre/web/feeds/recipes/__init__.py b/src/calibre/web/feeds/recipes/__init__.py index b753886fc7..fe8ae80997 100644 --- a/src/calibre/web/feeds/recipes/__init__.py +++ b/src/calibre/web/feeds/recipes/__init__.py @@ -59,7 +59,7 @@ recipe_modules = ['recipe_' + r for r in ( 'intelligencer', 'theoldfoodie', 'hln_be', 'honvedelem', 'the_new_republic', 'philly', 'salon', 'tweakers', 'smashing', 'thestar', 'business_standard', 'lemonde_dip', 'javalobby', - 'serverside', 'infoworld', 'sanjosemercurynews', + 'serverside', 'infoworld', 'sanjosemercurynews', 'businessworldin', )] diff --git a/src/calibre/web/feeds/recipes/recipe_businessworldin.py b/src/calibre/web/feeds/recipes/recipe_businessworldin.py new file mode 100644 index 0000000000..99d56e850f --- /dev/null +++ b/src/calibre/web/feeds/recipes/recipe_businessworldin.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2009, Darko Miletic ' +''' +www.businessworld.in +''' + +from calibre import strftime +from calibre.web.feeds.news import BasicNewsRecipe + +class BusinessWorldMagazine(BasicNewsRecipe): + title = 'Business World Magazine' + __author__ = 'Darko Miletic' + description = 'News from India' + publisher = 'ABP Pvt Ltd Publication' + category = 'news, politics, finances, India, Asia' + delay = 1 + no_stylesheets = True + INDEX = 'http://www.businessworld.in/bw/Magazine_Current_Issue' + ROOT = 'http://www.businessworld.in' + use_embedded_content = False + encoding = 'utf-8' + language = 'en_IN' + + + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : language + } + + def is_in_list(self,linklist,url): + for litem in linklist: + if litem == url: + return True + return False + + + def parse_index(self): + articles = [] + linklist = [] + soup = self.index_to_soup(self.INDEX) + + for item in soup.findAll('div', attrs={'class':'nametitle'}): + description = '' + title_prefix = '' + feed_link = item.find('a') + if feed_link and feed_link.has_key('href'): + url = self.ROOT + feed_link['href'] + if not self.is_in_list(linklist,url): + title = title_prefix + self.tag_to_string(feed_link) + date = strftime(self.timefmt) + articles.append({ + 'title' :title + ,'date' :date + ,'url' :url + ,'description':description + }) + linklist.append(url) + return [(soup.head.title.string, articles)] + + + keep_only_tags = [dict(name='div', attrs={'id':['register-panel','printwrapper']})] + remove_tags = [dict(name=['object','link'])] + + def print_version(self, url): + return url.replace('/bw/','/bw/storyContent/') + + def get_cover_url(self): + cover_url = None + soup = self.index_to_soup(self.INDEX) + cover_item = soup.find('img',attrs={'class':'toughbor'}) + if cover_item: + cover_url = self.ROOT + cover_item['src'] + return cover_url From b20f8ea54c88a14ef4a66cb42450f381953e6c1e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Oct 2009 00:12:27 -0600 Subject: [PATCH 04/11] calibre_mount_helper now works on NetBSD as well for mounting of reader devices --- src/calibre/devices/linux_mount_helper.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/calibre/devices/linux_mount_helper.c b/src/calibre/devices/linux_mount_helper.c index 076587c670..7399277377 100644 --- a/src/calibre/devices/linux_mount_helper.c +++ b/src/calibre/devices/linux_mount_helper.c @@ -35,6 +35,9 @@ void ensure_root() { int do_mount(const char *dev, const char *mp) { char options[1000], marker[2000]; +#ifdef __NetBSD__ + char uids[100], gids[100]; +#endif int errsv; if (!exists(dev)) { @@ -57,9 +60,19 @@ int do_mount(const char *dev, const char *mp) { } close(fd); } +#ifdef __NetBSD__ + snprintf(options, 1000, "rw,noexec,nosuid,sync,nodev"); + snprintf(uids, 100, "%d", getuid()); + snprintf(gids, 100, "%d", getgid()); +#else snprintf(options, 1000, "rw,noexec,nosuid,sync,nodev,quiet,shortname=mixed,uid=%d,gid=%d,umask=077,fmask=0177,dmask=0077,utf8,iocharset=iso8859-1", getuid(), getgid()); +#endif ensure_root(); +#ifdef __NetBSD__ + execlp("mount_msdos", "mount_msdos", "-u", uids, "-g", gids, "-o", options, dev, mp, NULL); +#else execlp("mount", "mount", "-t", "vfat", "-o", options, dev, mp, NULL); +#endif errsv = errno; fprintf(stderr, "Failed to mount with error: %s\n", strerror(errsv)); return EXIT_FAILURE; @@ -76,7 +89,11 @@ int call_eject(const char *dev, const char *mp) { if (pid == 0) { /* Child process */ ensure_root(); +#ifdef __NetBSD__ + execlp("eject", "eject", dev, NULL); +#else execlp("eject", "eject", "-s", dev, NULL); +#endif /* execlp failed */ errsv = errno; fprintf(stderr, "Failed to eject with error: %s\n", strerror(errsv)); From a190faea9c8a84b2854fcd68918e7d5e511191b4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Oct 2009 07:20:09 -0600 Subject: [PATCH 05/11] IGN:Switch primary hosting of calibre download files from Mobileread to Google code --- README | 2 + setup/commands.py | 9 +- setup/publish.py | 5 +- setup/pypi.py | 2 - setup/upload.py | 213 +++++++++++++++++- src/calibre/__init__.py | 12 + src/calibre/debug.py | 10 +- src/calibre/trac/plugins/download.py | 7 +- src/calibre/trac/plugins/templates/linux.html | 2 +- 9 files changed, 240 insertions(+), 22 deletions(-) diff --git a/README b/README index 6697b3f7f9..945592e7ba 100644 --- a/README +++ b/README @@ -15,3 +15,5 @@ bzr branch lp:calibre To update your copy of the source code: bzr merge +Tarballs of the source code for each release are now available \ +at http://code.google.com/p/calibre-ebook diff --git a/setup/commands.py b/setup/commands.py index ba870072d6..0a09efa935 100644 --- a/setup/commands.py +++ b/setup/commands.py @@ -16,7 +16,8 @@ __all__ = [ 'sdist', 'manual', 'tag_release', 'upload_rss', 'pypi_register', 'pypi_upload', 'upload_to_server', - 'upload_user_manual', 'upload_installers', 'upload_demo', + 'upload_user_manual', 'upload_to_mobileread', 'upload_demo', + 'upload_to_sourceforge', 'upload_to_google_code', 'linux32', 'linux64', 'linux', 'linux_freeze', 'osx32_freeze', 'osx32', 'osx', 'rsync', 'win32_freeze', 'win32', 'win', @@ -59,11 +60,13 @@ stage3 = Stage3() publish = Publish() from setup.upload import UploadUserManual, UploadInstallers, UploadDemo, \ - UploadToServer + UploadToServer, UploadToSourceForge, UploadToGoogleCode upload_user_manual = UploadUserManual() -upload_installers = UploadInstallers() +upload_to_mobileread = UploadInstallers() upload_demo = UploadDemo() upload_to_server = UploadToServer() +upload_to_sourceforge = UploadToSourceForge() +upload_to_google_code = UploadToGoogleCode() from setup.installer import Rsync rsync = Rsync() diff --git a/setup/publish.py b/setup/publish.py index cb8f5e8b06..647b26072c 100644 --- a/setup/publish.py +++ b/setup/publish.py @@ -44,8 +44,9 @@ class Stage3(Command): description = 'Stage 3 of the publish process' sub_commands = ['upload_rss', 'upload_user_manual', 'upload_demo', - 'pypi_upload', 'tag_release', 'upload_installers', - 'upload_to_server'] + 'upload_to_sourceforge', 'upload_to_google_code', 'tag_release', + 'upload_to_server', 'upload_to_mobileread', +] class Publish(Command): diff --git a/setup/pypi.py b/setup/pypi.py index 2d83309b76..7c5d1a54d5 100644 --- a/setup/pypi.py +++ b/setup/pypi.py @@ -124,8 +124,6 @@ class PyPIRegister(Command): auth) self.info('Server response (%s): %s' % (code, result)) - - def verify_metadata(self): ''' Send the metadata to the package index server to be checked. ''' diff --git a/setup/upload.py b/setup/upload.py index e0bec83a32..17411e6f04 100644 --- a/setup/upload.py +++ b/setup/upload.py @@ -6,10 +6,11 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, re, cStringIO +import os, re, cStringIO, base64, httplib, subprocess from subprocess import check_call +from tempfile import NamedTemporaryFile -from setup import Command, __version__, installer_name +from setup import Command, __version__, installer_name, __appname__ PREFIX = "/var/www/calibre.kovidgoyal.net" DOWNLOADS = PREFIX+"/htdocs/downloads" @@ -21,6 +22,214 @@ TXT2LRF = "src/calibre/ebooks/lrf/txt/demo" MOBILEREAD = 'ftp://dev.mobileread.com/calibre/' +def installers(): + installers = list(map(installer_name, ('dmg', 'msi', 'tar.bz2'))) + installers.append(installer_name('tar.bz2', is64bit=True)) + installers.insert(0, 'dist/%s-%s.tar.gz'%(__appname__, __version__)) + return installers + +def installer_description(fname): + if fname.endswith('.tar.gz'): + return 'Source code' + if fname.endswith('.tar.bz2'): + bits = '32' if 'i686' in fname else '64' + return bits + 'bit Linux binary' + if fname.endswith('.msi'): + return 'Windows installer' + if fname.endswith('.dmg'): + return 'OS X dmg' + return 'Unknown file' + + +class UploadToGoogleCode(Command): + + USERNAME = 'kovidgoyal' + # Password can be gotten by going to + # http://code.google.com/hosting/settings + # while logged into gmail + PASSWORD_FILE = os.path.expanduser('~/.googlecodecalibre') + OFFLINEIMAP = os.path.expanduser('~/work/kde/conf/offlineimap/rc') + GPATHS = '/var/www/status.calibre-ebook.com/googlepaths' + UPLOAD_HOST = 'calibre-ebook.googlecode.com' + FILES_LIST = 'http://code.google.com/p/calibre-ebook/downloads/list' + + def run(self, opts): + self.opts = opts + self.password = open(self.PASSWORD_FILE).read().strip() + self.paths = {} + self.old_files = self.get_files_hosted_by_google_code() + + for fname in installers(): + self.info('Uploading', fname) + typ = 'Type-Source' if fname.endswith('.gz') else 'Type-Installer' + ext = os.path.splitext(fname)[1][1:] + op = 'OpSys-'+{'msi':'Windows','dmg':'OSX','bz2':'Linux','gz':'All'}[ext] + desc = installer_description(fname) + path = self.upload(os.path.abspath(fname), desc, + labels=[typ, op, 'Featured']) + self.info('\tUploaded to:', path) + self.paths[os.path.basename(fname)] = path + self.info('Updating path map') + self.info(repr(self.paths)) + raw = subprocess.Popen(['ssh', 'divok', 'cat', self.GPATHS], + stdout=subprocess.PIPE).stdout.read() + paths = eval(raw) + paths.update(self.paths) + rem = [x for x in paths if __version__ not in x] + for x in rem: paths.pop(x) + raw = ['%r : %r,'%(k, v) for k, v in paths.items()] + raw = '{\n\n%s\n\n}\n'%('\n'.join(raw)) + t = NamedTemporaryFile() + t.write(raw) + t.flush() + check_call(['scp', t.name, 'divok:'+self.GPATHS]) + self.br = self.login_to_gmail() + self.delete_old_files() + if len(self.get_files_hosted_by_google_code()) > len(installers()): + self.warn('Some old files were not deleted from Google Code') + + def login_to_gmail(self): + import mechanize + self.info('Logging into Gmail') + raw = open(self.OFFLINEIMAP).read() + pw = re.search(r'(?s)remoteuser = .*@gmail.com.*?remotepass = (\S+)', + raw).group(1).strip() + br = mechanize.Browser() + br.open('http://gmail.com') + br.select_form(nr=0) + br.form['Email'] = self.USERNAME + br.form['Passwd'] = pw + res = br.submit() + return br + + def get_files_hosted_by_google_code(self): + import urllib2 + from lxml import html + self.info('Getting existing files in google code') + raw = urllib2.urlopen(self.FILES_LIST).read() + root = html.fromstring(raw) + ans = {} + for a in root.xpath('//td[@class="vt id col_0"]/a[@href]'): + ans[a.text.strip()] = a.get('href') + return ans + + def delete_old_files(self): + self.info('Deleting old files from Google Code...') + for fname in self.old_files: + self.info('\tDeleting', fname) + self.br.open('http://code.google.com/p/calibre-ebook/downloads/delete?name=%s'%fname) + self.br.select_form(predicate=lambda x: 'delete.do' in x.action) + submit = self.br.form.find_control(name='delete') + res = self.br.submit(name='delete') + #from calibre import ipython + #ipython({'br':self.br, 'res':res}) + #return + + + + def encode_upload_request(self, fields, file_path): + BOUNDARY = '----------Googlecode_boundary_reindeer_flotilla' + CRLF = '\r\n' + + body = [] + + # Add the metadata about the upload first + for key, value in fields: + body.extend( + ['--' + BOUNDARY, + 'Content-Disposition: form-data; name="%s"' % key, + '', + value, + ]) + + # Now add the file itself + file_name = os.path.basename(file_path) + f = open(file_path, 'rb') + file_content = f.read() + f.close() + + body.extend( + ['--' + BOUNDARY, + 'Content-Disposition: form-data; name="filename"; filename="%s"' + % file_name, + # The upload server determines the mime-type, no need to set it. + 'Content-Type: application/octet-stream', + '', + file_content, + ]) + + # Finalize the form body + body.extend(['--' + BOUNDARY + '--', '']) + + return 'multipart/form-data; boundary=%s' % BOUNDARY, CRLF.join(body) + + def upload(self, fname, desc, labels=[]): + form_fields = [('summary', desc)] + form_fields.extend([('label', l.strip()) for l in labels]) + + content_type, body = self.encode_upload_request(form_fields, fname) + upload_uri = '/files' + auth_token = base64.b64encode('%s:%s'% (self.USERNAME, self.password)) + headers = { + 'Authorization': 'Basic %s' % auth_token, + 'User-Agent': 'Calibre googlecode.com uploader v0.1.0', + 'Content-Type': content_type, + } + + server = httplib.HTTPSConnection(self.UPLOAD_HOST) + server.request('POST', upload_uri, body, headers) + resp = server.getresponse() + server.close() + + if resp.status == 201: + return resp.getheader('Location') + + print 'Failed to upload with code %d and reason: %s'%(resp.status, + resp.reason) + raise Exception('Failed to upload '+fname) + + + + + +class UploadToSourceForge(Command): + + description = 'Upload release files to sourceforge' + + USERNAME = 'kovidgoyal' + PROJECT = 'calibre' + BASE = '/home/frs/project/c/ca/'+PROJECT + + def create(self): + self.info('Creating shell...') + check_call(['ssh', '-x', + '%s,%s@shell.sourceforge.net'%(self.USERNAME, self.PROJECT), + 'create']) + + @property + def rdir(self): + return self.BASE+'/'+__version__ + + def mk_release_dir(self): + self.info('Creating release directory...') + check_call(['ssh', '-x', + '%s,%s@shell.sourceforge.net'%(self.USERNAME, self.PROJECT), + 'mkdir', '-p', self.rdir]) + + def upload_installers(self): + for x in installers(): + if not os.path.exists(x): continue + self.info('Uploading', x) + check_call(['rsync', '-v', '-e', 'ssh -x', x, + '%s,%s@frs.sourceforge.net:%s'%(self.USERNAME, self.PROJECT, + self.rdir+'/')]) + + def run(self, opts): + self.opts = opts + self.create() + self.mk_release_dir() + self.upload_installers() + class UploadInstallers(Command): description = 'Upload any installers present in dist/' diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index ae5946bcb3..424f40382d 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -419,3 +419,15 @@ if isosx: except: import traceback traceback.print_exc() + +def ipython(user_ns=None): + if user_ns is None: + user_ns = locals() + from calibre.utils.config import config_dir + ipydir = os.path.join(config_dir, ('_' if iswindows else '.')+'ipython') + os.environ['IPYTHONDIR'] = ipydir + from IPython.Shell import IPShellEmbed + ipshell = IPShellEmbed(user_ns=user_ns) + ipshell() + + diff --git a/src/calibre/debug.py b/src/calibre/debug.py index 55e34c7963..f9ee3b0f44 100644 --- a/src/calibre/debug.py +++ b/src/calibre/debug.py @@ -190,14 +190,8 @@ def main(args=sys.argv): elif opts.develop_from is not None: develop_from(opts.develop_from) else: - from calibre.utils.config import config_dir - ipydir = os.path.join(config_dir, ('_' if iswindows else '.')+'ipython') - os.environ['IPYTHONDIR'] = ipydir - from IPython.Shell import IPShellEmbed - ipshell = IPShellEmbed() - ipshell() - - + from calibre import ipython + ipython() return 0 diff --git a/src/calibre/trac/plugins/download.py b/src/calibre/trac/plugins/download.py index 92ac83d779..87a1fce42d 100644 --- a/src/calibre/trac/plugins/download.py +++ b/src/calibre/trac/plugins/download.py @@ -6,7 +6,6 @@ import re, textwrap DEPENDENCIES = [ #(Generic, version, gentoo, ubuntu, fedora) ('python', '2.6', None, None, None), - ('setuptools', '0.6c5', 'setuptools', 'python-setuptools', 'python-setuptools-devel'), ('Python Imaging Library', '1.1.6', 'imaging', 'python-imaging', 'python-imaging'), ('libusb', '0.1.12', None, None, None), ('Qt', '4.5.1', 'qt', 'libqt4-core libqt4-gui', 'qt4'), @@ -23,7 +22,7 @@ DEPENDENCIES = [ ('podofo', '0.7', 'podofo', 'podofo', 'podofo', 'podofo'), ('libwmf', '0.2.8', 'libwmf', 'libwmf', 'libwmf', 'libwmf'), ] - +STATUS = 'http://status.calibre-ebook.com/dist' class CoolDistro: @@ -148,7 +147,7 @@ else: compatibility=('%(a)s works on Windows XP, Vista and 7.' 'If you are upgrading from a version older than 0.6.17, ' 'please uninstall %(a)s first.')%dict(a=__appname__,), - path=MOBILEREAD+file, app=__appname__, + path=STATUS+'/win32', app=__appname__, note=Markup(\ '''

If you are updating from a version of calibre older than 0.6.12 on @@ -189,7 +188,7 @@ else: installer_name='OS X universal dmg', title='Download %s for OS X'%(__appname__), compatibility='%s works on OS X Tiger, Leopard, and Snow Leopard.'%(__appname__,), - path=MOBILEREAD+file, app=__appname__, + path=STATUS+'/osx32', app=__appname__, note=Markup(\ u'''

    diff --git a/src/calibre/trac/plugins/templates/linux.html b/src/calibre/trac/plugins/templates/linux.html index a55105d029..306ee8a01b 100644 --- a/src/calibre/trac/plugins/templates/linux.html +++ b/src/calibre/trac/plugins/templates/linux.html @@ -106,7 +106,7 @@ sudo python -c "import urllib2; exec urllib2.urlopen('http://status.calibre-eboo
-wget -O- http://calibre.kovidgoyal.net/downloads/${app}-${version}.tar.gz | tar xvz 
+wget -O- http://status.calibre-ebook.com/dist/src | tar xvz 
 cd calibre*
 sudo python setup.py install
                                 
From 4865f55e4037317cb179c95023d1e62812cc3350 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Oct 2009 07:33:45 -0600 Subject: [PATCH 06/11] IGN:seriescmp cleanup --- src/calibre/library/database2.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index ef5583fee9..44685aa8aa 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -323,17 +323,16 @@ class ResultCache(SearchQueryParser): def seriescmp(self, x, y): try: - ans = cmp(self._data[x][9].lower(), self._data[y][9].lower()) if str else\ - cmp(self._data[x][9], self._data[y][9]) + ans = cmp(self._data[x][9].lower(), self._data[y][9].lower()) except AttributeError: # Some entries may be None ans = cmp(self._data[x][9], self._data[y][9]) if ans != 0: return ans return cmp(self._data[x][10], self._data[y][10]) - def cmp(self, loc, x, y, str=True, subsort=False): + def cmp(self, loc, x, y, asstr=True, subsort=False): try: - ans = cmp(self._data[x][loc].lower(), self._data[y][loc].lower()) if str else\ - cmp(self._data[x][loc], self._data[y][loc]) + ans = cmp(self._data[x][loc].lower(), self._data[y][loc].lower()) if \ + asstr else cmp(self._data[x][loc], self._data[y][loc]) except AttributeError: # Some entries may be None ans = cmp(self._data[x][loc], self._data[y][loc]) if subsort and ans == 0: @@ -352,7 +351,7 @@ class ResultCache(SearchQueryParser): self.first_sort = False fcmp = self.seriescmp if field == 'series' else \ functools.partial(self.cmp, FIELD_MAP[field], subsort=subsort, - str=field not in ('size', 'rating', 'timestamp')) + asstr=field not in ('size', 'rating', 'timestamp')) self._map.sort(cmp=fcmp, reverse=not ascending) self._map_filtered = [id for id in self._map if id in self._map_filtered] From b3ad9f0160839ecc1115a038608128f594261ee3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Oct 2009 07:35:27 -0600 Subject: [PATCH 07/11] eReader PDB output: proper length of indexes and do not try to add them if they are not avaliable. PML Outpu: cleanup. PML Input: read unicode and entity PML tags correctly. --- src/calibre/ebooks/fb2/fb2ml.py | 1 - src/calibre/ebooks/pdb/ereader/reader132.py | 1 - src/calibre/ebooks/pdb/ereader/writer.py | 184 +++++++++++++++----- src/calibre/ebooks/pml/pmlconverter.py | 15 +- src/calibre/ebooks/pml/pmlml.py | 5 + 5 files changed, 149 insertions(+), 57 deletions(-) diff --git a/src/calibre/ebooks/fb2/fb2ml.py b/src/calibre/ebooks/fb2/fb2ml.py index ff914568d2..aaf8361b99 100644 --- a/src/calibre/ebooks/fb2/fb2ml.py +++ b/src/calibre/ebooks/fb2/fb2ml.py @@ -75,7 +75,6 @@ class FB2MLizer(object): output.append(self.fb2mlize_images()) output.append(self.fb2_footer()) output = ''.join(output).replace(u'ghji87yhjko0Caliblre-toc-placeholder-for-insertion-later8ujko0987yjk', self.get_toc()) - return output return u'\n%s' % etree.tostring(etree.fromstring(output), encoding=unicode, pretty_print=True) def fb2_header(self): diff --git a/src/calibre/ebooks/pdb/ereader/reader132.py b/src/calibre/ebooks/pdb/ereader/reader132.py index 98dbe13790..49fdfb8980 100644 --- a/src/calibre/ebooks/pdb/ereader/reader132.py +++ b/src/calibre/ebooks/pdb/ereader/reader132.py @@ -34,7 +34,6 @@ class HeaderRecord(object): self.has_metadata, = struct.unpack('>H', raw[24:26]) self.footnote_rec, = struct.unpack('>H', raw[28:30]) self.sidebar_rec, = struct.unpack('>H', raw[30:32]) - self.bookmark_offset, = struct.unpack('>H', raw[32:34]) self.image_data_offset, = struct.unpack('>H', raw[40:42]) self.metadata_offset, = struct.unpack('>H', raw[44:46]) self.footnote_offset, = struct.unpack('>H', raw[48:50]) diff --git a/src/calibre/ebooks/pdb/ereader/writer.py b/src/calibre/ebooks/pdb/ereader/writer.py index 2f4e3bf16f..263f6964bf 100644 --- a/src/calibre/ebooks/pdb/ereader/writer.py +++ b/src/calibre/ebooks/pdb/ereader/writer.py @@ -8,6 +8,7 @@ __license__ = 'GPL v3' __copyright__ = '2009, John Schember ' __docformat__ = 'restructuredtext en' +import re import struct import zlib @@ -28,7 +29,7 @@ IDENTITY = 'PNRdPPrs' # This is an arbitrary number that is small enough to work. The actual maximum # record size is unknown. -MAX_RECORD_SIZE = 3560 +MAX_RECORD_SIZE = 8192 class Writer(FormatWriter): @@ -37,13 +38,33 @@ class Writer(FormatWriter): self.log = log def write_content(self, oeb_book, out_stream, metadata=None): - text, image_hrefs = self._text(oeb_book) - images = self._images(oeb_book.manifest, image_hrefs) + pmlmlizer = PMLMLizer(self.log) + pml = unicode(pmlmlizer.extract_content(oeb_book, self.opts)).encode('cp1252', 'replace') + + text, text_sizes = self._text(pml) + chapter_index = self._chapter_index(pml) + link_index = self._link_index(pml) + images = self._images(oeb_book.manifest, pmlmlizer.image_hrefs) metadata = [self._metadata(metadata)] + hr = [self._header_record(len(text), len(chapter_index), len(link_index), len(images))] - hr = [self._header_record(len(text), len(images))] - - sections = hr+text+images+metadata+['MeTaInFo\x00'] + ''' + Record order as generated by Dropbook. + 1. eReader Header + 2. Compressed text + 3. Small font page index + 4. Large font page index + 5. Chapter index + 6. Links index + 7. Images + 8. (Extrapolation: there should be one more record type here though yet uncovered what it might be). + 9. Metadata + 10. Sidebar records + 11. Footnote records + 12. Text block size record + 13. "MeTaInFo\x00" word record + ''' + sections = hr+text+chapter_index+link_index+images+metadata+[text_sizes]+['MeTaInFo\x00'] lengths = [len(i) if i not in images else len(i[0]) + len(i[1]) for i in sections] @@ -57,17 +78,74 @@ class Writer(FormatWriter): else: out_stream.write(item) - def _text(self, oeb_book): - pmlmlizer = PMLMLizer(self.log) - pml = unicode(pmlmlizer.extract_content(oeb_book, self.opts)).encode('cp1252', 'replace') - + def _text(self, pml): pml_pages = [] - for i in range(0, (len(pml) / MAX_RECORD_SIZE) + 1): - pml_pages.append(zlib.compress(pml[i * MAX_RECORD_SIZE : (i * MAX_RECORD_SIZE) + MAX_RECORD_SIZE])) + text_sizes = '' + index = 0 + while index < len(pml): + ''' + Split on the space character closest to MAX_RECORD_SIZE when possible. + ''' + split = pml.rfind(' ', index, MAX_RECORD_SIZE) + if split == -1: + len_end = len(pml[index:]) + if len_end > MAX_RECORD_SIZE: + split = MAX_RECORD_SIZE + else: + split = len_end + if split == 0: + split = 1 + pml_pages.append(zlib.compress(pml[index:index+split])) + text_sizes += struct.pack('>H', split) + index += split - return pml_pages, pmlmlizer.image_hrefs + return pml_pages, text_sizes + + def _index_item(self, mo): + index = '' + if 'text' in mo.groupdict().keys(): + index += struct.pack('>L', mo.start()) + text = mo.group('text') + # Strip all PML tags from text + text = re.sub(r'\\U[0-9a-z]{4}', '', text) + text = re.sub(r'\\a\d{3}', '', text) + text = re.sub(r'\\.', '', text) + # Add appropriate spacing to denote the various levels of headings + if 'val' in mo.groupdict().keys(): + text = '%s%s' % (' ' * 4 * int(mo.group('val')), text) + index += text + index += '\x00' + return index + + def _chapter_index(self, pml): + chapter_marks = [ + r'(?s)\\x(?P.+?)\\x', + r'(?s)\\X(?P[0-4])(?P.*?)\\X[0-4]', + r'(?s)\\C(?P\d)="(?P.+?)"', + ] + index = [] + for chapter_mark in chapter_marks: + for mo in re.finditer(chapter_mark, pml): + index.append(self._index_item(mo)) + return index + + def _link_index(self, pml): + index = [] + for mo in re.finditer(r'(?s)\\Q="(?P.+?)"', pml): + index.append(self._index_item(mo)) + return index def _images(self, manifest, image_hrefs): + ''' + Image format. + + 0-4 : 'PNG '. There must be a space after PNG. + 4-36 : Image name. Must be exactly 32 bytes long. Pad with \x00 for names shorter than 32 bytes + 36-58 : Unknown. + 58-60 : Width. + 60-62 : Height. + 62-...: Raw image data in 8 bit PNG format. + ''' images = [] for item in manifest: @@ -82,6 +160,8 @@ class Writer(FormatWriter): header = 'PNG ' header += image_hrefs[item.href].ljust(32, '\x00')[:32] + header = header.ljust(58, '\x00') + header += struct.pack('>HH', im.size[0], im.size[1]) header = header.ljust(62, '\x00') if len(data) + len(header) < 65505: @@ -121,52 +201,60 @@ class Writer(FormatWriter): return '%s\x00%s\x00%s\x00%s\x00%s\x00' % (title, author, copyright, publisher, isbn) - def _header_record(self, text_items, image_items): + def _header_record(self, text_count, chapter_count, link_count, image_count): ''' - text_items = the number of text pages - image_items = the number of images + text_count = the number of text pages + image_count = the number of images ''' - version = 10 # Zlib compression - non_text_offset = text_items + 1 + compression = 10 # zlib compression. + non_text_offset = text_count + 1 - if image_items > 0: - image_data_offset = text_items + 1 - meta_data_offset = image_data_offset + image_items + chapter_offset = non_text_offset + link_offset = chapter_offset + chapter_count + + if image_count > 0: + image_data_offset = link_offset + link_count + meta_data_offset = image_data_offset + image_count last_data_offset = meta_data_offset + 1 else: - meta_data_offset = text_items + 1 + meta_data_offset = link_offset + link_count last_data_offset = meta_data_offset + 1 image_data_offset = last_data_offset + if chapter_count == 0: + chapter_offset = last_data_offset + if link_count == 0: + link_offset = last_data_offset + record = '' - record += struct.pack('>H', version) # [0:2] # Version. Specifies compression and drm. 2 = palmdoc, 10 = zlib. 260 and 272 = DRM - record += struct.pack('>H', 0) # [2:4] - record += struct.pack('>H', 0) # [4:6] + record += struct.pack('>H', compression) # [0:2] # Compression. Specifies compression and drm. 2 = palmdoc, 10 = zlib. 260 and 272 = DRM + record += struct.pack('>H', 0) # [2:4] # Unknown. + record += struct.pack('>H', 0) # [4:6] # Unknown. record += struct.pack('>H', 25152) # [6:8] # 25152 is MAGIC. Somehow represents the cp1252 encoding of the text - record += struct.pack('>H', 0) # [8:10] - record += struct.pack('>H', 0) # [10:12] - record += struct.pack('>H', non_text_offset) # [12:14] # non_text_offset - record += struct.pack('>H', 0) # [14:16] - record += struct.pack('>H', 0) # [16:18] - record += struct.pack('>H', 0) # [18:20] - record += struct.pack('>H', image_items) # [20:22] # Number of images - record += struct.pack('>H', 0) # [22:24] - record += struct.pack('>H', 1) # [24:26] # 1 if has metadata, 0 if not - record += struct.pack('>H', 0) # [26:28] - record += struct.pack('>H', 0) # [28:30] # footnote_rec - record += struct.pack('>H', 0) # [30:32] # sidebar_rec - record += struct.pack('>H', last_data_offset) # [32:34] # bookmark_offset - record += struct.pack('>H', 2560) # [34:36] # 2560 is MAGIC - record += struct.pack('>H', 0) # [36:38] - record += struct.pack('>H', 0) # [38:40] - record += struct.pack('>H', image_data_offset) # [40:42] # image_data_offset. This will be the last data offset if there are no images - record += struct.pack('>H', 0) # [42:44] - record += struct.pack('>H', meta_data_offset) # [44:46] # meta_data_offset. This will be the last data offset if there are no images - record += struct.pack('>H', 0) # [46:48] - record += struct.pack('>H', last_data_offset) # [48:50] # footnote_offset. This will be the last data offset if there are no images - record += struct.pack('>H', last_data_offset) # [50:52] # sidebar_offset. This will be the last data offset if there are no images - record += struct.pack('>H', last_data_offset) # [52:54] # last_data_offset + record += struct.pack('>H', 0) # [8:10] # Number of small font pages. 0 if page index is not built. + record += struct.pack('>H', 0) # [10:12] # Number of large font pages. 0 if page index is not built. + record += struct.pack('>H', non_text_offset) # [12:14] # Non-Text record start. + record += struct.pack('>H', chapter_count) # [14:16] # Number of chapter index records. + record += struct.pack('>H', 0) # [16:18] # Number of small font page index records. + record += struct.pack('>H', 0) # [18:20] # Number of large font page index records. + record += struct.pack('>H', image_count) # [20:22] # Number of images. + record += struct.pack('>H', link_count) # [22:24] # Number of links. + record += struct.pack('>H', 1) # [24:26] # 1 if has metadata, 0 if not. + record += struct.pack('>H', 0) # [26:28] # Unknown. + record += struct.pack('>H', 0) # [28:30] # Number of Footnotes. + record += struct.pack('>H', 0) # [30:32] # Number of Sidebars. + record += struct.pack('>H', chapter_offset) # [32:34] # Chapter index offset. + record += struct.pack('>H', 2560) # [34:36] # 2560 is MAGIC. + record += struct.pack('>H', last_data_offset) # [36:38] # Small font page offset. This will be the last data offset if there are none. + record += struct.pack('>H', last_data_offset) # [38:40] # Large font page offset. This will be the last data offset if there are none. + record += struct.pack('>H', image_data_offset) # [40:42] # Image offset. This will be the last data offset if there are none. + record += struct.pack('>H', link_offset) # [42:44] # Links offset. This will be the last data offset if there are none. + record += struct.pack('>H', meta_data_offset) # [44:46] # Metadata offset. This will be the last data offset if there are none. + record += struct.pack('>H', 0) # [46:48] # Unknown. + record += struct.pack('>H', last_data_offset) # [48:50] # Footnote offset. This will be the last data offset if there are none. + record += struct.pack('>H', last_data_offset) # [50:52] # Sidebar offset. This will be the last data offset if there are none. + record += struct.pack('>H', last_data_offset) # [52:54] # Last data offset. for i in range(54, 132, 2): record += struct.pack('>H', 0) # [54:132] diff --git a/src/calibre/ebooks/pml/pmlconverter.py b/src/calibre/ebooks/pml/pmlconverter.py index b4ab238da9..c72a21a5f9 100644 --- a/src/calibre/ebooks/pml/pmlconverter.py +++ b/src/calibre/ebooks/pml/pmlconverter.py @@ -18,10 +18,10 @@ PML_HTML_RULES = [ (re.compile(r'\\x(?P.*?)\\x', re.DOTALL), lambda match: '

%s

' % match.group('text') if match.group('text') else ''), (re.compile(r'\\X(?P[0-4])(?P.*?)\\X[0-4]', re.DOTALL), lambda match: '%s' % (int(match.group('val')) + 1, match.group('text'), int(match.group('val')) + 1) if match.group('text') else ''), (re.compile(r'\\C\d=".+?"'), lambda match: ''), # This should be made to create a TOC entry - (re.compile(r'\\c(?P.*?)\\c', re.DOTALL), lambda match: '
%s
' % match.group('text') if match.group('text') else ''), - (re.compile(r'\\r(?P.*?)\\r', re.DOTALL), lambda match: '
%s
' % match.group('text') if match.group('text') else ''), + (re.compile(r'\\c(?P.*?)\\c', re.DOTALL), lambda match: '%s' % match.group('text') if match.group('text') else ''), + (re.compile(r'\\r(?P.*?)\\r', re.DOTALL), lambda match: '%s' % match.group('text') if match.group('text') else ''), (re.compile(r'\\i(?P.*?)\\i', re.DOTALL), lambda match: '%s' % match.group('text') if match.group('text') else ''), - (re.compile(r'\\u(?P.*?)\\u', re.DOTALL), lambda match: '
%s
' % match.group('text') if match.group('text') else ''), + (re.compile(r'\\u(?P.*?)\\u', re.DOTALL), lambda match: '%s' % match.group('text') if match.group('text') else ''), (re.compile(r'\\o(?P.*?)\\o', re.DOTALL), lambda match: '%s' % match.group('text') if match.group('text') else ''), (re.compile(r'\\v(?P.*?)\\v', re.DOTALL), lambda match: '' % match.group('text') if match.group('text') else ''), (re.compile(r'\\t(?P.*?)\\t', re.DOTALL), lambda match: '
%s
' % match.group('text') if match.group('text') else ''), @@ -35,8 +35,8 @@ PML_HTML_RULES = [ (re.compile(r'\\Sp(?P.*?)\\Sp', re.DOTALL), lambda match: '%s' % match.group('text') if match.group('text') else ''), (re.compile(r'\\Sb(?P.*?)\\Sb', re.DOTALL), lambda match: '%s' % match.group('text') if match.group('text') else ''), (re.compile(r'\\k(?P.*?)\\k', re.DOTALL), lambda match: '%s' % match.group('text').upper() if match.group('text') else ''), - (re.compile(r'\\a(?P\d\d\d)'), lambda match: '&#%s;' % match.group('num')), - (re.compile(r'\\U(?P\d\d\d\d)'), lambda match: '%s' % my_unichr(int(match.group('num'), 16))), + (re.compile(r'\\a(?P\d{3})'), lambda match: '&#%s;' % match.group('num')), + (re.compile(r'\\U(?P[0-9a-f]{4})'), lambda match: '%s' % my_unichr(int(match.group('num'), 16))), (re.compile(r'\\m="(?P.+?)"'), lambda match: '' % image_name(match.group('name')).strip('\x00')), (re.compile(r'\\q="(?P#.+?)"(?P.*?)\\q', re.DOTALL), lambda match: '%s' % (match.group('target'), match.group('text')) if match.group('text') else ''), (re.compile(r'\\Q="(?P.+?)"'), lambda match: '' % match.group('target')), @@ -64,7 +64,7 @@ PML_HTML_RULES = [ (re.compile(r'(?<=[^\\])\\Sp'), lambda match: ''), (re.compile(r'(?<=[^\\])\\Sb'), lambda match: ''), # Remove invalid single item pml codes. - (re.compile(r'(?<=[^\\])\\.'), lambda match: ''), + (re.compile(r'(?<=[^\\])\\[^\\]'), lambda match: ''), # Replace \\ with \. (re.compile(r'\\\\'), lambda match: '\\'), @@ -78,6 +78,7 @@ def pml_to_html(pml): return html def footnote_sidebar_to_html(id, pml): + if id.startswith('\x01'): + id = id[2:] html = '
%s
' % (id, id, pml_to_html(pml)) return html - diff --git a/src/calibre/ebooks/pml/pmlml.py b/src/calibre/ebooks/pml/pmlml.py index 2438fd9bef..9582d2bfbb 100644 --- a/src/calibre/ebooks/pml/pmlml.py +++ b/src/calibre/ebooks/pml/pmlml.py @@ -154,10 +154,15 @@ class PMLMLizer(object): for unused in anchors.difference(links): text = text.replace('\\Q="%s"' % unused, '') + # Turn all html entities into unicode. This should not be necessary as + # lxml should have already done this but we want to be sure it happens. for entity in set(re.findall('&.+?;', text)): mo = re.search('(%s)' % entity[1:-1], text) text = text.replace(entity, entity_to_unicode(mo)) + # Turn all unicode characters into their PML hex equivelent + text = re.sub('[^\x00-\x7f]', lambda x: '\\U%04x' % ord(x.group()), text) + return text def dump_text(self, elem, stylizer, page, tag_stack=[]): From a53c32ae8116a82f9e3ccf11c212b2596a0f4d6c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Oct 2009 12:24:26 -0600 Subject: [PATCH 08/11] Fix #3763 (Cannot close server log window) --- src/calibre/gui2/dialogs/config/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/config/__init__.py b/src/calibre/gui2/dialogs/config/__init__.py index 6197fd4521..7bb40bc3b1 100644 --- a/src/calibre/gui2/dialogs/config/__init__.py +++ b/src/calibre/gui2/dialogs/config/__init__.py @@ -623,7 +623,10 @@ class ConfigDialog(QDialog, Ui_Dialog): try: al.setPlainText(open(log_access_file, 'rb').read().decode('utf8', 'replace')) except IOError: - el.setPlainText('No access log found') + al.setPlainText('No access log found') + bx = QDialogButtonBox(QDialogButtonBox.Ok) + layout.addWidget(bx) + self.connect(bx, SIGNAL('accepted()'), d.accept) d.show() def set_server_options(self): From 5040b8178e47de80057a9004cc832ec30e308d65 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Oct 2009 12:46:59 -0600 Subject: [PATCH 09/11] IGN:Fix #3741 (Update FAQ about using Calibre & Sony software) --- src/calibre/manual/faq.rst | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index a4bf0fd275..9a6b469424 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -80,13 +80,25 @@ What devices does |app| support? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ At the moment |app| has full support for the SONY PRS 300/500/505/600/700, Cybook Gen 3/Opus, Amazon Kindle 1/2/DX, Netronix EB600, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, Android phones and the iPhone. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk. -I used |app| to transfer some books to my reader, and now the SONY software hangs every time I connect the reader? +Can I use both |app| and the SONY software to manage my reader? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You should not use both |app| and Connect to transfer books to the reader. You can fix this problem by: - * Removing any storage cards from your reader. - * Deleting the file media.xml from the reader's main memory using windows explorer (search for the file to find all locations where it is present). Note that by doing this you will lose all your collections, bookmarks, history etc. - * Unplugging the reader and waiting till the list of books shows up again - * Re-connecting the reader and starting the SONY software + +Yes, you can use both, provided you don not run them at the same time. That is, you should use the following sequence: +Connect reader->Use one of the programs->Disconnect reader. Reconnect reader->Use the other program->disconnect reader. + +The underlying reason is that the Reader uses a single file to keep track +of 'meta' information, such as collections, and this is written to by both +|app| and the Sony software when either updates something on the Reader. +The file will be saved when the Reader is (safely) disconnected, so using one +or the other is safe if there's a disconnection between them, but if +you're not the type to remember this, then the simple answer is to stick +to one or the other for the transfer and just export/import from/to the +other via the computers hard disk. + +If you do need to reset your metadata due to problems caused by using both +at the same time, then just delete the media.xml file on the Reader using +your PC's file explorer and it'll be recreated after disconnection. + Can I use the collections feature of the SONY reader? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From dcca0f9a3e119a556cb75a40241107fe9094327e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Oct 2009 13:21:23 -0600 Subject: [PATCH 10/11] Welcome wizard now allows you to set interface language. Fixes #3736 (First question of wizard...) --- src/calibre/debug.py | 4 ++- src/calibre/gui2/wizard/__init__.py | 45 ++++++++++++++++++++++++--- src/calibre/gui2/wizard/library.ui | 48 ++++++++++++++++++++++------- 3 files changed, 81 insertions(+), 16 deletions(-) diff --git a/src/calibre/debug.py b/src/calibre/debug.py index f9ee3b0f44..1b913318e3 100644 --- a/src/calibre/debug.py +++ b/src/calibre/debug.py @@ -168,7 +168,9 @@ def main(args=sys.argv): sys.argv = args[:1] base = os.path.dirname(os.path.abspath(opts.exec_file)) sys.path.insert(0, base) - execfile(opts.exec_file) + g = globals() + g['__name__'] = '__main__' + execfile(opts.exec_file, g) elif opts.debug_device_driver: debug_device_driver() elif opts.migrate: diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py index 68f2777daa..13222861ef 100644 --- a/src/calibre/gui2/wizard/__init__.py +++ b/src/calibre/gui2/wizard/__init__.py @@ -33,8 +33,8 @@ class Device(object): output_profile = 'default' output_format = 'EPUB' - name = _('Default') - manufacturer = _('Default') + name = 'Default' + manufacturer = 'Default' id = 'default' @classmethod @@ -146,8 +146,9 @@ def get_manufacturers(): mans = set([]) for x in get_devices(): mans.add(x.manufacturer) - mans.remove(_('Default')) - return [_('Default')] + sorted(mans) + if 'Default' in mans: + mans.remove('Default') + return ['Default'] + sorted(mans) def get_devices_of(manufacturer): ans = [d for d in get_devices() if d.manufacturer == manufacturer] @@ -464,6 +465,36 @@ class LibraryPage(QWizardPage, LibraryUI): self.setupUi(self) self.registerField('library_location', self.location) self.connect(self.button_change, SIGNAL('clicked()'), self.change) + self.init_languages() + self.connect(self.language, SIGNAL('currentIndexChanged(int)'), + self.change_language) + + def init_languages(self): + self.language.blockSignals(True) + self.language.clear() + from calibre.utils.localization import available_translations, \ + get_language, get_lang + lang = get_lang() + if lang is None or lang not in available_translations(): + lang = 'en' + self.language.addItem(get_language(lang), QVariant(lang)) + items = [(l, get_language(l)) for l in available_translations() \ + if l != lang] + if lang != 'en': + items.append(('en', get_language('en'))) + items.sort(cmp=lambda x, y: cmp(x[1], y[1])) + for item in items: + self.language.addItem(item[1], QVariant(item[0])) + self.language.blockSignals(False) + + def change_language(self, idx): + prefs['language'] = str(self.language.itemData(self.language.currentIndex()).toString()) + import __builtin__ + __builtin__.__dict__['_'] = lambda(x): x + from calibre.utils.localization import set_translators + set_translators() + self.emit(SIGNAL('retranslate()')) + self.init_languages() def change(self): dir = choose_dir(self, 'database location dialog', @@ -548,6 +579,8 @@ class Wizard(QWizard): self.setPixmap(self.BackgroundPixmap, QPixmap(I('wizard.svg'))) self.device_page = DevicePage() self.library_page = LibraryPage() + self.connect(self.library_page, SIGNAL('retranslate()'), + self.retranslate) self.finish_page = FinishPage() bt = unicode(self.buttonText(self.FinishButton)).replace('&', '') t = unicode(self.finish_page.finish_text.text()) @@ -572,6 +605,10 @@ class Wizard(QWizard): nw = min(580, nw) self.resize(nw, nh) + def retranslate(self): + for pid in self.pageIds(): + page = self.page(pid) + page.retranslateUi(page) def accept(self): pages = map(self.page, self.visitedPages()) diff --git a/src/calibre/gui2/wizard/library.ui b/src/calibre/gui2/wizard/library.ui index 756f7ab851..0d43f4ccee 100644 --- a/src/calibre/gui2/wizard/library.ui +++ b/src/calibre/gui2/wizard/library.ui @@ -20,7 +20,20 @@ The one stop solution to all your e-book needs. - + + + + Choose your &language: + + + language + + + + + + + Choose a location for your books. When you add books to calibre, they will be copied here: @@ -30,21 +43,31 @@ - + true - + &Change - + + + + If you have an existing calibre library, it will be copied to the new location. If a calibre library already exists at the new location, calibre will switch to using it. + + + true + + + + Qt::Vertical @@ -57,15 +80,18 @@ - - - - If you have an existing calibre library, it will be copied to the new location. If a calibre library already exists at the new location, calibre will switch to using it. + + + + Qt::Vertical - - true + + + 20 + 40 + - + From e492d8720606c08d51166785221273ef1747d962 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 12 Oct 2009 14:17:59 -0600 Subject: [PATCH 11/11] IGN:... --- src/calibre/gui2/__init__.py | 11 +++++++++-- src/calibre/gui2/wizard/__init__.py | 6 ++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 56dbe4fab6..32f7a32efa 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -508,14 +508,21 @@ class ResizableDialog(QDialog): gui_thread = None - +qt_app = None class Application(QApplication): def __init__(self, args): qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args] QApplication.__init__(self, qargs) - global gui_thread + global gui_thread, qt_app gui_thread = QThread.currentThread() + self._translator = None + self.load_translations() + qt_app = self + + def load_translations(self): + if self._translator is not None: + self.removeTranslator(self._translator) self._translator = QTranslator(self) if set_qt_translator(self._translator): self.installTranslator(self._translator) diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py index 13222861ef..adde8c7fde 100644 --- a/src/calibre/gui2/wizard/__init__.py +++ b/src/calibre/gui2/wizard/__init__.py @@ -492,7 +492,9 @@ class LibraryPage(QWizardPage, LibraryUI): import __builtin__ __builtin__.__dict__['_'] = lambda(x): x from calibre.utils.localization import set_translators + from calibre.gui2 import qt_app set_translators() + qt_app.load_translations() self.emit(SIGNAL('retranslate()')) self.init_languages() @@ -627,7 +629,7 @@ def wizard(parent=None): return w if __name__ == '__main__': - from PyQt4.Qt import QApplication - app = QApplication([]) + from calibre.gui2 import Application + app = Application([]) wizard().exec_()