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/resources/images/news/businessworldin.png b/resources/images/news/businessworldin.png
new file mode 100644
index 0000000000..4dd8612fd2
Binary files /dev/null and b/resources/images/news/businessworldin.png differ
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..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:
@@ -190,14 +192,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/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));
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/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):
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
diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py
index 68f2777daa..adde8c7fde 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,38 @@ 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
+ from calibre.gui2 import qt_app
+ set_translators()
+ qt_app.load_translations()
+ self.emit(SIGNAL('retranslate()'))
+ self.init_languages()
def change(self):
dir = choose_dir(self, 'database location dialog',
@@ -548,6 +581,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 +607,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())
@@ -590,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_()
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
+
-
+
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]
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?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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/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.