'
__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_()