Sync to trunk.

This commit is contained in:
John Schember 2011-06-06 17:26:06 -04:00
commit fff3cb147a
14 changed files with 95 additions and 35 deletions

View File

@ -20,8 +20,8 @@
<script type="text/javascript" <script type="text/javascript"
src="{prefix}/static/jquery.multiselect.min.js"></script> src="{prefix}/static/jquery.multiselect.min.js"></script>
<script type="text/javascript" src="{prefix}/static/stacktrace.js"></script>
<script type="text/javascript" src="{prefix}/static/browse/browse.js"></script> <script type="text/javascript" src="{prefix}/static/browse/browse.js"></script>
<script type="text/javascript"> <script type="text/javascript">
var sort_cookie_name = "{sort_cookie_name}"; var sort_cookie_name = "{sort_cookie_name}";

View File

@ -129,7 +129,13 @@ function toplevel() {
// }}} // }}}
function render_error(msg) { function render_error(msg) {
return '<div class="ui-widget"><div class="ui-state-error ui-corner-all" style="padding: 0pt 0.7em"><p><span class="ui-icon ui-icon-alert" style="float: left; margin-right: 0.3em">&nbsp;</span><strong>Error: </strong>'+msg+"</p></div></div>" var st = "";
try {
var st = printStackTrace();
st = st.join('\n\n');
} catch(e) {
}
return '<div class="ui-widget"><div class="ui-state-error ui-corner-all" style="padding: 0pt 0.7em"><p><span class="ui-icon ui-icon-alert" style="float: left; margin-right: 0.3em">&nbsp;</span><strong>Error: </strong>'+msg+"<pre>"+st+"</pre></p></div></div>"
} }
// Category feed {{{ // Category feed {{{

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization"> <WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization">
<String Id="AdvancedWelcomeEulaDlgDescriptionPerUser">If you are upgrading from a {app} version older than 0.6.17, please uninstall {app} first. Click Advanced to change installation settings.</String> <String Id="AdvancedWelcomeEulaDlgDescriptionPerUser">Click Advanced to change installation settings.</String>
<String Id="ProgressTextFileCost">Computing space requirements, this may take upto five minutes...</String> <String Id="ProgressTextFileCost">Computing space requirements, this may take upto five minutes...</String>
<String Id="ProgressTextCostInitialize">Computing space requirements, this may take upto five minutes...</String> <String Id="ProgressTextCostInitialize">Computing space requirements, this may take upto five minutes...</String>
<String Id="ProgressTextCostFinalize">Computing space requirements, this may take upto five minutes...</String> <String Id="ProgressTextCostFinalize">Computing space requirements, this may take upto five minutes...</String>

View File

@ -8,8 +8,8 @@ __docformat__ = 'restructuredtext en'
import sys, os, shutil, glob, py_compile, subprocess, re, zipfile, time import sys, os, shutil, glob, py_compile, subprocess, re, zipfile, time
from setup import Command, modules, functions, basenames, __version__, \ from setup import (Command, modules, functions, basenames, __version__,
__appname__ __appname__)
from setup.build_environment import msvc, MT, RC from setup.build_environment import msvc, MT, RC
from setup.installer.windows.wix import WixMixIn from setup.installer.windows.wix import WixMixIn
@ -20,6 +20,7 @@ LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
SW = r'C:\cygwin\home\kovid\sw' SW = r'C:\cygwin\home\kovid\sw'
IMAGEMAGICK = os.path.join(SW, 'build', 'ImageMagick-6.6.6', IMAGEMAGICK = os.path.join(SW, 'build', 'ImageMagick-6.6.6',
'VisualMagick', 'bin') 'VisualMagick', 'bin')
CRT = r'C:\Microsoft.VC90.CRT'
VERSION = re.sub('[a-z]\d+', '', __version__) VERSION = re.sub('[a-z]\d+', '', __version__)
WINVER = VERSION+'.0' WINVER = VERSION+'.0'
@ -72,7 +73,7 @@ class Win32Freeze(Command, WixMixIn):
self.lib_dir = self.j(self.base, 'Lib') self.lib_dir = self.j(self.base, 'Lib')
self.pylib = self.j(self.base, 'pylib.zip') self.pylib = self.j(self.base, 'pylib.zip')
self.dll_dir = self.j(self.base, 'DLLs') self.dll_dir = self.j(self.base, 'DLLs')
self.plugins_dir = os.path.join(self.base, 'plugins') self.plugins_dir = os.path.join(self.base, 'plugins2')
self.initbase() self.initbase()
self.build_launchers() self.build_launchers()
@ -81,8 +82,33 @@ class Win32Freeze(Command, WixMixIn):
self.embed_manifests() self.embed_manifests()
self.install_site_py() self.install_site_py()
self.archive_lib_dir() self.archive_lib_dir()
self.remove_CRT_from_manifests()
self.create_installer() self.create_installer()
def remove_CRT_from_manifests(self):
'''
The dependency on the CRT is removed from the manifests of all DLLs.
This allows the CRT loaded by the .exe files to be used instead.
'''
search_pat = re.compile(r'(?is)<dependency>.*Microsoft\.VC\d+\.CRT')
repl_pat = re.compile(
r'(?is)<dependency>.*?Microsoft\.VC\d+\.CRT.*?</dependency>')
for dll in glob.glob(self.j(self.dll_dir, '*.dll')):
bn = self.b(dll)
with open(dll, 'rb') as f:
raw = f.read()
match = search_pat.search(raw)
if match is None:
continue
self.info('Removing CRT dependency from manifest of: %s'%bn)
# Blank out the bytes corresponding to the dependency specification
nraw = repl_pat.sub(lambda m: b' '*len(m.group()), raw)
if len(nraw) != len(raw):
raise Exception('Something went wrong with %s'%bn)
with open(dll, 'wb') as f:
f.write(nraw)
def initbase(self): def initbase(self):
if self.e(self.base): if self.e(self.base):
shutil.rmtree(self.base) shutil.rmtree(self.base)
@ -103,6 +129,9 @@ class Win32Freeze(Command, WixMixIn):
def freeze(self): def freeze(self):
shutil.copy2(self.j(self.src_root, 'LICENSE'), self.base) shutil.copy2(self.j(self.src_root, 'LICENSE'), self.base)
self.info('Adding CRT')
shutil.copytree(CRT, self.j(self.base, os.path.basename(CRT)))
self.info('Adding resources...') self.info('Adding resources...')
tgt = self.j(self.base, 'resources') tgt = self.j(self.base, 'resources')
if os.path.exists(tgt): if os.path.exists(tgt):
@ -197,6 +226,10 @@ class Win32Freeze(Command, WixMixIn):
if os.path.exists(tg): if os.path.exists(tg):
shutil.rmtree(tg) shutil.rmtree(tg)
shutil.copytree(imfd, tg) shutil.copytree(imfd, tg)
for dirpath, dirnames, filenames in os.walk(tdir):
for x in filenames:
if not x.endswith('.dll'):
os.remove(self.j(dirpath, x))
print print
print 'Adding third party dependencies' print 'Adding third party dependencies'

View File

@ -92,7 +92,7 @@ def aliasmbcs():
def add_calibre_vars(): def add_calibre_vars():
sys.resources_location = os.path.join(sys.app_dir, 'resources') sys.resources_location = os.path.join(sys.app_dir, 'resources')
sys.extensions_location = os.path.join(sys.app_dir, 'plugins') sys.extensions_location = os.path.join(sys.app_dir, 'plugins2')
dv = os.environ.get('CALIBRE_DEVELOP_FROM', None) dv = os.environ.get('CALIBRE_DEVELOP_FROM', None)
if dv and os.path.exists(dv): if dv and os.path.exists(dv):

View File

@ -11,6 +11,10 @@
SummaryCodepage='1252' /> SummaryCodepage='1252' />
<Media Id="1" Cabinet="{app}.cab" CompressionLevel="{compression}" EmbedCab="yes" /> <Media Id="1" Cabinet="{app}.cab" CompressionLevel="{compression}" EmbedCab="yes" />
<!-- The following line ensures that DLLs are replaced even if their version is the same as before. This
is necessary because of the manifest nuking that was part of making calibre isolated. But I think it
is more rigorous anyway. -->
<Property Id='REINSTALLMODE' Value='emus'/>
<Upgrade Id="{upgrade_code}"> <Upgrade Id="{upgrade_code}">
<UpgradeVersion Maximum="{version}" <UpgradeVersion Maximum="{version}"
@ -33,7 +37,6 @@
</Property> </Property>
<Directory Id='TARGETDIR' Name='SourceDir'> <Directory Id='TARGETDIR' Name='SourceDir'>
<Merge Id="VCRedist" SourceFile="{crt_msm}" DiskId="1" Language="0"/>
<Directory Id='ProgramFilesFolder' Name='PFiles'> <Directory Id='ProgramFilesFolder' Name='PFiles'>
<Directory Id='APPLICATIONFOLDER' Name='{app}' /> <Directory Id='APPLICATIONFOLDER' Name='{app}' />
</Directory> </Directory>
@ -100,10 +103,6 @@
<ComponentRef Id="RememberInstallDir"/> <ComponentRef Id="RememberInstallDir"/>
</Feature> </Feature>
<Feature Id="VCRedist" Title="Visual C++ 8.0 Runtime" AllowAdvertise="no" Display="hidden" Level="1">
<MergeRef Id="VCRedist"/>
</Feature>
<Feature Id="FSMS" Title="Start menu shortcuts" Level="1" <Feature Id="FSMS" Title="Start menu shortcuts" Level="1"
Description="Program shortcuts installed in the Start Menu"> Description="Program shortcuts installed in the Start Menu">
<ComponentRef Id="StartMenuShortcuts"/> <ComponentRef Id="StartMenuShortcuts"/>
@ -149,12 +148,13 @@
Set default folder name and allow only per machine installs. Set default folder name and allow only per machine installs.
For a per-machine installation, the default installation location For a per-machine installation, the default installation location
will be [ProgramFilesFolder][ApplicationFolderName] and the user will be [ProgramFilesFolder][ApplicationFolderName] and the user
will be able to change it in the setup UI. This is because the installer will be able to change it in the setup UI. This is no longer necessary
has to install the VC90 merge module into the system winsxs folder for python (i.e. per user installs should work) but left this way as I
to work, so per user installs are impossible anyway. dont want to deal with the complications
--> -->
<Property Id="ApplicationFolderName" Value="Calibre2" /> <Property Id="ApplicationFolderName" Value="Calibre2" />
<Property Id="WixAppFolder" Value="WixPerMachineFolder" /> <Property Id="WixAppFolder" Value="WixPerMachineFolder" />
<Property Id="ALLUSERS" Value="1" />
<WixVariable Id="WixUISupportPerUser" Value="0" /> <WixVariable Id="WixUISupportPerUser" Value="0" />
<!-- Add option to launch calibre after install --> <!-- Add option to launch calibre after install -->

View File

@ -35,7 +35,6 @@ class WixMixIn:
exe_map = self.smap, exe_map = self.smap,
main_icon = self.j(self.src_root, 'icons', 'library.ico'), main_icon = self.j(self.src_root, 'icons', 'library.ico'),
web_icon = self.j(self.src_root, 'icons', 'web.ico'), web_icon = self.j(self.src_root, 'icons', 'web.ico'),
crt_msm = self.j(self.SW, 'Microsoft_VC90_CRT_x86.msm')
) )
template = open(self.j(self.d(__file__), 'en-us.xml'), template = open(self.j(self.d(__file__), 'en-us.xml'),
'rb').read() 'rb').read()

View File

@ -11,7 +11,7 @@ import os, shutil, traceback, textwrap, time, codecs
from Queue import Empty from Queue import Empty
from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation from calibre.customize.conversion import InputFormatPlugin, OptionRecommendation
from calibre import extract, CurrentDir, prints from calibre import extract, CurrentDir, prints, walk
from calibre.constants import filesystem_encoding from calibre.constants import filesystem_encoding
from calibre.ptempfile import PersistentTemporaryDirectory from calibre.ptempfile import PersistentTemporaryDirectory
from calibre.utils.ipc.server import Server from calibre.utils.ipc.server import Server
@ -27,6 +27,11 @@ def extract_comic(path_to_comic_file):
# names # names
tdir = tdir.decode(filesystem_encoding) tdir = tdir.decode(filesystem_encoding)
extract(path_to_comic_file, tdir) extract(path_to_comic_file, tdir)
for x in walk(tdir):
bn = os.path.basename(x)
nbn = bn.replace('#', '_')
if nbn != bn:
os.rename(x, os.path.join(os.path.dirname(x), nbn))
return tdir return tdir
def find_pages(dir, sort_on_mtime=False, verbose=False): def find_pages(dir, sort_on_mtime=False, verbose=False):
@ -362,6 +367,7 @@ class ComicInput(InputFormatPlugin):
if not line: if not line:
continue continue
fname, title = line.partition(':')[0], line.partition(':')[-1] fname, title = line.partition(':')[0], line.partition(':')[-1]
fname = fname.replace('#', '_')
fname = os.path.join(tdir, *fname.split('/')) fname = os.path.join(tdir, *fname.split('/'))
if not title: if not title:
title = os.path.basename(fname).rpartition('.')[0] title = os.path.basename(fname).rpartition('.')[0]

View File

@ -154,13 +154,16 @@ _proceed_memory = []
class ProceedNotification(MessageBox): # {{{ class ProceedNotification(MessageBox): # {{{
def __init__(self, callback, payload, html_log, log_viewer_title, title, msg, def __init__(self, callback, payload, html_log, log_viewer_title, title, msg,
det_msg='', show_copy_button=False, parent=None): det_msg='', show_copy_button=False, parent=None,
cancel_callback=None):
''' '''
A non modal popup that notifies the user that a background task has A non modal popup that notifies the user that a background task has
been completed. been completed.
:param callback: A callable that is called with payload if the user :param callback: A callable that is called with payload if the user
asks to proceed. Note that this is always called in the GUI thread asks to proceed. Note that this is always called in the GUI thread.
:param cancel_callback: A callable that is called with the payload if
the users asks not to proceed.
:param payload: Arbitrary object, passed to callback :param payload: Arbitrary object, passed to callback
:param html_log: An HTML or plain text log :param html_log: An HTML or plain text log
:param log_viewer_title: The title for the log viewer window :param log_viewer_title: The title for the log viewer window
@ -181,7 +184,7 @@ class ProceedNotification(MessageBox): # {{{
self.vlb.clicked.connect(self.show_log) self.vlb.clicked.connect(self.show_log)
self.det_msg_toggle.setVisible(bool(det_msg)) self.det_msg_toggle.setVisible(bool(det_msg))
self.setModal(False) self.setModal(False)
self.callback = callback self.callback, self.cancel_callback = callback, cancel_callback
_proceed_memory.append(self) _proceed_memory.append(self)
def show_log(self): def show_log(self):
@ -192,9 +195,11 @@ class ProceedNotification(MessageBox): # {{{
try: try:
if result == self.Accepted: if result == self.Accepted:
self.callback(self.payload) self.callback(self.payload)
elif self.cancel_callback is not None:
self.cancel_callback(self.payload)
finally: finally:
# Ensure this notification is garbage collected # Ensure this notification is garbage collected
self.callback = None self.callback = self.cancel_callback = None
self.setParent(None) self.setParent(None)
self.finished.disconnect() self.finished.disconnect()
self.vlb.clicked.disconnect() self.vlb.clicked.disconnect()

View File

@ -197,10 +197,12 @@ class JobManager(QAbstractTableModel): # {{{
def row_to_job(self, row): def row_to_job(self, row):
return self.jobs[row] return self.jobs[row]
def has_device_jobs(self): def has_device_jobs(self, queued_also=False):
for job in self.jobs: for job in self.jobs:
if job.is_running and isinstance(job, DeviceJob): if isinstance(job, DeviceJob):
return True if job.duration is None: # Running or waiting
if (job.is_running or queued_also):
return True
return False return False
def has_jobs(self): def has_jobs(self):

View File

@ -6,15 +6,15 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>584</width> <width>872</width>
<height>533</height> <height>610</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Get Books</string> <string>Get Books</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset> <iconset resource="../../../../../resources/images.qrc">
<normaloff>:/images/store.png</normaloff>:/images/store.png</iconset> <normaloff>:/images/store.png</normaloff>:/images/store.png</iconset>
</property> </property>
<property name="sizeGripEnabled"> <property name="sizeGripEnabled">
@ -82,8 +82,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>125</width> <width>173</width>
<height>127</height> <height>106</height>
</rect> </rect>
</property> </property>
</widget> </widget>
@ -255,7 +255,7 @@
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../../../../resources/images.qrc"/> <include location="../../../../../resources/images.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>

View File

@ -770,7 +770,8 @@ class BrowseServer(object):
summs.append(self.browse_summary_template.format(**args)) summs.append(self.browse_summary_template.format(**args))
return json.dumps('\n'.join(summs), ensure_ascii=False) raw = json.dumps('\n'.join(summs), ensure_ascii=False)
return raw
def browse_render_details(self, id_): def browse_render_details(self, id_):
try: try:

View File

@ -65,7 +65,8 @@ def test_sqlite():
def test_qt(): def test_qt():
from PyQt4.Qt import (QWebView, QDialog, QImageReader, QNetworkAccessManager) from PyQt4.Qt import (QWebView, QDialog, QImageReader, QNetworkAccessManager)
fmts = set(map(unicode, QImageReader.supportedImageFormats())) fmts = set(map(unicode, QImageReader.supportedImageFormats()))
if 'jpg' not in fmts or 'png' not in fmts: testf = set(['jpg', 'png', 'mng', 'svg', 'ico', 'gif'])
if testf.intersection(fmts) != testf:
raise RuntimeError( raise RuntimeError(
"Qt doesn't seem to be able to load its image plugins") "Qt doesn't seem to be able to load its image plugins")
QWebView, QDialog QWebView, QDialog
@ -87,6 +88,12 @@ def test_imaging():
raise RuntimeError('PIL choked!') raise RuntimeError('PIL choked!')
print ('PIL OK!') print ('PIL OK!')
def test_unrar():
from calibre.libunrar import _libunrar
if not _libunrar:
raise RuntimeError('Failed to load libunrar')
print ('Unrar OK!')
def test(): def test():
test_plugins() test_plugins()
test_lxml() test_lxml()
@ -97,6 +104,7 @@ def test():
test_win32() test_win32()
test_qt() test_qt()
test_imaging() test_imaging()
test_unrar()
if __name__ == '__main__': if __name__ == '__main__':
test() test()