mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sync to trunk.
This commit is contained in:
commit
30fd38cdab
2
README
2
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
|
||||
|
BIN
resources/images/news/businessworldin.png
Normal file
BIN
resources/images/news/businessworldin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 444 B |
@ -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()
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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.
|
||||
'''
|
||||
|
213
setup/upload.py
213
setup/upload.py
@ -6,10 +6,11 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__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/'
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -192,6 +192,12 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>700</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><p>Search the list of books by title, author, publisher, tags, comments, etc.<br><br>Words separated by spaces are ANDed</string>
|
||||
</property>
|
||||
|
@ -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_()
|
||||
|
||||
|
@ -20,7 +20,20 @@
|
||||
<string>The one stop solution to all your e-book needs.</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Choose your &language:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>language</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" colspan="2">
|
||||
<widget class="QComboBox" name="language"/>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Choose a location for your books. When you add books to calibre, they will be copied here:</string>
|
||||
@ -30,21 +43,31 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QLineEdit" name="location">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="3" column="2">
|
||||
<widget class="QPushButton" name="button_change">
|
||||
<property name="text">
|
||||
<string>&Change</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="4" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>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.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@ -57,15 +80,18 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>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.</string>
|
||||
<item row="5" column="0">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
@ -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]
|
||||
|
@ -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?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -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(\
|
||||
'''
|
||||
<p>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'''
|
||||
<ol>
|
||||
|
@ -21,7 +21,8 @@
|
||||
</a> (Version: $version <a href="/wiki/Changelog">Changelog</a>)
|
||||
</p>
|
||||
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
|
||||
<a href="https://sourceforge.net/projects/calibre/">sourceforge</a>.
|
||||
<div>
|
||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
|
||||
<input type="hidden" name="cmd" value="_s-xclick" />
|
||||
|
@ -106,7 +106,7 @@ sudo python -c "import urllib2; exec urllib2.urlopen('http://status.calibre-eboo
|
||||
</li>
|
||||
</ol>
|
||||
<pre class="wiki">
|
||||
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
|
||||
</pre>
|
||||
|
@ -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',
|
||||
)]
|
||||
|
||||
|
||||
|
77
src/calibre/web/feeds/recipes/recipe_businessworldin.py
Normal file
77
src/calibre/web/feeds/recipes/recipe_businessworldin.py
Normal file
@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
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
|
Loading…
x
Reference in New Issue
Block a user