Sync to trunk.

This commit is contained in:
John Schember 2009-10-12 19:53:43 -04:00
commit 30fd38cdab
22 changed files with 470 additions and 57 deletions

2
README
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

View File

@ -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()

View File

@ -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):

View File

@ -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.
'''

View File

@ -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/'

View File

@ -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()

View File

@ -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

View File

@ -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));

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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>&lt;p&gt;Search the list of books by title, author, publisher, tags, comments, etc.&lt;br&gt;&lt;br&gt;Words separated by spaces are ANDed</string>
</property>

View File

@ -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_()

View File

@ -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 &amp;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>&amp;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>

View File

@ -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]

View File

@ -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?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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>

View File

@ -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" />

View File

@ -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>

View File

@ -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',
)]

View 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