mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
IGN:Sync with trunk
This commit is contained in:
commit
d832563871
@ -10,7 +10,6 @@ import glob, sys, subprocess, tarfile, os, re, py_compile, shutil
|
||||
HOME = '/home/kovid'
|
||||
PYINSTALLER = os.path.expanduser('~/build/pyinstaller')
|
||||
CALIBREPREFIX = '___'
|
||||
CLIT = '/usr/bin/clit'
|
||||
PDFTOHTML = '/usr/bin/pdftohtml'
|
||||
LIBUNRAR = '/usr/lib/libunrar.so'
|
||||
QTDIR = '/usr/lib/qt4'
|
||||
@ -20,7 +19,9 @@ SQLITE = '/usr/lib/libsqlite3.so.0'
|
||||
DBUS = '/usr/lib/libdbus-1.so.3'
|
||||
LIBMNG = '/usr/lib/libmng.so.1'
|
||||
LIBZ = '/lib/libz.so.1'
|
||||
LIBUSB = '/lib/libusb.so'
|
||||
LIBBZ2 = '/lib/libbz2.so.1'
|
||||
LIBUSB = '/usr/lib/libusb.so'
|
||||
LIBPOPPLER = '/usr/lib/libpoppler.so.3'
|
||||
|
||||
|
||||
CALIBRESRC = os.path.join(CALIBREPREFIX, 'src')
|
||||
@ -115,11 +116,12 @@ for f in glob.glob(os.path.join(CALIBREPLUGINS, '*.so.*')):
|
||||
binaries += [(os.path.basename(f), f, 'BINARY')]
|
||||
|
||||
print 'Adding external programs...'
|
||||
binaries += [('clit', CLIT, 'BINARY'), ('pdftohtml', PDFTOHTML, 'BINARY'),
|
||||
binaries += [('pdftohtml', PDFTOHTML, 'BINARY'),
|
||||
('libunrar.so', LIBUNRAR, 'BINARY')]
|
||||
|
||||
print 'Adding external libraries...'
|
||||
binaries += [ (os.path.basename(x), x, 'BINARY') for x in (SQLITE, DBUS, LIBMNG, LIBZ, LIBUSB)]
|
||||
binaries += [ (os.path.basename(x), x, 'BINARY') for x in (SQLITE, DBUS,
|
||||
LIBMNG, LIBZ, LIBBZ2, LIBUSB, LIBPOPPLER)]
|
||||
|
||||
|
||||
qt = []
|
||||
|
@ -243,12 +243,6 @@ _check_symlinks_prescript()
|
||||
self.add_plugins()
|
||||
|
||||
|
||||
print
|
||||
print 'Adding clit'
|
||||
os.link(os.path.expanduser('~/clit'), os.path.join(frameworks_dir, 'clit'))
|
||||
print
|
||||
print 'Adding unrtf'
|
||||
os.link(os.path.expanduser('~/unrtf'), os.path.join(frameworks_dir, 'unrtf'))
|
||||
print
|
||||
print 'Adding pdftohtml'
|
||||
os.link(os.path.expanduser('~/pdftohtml'), os.path.join(frameworks_dir, 'pdftohtml'))
|
||||
|
@ -213,11 +213,9 @@ File ::D9A3AF75-5939-CB51-9F33-5A048911103E -type dir -name etc -parent 6CCF3F71
|
||||
File ::A628E495-239B-DAF4-D858-BCE36CB41E6E -type dir -name imageformats -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::0A533DB2-D494-A9ED-1334-DECC357BD426 -type dir -name codecs -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::0F47A44E-E347-1CD4-E89F-37B447C4A270 -type dir -name driver -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::19A416A8-8DDD-658B-A3D4-F89ABD02CFBB -type dir -name ImageMagick -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::A146565C-D163-7F68-7C70-A6A336B32526 -type dir -name iconengines -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::3245B06C-1C22-1A8A-5710-6D36651AAA70 -name etree.pyd -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::B49A5610-13F6-FB5D-0673-DB47C6BB385D -name rtf-meta.exe.local -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::1F7D32C6-D61D-A09F-6D9E-690F7DD10D04 -name clit.exe.local -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::CE4F2A21-12CC-2B9A-6D48-6A0FEA7C9D13 -name _compiled_base.pyd -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::D6C46340-8335-7FC4-A027-D701DF1B70AB -name pdf2lrf.exe -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::53F2B07D-8F92-2328-C55E-5F7F0E63D5DB -name opf-meta.exe.local -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
@ -348,13 +346,150 @@ File ::ACE1537B-B234-3C90-759A-8947A7AADC77 -name mobi2oeb.exe -parent 6CCF3F71-
|
||||
File ::92701E8F-1D91-A796-C899-2A266029F61D -name _socket.pyd -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::45BD27B5-B910-7633-C827-37E82E89C27C -name w9xpopen.exe.local -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::45C27909-D761-787F-84B2-66596E5C4E99 -name bz2.pyd -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::7B2DE5D8-17A6-B167-ABC7-799AEBCC1C02 -name clit.exe -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::36E8EEAC-F54D-5DE9-02D8-ECDFEBB4B5E2 -type dir -name plugins -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::71930E14-A27B-C23C-8D94-C7E97ADB8723 -name pictureflow.pyd -parent 36E8EEAC-F54D-5DE9-02D8-ECDFEBB4B5E2
|
||||
File ::293E6ABE-17C9-5E53-1B44-C27029C8C061 -name winutil.pyd -parent 36E8EEAC-F54D-5DE9-02D8-ECDFEBB4B5E2
|
||||
File ::A5737158-18DF-7F20-2BDF-2DF615663891 -name lzx.pyd -parent 36E8EEAC-F54D-5DE9-02D8-ECDFEBB4B5E2
|
||||
File ::CA9E098C-2931-9781-1303-213C242F9A5E -name lit2oeb.exe -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::16B5A447-066C-C93E-F63D-8BC0D57CA544 -name lit2oeb.exe.local -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::ABF342D2-82A9-2A20-BA97-54AD5BAF1A2A -name IM_MOD_RL_sfw_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::3877E295-C7EB-DF63-DA91-B9E2F9D8035A -name comic2lrf.exe.local -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::F47345A3-6CE0-4F5B-9CAE-3F4A18D4AA5A -name IM_MOD_RL_rle_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::3A211C93-1B8B-A8AA-E240-A3287974DAB4 -name IM_MOD_RL_tiff_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::F24F3123-05A3-B452-D12B-CE6C126501B1 -name CORE_RL_libxml_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::D7034E85-2733-DB61-DB49-C34B767B4B45 -name IM_MOD_RL_sun_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::21C1F4D3-487E-5FFF-C8CE-8E5FE779A786 -name IM_MOD_RL_msl_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::01EC7979-C9CB-696C-E8B3-F5945E1115BB -name IM_MOD_RL_jp2_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::0F86B693-D83A-DB03-8641-219FE766D980 -name CORE_RL_ttf_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::DDCAEB76-7AC4-CA1A-0742-34B556592D2A -name IM_MOD_RL_exr_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::4D274537-6B6D-63F2-2615-E0CD279880A3 -name CORE_RL_Magick++_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::A87CE00E-9F87-DBE3-DDA5-FC68D6D0731E -name IM_MOD_RL_dib_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::9114D530-B73B-CC7C-F6A6-655B7271AB35 -name IM_MOD_RL_preview_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::31EE0880-F1C8-94D6-4EDC-B09A576E0908 -name CORE_RL_magick_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::BEA2B769-1A54-4398-E8B4-5BE15637B705 -name IM_MOD_RL_svg_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::7E300AD4-7C03-5835-0DD6-E9FA8737585A -name IM_MOD_RL_mpeg_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::5E9901D8-BBB7-A17C-5A04-837C0ADF8CAE -name IM_MOD_RL_clipboard_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::F13497D2-87C0-243D-916A-0A160F1A1896 -name IM_MOD_RL_png_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::DBD1CF95-1B01-9F5C-66D9-C7B4E1B44CC7 -name CORE_RL_bzlib_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::149C038D-9CD6-20C5-49C3-FC6948D0709D -name IM_MOD_RL_wmf_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::F77F5E54-1D54-F7D3-9520-BB1811C11AA6 -name IM_MOD_RL_txt_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::3545B38D-1BDF-B355-F779-4D83F292E2B6 -name IM_MOD_RL_viff_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::6E33F2FD-17BB-F096-4551-0E3B22924A4D -name IM_MOD_RL_ps2_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::F4C810FF-4291-4491-0FA2-CFAD0BA690A9 -name type.xml -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::02A3CD7D-743C-FAA8-9C20-3E8E59B8C2C2 -name IM_MOD_RL_ps3_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::697020D6-C5DA-A7DC-9454-1F9523D7748D -name IM_MOD_RL_dot_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::E9D0609E-2D12-A8C0-9B47-D09CACB4A3AF -name IM_MOD_RL_xwd_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::F237A6C8-4037-B9E5-8D65-29A5A69CADFE -name IM_MOD_RL_fits_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::BDB45C50-E57A-357D-1D5A-392036227E6B -name IM_MOD_RL_histogram_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::5A6DBEB5-CD8A-4109-A04C-EF0436BC1CDC -name mfc71.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::FCC2A44D-D2F9-74DC-0C27-86F094E2C3E9 -name IM_MOD_RL_pnm_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::8E71473E-34AE-B7A3-B506-8A6AA622DAD7 -name IM_MOD_RL_ipl_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::B680E84A-BA1C-5EA2-902E-095DD22A48F2 -name msvcp71.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::6AF09D1D-8889-8A87-9FD4-1471DBB1354C -name IM_MOD_RL_rgb_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::941B86E2-428A-3F4A-EB34-CBDBDDAD648C -name IM_MOD_RL_xbm_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::1613269D-8A63-C843-E862-9B80CC17E60F -name IM_MOD_RL_otb_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::334E3925-703D-DDCA-A079-C53DB06AA069 -name IM_MOD_RL_avi_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::23549B03-F856-3B90-C9C5-3B64A5910C7B -name CORE_RL_lcms_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::0E3F5727-D99A-44CD-35E0-4FDFBB95FCBC -name IM_MOD_RL_xpm_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::955B5799-4DB3-F422-589A-CDC20A82B6CB -name IM_MOD_RL_xcf_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::D3AB494C-3218-0137-4399-3FB1662C05D3 -name IM_MOD_RL_emf_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::1DEF5AF0-2376-539B-2A61-35B6ADC2F4BA -name log.xml -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::D472B449-3644-C538-30EF-EC42E3B84C43 -name IM_MOD_RL_mtv_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::8B4E61F1-8FC2-7E65-4B94-3F19100DF58B -name CORE_RL_tiff_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::B747FE2A-0054-6815-40D0-74F89FC8C757 -name IM_MOD_RL_dps_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::8B1660CC-7A97-96A2-1280-34554028CB9F -name IM_MOD_RL_dcm_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::21B7EBEC-30C8-F2E8-9D73-E4E6965EA856 -name locale.xml -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::85087BFC-42D6-C583-586E-19CAD45E6A61 -name CORE_RL_wand_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::BE24EB64-E7BB-0E63-256E-DEDC2BBF1C2B -name IM_MOD_RL_ttf_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::7693E752-1A81-F6F3-C55D-9E8D94D6E4DC -name IM_MOD_RL_dpx_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::A1451D28-A06B-3F03-4DCA-884729C5A030 -name IM_MOD_RL_jpeg_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::3F0D8F7A-906F-8CAE-84D7-E3480A09D39D -name IM_MOD_RL_fax_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::CA8F1852-F5C1-86E8-31B9-8B1EFE837ADB -name IM_MOD_RL_avs_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::72370AAC-67CF-F570-2AA2-658E4C81C859 -name IM_MOD_RL_mvg_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::A8265A1E-E9B5-A38F-9ACF-99669CAE1E9F -name IM_MOD_RL_tga_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::A8A9A383-0364-515F-C1D8-F82C274D652B -name configure.xml -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::A2167661-AF2B-E15E-60DA-715F47E5AA30 -name IM_MOD_RL_uil_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::7608E2FF-1BF6-E18A-A884-244794BDA01B -name IM_MOD_RL_cut_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::772CA344-5EFD-78A0-3542-777F12356C8D -name Xext.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::37F8D85E-4EE9-80E5-A4A2-8F30444AD5CC -name IM_MOD_RL_scr_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::973011B9-D193-6D64-D4EB-D82B0C730379 -name IM_MOD_RL_map_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::9B9F088C-A20A-0C19-EF7D-52908A020D36 -name thresholds.xml -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::831AAF1C-7CBE-CAD3-79A8-7430E8DE484E -name IM_MOD_RL_thumbnail_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::85236603-D71F-359C-B235-98C77809DDF1 -name IM_MOD_RL_mpc_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::94300805-117F-8337-A9BF-41E10D8AB437 -name IM_MOD_RL_cmyk_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::618A299D-4A28-E37A-D4BF-9209B594FAAF -name IM_MOD_RL_pcd_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::9F6572D8-6BE6-290B-D4A7-A0D4E4DBAC23 -name IM_MOD_RL_sct_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::942E63AC-F579-0D17-FF56-E2C8CC5DECA3 -name IM_MOD_RL_pict_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::F68DE3F9-742C-D8EE-B2FC-FF9B37EED8F3 -name IM_MOD_RL_gradient_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::C460E29B-38EE-6FC0-757B-69563EFC3225 -name IM_MOD_RL_icon_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::AE00BD3D-734C-78F6-9078-C04749F4652A -name X11.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::B70ED455-A480-56E3-3BDE-E06CDDB62C04 -name IM_MOD_RL_jbig_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::CFFC9A5D-2902-FD37-DBD1-6800C7C0C1AE -name magic.xml -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::32DA3775-410C-0391-7ADB-B58028CC04E2 -name IM_MOD_RL_mat_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::AB64F079-1F8D-BE3A-731B-4B20ABD20289 -name IM_MOD_RL_meta_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::5A7F49E9-119A-FD9B-8186-0BE6B9DCF210 -name IM_MOD_RL_gray_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::D92B4157-F307-64A4-9AA5-C5AA1F138E1B -name IM_MOD_RL_pwp_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::BA631BF0-CB17-D0EC-FAA9-D7B426457DD3 -name IM_MOD_RL_fpx_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::9EA95108-72D5-13B5-2BD4-87CECED9B367 -name IM_MOD_RL_pcl_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::3111AC7E-2387-AD7D-253F-979195AC4EA1 -name english.xml -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::14C1E910-6F5D-9540-7430-6B0B92311EB2 -name IM_MOD_RL_wpg_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::5D7050F4-177A-03A2-3DD1-A7DFC968E4ED -name IM_MOD_RL_pdb_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::5BACE29D-FAFD-E673-16A9-D22DCE6E0655 -name IM_MOD_RL_label_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::25F84452-26F7-4305-B405-B1D0C7D072D2 -name IM_MOD_RL_clip_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::807E6FF7-2D61-F308-BA2A-BD07A213078A -name IM_MOD_RL_pix_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::1A951976-DBCC-9FAE-190C-B24BBA38A97A -name colors.xml -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::8608BB2C-6CDE-BBE7-39C6-DF83625D5BFB -name IM_MOD_RL_cin_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::CBE1DFDA-7E32-759F-346E-DD469B1CE1F0 -name IM_MOD_RL_bmp_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::7AD432A3-5146-4966-8C8E-85ACDCC8CA7A -name IM_MOD_RL_raw_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::9DC22033-0F40-26CC-9E09-959738F62855 -name IM_MOD_RL_cip_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::A8C777EC-AEAA-6B3F-22A6-CEC28A2E5058 -name IM_MOD_RL_pdf_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::ACA1A829-27AE-EFE7-4EDD-01D050A2E0A6 -name analyze.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::C883300E-0C2A-EAF6-D72E-81E8B99535E1 -name IM_MOD_RL_mpr_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::A223D40F-EFC5-31E3-8E33-B90984080A3E -name CORE_RL_jpeg_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::DE197248-9758-A368-6058-B72C5169E0DD -name IM_MOD_RL_wbmp_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::FD15A9C0-5C14-11CA-AA27-D66D638E58FC -name IM_MOD_RL_stegano_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::6668D58B-E040-328B-4AF4-14C738C172BA -name CORE_RL_jp2_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::871E464B-4566-1FC2-55CB-B65AEB416413 -name IM_MOD_RL_yuv_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::7D7A8325-4C69-B9D3-C832-803BCF999B5C -name coder.xml -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::B0A3651D-19B1-09F1-8197-1E58ED2CC704 -name IM_MOD_RL_null_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::AB877243-6DAE-BF0C-70C2-F2D702B16231 -name IM_MOD_RL_pattern_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::F571F366-1737-7E65-5441-DEBD166DE247 -name IM_MOD_RL_plasma_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::93F18CDE-B871-B2D4-3C0F-7C1B933E1ACB -name IM_MOD_RL_pcx_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::D1567C76-29D0-C200-9FC7-F7E1399D3011 -name CORE_RL_xlib_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::BEF1E1AC-9564-EA49-2B8F-1AAC9F6A7669 -name IM_MOD_RL_caption_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::2D14864E-6A39-FE03-4EA8-CCE7AC94487D -name comic2lrf.exe -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::27CD65A5-D5F9-C982-5096-65298417EE61 -name IM_MOD_RL_url_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::A273E901-0B63-390B-D44A-7240491C6F59 -name IM_MOD_RL_info_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::B39B27EC-325A-D222-01FC-F6B3BC92E99A -name IM_MOD_RL_hdf_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::BA634D39-716B-C895-73DD-2E5FA3CA2F9C -name CORE_RL_png_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::35560FB0-A7BD-54C7-C799-3EB2922BED2C -name delegates.xml -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::FCE51670-E4AE-B813-6CFC-A7A9B627F72C -name IM_MOD_RL_matte_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::D10DB719-887D-4898-DAA8-8F1C6A4203B2 -name IM_MOD_RL_mono_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::78A66F97-ECEC-BFEC-75F2-2FA2087927C2 -name CORE_RL_jbig_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::FB74C41B-3F08-A9E8-B38D-C7C2FDFE9560 -name IM_MOD_RL_xtrn_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::ABC0A7AF-B14B-09BE-4756-76C8FE771517 -name IM_MOD_RL_vicar_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::F69B9AAD-EC2B-5EC7-5ED8-1395033DE0F5 -name IM_MOD_RL_psd_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::66CB1D9D-9995-F71C-155D-F1F4AA3B6D40 -name IM_MOD_RL_uyvy_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::4DB66BC3-4C48-C763-9BCA-9E831CA1FF0B -name IM_MOD_RL_art_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::EB24F574-4226-6404-B069-7B46C04988E0 -name IM_MOD_RL_ycbcr_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::A9990A18-D6A1-AA14-1EDF-FC43D8AE0C7E -name type-ghostscript.xml -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::5B72558A-192B-76EB-1BA8-C4CBA43C6C05 -name IM_MOD_RL_x_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::A3937F85-1D17-D3DD-2DF5-FB9FE4A99ADB -name IM_MOD_RL_dng_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::B2C41CC1-EB2D-F7E7-B22E-0C154C4C96C1 -name IM_MOD_RL_html_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::68A62902-7F48-6E7A-E5D3-1F58C895B409 -name IM_MOD_RL_tim_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::E5C83E45-56B1-9BD7-7676-07CABD98E0BF -name IM_MOD_RL_tile_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::084206D9-98DB-DE2A-19BC-FD17A191096D -name IM_MOD_RL_xc_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::43BE7C18-6369-E035-8390-2E13C8CBB33C -name CORE_RL_zlib_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::EFC4D6E5-4FC9-25D5-B308-8CC8C13EF3A1 -name IM_MOD_RL_gif_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::05A8646B-F100-4803-5916-4CBAC154BFE9 -name IM_MOD_RL_sgi_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::1B354F22-4795-739A-A47D-8F2D99DFB58A -name IM_MOD_RL_ept_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::B6725A29-1F09-2982-6BE1-29062A90F684 -name IM_MOD_RL_palm_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::445BD28F-0E70-B452-15B3-9E0C353CE345 -name IM_MOD_RL_ps_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::183A1789-2ED2-D555-AE4B-B7EBC97EB1D5 -name IM_MOD_RL_miff_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::69178142-77D3-D7C5-74C7-6F1597474123 -name IM_MOD_RL_vid_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::9D84C810-6DEC-5831-CFC6-AD0543D95881 -name IM_MOD_RL_rla_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::514DCC61-0BE9-6C5C-A970-170219D3A87E -name IM_MOD_RL_magick_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
File ::AAF94AED-250D-DE8D-14C5-FA8BC05AAE74 -name IM_MOD_RL_djvu_.dll -parent 6CCF3F71-74BB-ED69-D0E6-9F12348ABDD3
|
||||
Component ::F6829AB7-9F66-4CEE-CA0E-21F54C6D3609 -setup Install -active Yes -platforms {AIX-ppc FreeBSD-4-x86 FreeBSD-x86 HPUX-hppa Linux-x86 Solaris-sparc Windows} -name Main -parent Components
|
||||
SetupType ::D9ADE41C-B744-690C-2CED-CF826BF03D2E -setup Install -active Yes -platforms {AIX-ppc FreeBSD-4-x86 FreeBSD-x86 HPUX-hppa Linux-x86 Solaris-sparc Windows} -name Typical -parent SetupTypes
|
||||
|
||||
|
@ -10,7 +10,6 @@ QT_DIR = 'C:\\Qt\\4.4.0'
|
||||
DEVCON = 'C:\\devcon\\i386\\devcon.exe'
|
||||
LIBUSB_DIR = 'C:\\libusb'
|
||||
LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
|
||||
CLIT = 'C:\\clit\\clit.exe'
|
||||
PDFTOHTML = 'C:\\pdftohtml\\pdftohtml.exe'
|
||||
IMAGEMAGICK_DIR = 'C:\\ImageMagick'
|
||||
FONTCONFIG_DIR = 'C:\\fontconfig'
|
||||
@ -105,8 +104,6 @@ class BuildEXE(py2exe.build_exe.py2exe):
|
||||
shutil.copyfile(DEVCON, os.path.join(tdir, os.path.basename(DEVCON)))
|
||||
print '\tAdding unrar'
|
||||
shutil.copyfile(LIBUNRAR, os.path.join(PY2EXE_DIR, os.path.basename(LIBUNRAR)))
|
||||
print '\tAdding ConvertLIT'
|
||||
shutil.copyfile(CLIT, os.path.join(PY2EXE_DIR, os.path.basename(CLIT)))
|
||||
print '\tAdding pdftohtml'
|
||||
shutil.copyfile(PDFTOHTML, os.path.join(PY2EXE_DIR, os.path.basename(PDFTOHTML)))
|
||||
print '\tAdding ImageMagick'
|
||||
|
196
pyqtdistutils.py
Normal file
196
pyqtdistutils.py
Normal file
@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
Build PyQt extensions. Integrates with distutils (but uses the PyQt build system).
|
||||
'''
|
||||
from distutils.core import Extension
|
||||
from distutils.command.build_ext import build_ext as _build_ext
|
||||
from distutils.dep_util import newer_group
|
||||
from distutils import log
|
||||
|
||||
import sipconfig, os, sys, string, glob, shutil
|
||||
from PyQt4 import pyqtconfig
|
||||
iswindows = 'win32' in sys.platform
|
||||
QMAKE = os.path.expanduser('~/qt/bin/qmake') if 'darwin' in sys.platform else'qmake'
|
||||
WINDOWS_PYTHON = ['C:/Python25/libs']
|
||||
OSX_SDK = '/Developer/SDKs/MacOSX10.4u.sdk'
|
||||
|
||||
def replace_suffix(path, new_suffix):
|
||||
return os.path.splitext(path)[0] + new_suffix
|
||||
|
||||
class PyQtExtension(Extension):
|
||||
|
||||
def __init__(self, name, sources, sip_sources, **kw):
|
||||
'''
|
||||
:param sources: Qt .cpp and .h files needed for this extension
|
||||
:param sip_sources: List of .sip files this extension depends on. The
|
||||
first .sip file will be used toactually build the extension.
|
||||
'''
|
||||
self.module_makefile = pyqtconfig.QtGuiModuleMakefile
|
||||
self.sip_sources = map(lambda x: x.replace('/', os.sep), sip_sources)
|
||||
Extension.__init__(self, name, sources, **kw)
|
||||
|
||||
|
||||
class build_ext(_build_ext):
|
||||
|
||||
def make(self, makefile):
|
||||
make = 'make'
|
||||
if iswindows:
|
||||
make = 'mingw32-make'
|
||||
self.spawn([make, '-f', makefile])
|
||||
|
||||
def build_qt_objects(self, ext, bdir):
|
||||
if not iswindows:
|
||||
bdir = os.path.join(bdir, 'qt')
|
||||
if not os.path.exists(bdir):
|
||||
os.makedirs(bdir)
|
||||
cwd = os.getcwd()
|
||||
sources = map(os.path.abspath, ext.sources)
|
||||
os.chdir(bdir)
|
||||
try:
|
||||
headers = set([f for f in sources if f.endswith('.h')])
|
||||
sources = set(sources) - headers
|
||||
name = ext.name.rpartition('.')[-1]
|
||||
pro = '''\
|
||||
TARGET = %s
|
||||
TEMPLATE = lib
|
||||
HEADERS = %s
|
||||
SOURCES = %s
|
||||
VERSION = 1.0.0
|
||||
CONFIG += x86 ppc
|
||||
'''%(name, ' '.join(headers), ' '.join(sources))
|
||||
open(name+'.pro', 'wb').write(pro)
|
||||
self.spawn([QMAKE, '-o', 'Makefile.qt', name+'.pro'])
|
||||
self.make('Makefile.qt')
|
||||
pat = 'release\\*.o' if iswindows else '*.o'
|
||||
return map(os.path.abspath, glob.glob(pat))
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
|
||||
def build_sbf(self, sip, sbf, bdir):
|
||||
sip_bin = self.sipcfg.sip_bin
|
||||
self.spawn([sip_bin,
|
||||
"-c", bdir,
|
||||
"-b", sbf,
|
||||
'-I', self.pyqtcfg.pyqt_sip_dir,
|
||||
] + self.pyqtcfg.pyqt_sip_flags.split()+
|
||||
[sip])
|
||||
|
||||
def build_pyqt(self, bdir, sbf, ext, qtobjs, headers):
|
||||
makefile = ext.module_makefile(configuration=self.pyqtcfg,
|
||||
build_file=sbf, dir=bdir,
|
||||
makefile='Makefile.pyqt',
|
||||
universal=OSX_SDK, qt=1)
|
||||
if 'win32' in sys.platform:
|
||||
makefile.extra_lib_dirs += WINDOWS_PYTHON
|
||||
makefile.extra_include_dirs = list(set(map(os.path.dirname, headers)))
|
||||
makefile.extra_lflags += qtobjs
|
||||
makefile.generate()
|
||||
cwd = os.getcwd()
|
||||
os.chdir(bdir)
|
||||
try:
|
||||
self.make('Makefile.pyqt')
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
|
||||
|
||||
|
||||
def build_extension(self, ext):
|
||||
self.inplace = True # Causes extensions to be built in the source tree
|
||||
if not isinstance(ext, PyQtExtension):
|
||||
return _build_ext.build_extension(self, ext)
|
||||
|
||||
fullname = self.get_ext_fullname(ext.name)
|
||||
if self.inplace:
|
||||
# ignore build-lib -- put the compiled extension into
|
||||
# the source tree along with pure Python modules
|
||||
|
||||
modpath = string.split(fullname, '.')
|
||||
package = string.join(modpath[0:-1], '.')
|
||||
base = modpath[-1]
|
||||
|
||||
build_py = self.get_finalized_command('build_py')
|
||||
package_dir = build_py.get_package_dir(package)
|
||||
ext_filename = os.path.join(package_dir,
|
||||
self.get_ext_filename(base))
|
||||
else:
|
||||
ext_filename = os.path.join(self.build_lib,
|
||||
self.get_ext_filename(fullname))
|
||||
bdir = os.path.abspath(os.path.join(self.build_temp, fullname))
|
||||
if not os.path.exists(bdir):
|
||||
os.makedirs(bdir)
|
||||
ext.sources = map(os.path.abspath, ext.sources)
|
||||
qt_dir = 'qt\\release' if iswindows else 'qt'
|
||||
objects = set(map(lambda x: os.path.join(bdir, qt_dir, replace_suffix(os.path.basename(x), '.o')),
|
||||
[s for s in ext.sources if not s.endswith('.h')]))
|
||||
newer = False
|
||||
for object in objects:
|
||||
if newer_group(ext.sources, object, missing='newer'):
|
||||
newer = True
|
||||
break
|
||||
headers = [f for f in ext.sources if f.endswith('.h')]
|
||||
if self.force or newer:
|
||||
log.info('building \'%s\' extension', ext.name)
|
||||
objects = self.build_qt_objects(ext, bdir)
|
||||
|
||||
self.sipcfg = sipconfig.Configuration()
|
||||
self.pyqtcfg = pyqtconfig.Configuration()
|
||||
sbf_sources = []
|
||||
for sip in ext.sip_sources:
|
||||
sipbasename = os.path.basename(sip)
|
||||
sbf = os.path.join(bdir, replace_suffix(sipbasename, ".sbf"))
|
||||
sbf_sources.append(sbf)
|
||||
if self.force or newer_group(ext.sip_sources, sbf, 'newer'):
|
||||
self.build_sbf(sip, sbf, bdir)
|
||||
generated_sources = []
|
||||
for sbf in sbf_sources:
|
||||
generated_sources += self.get_sip_output_list(sbf, bdir)
|
||||
|
||||
depends = generated_sources + list(objects)
|
||||
mod = os.path.join(bdir, os.path.basename(ext_filename))
|
||||
|
||||
if self.force or newer_group(depends, mod, 'newer'):
|
||||
self.build_pyqt(bdir, sbf_sources[0], ext, list(objects), headers)
|
||||
|
||||
if self.force or newer_group([mod], ext_filename, 'newer'):
|
||||
if os.path.exists(ext_filename):
|
||||
os.unlink(ext_filename)
|
||||
shutil.copyfile(mod, ext_filename)
|
||||
shutil.copymode(mod, ext_filename)
|
||||
|
||||
def get_sip_output_list(self, sbf, bdir):
|
||||
"""
|
||||
Parse the sbf file specified to extract the name of the generated source
|
||||
files. Make them absolute assuming they reside in the temp directory.
|
||||
"""
|
||||
for L in file(sbf):
|
||||
key, value = L.split("=", 1)
|
||||
if key.strip() == "sources":
|
||||
out = []
|
||||
for o in value.split():
|
||||
out.append(os.path.join(bdir, o))
|
||||
return out
|
||||
|
||||
raise RuntimeError, "cannot parse SIP-generated '%s'" % sbf
|
||||
|
||||
def run_sip(self, sip_files):
|
||||
sip_bin = self.sipcfg.sip_bin
|
||||
sip_sources = [i[0] for i in sip_files]
|
||||
generated_sources = []
|
||||
for sip, sbf in sip_files:
|
||||
if not (self.force or newer_group(sip_sources, sbf, 'newer')):
|
||||
log.info(sbf + ' is up to date')
|
||||
continue
|
||||
self.spawn([sip_bin,
|
||||
"-c", self.build_temp,
|
||||
"-b", sbf,
|
||||
'-I', self.pyqtcfg.pyqt_sip_dir,
|
||||
] + self.pyqtcfg.pyqt_sip_flags.split()+
|
||||
[sip])
|
||||
generated_sources += self.get_sip_output_list(sbf)
|
||||
return generated_sources
|
||||
|
||||
|
53
setup.py
53
setup.py
@ -7,7 +7,7 @@ sys.path.append('src')
|
||||
iswindows = re.search('win(32|64)', sys.platform)
|
||||
isosx = 'darwin' in sys.platform
|
||||
islinux = not isosx and not iswindows
|
||||
src = open('src/calibre/__init__.py', 'rb').read()
|
||||
src = open('src/calibre/constants.py', 'rb').read()
|
||||
VERSION = re.search(r'__version__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
|
||||
APPNAME = re.search(r'__appname__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
|
||||
print 'Setup', APPNAME, 'version:', VERSION
|
||||
@ -47,17 +47,25 @@ main_functions = {
|
||||
|
||||
if __name__ == '__main__':
|
||||
from setuptools import setup, find_packages, Extension
|
||||
from pyqtdistutils import PyQtExtension, build_ext
|
||||
import subprocess, glob
|
||||
|
||||
entry_points['console_scripts'].append('calibre_postinstall = calibre.linux:post_install')
|
||||
ext_modules = [Extension('calibre.plugins.lzx',
|
||||
ext_modules = [
|
||||
Extension('calibre.plugins.lzx',
|
||||
sources=['src/calibre/utils/lzx/lzxmodule.c',
|
||||
'src/calibre/utils/lzx/lzxd.c'],
|
||||
include_dirs=['src/calibre/utils/lzx']),
|
||||
Extension('calibre.plugins.msdes',
|
||||
sources=['src/calibre/utils/msdes/msdesmodule.c',
|
||||
'src/calibre/utils/msdes/des.c'],
|
||||
include_dirs=['src/calibre/utils/msdes'])]
|
||||
include_dirs=['src/calibre/utils/msdes']),
|
||||
PyQtExtension('calibre.plugins.pictureflow',
|
||||
['src/calibre/gui2/pictureflow/pictureflow.cpp',
|
||||
'src/calibre/gui2/pictureflow/pictureflow.h'],
|
||||
['src/calibre/gui2/pictureflow/pictureflow.sip']
|
||||
)
|
||||
]
|
||||
if iswindows:
|
||||
ext_modules.append(Extension('calibre.plugins.winutil',
|
||||
sources=['src/calibre/utils/windows/winutil.c'],
|
||||
@ -69,42 +77,6 @@ if __name__ == '__main__':
|
||||
sources=['src/calibre/devices/usbobserver/usbobserver.c'])
|
||||
)
|
||||
|
||||
def build_PyQt_extension(path):
|
||||
pro = glob.glob(os.path.join(path, '*.pro'))[0]
|
||||
raw = open(pro).read()
|
||||
base = qtplugin = re.search(r'TARGET\s*=\s*(.*)', raw).group(1)
|
||||
ver = re.search(r'VERSION\s*=\s*(\d+)', raw).group(1)
|
||||
cwd = os.getcwd()
|
||||
os.chdir(os.path.dirname(pro))
|
||||
try:
|
||||
if not os.path.exists('.build'):
|
||||
os.mkdir('.build')
|
||||
os.chdir('.build')
|
||||
subprocess.check_call(( (os.path.expanduser('~/qt/bin/qmake') if isosx else 'qmake'), '..'+os.sep+os.path.basename(pro)))
|
||||
subprocess.check_call(['mingw32-make' if iswindows else 'make'])
|
||||
os.chdir(os.path.join('..', 'PyQt'))
|
||||
if not os.path.exists('.build'):
|
||||
os.mkdir('.build')
|
||||
os.chdir('.build')
|
||||
python = '/Library/Frameworks/Python.framework/Versions/Current/bin/python' if isosx else 'python'
|
||||
subprocess.check_call([python, '..'+os.sep+'configure.py'])
|
||||
subprocess.check_call(['mingw32-make' if iswindows else 'make'])
|
||||
ext = '.pyd' if iswindows else '.so'
|
||||
plugin = glob.glob(base+ext)[0]
|
||||
shutil.copyfile(plugin, os.path.join(cwd, 'src', 'calibre', 'plugins', plugin))
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
if islinux or isosx:
|
||||
for f in glob.glob(os.path.join('src', 'calibre', 'plugins', '*')):
|
||||
try:
|
||||
os.readlink(f)
|
||||
os.unlink(f)
|
||||
except:
|
||||
continue
|
||||
|
||||
for path in [(os.path.join('src', 'calibre', 'gui2', 'pictureflow'))]:
|
||||
build_PyQt_extension(path)
|
||||
|
||||
setup(
|
||||
name=APPNAME,
|
||||
packages = find_packages('src'),
|
||||
@ -152,7 +124,8 @@ if __name__ == '__main__':
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Topic :: System :: Hardware :: Hardware Drivers'
|
||||
]
|
||||
],
|
||||
cmdclass = {'build_ext': build_ext},
|
||||
)
|
||||
|
||||
if 'develop' in ' '.join(sys.argv) and islinux:
|
||||
|
@ -1,122 +1,28 @@
|
||||
''' E-book management software'''
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__version__ = '0.4.80'
|
||||
__docformat__ = "epytext"
|
||||
__author__ = "Kovid Goyal <kovid at kovidgoyal.net>"
|
||||
__appname__ = 'calibre'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import sys, os, logging, mechanize, locale, copy, cStringIO, re, subprocess, \
|
||||
textwrap, atexit, cPickle, codecs, time
|
||||
from gettext import GNUTranslations
|
||||
import sys, os, re, logging, time, subprocess, mechanize, atexit
|
||||
from htmlentitydefs import name2codepoint
|
||||
from math import floor
|
||||
from optparse import OptionParser as _OptionParser
|
||||
from optparse import IndentedHelpFormatter
|
||||
from logging import Formatter
|
||||
|
||||
from PyQt4.QtCore import QSettings, QVariant, QUrl, QByteArray, QString
|
||||
from PyQt4.QtGui import QDesktopServices
|
||||
|
||||
from calibre.translations.msgfmt import make
|
||||
from calibre.ebooks.chardet import detect
|
||||
from calibre.utils.terminfo import TerminalController
|
||||
|
||||
terminal_controller = TerminalController(sys.stdout)
|
||||
iswindows = 'win32' in sys.platform.lower() or 'win64' in sys.platform.lower()
|
||||
isosx = 'darwin' in sys.platform.lower()
|
||||
islinux = not(iswindows or isosx)
|
||||
isfrozen = hasattr(sys, 'frozen')
|
||||
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
except:
|
||||
dl = locale.getdefaultlocale()
|
||||
try:
|
||||
if dl:
|
||||
locale.setlocale(dl[0])
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
preferred_encoding = locale.getpreferredencoding()
|
||||
codecs.lookup(preferred_encoding)
|
||||
except:
|
||||
preferred_encoding = 'utf-8'
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
if iswindows:
|
||||
plugin_path = os.path.join(os.path.dirname(sys.executable), 'plugins')
|
||||
elif isosx:
|
||||
plugin_path = os.path.join(getattr(sys, 'frameworks_dir'), 'plugins')
|
||||
elif islinux:
|
||||
plugin_path = os.path.join(getattr(sys, 'frozen_path'), 'plugins')
|
||||
sys.path.insert(0, plugin_path)
|
||||
else:
|
||||
import pkg_resources
|
||||
plugins = getattr(pkg_resources, 'resource_filename')(__appname__, 'plugins')
|
||||
sys.path.insert(0, plugins)
|
||||
|
||||
if iswindows and getattr(sys, 'frozen', False):
|
||||
sys.path.insert(1, os.path.dirname(sys.executable))
|
||||
from PyQt4.QtCore import QUrl
|
||||
from PyQt4.QtGui import QDesktopServices
|
||||
from calibre.startup import plugins, winutil, winutilerror
|
||||
from calibre.constants import iswindows, isosx, islinux, isfrozen, \
|
||||
terminal_controller, preferred_encoding, \
|
||||
__appname__, __version__, __author__, \
|
||||
win32event, win32api, winerror, fcntl
|
||||
|
||||
|
||||
plugins = {}
|
||||
for plugin in ['pictureflow', 'lzx', 'msdes'] + \
|
||||
(['winutil'] if iswindows else []) + \
|
||||
(['usbobserver'] if isosx else []):
|
||||
try:
|
||||
p, err = __import__(plugin), ''
|
||||
except Exception, err:
|
||||
p = None
|
||||
err = str(err)
|
||||
plugins[plugin] = (p, err)
|
||||
|
||||
if iswindows:
|
||||
winutil, winutilerror = plugins['winutil']
|
||||
if not winutil:
|
||||
raise RuntimeError('Failed to load the winutil plugin: %s'%winutilerror)
|
||||
sys.argv[1:] = winutil.argv()[1:]
|
||||
win32event = __import__('win32event')
|
||||
winerror = __import__('winerror')
|
||||
win32api = __import__('win32api')
|
||||
else:
|
||||
import fcntl
|
||||
|
||||
_abspath = os.path.abspath
|
||||
def my_abspath(path, encoding=sys.getfilesystemencoding()):
|
||||
'''
|
||||
Work around for buggy os.path.abspath. This function accepts either byte strings,
|
||||
in which it calls os.path.abspath, or unicode string, in which case it first converts
|
||||
to byte strings using `encoding`, calls abspath and then decodes back to unicode.
|
||||
'''
|
||||
to_unicode = False
|
||||
if isinstance(path, unicode):
|
||||
path = path.encode(encoding)
|
||||
to_unicode = True
|
||||
res = _abspath(path)
|
||||
if to_unicode:
|
||||
res = res.decode(encoding)
|
||||
return res
|
||||
|
||||
os.path.abspath = my_abspath
|
||||
_join = os.path.join
|
||||
def my_join(a, *p):
|
||||
encoding=sys.getfilesystemencoding()
|
||||
p = [a] + list(p)
|
||||
_unicode = False
|
||||
for i in p:
|
||||
if isinstance(i, unicode):
|
||||
_unicode = True
|
||||
break
|
||||
p = [i.encode(encoding) if isinstance(i, unicode) else i for i in p]
|
||||
|
||||
res = _join(*p)
|
||||
if _unicode:
|
||||
res = res.decode(encoding)
|
||||
return res
|
||||
|
||||
os.path.join = my_join
|
||||
def unicode_path(path, abs=False):
|
||||
if not isinstance(path, unicode):
|
||||
path = path.decode(sys.getfilesystemencoding())
|
||||
if abs:
|
||||
path = os.path.abspath(path)
|
||||
return path
|
||||
|
||||
def osx_version():
|
||||
if isosx:
|
||||
@ -127,10 +33,6 @@ def osx_version():
|
||||
return int(m.group(1)), int(m.group(2)), int(m.group(3))
|
||||
|
||||
|
||||
# Default translation is NOOP
|
||||
import __builtin__
|
||||
__builtin__.__dict__['_'] = lambda s: s
|
||||
|
||||
class CommandLineError(Exception):
|
||||
pass
|
||||
|
||||
@ -172,121 +74,6 @@ def setup_cli_handlers(logger, level):
|
||||
|
||||
logger.addHandler(handler)
|
||||
|
||||
class CustomHelpFormatter(IndentedHelpFormatter):
|
||||
|
||||
def format_usage(self, usage):
|
||||
return _("%sUsage%s: %s\n") % (terminal_controller.BLUE, terminal_controller.NORMAL, usage)
|
||||
|
||||
def format_heading(self, heading):
|
||||
return "%*s%s%s%s:\n" % (self.current_indent, terminal_controller.BLUE,
|
||||
"", heading, terminal_controller.NORMAL)
|
||||
|
||||
def format_option(self, option):
|
||||
result = []
|
||||
opts = self.option_strings[option]
|
||||
opt_width = self.help_position - self.current_indent - 2
|
||||
if len(opts) > opt_width:
|
||||
opts = "%*s%s\n" % (self.current_indent, "",
|
||||
terminal_controller.GREEN+opts+terminal_controller.NORMAL)
|
||||
indent_first = self.help_position
|
||||
else: # start help on same line as opts
|
||||
opts = "%*s%-*s " % (self.current_indent, "", opt_width + len(terminal_controller.GREEN + terminal_controller.NORMAL),
|
||||
terminal_controller.GREEN + opts + terminal_controller.NORMAL)
|
||||
indent_first = 0
|
||||
result.append(opts)
|
||||
if option.help:
|
||||
help_text = self.expand_default(option).split('\n')
|
||||
help_lines = []
|
||||
|
||||
for line in help_text:
|
||||
help_lines.extend(textwrap.wrap(line, self.help_width))
|
||||
result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
|
||||
result.extend(["%*s%s\n" % (self.help_position, "", line)
|
||||
for line in help_lines[1:]])
|
||||
elif opts[-1] != "\n":
|
||||
result.append("\n")
|
||||
return "".join(result)+'\n'
|
||||
|
||||
class OptionParser(_OptionParser):
|
||||
|
||||
def __init__(self,
|
||||
usage='%prog [options] filename',
|
||||
version='%%prog (%s %s)'%(__appname__, __version__),
|
||||
epilog=_('Created by ')+terminal_controller.RED+__author__+terminal_controller.NORMAL,
|
||||
gui_mode=False,
|
||||
conflict_handler='resolve',
|
||||
**kwds):
|
||||
usage += '''\n\nWhenever you pass arguments to %prog that have spaces in them, '''\
|
||||
'''enclose the arguments in quotation marks.'''
|
||||
_OptionParser.__init__(self, usage=usage, version=version, epilog=epilog,
|
||||
formatter=CustomHelpFormatter(),
|
||||
conflict_handler=conflict_handler, **kwds)
|
||||
self.gui_mode = gui_mode
|
||||
|
||||
def error(self, msg):
|
||||
if self.gui_mode:
|
||||
raise Exception(msg)
|
||||
_OptionParser.error(self, msg)
|
||||
|
||||
def merge(self, parser):
|
||||
'''
|
||||
Add options from parser to self. In case of conflicts, confilicting options from
|
||||
parser are skipped.
|
||||
'''
|
||||
opts = list(parser.option_list)
|
||||
groups = list(parser.option_groups)
|
||||
|
||||
def merge_options(options, container):
|
||||
for opt in copy.deepcopy(options):
|
||||
if not self.has_option(opt.get_opt_string()):
|
||||
container.add_option(opt)
|
||||
|
||||
merge_options(opts, self)
|
||||
|
||||
for group in groups:
|
||||
g = self.add_option_group(group.title)
|
||||
merge_options(group.option_list, g)
|
||||
|
||||
def subsume(self, group_name, msg=''):
|
||||
'''
|
||||
Move all existing options into a subgroup named
|
||||
C{group_name} with description C{msg}.
|
||||
'''
|
||||
opts = [opt for opt in self.options_iter() if opt.get_opt_string() not in ('--version', '--help')]
|
||||
self.option_groups = []
|
||||
subgroup = self.add_option_group(group_name, msg)
|
||||
for opt in opts:
|
||||
self.remove_option(opt.get_opt_string())
|
||||
subgroup.add_option(opt)
|
||||
|
||||
def options_iter(self):
|
||||
for opt in self.option_list:
|
||||
if str(opt).strip():
|
||||
yield opt
|
||||
for gr in self.option_groups:
|
||||
for opt in gr.option_list:
|
||||
if str(opt).strip():
|
||||
yield opt
|
||||
|
||||
def option_by_dest(self, dest):
|
||||
for opt in self.options_iter():
|
||||
if opt.dest == dest:
|
||||
return opt
|
||||
|
||||
def merge_options(self, lower, upper):
|
||||
'''
|
||||
Merge options in lower and upper option lists into upper.
|
||||
Default values in upper are overriden by
|
||||
non default values in lower.
|
||||
'''
|
||||
for dest in lower.__dict__.keys():
|
||||
if not upper.__dict__.has_key(dest):
|
||||
continue
|
||||
opt = self.option_by_dest(dest)
|
||||
if lower.__dict__[dest] != opt.default and \
|
||||
upper.__dict__[dest] == opt.default:
|
||||
upper.__dict__[dest] = lower.__dict__[dest]
|
||||
|
||||
|
||||
def load_library(name, cdll):
|
||||
if iswindows:
|
||||
@ -319,36 +106,37 @@ def extract(path, dir):
|
||||
extractor(path, dir)
|
||||
|
||||
def get_proxies():
|
||||
proxies = {}
|
||||
if iswindows:
|
||||
try:
|
||||
winreg = __import__('_winreg')
|
||||
settings = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
|
||||
'Software\\Microsoft\\Windows'
|
||||
'\\CurrentVersion\\Internet Settings')
|
||||
proxy = winreg.QueryValueEx(settings, "ProxyEnable")[0]
|
||||
if proxy:
|
||||
server = str(winreg.QueryValueEx(settings, 'ProxyServer')[0])
|
||||
if ';' in server:
|
||||
for p in server.split(';'):
|
||||
protocol, address = p.split('=')
|
||||
proxies[protocol] = address
|
||||
else:
|
||||
proxies['http'] = server
|
||||
proxies['ftp'] = server
|
||||
settings.Close()
|
||||
except Exception, e:
|
||||
print('Unable to detect proxy settings: %s' % str(e))
|
||||
if proxies:
|
||||
print('Using proxies: %s' % proxies)
|
||||
else:
|
||||
for q in ('http', 'ftp'):
|
||||
proxy = os.environ.get(q+'_proxy', None)
|
||||
if not proxy: continue
|
||||
if proxy.startswith(q+'://'):
|
||||
proxy = proxy[7:]
|
||||
proxies[q] = proxy
|
||||
return proxies
|
||||
proxies = {}
|
||||
|
||||
for q in ('http', 'ftp'):
|
||||
proxy = os.environ.get(q+'_proxy', None)
|
||||
if not proxy: continue
|
||||
if proxy.startswith(q+'://'):
|
||||
proxy = proxy[7:]
|
||||
proxies[q] = proxy
|
||||
|
||||
if iswindows:
|
||||
try:
|
||||
winreg = __import__('_winreg')
|
||||
settings = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
|
||||
'Software\\Microsoft\\Windows'
|
||||
'\\CurrentVersion\\Internet Settings')
|
||||
proxy = winreg.QueryValueEx(settings, "ProxyEnable")[0]
|
||||
if proxy:
|
||||
server = str(winreg.QueryValueEx(settings, 'ProxyServer')[0])
|
||||
if ';' in server:
|
||||
for p in server.split(';'):
|
||||
protocol, address = p.split('=')
|
||||
proxies[protocol] = address
|
||||
else:
|
||||
proxies['http'] = server
|
||||
proxies['ftp'] = server
|
||||
settings.Close()
|
||||
except Exception, e:
|
||||
print('Unable to detect proxy settings: %s' % str(e))
|
||||
if proxies:
|
||||
print('Using proxies: %s' % proxies)
|
||||
return proxies
|
||||
|
||||
|
||||
def browser(honor_time=False):
|
||||
@ -383,40 +171,6 @@ def fit_image(width, height, pwidth, pheight):
|
||||
|
||||
return scaled, int(width), int(height)
|
||||
|
||||
def get_lang():
|
||||
lang = locale.getdefaultlocale()[0]
|
||||
if lang is None and os.environ.has_key('LANG'): # Needed for OS X
|
||||
try:
|
||||
lang = os.environ['LANG']
|
||||
except:
|
||||
pass
|
||||
if lang:
|
||||
match = re.match('[a-z]{2,3}', lang)
|
||||
if match:
|
||||
lang = match.group()
|
||||
return lang
|
||||
|
||||
def set_translator():
|
||||
# To test different translations invoke as
|
||||
# LC_ALL=de_DE.utf8 program
|
||||
try:
|
||||
from calibre.translations.compiled import translations
|
||||
except:
|
||||
return
|
||||
lang = get_lang()
|
||||
if lang:
|
||||
buf = None
|
||||
if os.access(lang+'.po', os.R_OK):
|
||||
buf = cStringIO.StringIO()
|
||||
make(lang+'.po', buf)
|
||||
buf = cStringIO.StringIO(buf.getvalue())
|
||||
elif translations.has_key(lang):
|
||||
buf = cStringIO.StringIO(translations[lang])
|
||||
if buf is not None:
|
||||
t = GNUTranslations(buf)
|
||||
t.install(unicode=True)
|
||||
|
||||
set_translator()
|
||||
|
||||
def sanitize_file_name(name):
|
||||
'''
|
||||
@ -501,120 +255,6 @@ def relpath(target, base=os.curdir):
|
||||
rel_list = [os.pardir] * (len(base_list)-i) + target_list[i:]
|
||||
return os.path.join(*rel_list)
|
||||
|
||||
def _clean_lock_file(file):
|
||||
try:
|
||||
file.close()
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
os.remove(file.name)
|
||||
except:
|
||||
pass
|
||||
|
||||
class LockError(Exception):
|
||||
pass
|
||||
class ExclusiveFile(object):
|
||||
|
||||
def __init__(self, path, timeout=10):
|
||||
self.path = path
|
||||
self.timeout = timeout
|
||||
|
||||
def __enter__(self):
|
||||
self.file = open(self.path, 'a+b')
|
||||
self.file.seek(0)
|
||||
timeout = self.timeout
|
||||
if iswindows:
|
||||
name = ('Local\\'+(__appname__+self.file.name).replace('\\', '_'))[:201]
|
||||
while self.timeout < 0 or timeout >= 0:
|
||||
self.mutex = win32event.CreateMutex(None, False, name)
|
||||
if win32api.GetLastError() != winerror.ERROR_ALREADY_EXISTS: break
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
else:
|
||||
while self.timeout < 0 or timeout >= 0:
|
||||
try:
|
||||
fcntl.lockf(self.file.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
|
||||
break
|
||||
except IOError:
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
if timeout < 0 and self.timeout >= 0:
|
||||
self.file.close()
|
||||
raise LockError
|
||||
return self.file
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.file.close()
|
||||
if iswindows:
|
||||
win32api.CloseHandle(self.mutex)
|
||||
|
||||
def singleinstance(name):
|
||||
'''
|
||||
Return True if no other instance of the application identified by name is running,
|
||||
False otherwise.
|
||||
@param name: The name to lock.
|
||||
@type name: string
|
||||
'''
|
||||
if iswindows:
|
||||
mutexname = 'mutexforsingleinstanceof'+__appname__+name
|
||||
mutex = win32event.CreateMutex(None, False, mutexname)
|
||||
if mutex:
|
||||
atexit.register(win32api.CloseHandle, mutex)
|
||||
return not win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS
|
||||
else:
|
||||
global _lock_file
|
||||
path = os.path.expanduser('~/.'+__appname__+'_'+name+'.lock')
|
||||
try:
|
||||
f = open(path, 'w')
|
||||
fcntl.lockf(f.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
|
||||
atexit.register(_clean_lock_file, f)
|
||||
return True
|
||||
except IOError:
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
class Settings(QSettings):
|
||||
|
||||
def __init__(self, name='calibre2'):
|
||||
QSettings.__init__(self, QSettings.IniFormat, QSettings.UserScope,
|
||||
'kovidgoyal.net', name)
|
||||
|
||||
def get(self, key, default=None):
|
||||
try:
|
||||
key = str(key)
|
||||
if not self.contains(key):
|
||||
return default
|
||||
val = str(self.value(key, QVariant()).toByteArray())
|
||||
if not val:
|
||||
return None
|
||||
return cPickle.loads(val)
|
||||
except:
|
||||
return default
|
||||
|
||||
def set(self, key, val):
|
||||
val = cPickle.dumps(val, -1)
|
||||
self.setValue(str(key), QVariant(QByteArray(val)))
|
||||
|
||||
_settings = Settings()
|
||||
|
||||
if not _settings.get('rationalized'):
|
||||
__settings = Settings(name='calibre')
|
||||
dbpath = os.path.join(os.path.expanduser('~'), 'library1.db').decode(sys.getfilesystemencoding())
|
||||
dbpath = unicode(__settings.value('database path',
|
||||
QVariant(QString.fromUtf8(dbpath.encode('utf-8')))).toString())
|
||||
cmdline = __settings.value('LRF conversion defaults', QVariant(QByteArray(''))).toByteArray().data()
|
||||
_settings.set('database path', dbpath)
|
||||
if cmdline:
|
||||
cmdline = cPickle.loads(cmdline)
|
||||
_settings.set('LRF conversion defaults', cmdline)
|
||||
_settings.set('rationalized', True)
|
||||
try:
|
||||
os.unlink(unicode(__settings.fileName()))
|
||||
except:
|
||||
pass
|
||||
_settings.set('database path', dbpath)
|
||||
|
||||
_spat = re.compile(r'^the\s+|^a\s+|^an\s+', re.IGNORECASE)
|
||||
def english_sort(x, y):
|
||||
'''
|
||||
@ -662,10 +302,7 @@ def strftime(fmt, t=time.localtime()):
|
||||
A version of strtime that returns unicode strings.
|
||||
'''
|
||||
result = time.strftime(fmt, t)
|
||||
try:
|
||||
return unicode(result, locale.getpreferredencoding(), 'replace')
|
||||
except:
|
||||
return unicode(result, 'utf-8', 'replace')
|
||||
return unicode(result, preferred_encoding, 'replace')
|
||||
|
||||
def entity_to_unicode(match, exceptions=[], encoding='cp1252'):
|
||||
'''
|
||||
@ -707,5 +344,10 @@ if isosx:
|
||||
if not os.path.exists(os.path.join(fdir, 'LiberationSans_Regular.ttf')):
|
||||
from calibre.ebooks.lrf.fonts.liberation import __all__ as fonts
|
||||
for font in fonts:
|
||||
exec 'from calibre.ebooks.lrf.fonts.liberation.'+font+' import font_data'
|
||||
open(os.path.join(fdir, font+'.ttf'), 'wb').write(font_data)
|
||||
l = {}
|
||||
exec 'from calibre.ebooks.lrf.fonts.liberation.'+font+' import font_data' in l
|
||||
open(os.path.join(fdir, font+'.ttf'), 'wb').write(l['font_data'])
|
||||
|
||||
# Migrate from QSettings based config system
|
||||
from calibre.utils.config import migrate
|
||||
migrate()
|
||||
|
30
src/calibre/constants.py
Normal file
30
src/calibre/constants.py
Normal file
@ -0,0 +1,30 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = 'calibre'
|
||||
__version__ = '0.4.83'
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
'''
|
||||
Various run time constants.
|
||||
'''
|
||||
|
||||
import sys, locale, codecs
|
||||
from calibre.utils.terminfo import TerminalController
|
||||
|
||||
terminal_controller = TerminalController(sys.stdout)
|
||||
|
||||
iswindows = 'win32' in sys.platform.lower() or 'win64' in sys.platform.lower()
|
||||
isosx = 'darwin' in sys.platform.lower()
|
||||
islinux = not(iswindows or isosx)
|
||||
isfrozen = hasattr(sys, 'frozen')
|
||||
|
||||
try:
|
||||
preferred_encoding = locale.getpreferredencoding()
|
||||
codecs.lookup(preferred_encoding)
|
||||
except:
|
||||
preferred_encoding = 'utf-8'
|
||||
|
||||
win32event = __import__('win32event') if iswindows else None
|
||||
winerror = __import__('winerror') if iswindows else None
|
||||
win32api = __import__('win32api') if iswindows else None
|
||||
fcntl = None if iswindows else __import__('fcntl')
|
@ -7,7 +7,8 @@ Embedded console for debugging.
|
||||
'''
|
||||
|
||||
import sys, os, re
|
||||
from calibre import OptionParser, iswindows
|
||||
from calibre.utils.config import OptionParser
|
||||
from calibre.constants import iswindows, isosx
|
||||
from calibre.libunzip import update
|
||||
|
||||
def option_parser():
|
||||
@ -25,7 +26,7 @@ Run an embedded python interpreter.
|
||||
def update_zipfile(zipfile, mod, path):
|
||||
if 'win32' in sys.platform:
|
||||
print 'WARNING: On Windows Vista you must run this from a console that has been started in Administrator mode.'
|
||||
print 'Press Enter to continue or Ctrl-C to Cancel'
|
||||
print 'Press Enter to continue if this is an Administrator console or Ctrl-C to Cancel'
|
||||
raw_input()
|
||||
pat = re.compile(mod.replace('.', '/')+r'\.py[co]*')
|
||||
name = mod.replace('.', '/') + os.path.splitext(path)[-1]
|
||||
@ -35,8 +36,13 @@ def update_zipfile(zipfile, mod, path):
|
||||
def update_module(mod, path):
|
||||
if not hasattr(sys, 'frozen'):
|
||||
raise RuntimeError('Modules can only be updated in frozen installs.')
|
||||
if True or iswindows:
|
||||
zp = None
|
||||
if iswindows:
|
||||
zp = os.path.join(os.path.dirname(sys.executable), 'library.zip')
|
||||
elif isosx:
|
||||
zp = os.path.join(os.path.dirname(getattr(sys, 'frameworks_dir')),
|
||||
'Resources', 'lib', 'python2.5', 'site-packages.zip')
|
||||
if zp is not None:
|
||||
update_zipfile(zp, mod, path)
|
||||
else:
|
||||
raise ValueError('Updating modules is not supported on this platform.')
|
||||
|
@ -11,7 +11,7 @@ from optparse import OptionParser
|
||||
|
||||
from calibre import __version__, iswindows, __appname__
|
||||
from calibre.devices.errors import PathError
|
||||
from calibre.terminfo import TerminalController
|
||||
from calibre.utils.terminfo import TerminalController
|
||||
from calibre.devices.errors import ArgumentError, DeviceError, DeviceLocked
|
||||
from calibre.devices import devices
|
||||
from calibre.devices.scanner import DeviceScanner
|
||||
|
@ -135,7 +135,7 @@ class PRS505(Device):
|
||||
if 'PRS-505/UC&' in device_id:
|
||||
main = volumes[device_id]+':\\'
|
||||
if not main:
|
||||
DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__)
|
||||
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__)
|
||||
self._main_prefix = main
|
||||
card = self._card_prefix = None
|
||||
win32api = __import__('win32api')
|
||||
|
8
src/calibre/ebooks/epub/__init__.py
Normal file
8
src/calibre/ebooks/epub/__init__.py
Normal file
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
Conversion to EPUB.
|
||||
'''
|
199
src/calibre/ebooks/epub/traverse.py
Normal file
199
src/calibre/ebooks/epub/traverse.py
Normal file
@ -0,0 +1,199 @@
|
||||
from __future__ import with_statement
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
Recursively parse HTML files to find all linked files. See :function:`traverse`.
|
||||
'''
|
||||
|
||||
import sys, os, re
|
||||
from urlparse import urlparse
|
||||
from urllib import unquote
|
||||
from calibre import unicode_path
|
||||
from calibre.ebooks.chardet import xml_to_unicode
|
||||
|
||||
class Link(object):
|
||||
'''
|
||||
Represents a link in a HTML file.
|
||||
'''
|
||||
|
||||
@classmethod
|
||||
def url_to_local_path(cls, url, base):
|
||||
path = url.path
|
||||
if os.path.isabs(path):
|
||||
return path
|
||||
return os.path.abspath(os.path.join(base, path))
|
||||
|
||||
def __init__(self, url, base):
|
||||
'''
|
||||
:param url: The url this link points to. Must be an unquoted unicode string.
|
||||
:param base: The base directory that relative URLs are with respect to.
|
||||
Must be a unicode string.
|
||||
'''
|
||||
assert isinstance(url, unicode) and isinstance(base, unicode)
|
||||
self.url = url
|
||||
self.parsed_url = urlparse(unquote(self.url))
|
||||
self.is_local = self.parsed_url.scheme in ('', 'file')
|
||||
self.is_internal = self.is_local and not bool(self.parsed_url.path)
|
||||
self.path = None
|
||||
self.fragment = self.parsed_url.fragment
|
||||
if self.is_local and not self.is_internal:
|
||||
self.path = self.url_to_local_path(self.parsed_url, base)
|
||||
|
||||
def __hash__(self):
|
||||
if self.path is None:
|
||||
return hash(self.url)
|
||||
return hash(self.path)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.path == getattr(other, 'path', other)
|
||||
|
||||
def __str__(self):
|
||||
return u'Link: %s --> %s'%(self.url, self.path)
|
||||
|
||||
|
||||
class IgnoreFile(Exception):
|
||||
|
||||
def __init__(self, msg, errno):
|
||||
Exception.__init__(self, msg)
|
||||
self.doesnt_exist = errno == 2
|
||||
self.errno = errno
|
||||
|
||||
class HTMLFile(object):
|
||||
'''
|
||||
Contains basic information about an HTML file. This
|
||||
includes a list of links to other files as well as
|
||||
the encoding of each file. Also tries to detect if the file is not a HTML
|
||||
file in which case :member:`is_binary` is set to True.
|
||||
|
||||
The encoding of the file is available as :member:`encoding`.
|
||||
'''
|
||||
|
||||
HTML_PAT = re.compile(r'<\s*html', re.IGNORECASE)
|
||||
LINK_PAT = re.compile(
|
||||
r'<\s*a\s+.*?href\s*=\s*(?:(?:"(?P<url1>[^"]+)")|(?:\'(?P<url2>[^\']+)\')|(?P<url3>[^\s]+))',
|
||||
re.DOTALL|re.IGNORECASE)
|
||||
|
||||
def __init__(self, path_to_html_file, level, encoding, verbose):
|
||||
'''
|
||||
:param level: The level of this file. Should be 0 for the root file.
|
||||
:param encoding: Use `encoding` to decode HTML.
|
||||
'''
|
||||
self.path = unicode_path(path_to_html_file, abs=True)
|
||||
self.base = os.path.dirname(self.path)
|
||||
self.level = level
|
||||
self.links = []
|
||||
|
||||
try:
|
||||
with open(self.path, 'rb') as f:
|
||||
src = f.read()
|
||||
except IOError, err:
|
||||
msg = 'Could not read from file: %s with error: %s'%(self.path, unicode(err))
|
||||
if level == 0:
|
||||
raise IOError(msg)
|
||||
raise IgnoreFile(msg, err.errno)
|
||||
|
||||
self.is_binary = not bool(self.HTML_PAT.search(src[:1024]))
|
||||
|
||||
if not self.is_binary:
|
||||
if encoding is None:
|
||||
encoding = xml_to_unicode(src[:4096], verbose=verbose)[-1]
|
||||
self.encoding = encoding
|
||||
|
||||
src = src.decode(encoding, 'replace')
|
||||
self.find_links(src)
|
||||
|
||||
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.path == getattr(other, 'path', other)
|
||||
|
||||
def __str__(self):
|
||||
return u'HTMLFile:%d:%s:%s'%(self.level, 'b' if self.is_binary else 'a', self.path)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
def find_links(self, src):
|
||||
for match in self.LINK_PAT.finditer(src):
|
||||
url = None
|
||||
for i in ('url1', 'url2', 'url3'):
|
||||
url = match.group(i)
|
||||
if url:
|
||||
break
|
||||
link = Link(url, self.base)
|
||||
if link not in self.links:
|
||||
self.links.append(link)
|
||||
|
||||
|
||||
def depth_first(root, flat, visited=set([])):
|
||||
yield root
|
||||
visited.add(root)
|
||||
for link in root.links:
|
||||
if link.path is not None and link not in visited:
|
||||
try:
|
||||
index = flat.index(link)
|
||||
except ValueError: # Can happen if max_levels is used
|
||||
continue
|
||||
hf = flat[index]
|
||||
if hf not in visited:
|
||||
yield hf
|
||||
visited.add(hf)
|
||||
for hf in depth_first(hf, flat, visited):
|
||||
if hf not in visited:
|
||||
yield hf
|
||||
visited.add(hf)
|
||||
|
||||
|
||||
def traverse(path_to_html_file, max_levels=sys.maxint, verbose=0, encoding=None):
|
||||
'''
|
||||
Recursively traverse all links in the HTML file.
|
||||
|
||||
:param max_levels: Maximum levels of recursion. Must be non-negative. 0
|
||||
implies that no links in hte root HTML file are followed.
|
||||
:param encoding: Specify character encoding of HTML files. If `None` it is
|
||||
auto-detected.
|
||||
:return: A pair of lists (breadth_first, depth_first). Each list contains
|
||||
:class:`HTMLFile` objects.
|
||||
'''
|
||||
assert max_levels >= 0
|
||||
level = 0
|
||||
flat = [HTMLFile(path_to_html_file, level, encoding, verbose)]
|
||||
next_level = list(flat)
|
||||
while level < max_levels and len(next_level) > 0:
|
||||
level += 1
|
||||
nl = []
|
||||
for hf in next_level:
|
||||
rejects = []
|
||||
for link in hf.links:
|
||||
if link.path is None or link.path in flat:
|
||||
continue
|
||||
try:
|
||||
nf = HTMLFile(link.path, level, encoding, verbose)
|
||||
nl.append(nf)
|
||||
flat.append(nf)
|
||||
except IgnoreFile, err:
|
||||
rejects.append(link)
|
||||
if not err.doesnt_exist or verbose > 1:
|
||||
print str(err)
|
||||
for link in rejects:
|
||||
hf.links.remove(link)
|
||||
|
||||
next_level = list(nl)
|
||||
|
||||
return flat, list(depth_first(flat[0], flat))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
breadth_first, depth_first = traverse(sys.argv[1], verbose=2)
|
||||
print 'Breadth first...'
|
||||
for f in breadth_first: print f
|
||||
print '\n\nDepth first...'
|
||||
for f in depth_first: print f
|
||||
|
@ -10,6 +10,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net> ' \
|
||||
import sys, struct, cStringIO, os
|
||||
import functools
|
||||
import re
|
||||
from lxml import etree
|
||||
from calibre.ebooks.lit import LitError
|
||||
from calibre.ebooks.lit.maps import OPF_MAP, HTML_MAP
|
||||
import calibre.ebooks.lit.mssha1 as mssha1
|
||||
@ -17,6 +18,8 @@ from calibre import plugins
|
||||
lzx, lxzerror = plugins['lzx']
|
||||
msdes, msdeserror = plugins['msdes']
|
||||
|
||||
XML_DECL = """<?xml version="1.0" encoding="UTF-8" ?>
|
||||
"""
|
||||
OPF_DECL = """<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE package
|
||||
PUBLIC "+//ISBN 0-9673008-1-9//DTD OEB 1.0.1 Package//EN"
|
||||
@ -107,11 +110,12 @@ class UnBinary(object):
|
||||
AMPERSAND_RE = re.compile(
|
||||
r'&(?!(?:#[0-9]+|#x[0-9a-fA-F]+|[a-zA-Z_:][a-zA-Z0-9.-_:]+);)')
|
||||
|
||||
def __init__(self, bin, manifest, map=OPF_MAP):
|
||||
def __init__(self, bin, path, manifest, map=OPF_MAP):
|
||||
self.manifest = manifest
|
||||
self.tag_map, self.attr_map, self.tag_to_attr_map = map
|
||||
self.opf = map is OPF_MAP
|
||||
self.bin = bin
|
||||
self.dir = os.path.dirname(path)
|
||||
self.buf = cStringIO.StringIO()
|
||||
self.binary_to_text()
|
||||
self.raw = self.buf.getvalue().lstrip().decode('utf-8')
|
||||
@ -122,9 +126,19 @@ class UnBinary(object):
|
||||
|
||||
def item_path(self, internal_id):
|
||||
try:
|
||||
return self.manifest[internal_id].path
|
||||
target = self.manifest[internal_id].path
|
||||
except KeyError:
|
||||
return internal_id
|
||||
if not self.dir:
|
||||
return target
|
||||
target = target.split('/')
|
||||
base = self.dir.split('/')
|
||||
for index in xrange(min(len(base), len(target))):
|
||||
if base[index] != target[index]: break
|
||||
else:
|
||||
index += 1
|
||||
relpath = (['..'] * (len(base) - index)) + target[index:]
|
||||
return '/'.join(relpath)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.raw
|
||||
@ -147,7 +161,7 @@ class UnBinary(object):
|
||||
continue
|
||||
elif c == '\v':
|
||||
c = '\n'
|
||||
self.buf.write(c.encode('utf-8'))
|
||||
self.buf.write(c.encode('ascii', 'xmlcharrefreplace'))
|
||||
|
||||
elif state == 'get flags':
|
||||
if oc == 0:
|
||||
@ -206,7 +220,7 @@ class UnBinary(object):
|
||||
state = 'get attr length'
|
||||
continue
|
||||
attr = None
|
||||
if oc in current_map and current_map[oc]:
|
||||
if current_map and oc in current_map and current_map[oc]:
|
||||
attr = current_map[oc]
|
||||
elif oc in self.attr_map:
|
||||
attr = self.attr_map[oc]
|
||||
@ -247,7 +261,8 @@ class UnBinary(object):
|
||||
state = 'get attr'
|
||||
elif count > 0:
|
||||
if not in_censorship:
|
||||
self.buf.write(unicode(c).encode('utf-8'))
|
||||
self.buf.write(c.encode(
|
||||
'ascii', 'xmlcharrefreplace'))
|
||||
count -= 1
|
||||
if count == 0:
|
||||
if not in_censorship:
|
||||
@ -299,7 +314,8 @@ class UnBinary(object):
|
||||
path = self.item_path(doc)
|
||||
if m and frag:
|
||||
path += m + frag
|
||||
self.buf.write((u'"%s"' % path).encode('utf-8'))
|
||||
self.buf.write((u'"%s"' % path).encode(
|
||||
'ascii', 'xmlcharrefreplace'))
|
||||
state = 'get attr'
|
||||
return index
|
||||
|
||||
@ -354,6 +370,8 @@ def preserve(function):
|
||||
|
||||
class LitReader(object):
|
||||
PIECE_SIZE = 16
|
||||
XML_PARSER = etree.XMLParser(
|
||||
remove_blank_text=True, resolve_entities=False)
|
||||
|
||||
def magic():
|
||||
@preserve
|
||||
@ -596,16 +614,23 @@ class LitReader(object):
|
||||
if item.path[0] == '/':
|
||||
item.path = os.path.basename(item.path)
|
||||
|
||||
def _pretty_print(self, xml):
|
||||
f = cStringIO.StringIO(xml.encode('utf-8'))
|
||||
doc = etree.parse(f, parser=self.XML_PARSER)
|
||||
pretty = etree.tostring(doc, encoding='ascii', pretty_print=True)
|
||||
return XML_DECL + unicode(pretty)
|
||||
|
||||
def _read_meta(self):
|
||||
path = 'content.opf'
|
||||
raw = self.get_file('/meta')
|
||||
try:
|
||||
xml = OPF_DECL + unicode(UnBinary(raw, self.manifest, OPF_MAP))
|
||||
xml = OPF_DECL + unicode(UnBinary(raw, path, self.manifest, OPF_MAP))
|
||||
except LitError:
|
||||
if 'PENGUIN group' not in raw: raise
|
||||
print "WARNING: attempting PENGUIN malformed OPF fix"
|
||||
raw = raw.replace(
|
||||
'PENGUIN group', '\x00\x01\x18\x00PENGUIN group', 1)
|
||||
xml = OPF_DECL + unicode(UnBinary(raw, self.manifest, OPF_MAP))
|
||||
xml = OPF_DECL + unicode(UnBinary(raw, path, self.manifest, OPF_MAP))
|
||||
self.meta = xml
|
||||
|
||||
def _read_drm(self):
|
||||
@ -645,13 +670,6 @@ class LitReader(object):
|
||||
key[i % 8] ^= ord(digest[i])
|
||||
return ''.join(chr(x) for x in key)
|
||||
|
||||
def get_markup_file(self, name):
|
||||
raw = self.get_file(name)
|
||||
decl, map = (OPF_DECL, OPF_MAP) \
|
||||
if name == '/meta' else (HTML_DECL, HTML_MAP)
|
||||
xml = decl + unicode(UnBinary(raw, self.manifest, map))
|
||||
return xml
|
||||
|
||||
def get_file(self, name):
|
||||
entry = self.entries[name]
|
||||
if entry.section == 0:
|
||||
@ -748,7 +766,23 @@ class LitReader(object):
|
||||
raise LitError("Failed to completely decompress section")
|
||||
return ''.join(result)
|
||||
|
||||
def extract_content(self, output_dir=os.getcwdu()):
|
||||
def get_entry_content(self, entry, pretty_print=False):
|
||||
if 'spine' in entry.state:
|
||||
name = '/'.join(('/data', entry.internal, 'content'))
|
||||
path = entry.path
|
||||
raw = self.get_file(name)
|
||||
decl, map = (OPF_DECL, OPF_MAP) \
|
||||
if name == '/meta' else (HTML_DECL, HTML_MAP)
|
||||
content = decl + unicode(UnBinary(raw, path, self.manifest, map))
|
||||
if pretty_print:
|
||||
content = self._pretty_print(content)
|
||||
content = content.encode('utf-8')
|
||||
else:
|
||||
name = '/'.join(('/data', entry.internal))
|
||||
content = self.get_file(name)
|
||||
return content
|
||||
|
||||
def extract_content(self, output_dir=os.getcwdu(), pretty_print=False):
|
||||
output_dir = os.path.abspath(output_dir)
|
||||
try:
|
||||
opf_path = os.path.splitext(
|
||||
@ -758,17 +792,15 @@ class LitReader(object):
|
||||
opf_path = os.path.join(output_dir, opf_path)
|
||||
self._ensure_dir(opf_path)
|
||||
with open(opf_path, 'wb') as f:
|
||||
f.write(self.meta.encode('utf-8'))
|
||||
xml = self.meta
|
||||
if pretty_print:
|
||||
xml = self._pretty_print(xml)
|
||||
f.write(xml.encode('utf-8'))
|
||||
for entry in self.manifest.values():
|
||||
path = os.path.join(output_dir, entry.path)
|
||||
self._ensure_dir(path)
|
||||
with open(path, 'wb') as f:
|
||||
if 'spine' in entry.state:
|
||||
name = '/'.join(('/data', entry.internal, 'content'))
|
||||
f.write(self.get_markup_file(name).encode('utf-8'))
|
||||
else:
|
||||
name = '/'.join(('/data', entry.internal))
|
||||
f.write(self.get_file(name))
|
||||
f.write(self.get_entry_content(entry, pretty_print))
|
||||
|
||||
def _ensure_dir(self, path):
|
||||
dir = os.path.dirname(path)
|
||||
@ -776,11 +808,14 @@ class LitReader(object):
|
||||
os.makedirs(dir)
|
||||
|
||||
def option_parser():
|
||||
from calibre import OptionParser
|
||||
from calibre.utils.config import OptionParser
|
||||
parser = OptionParser(usage=_('%prog [options] LITFILE'))
|
||||
parser.add_option(
|
||||
'-o', '--output-dir', default='.',
|
||||
help=_('Output directory. Defaults to current directory.'))
|
||||
parser.add_option(
|
||||
'-p', '--pretty-print', default=False, action='store_true',
|
||||
help=_('Legibly format extracted markup. May modify meaningful whitespace.'))
|
||||
parser.add_option(
|
||||
'--verbose', default=False, action='store_true',
|
||||
help=_('Useful for debugging.'))
|
||||
@ -793,7 +828,7 @@ def main(args=sys.argv):
|
||||
parser.print_help()
|
||||
return 1
|
||||
lr = LitReader(args[1])
|
||||
lr.extract_content(opts.output_dir)
|
||||
lr.extract_content(opts.output_dir, opts.pretty_print)
|
||||
print _('OEB ebook created in'), opts.output_dir
|
||||
return 0
|
||||
|
||||
|
@ -14,7 +14,8 @@ from calibre.ebooks.lrf.pylrs.pylrs import TextBlock, Header, PutObj, \
|
||||
Paragraph, TextStyle, BlockStyle
|
||||
from calibre.ebooks.lrf.fonts import FONT_FILE_MAP
|
||||
from calibre.ebooks import ConversionError
|
||||
from calibre import __appname__, __version__, __author__, iswindows, OptionParser
|
||||
from calibre import __appname__, __version__, __author__, iswindows
|
||||
from calibre.utils.config import OptionParser
|
||||
|
||||
__docformat__ = "epytext"
|
||||
|
||||
|
@ -121,7 +121,7 @@ class PageProcessor(list):
|
||||
DestroyMagickWand(img)
|
||||
MagickCropImage(split1, (width/2)-1, height, 0, 0)
|
||||
MagickCropImage(split2, (width/2)-1, height, width/2, 0 )
|
||||
self.pages = [split1, split2]
|
||||
self.pages = [split2, split1] if self.opts.right2left else [split1, split2]
|
||||
|
||||
self.process_pages()
|
||||
except Exception, err:
|
||||
@ -180,7 +180,7 @@ class PageProcessor(list):
|
||||
|
||||
MagickSetImageType(wand, GrayscaleType)
|
||||
MagickQuantizeImage(wand, self.opts.colors, RGBColorspace, 0, 1, 0)
|
||||
dest = '%d_%d%s'%(self.num, i, os.path.splitext(self.path_to_page)[-1])
|
||||
dest = '%d_%d.png'%(self.num, i)
|
||||
dest = os.path.join(self.dest, dest)
|
||||
MagickWriteImage(wand, dest)
|
||||
self.append(dest)
|
||||
@ -219,7 +219,7 @@ def process_pages(pages, opts, update):
|
||||
|
||||
for pp in processed_pages:
|
||||
if len(pp) == 0:
|
||||
failures.append(os.path.basename(pp.path_to_page()))
|
||||
failures.append(os.path.basename(pp.path_to_page))
|
||||
else:
|
||||
ans += pp
|
||||
return ans, failures, tdir
|
||||
@ -238,7 +238,7 @@ def config(defaults=None):
|
||||
c.add_opt('output', ['-o', '--output'],
|
||||
help=_('Path to output LRF file. By default a file is created in the current directory.'))
|
||||
c.add_opt('colors', ['-c', '--colors'], type='int', default=64,
|
||||
help=_('Number of colors for Grayscale image conversion. Default: %default'))
|
||||
help=_('Number of colors for grayscale image conversion. Default: %default'))
|
||||
c.add_opt('dont_normalize', ['-n', '--disable-normalize'], default=False,
|
||||
help=_('Disable normalize (improve contrast) color range for pictures. Default: False'))
|
||||
c.add_opt('keep_aspect_ratio', ['-r', '--keep-aspect-ratio'], default=False,
|
||||
@ -247,6 +247,8 @@ def config(defaults=None):
|
||||
help=_('Disable sharpening.'))
|
||||
c.add_opt('landscape', ['-l', '--landscape'], default=False,
|
||||
help=_("Don't split landscape images into two portrait images"))
|
||||
c.add_opt('right2left', ['--right2left'], default=False, action='store_true',
|
||||
help=_('Used for right-to-left publications like manga. Causes landscape pages to be split into portrait pages from right to left.'))
|
||||
c.add_opt('no_sort', ['--no-sort'], default=False,
|
||||
help=_("Don't sort the files found in the comic alphabetically by name. Instead use the order they were added to the comic."))
|
||||
c.add_opt('profile', ['-p', '--profile'], default='prs500', choices=PROFILES.keys(),
|
||||
|
@ -8,7 +8,7 @@ from calibre.ebooks import ConversionError
|
||||
from calibre.ebooks.lrf.html.convert_from import process_file as html_process_file
|
||||
from calibre.ebooks.metadata.opf import OPF
|
||||
from calibre.ebooks.metadata.epub import OCFDirReader
|
||||
from calibre.libunzip import extract as zip_extract
|
||||
from calibre.utils.zipfile import ZipFile
|
||||
from calibre import __appname__, setup_cli_handlers
|
||||
|
||||
|
||||
@ -26,7 +26,7 @@ def generate_html(pathtoepub, logger):
|
||||
tdir = mkdtemp(prefix=__appname__+'_')
|
||||
os.rmdir(tdir)
|
||||
try:
|
||||
zip_extract(pathtoepub, tdir)
|
||||
ZipFile(pathtoepub).extractall(tdir)
|
||||
except:
|
||||
if os.path.exists(tdir) and os.path.isdir(tdir):
|
||||
shutil.rmtree(tdir)
|
||||
|
@ -2,7 +2,8 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import sys, logging, os
|
||||
|
||||
from calibre import setup_cli_handlers, OptionParser
|
||||
from calibre import setup_cli_handlers
|
||||
from calibre.utils.config import OptionParser
|
||||
from calibre.ebooks import ConversionError
|
||||
from calibre.ebooks.lrf.meta import get_metadata
|
||||
from calibre.ebooks.lrf.lrfparser import LRFDocument
|
||||
|
@ -10,6 +10,8 @@ import os, tempfile, atexit, shutil, time
|
||||
from PyQt4.Qt import QWebPage, QUrl, QApplication, QSize, \
|
||||
SIGNAL, QPainter, QImage, QObject, Qt
|
||||
|
||||
from calibre.parallel import ParallelJob
|
||||
|
||||
__app = None
|
||||
|
||||
class HTMLTableRenderer(QObject):
|
||||
@ -80,18 +82,17 @@ def render_table(server, soup, table, css, base_dir, width, height, dpi, factor=
|
||||
</body>
|
||||
</html>
|
||||
'''%(head, width-10, style, unicode(table))
|
||||
server.run_job(1, 'render_table',
|
||||
job = ParallelJob('render_table', lambda j : j, None,
|
||||
args=[html, base_dir, width, height, dpi, factor])
|
||||
res = None
|
||||
while res is None:
|
||||
server.add_job(job)
|
||||
while not job.has_run:
|
||||
time.sleep(2)
|
||||
res = server.result(1)
|
||||
result, exception, traceback = res
|
||||
if exception:
|
||||
|
||||
if job.exception is not None:
|
||||
print 'Failed to render table'
|
||||
print exception
|
||||
print traceback
|
||||
images, tdir = result
|
||||
print job.exception
|
||||
print job.traceback
|
||||
images, tdir = job.result
|
||||
atexit.register(shutil.rmtree, tdir)
|
||||
return images
|
||||
|
||||
|
@ -3,19 +3,12 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import os, sys, shutil, glob, logging
|
||||
from tempfile import mkdtemp
|
||||
from subprocess import Popen, PIPE
|
||||
from calibre.ebooks.lrf import option_parser as lrf_option_parser
|
||||
from calibre.ebooks.lit.reader import LitReader
|
||||
from calibre.ebooks import ConversionError
|
||||
from calibre.ebooks.lrf.html.convert_from import process_file as html_process_file
|
||||
from calibre.ebooks.metadata.opf import OPFReader
|
||||
from calibre import isosx, __appname__, setup_cli_handlers, islinux
|
||||
|
||||
CLIT = 'clit'
|
||||
if isosx and hasattr(sys, 'frameworks_dir'):
|
||||
CLIT = os.path.join(getattr(sys, 'frameworks_dir'), CLIT)
|
||||
if islinux and getattr(sys, 'frozen_path', False):
|
||||
CLIT = os.path.join(getattr(sys, 'frozen_path'), 'clit')
|
||||
from calibre import __appname__, setup_cli_handlers
|
||||
|
||||
def option_parser():
|
||||
parser = lrf_option_parser(
|
||||
@ -24,37 +17,15 @@ _('''Usage: %prog [options] mybook.lit
|
||||
|
||||
%prog converts mybook.lit to mybook.lrf''')
|
||||
)
|
||||
parser.add_option('--lit2oeb', default=False, dest='lit2oeb', action='store_true',
|
||||
help='Use the new lit2oeb to convert lit files instead of convertlit.')
|
||||
return parser
|
||||
|
||||
def generate_html2(pathtolit, logger):
|
||||
if not os.access(pathtolit, os.R_OK):
|
||||
raise ConversionError, 'Cannot read from ' + pathtolit
|
||||
tdir = mkdtemp(prefix=__appname__+'_')
|
||||
lr = LitReader(pathtolit)
|
||||
print 'Extracting LIT file to', tdir
|
||||
lr.extract_content(tdir)
|
||||
return tdir
|
||||
|
||||
def generate_html(pathtolit, logger):
|
||||
if not os.access(pathtolit, os.R_OK):
|
||||
raise ConversionError, 'Cannot read from ' + pathtolit
|
||||
tdir = mkdtemp(prefix=__appname__+'_')
|
||||
os.rmdir(tdir)
|
||||
cmd = [CLIT, pathtolit, '%s'%(tdir+os.sep)]
|
||||
logger.debug(repr(cmd))
|
||||
p = Popen(cmd, stderr=PIPE, stdout=PIPE)
|
||||
stdout = p.stdout.read()
|
||||
err = p.stderr.read()
|
||||
logger.info(p.stdout.read())
|
||||
ret = p.wait()
|
||||
if ret != 0:
|
||||
if os.path.exists(tdir) and os.path.isdir(tdir):
|
||||
shutil.rmtree(tdir)
|
||||
if 'keys.txt' in unicode(err)+unicode(stdout):
|
||||
raise ConversionError('This lit file is protected by DRM. You must first use the ConvertLIT program to remove the DRM. Doing so may be illegal, and so %s does not do this, nor does it provide instructions on how to do it.'%(__appname__,))
|
||||
raise ConversionError, err
|
||||
tdir = mkdtemp(prefix=__appname__+'_'+'lit2oeb_')
|
||||
lr = LitReader(pathtolit)
|
||||
print 'Extracting LIT file to', tdir
|
||||
lr.extract_content(tdir)
|
||||
return tdir
|
||||
|
||||
def process_file(path, options, logger=None):
|
||||
@ -63,8 +34,7 @@ def process_file(path, options, logger=None):
|
||||
logger = logging.getLogger('lit2lrf')
|
||||
setup_cli_handlers(logger, level)
|
||||
lit = os.path.abspath(os.path.expanduser(path))
|
||||
tdir = generate_html2(lit, logger) if getattr(options, 'lit2oeb', False) \
|
||||
else generate_html(lit, logger)
|
||||
tdir = generate_html(lit, logger)
|
||||
try:
|
||||
opf = glob.glob(os.path.join(tdir, '*.opf'))
|
||||
if opf:
|
||||
@ -96,7 +66,6 @@ def process_file(path, options, logger=None):
|
||||
options.output = os.path.abspath(os.path.basename(os.path.splitext(path)[0]) + ext)
|
||||
options.output = os.path.abspath(os.path.expanduser(options.output))
|
||||
options.use_spine = True
|
||||
|
||||
html_process_file(htmlfile, options, logger=logger)
|
||||
finally:
|
||||
try:
|
||||
|
@ -4,7 +4,8 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import sys, array, os, re, codecs, logging
|
||||
|
||||
from calibre import OptionParser, setup_cli_handlers
|
||||
from calibre import setup_cli_handlers
|
||||
from calibre.utils.config import OptionParser
|
||||
from calibre.ebooks.lrf.meta import LRFMetaFile
|
||||
from calibre.ebooks.lrf.objects import get_object, PageTree, StyleObject, \
|
||||
Font, Text, TOCObject, BookAttr, ruby_tags
|
||||
|
@ -6,7 +6,8 @@ Compile a LRS file into a LRF file.
|
||||
|
||||
import sys, os, logging
|
||||
|
||||
from calibre import OptionParser, setup_cli_handlers
|
||||
from calibre import setup_cli_handlers
|
||||
from calibre.utils.config import OptionParser
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, NavigableString, \
|
||||
CData, Tag
|
||||
from calibre.ebooks.lrf.pylrs.pylrs import Book, PageStyle, TextStyle, \
|
||||
|
@ -574,8 +574,8 @@ class LRFMetaFile(object):
|
||||
|
||||
|
||||
def option_parser():
|
||||
from optparse import OptionParser
|
||||
from calibre import __appname__, __version__
|
||||
from calibre.utils.config import OptionParser
|
||||
from calibre.constants import __appname__, __version__
|
||||
parser = OptionParser(usage = \
|
||||
_('''%prog [options] mybook.lrf
|
||||
|
||||
|
@ -25,6 +25,8 @@ def generate_html(pathtopdf, logger):
|
||||
Convert the pdf into html.
|
||||
@return: Path to a temporary file containing the HTML.
|
||||
'''
|
||||
if isinstance(pathtopdf, unicode):
|
||||
pathtopdf = pathtopdf.encode(sys.getfilesystemencoding())
|
||||
if not os.access(pathtopdf, os.R_OK):
|
||||
raise ConversionError, 'Cannot read from ' + pathtopdf
|
||||
tdir = PersistentTemporaryDirectory('pdftohtml')
|
||||
|
@ -7,7 +7,8 @@ Convert PDF to a reflowable format using pdftoxml.exe as the PDF parsing backend
|
||||
import sys, os, re, tempfile, subprocess, atexit, shutil, logging, xml.parsers.expat
|
||||
from xml.etree.ElementTree import parse
|
||||
|
||||
from calibre import isosx, OptionParser, setup_cli_handlers, __appname__
|
||||
from calibre import isosx, setup_cli_handlers, __appname__
|
||||
from calibre.utils.config import OptionParser
|
||||
from calibre.ebooks import ConversionError
|
||||
|
||||
PDFTOXML = 'pdftoxml.exe'
|
||||
|
@ -11,8 +11,9 @@ from urllib import unquote, quote
|
||||
from urlparse import urlparse
|
||||
|
||||
|
||||
from calibre import __version__ as VERSION, relpath
|
||||
from calibre import OptionParser
|
||||
from calibre.constants import __version__ as VERSION
|
||||
from calibre import relpath
|
||||
from calibre.utils.config import OptionParser
|
||||
|
||||
def get_parser(extension):
|
||||
''' Return an option parser with the basic metadata options already setup'''
|
||||
|
@ -7,7 +7,8 @@ Interface to isbndb.com. My key HLLXQX2A.
|
||||
import sys, logging, re, socket
|
||||
from urllib import urlopen, quote
|
||||
|
||||
from calibre import setup_cli_handlers, OptionParser
|
||||
from calibre import setup_cli_handlers
|
||||
from calibre.utils.config import OptionParser
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup
|
||||
|
||||
|
@ -6,7 +6,8 @@ Fetch cover from LibraryThing.com based on ISBN number.
|
||||
|
||||
import sys, socket, os, re, mechanize
|
||||
|
||||
from calibre import browser as _browser, OptionParser
|
||||
from calibre import browser as _browser
|
||||
from calibre.utils.config import OptionParser
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||
browser = None
|
||||
|
||||
|
@ -3,6 +3,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import os, re, collections
|
||||
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.ebooks.metadata.rtf import get_metadata as rtf_metadata
|
||||
from calibre.ebooks.metadata.fb2 import get_metadata as fb2_metadata
|
||||
from calibre.ebooks.lrf.meta import get_metadata as lrf_metadata
|
||||
@ -94,20 +95,11 @@ def set_metadata(stream, mi, stream_type='lrf'):
|
||||
elif stream_type == 'rtf':
|
||||
set_rtf_metadata(stream, mi)
|
||||
|
||||
_filename_pat = re.compile(ur'(?P<title>.+) - (?P<author>[^_]+)')
|
||||
|
||||
def get_filename_pat():
|
||||
return _filename_pat.pattern
|
||||
|
||||
def set_filename_pat(pat):
|
||||
global _filename_pat
|
||||
_filename_pat = re.compile(pat)
|
||||
|
||||
def metadata_from_filename(name, pat=None):
|
||||
name = os.path.splitext(name)[0]
|
||||
mi = MetaInformation(None, None)
|
||||
if pat is None:
|
||||
pat = _filename_pat
|
||||
pat = re.compile(prefs.get('filename_pattern'))
|
||||
match = pat.search(name)
|
||||
if match:
|
||||
try:
|
||||
@ -134,12 +126,12 @@ def metadata_from_filename(name, pat=None):
|
||||
try:
|
||||
si = match.group('series_index')
|
||||
mi.series_index = int(si)
|
||||
except IndexError, ValueError:
|
||||
except (IndexError, ValueError):
|
||||
pass
|
||||
try:
|
||||
si = match.group('isbn')
|
||||
mi.isbn = si
|
||||
except IndexError, ValueError:
|
||||
except (IndexError, ValueError):
|
||||
pass
|
||||
if not mi.title:
|
||||
mi.title = name
|
||||
|
@ -93,7 +93,7 @@ class BookHeader(object):
|
||||
1252 : 'cp1252',
|
||||
65001 : 'utf-8',
|
||||
}[self.codepage]
|
||||
except IndexError, KeyError:
|
||||
except (IndexError, KeyError):
|
||||
print '[WARNING] Unknown codepage %d. Assuming cp-1252'%self.codepage
|
||||
self.codec = 'cp1252'
|
||||
|
||||
@ -408,7 +408,7 @@ def get_metadata(stream):
|
||||
return mi
|
||||
|
||||
def option_parser():
|
||||
from calibre import OptionParser
|
||||
from calibre.utils.config import OptionParser
|
||||
parser = OptionParser(usage=_('%prog [options] myebook.mobi'))
|
||||
parser.add_option('-o', '--output-dir', default='.',
|
||||
help=_('Output directory. Defaults to current directory.'))
|
||||
|
@ -2,19 +2,49 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
""" The GUI """
|
||||
import sys, os, re, StringIO, traceback
|
||||
from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, \
|
||||
from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSize, \
|
||||
QByteArray, QLocale, QUrl, QTranslator, QCoreApplication
|
||||
from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
|
||||
QIcon, QTableView, QDialogButtonBox, QApplication
|
||||
|
||||
ORG_NAME = 'KovidsBrain'
|
||||
APP_UID = 'libprs500'
|
||||
from calibre import __author__, islinux, iswindows, Settings, isosx, get_lang
|
||||
from calibre import __author__, islinux, iswindows, isosx
|
||||
from calibre.startup import get_lang
|
||||
from calibre.utils.config import Config, ConfigProxy, dynamic
|
||||
import calibre.resources as resources
|
||||
|
||||
NONE = QVariant() #: Null value to return from the data function of item models
|
||||
|
||||
def _config():
|
||||
c = Config('gui', 'preferences for the calibre GUI')
|
||||
c.add_opt('frequently_used_directories', default=[],
|
||||
help=_('Frequently used directories'))
|
||||
c.add_opt('send_to_device_by_default', default=True,
|
||||
help=_('Send downloaded periodical content to device automatically'))
|
||||
c.add_opt('save_to_disk_single_format', default='lrf',
|
||||
help=_('The format to use when saving single files to disk'))
|
||||
c.add_opt('confirm_delete', default=False,
|
||||
help=_('Confirm before deleting'))
|
||||
c.add_opt('toolbar_icon_size', default=QSize(48, 48),
|
||||
help=_('Toolbar icon size')) # value QVariant.toSize
|
||||
c.add_opt('show_text_in_toolbar', default=True,
|
||||
help=_('Show button labels in the toolbar'))
|
||||
c.add_opt('main_window_geometry', default=None,
|
||||
help=_('Main window geometry')) # value QVariant.toByteArray
|
||||
c.add_opt('new_version_notification', default=True,
|
||||
help=_('Notify when a new version is available'))
|
||||
c.add_opt('use_roman_numerals_for_series_number', default=True,
|
||||
help=_('Use Roman numerals for series number'))
|
||||
c.add_opt('cover_flow_queue_length', default=6,
|
||||
help=_('Number of covers to show in the cover browsing mode'))
|
||||
c.add_opt('LRF_conversion_defaults', default=[],
|
||||
help=_('Defaults for conversion to LRF'))
|
||||
c.add_opt('LRF_ebook_viewer_options', default=None,
|
||||
help=_('Options for the LRF ebook viewer'))
|
||||
return ConfigProxy(c)
|
||||
|
||||
config = _config()
|
||||
# Turn off DeprecationWarnings in windows GUI
|
||||
if iswindows:
|
||||
import warnings
|
||||
@ -84,20 +114,33 @@ def human_readable(size):
|
||||
size = size[:-2]
|
||||
return size + " " + suffix
|
||||
|
||||
class Dispatcher(QObject):
|
||||
'''Convenience class to ensure that a function call always happens in the GUI thread'''
|
||||
|
||||
def __init__(self, func):
|
||||
QObject.__init__(self)
|
||||
self.func = func
|
||||
self.connect(self, SIGNAL('edispatch(PyQt_PyObject, PyQt_PyObject)'),
|
||||
self.dispatch, Qt.QueuedConnection)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
self.emit(SIGNAL('edispatch(PyQt_PyObject, PyQt_PyObject)'), args, kwargs)
|
||||
|
||||
def dispatch(self, args, kwargs):
|
||||
self.func(*args, **kwargs)
|
||||
|
||||
|
||||
class TableView(QTableView):
|
||||
def __init__(self, parent):
|
||||
QTableView.__init__(self, parent)
|
||||
self.read_settings()
|
||||
|
||||
|
||||
def read_settings(self):
|
||||
self.cw = Settings().get(self.__class__.__name__ + ' column widths')
|
||||
self.cw = dynamic[self.__class__.__name__+'column widths']
|
||||
|
||||
def write_settings(self):
|
||||
settings = Settings()
|
||||
settings.set(self.__class__.__name__ + ' column widths',
|
||||
tuple([int(self.columnWidth(i)) for i in range(self.model().columnCount(None))]))
|
||||
dynamic[self.__class__.__name__+'column widths'] = \
|
||||
tuple([int(self.columnWidth(i)) for i in range(self.model().columnCount(None))])
|
||||
|
||||
def restore_column_widths(self):
|
||||
if self.cw and len(self.cw):
|
||||
@ -110,10 +153,11 @@ class TableView(QTableView):
|
||||
@param cols: A list of booleans or None. If an entry is False the corresponding column
|
||||
is hidden, if True it is shown.
|
||||
'''
|
||||
key = self.__class__.__name__+'visible columns'
|
||||
if cols:
|
||||
Settings().set(self.__class__.__name__ + ' visible columns', cols)
|
||||
dynamic[key] = cols
|
||||
else:
|
||||
cols = Settings().get(self.__class__.__name__ + ' visible columns')
|
||||
cols = dynamic[key]
|
||||
if not cols:
|
||||
cols = [True for i in range(self.model().columnCount(self))]
|
||||
|
||||
@ -217,7 +261,7 @@ _sidebar_directories = []
|
||||
def set_sidebar_directories(dirs):
|
||||
global _sidebar_directories
|
||||
if dirs is None:
|
||||
dirs = Settings().get('frequently used directories', [])
|
||||
dirs = config['frequently_used_directories']
|
||||
_sidebar_directories = [QUrl.fromLocalFile(i) for i in dirs]
|
||||
|
||||
class FileDialog(QObject):
|
||||
@ -240,10 +284,10 @@ class FileDialog(QObject):
|
||||
if add_all_files_filter or not ftext:
|
||||
ftext += 'All files (*)'
|
||||
|
||||
settings = Settings()
|
||||
self.dialog_name = name if name else 'dialog_' + title
|
||||
self.selected_files = None
|
||||
self.fd = None
|
||||
|
||||
if islinux:
|
||||
self.fd = QFileDialog(parent)
|
||||
self.fd.setFileMode(mode)
|
||||
@ -251,15 +295,15 @@ class FileDialog(QObject):
|
||||
self.fd.setModal(modal)
|
||||
self.fd.setFilter(ftext)
|
||||
self.fd.setWindowTitle(title)
|
||||
state = settings.get(self.dialog_name, QByteArray())
|
||||
if not self.fd.restoreState(state):
|
||||
state = dynamic[self.dialog_name]
|
||||
if not state or not self.fd.restoreState(state):
|
||||
self.fd.setDirectory(os.path.expanduser('~'))
|
||||
osu = [i for i in self.fd.sidebarUrls()]
|
||||
self.fd.setSidebarUrls(osu + _sidebar_directories)
|
||||
QObject.connect(self.fd, SIGNAL('accepted()'), self.save_dir)
|
||||
self.accepted = self.fd.exec_() == QFileDialog.Accepted
|
||||
else:
|
||||
dir = settings.get(self.dialog_name, os.path.expanduser('~'))
|
||||
dir = dynamic.get(self.dialog_name, default=os.path.expanduser('~'))
|
||||
self.selected_files = []
|
||||
if mode == QFileDialog.AnyFile:
|
||||
f = qstring_to_unicode(
|
||||
@ -284,7 +328,7 @@ class FileDialog(QObject):
|
||||
self.selected_files.append(f)
|
||||
if self.selected_files:
|
||||
self.selected_files = [qstring_to_unicode(q) for q in self.selected_files]
|
||||
settings.set(self.dialog_name, os.path.dirname(self.selected_files[0]))
|
||||
dynamic[self.dialog_name] = os.path.dirname(self.selected_files[0])
|
||||
self.accepted = bool(self.selected_files)
|
||||
|
||||
|
||||
@ -298,8 +342,7 @@ class FileDialog(QObject):
|
||||
|
||||
def save_dir(self):
|
||||
if self.fd:
|
||||
settings = Settings()
|
||||
settings.set(self.dialog_name, self.fd.saveState())
|
||||
dynamic[self.dialog_name] = self.fd.saveState()
|
||||
|
||||
|
||||
def choose_dir(window, name, title):
|
||||
|
@ -12,7 +12,8 @@ import sys, os
|
||||
from PyQt4.QtGui import QImage, QSizePolicy
|
||||
from PyQt4.QtCore import Qt, QSize, SIGNAL, QObject
|
||||
|
||||
from calibre import Settings, plugins
|
||||
from calibre import plugins
|
||||
from calibre.gui2 import config
|
||||
pictureflow, pictureflowerror = plugins['pictureflow']
|
||||
|
||||
if pictureflow is not None:
|
||||
@ -70,7 +71,7 @@ if pictureflow is not None:
|
||||
|
||||
def __init__(self, height=300, parent=None):
|
||||
pictureflow.PictureFlow.__init__(self, parent,
|
||||
Settings().get('cover flow queue length', 6)+1)
|
||||
config['cover_flow_queue_length']+1)
|
||||
self.setSlideSize(QSize(int(2/3. * height), height))
|
||||
self.setMinimumSize(QSize(int(2.35*0.67*height), (5/3.)*height+25))
|
||||
self.setFocusPolicy(Qt.WheelFocus)
|
||||
|
@ -1,131 +1,185 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import os, traceback
|
||||
import os, traceback, Queue, time
|
||||
from threading import Thread
|
||||
|
||||
from PyQt4.QtCore import QThread, SIGNAL, QObject
|
||||
|
||||
from calibre import iswindows
|
||||
from calibre.devices import devices
|
||||
from calibre.parallel import Job
|
||||
from calibre.devices.scanner import DeviceScanner
|
||||
|
||||
class DeviceDetector(QThread):
|
||||
|
||||
class DeviceJob(Job):
|
||||
|
||||
def __init__(self, func, *args, **kwargs):
|
||||
Job.__init__(self, *args, **kwargs)
|
||||
self.func = func
|
||||
|
||||
def run(self):
|
||||
self.start_work()
|
||||
try:
|
||||
self.result = self.func(*self.args, **self.kwargs)
|
||||
except (Exception, SystemExit), err:
|
||||
self.exception = err
|
||||
self.traceback = traceback.format_exc()
|
||||
finally:
|
||||
self.job_done()
|
||||
|
||||
|
||||
class DeviceManager(Thread):
|
||||
'''
|
||||
Worker thread that polls the USB ports for devices. Emits the
|
||||
signal connected(PyQt_PyObject, PyQt_PyObject) on connection and
|
||||
disconnection events.
|
||||
'''
|
||||
def __init__(self, sleep_time=2000):
|
||||
def __init__(self, connected_slot, job_manager, sleep_time=2):
|
||||
'''
|
||||
@param sleep_time: Time to sleep between device probes in millisecs
|
||||
@type sleep_time: integer
|
||||
'''
|
||||
self.devices = [[d, False] for d in devices()]
|
||||
self.sleep_time = sleep_time
|
||||
QThread.__init__(self)
|
||||
self.keep_going = True
|
||||
Thread.__init__(self)
|
||||
self.setDaemon(True)
|
||||
self.devices = [[d, False] for d in devices()]
|
||||
self.device = None
|
||||
self.device_class = None
|
||||
self.sleep_time = sleep_time
|
||||
self.connected_slot = connected_slot
|
||||
self.jobs = Queue.Queue(0)
|
||||
self.keep_going = True
|
||||
self.job_manager = job_manager
|
||||
self.current_job = None
|
||||
self.scanner = DeviceScanner()
|
||||
|
||||
def detect_device(self):
|
||||
self.scanner.scan()
|
||||
for device in self.devices:
|
||||
connected = self.scanner.is_device_connected(device[0])
|
||||
if connected and not device[1]:
|
||||
try:
|
||||
dev = device[0]()
|
||||
dev.open()
|
||||
self.device = dev
|
||||
self.device_class = dev.__class__
|
||||
self.connected_slot(True)
|
||||
except:
|
||||
print 'Unable to open device'
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
device[1] = True
|
||||
elif not connected and device[1]:
|
||||
while True:
|
||||
try:
|
||||
job = self.jobs.get_nowait()
|
||||
job.abort(Exception(_('Device no longer connected.')))
|
||||
except Queue.Empty:
|
||||
break
|
||||
self.device = None
|
||||
self.connected_slot(False)
|
||||
device[1] ^= True
|
||||
|
||||
def next(self):
|
||||
if not self.jobs.empty():
|
||||
try:
|
||||
return self.jobs.get_nowait()
|
||||
except Queue.Empty:
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
scanner = DeviceScanner()
|
||||
while self.keep_going:
|
||||
scanner.scan()
|
||||
for device in self.devices:
|
||||
connected = scanner.is_device_connected(device[0])
|
||||
if connected and not device[1]:
|
||||
try:
|
||||
dev = device[0]()
|
||||
dev.open()
|
||||
self.emit(SIGNAL('connected(PyQt_PyObject, PyQt_PyObject)'), dev, True)
|
||||
except:
|
||||
print 'Unable to open device'
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
device[1] = True
|
||||
elif not connected and device[1]:
|
||||
self.emit(SIGNAL('connected(PyQt_PyObject, PyQt_PyObject)'), device[0], False)
|
||||
device[1] ^= True
|
||||
self.msleep(self.sleep_time)
|
||||
self.detect_device()
|
||||
while True:
|
||||
job = self.next()
|
||||
if job is not None:
|
||||
self.current_job = job
|
||||
self.device.set_progress_reporter(job.update_status)
|
||||
self.current_job.run()
|
||||
self.current_job = None
|
||||
else:
|
||||
break
|
||||
time.sleep(self.sleep_time)
|
||||
|
||||
def create_job(self, func, done, description, args=[], kwargs={}):
|
||||
job = DeviceJob(func, done, self.job_manager,
|
||||
args=args, kwargs=kwargs, description=description)
|
||||
self.job_manager.add_job(job)
|
||||
self.jobs.put(job)
|
||||
return job
|
||||
|
||||
def _get_device_information(self):
|
||||
info = self.device.get_device_information(end_session=False)
|
||||
info = [i.replace('\x00', '').replace('\x01', '') for i in info]
|
||||
cp = self.device.card_prefix(end_session=False)
|
||||
fs = self.device.free_space()
|
||||
return info, cp, fs
|
||||
|
||||
def get_device_information(self, done):
|
||||
'''Get device information and free space on device'''
|
||||
return self.create_job(self._get_device_information, done,
|
||||
description=_('Get device information'))
|
||||
|
||||
|
||||
def _books(self):
|
||||
'''Get metadata from device'''
|
||||
mainlist = self.device.books(oncard=False, end_session=False)
|
||||
cardlist = self.device.books(oncard=True)
|
||||
return (mainlist, cardlist)
|
||||
|
||||
class DeviceManager(QObject):
|
||||
def __init__(self, device):
|
||||
QObject.__init__(self)
|
||||
self.device_class = device.__class__
|
||||
self.device = device
|
||||
|
||||
def device_removed(self):
|
||||
self.device = None
|
||||
|
||||
def info_func(self):
|
||||
''' Return callable that returns device information and free space on device'''
|
||||
def get_device_information(updater):
|
||||
'''Get device information'''
|
||||
self.device.set_progress_reporter(updater)
|
||||
info = self.device.get_device_information(end_session=False)
|
||||
info = [i.replace('\x00', '').replace('\x01', '') for i in info]
|
||||
cp = self.device.card_prefix(end_session=False)
|
||||
fs = self.device.free_space()
|
||||
return info, cp, fs
|
||||
return get_device_information
|
||||
|
||||
def books_func(self):
|
||||
def books(self, done):
|
||||
'''Return callable that returns the list of books on device as two booklists'''
|
||||
def books(updater):
|
||||
'''Get metadata from device'''
|
||||
self.device.set_progress_reporter(updater)
|
||||
mainlist = self.device.books(oncard=False, end_session=False)
|
||||
cardlist = self.device.books(oncard=True)
|
||||
return (mainlist, cardlist)
|
||||
return books
|
||||
return self.create_job(self._books, done, description=_('Get list of books on device'))
|
||||
|
||||
def sync_booklists_func(self):
|
||||
'''Upload booklists to device'''
|
||||
def sync_booklists(updater, booklists):
|
||||
'''Sync metadata to device'''
|
||||
self.device.set_progress_reporter(updater)
|
||||
self.device.sync_booklists(booklists, end_session=False)
|
||||
return self.device.card_prefix(end_session=False), self.device.free_space()
|
||||
return sync_booklists
|
||||
def _sync_booklists(self, booklists):
|
||||
'''Sync metadata to device'''
|
||||
self.device.sync_booklists(booklists, end_session=False)
|
||||
return self.device.card_prefix(end_session=False), self.device.free_space()
|
||||
|
||||
def upload_books_func(self):
|
||||
'''Upload books to device'''
|
||||
def upload_books(updater, files, names, on_card=False):
|
||||
'''Upload books to device: '''
|
||||
self.device.set_progress_reporter(updater)
|
||||
return self.device.upload_books(files, names, on_card, end_session=False)
|
||||
return upload_books
|
||||
def sync_booklists(self, done, booklists):
|
||||
return self.create_job(self._sync_booklists, done, args=[booklists],
|
||||
description=_('Send metadata to device'))
|
||||
|
||||
def _upload_books(self, files, names, on_card=False):
|
||||
'''Upload books to device: '''
|
||||
return self.device.upload_books(files, names, on_card, end_session=False)
|
||||
|
||||
def upload_books(self, done, files, names, on_card=False, titles=None):
|
||||
desc = _('Upload %d books to device')%len(names)
|
||||
if titles:
|
||||
desc += u':' + u', '.join(titles)
|
||||
return self.create_job(self._upload_books, done, args=[files, names],
|
||||
kwargs={'on_card':on_card}, description=desc)
|
||||
|
||||
def add_books_to_metadata(self, locations, metadata, booklists):
|
||||
self.device_class.add_books_to_metadata(locations, metadata, booklists)
|
||||
self.device.add_books_to_metadata(locations, metadata, booklists)
|
||||
|
||||
def delete_books_func(self):
|
||||
def _delete_books(self, paths):
|
||||
'''Remove books from device'''
|
||||
def delete_books(updater, paths):
|
||||
'''Remove books from device'''
|
||||
self.device.delete_books(paths, end_session=True)
|
||||
return delete_books
|
||||
self.device.delete_books(paths, end_session=True)
|
||||
|
||||
def delete_books(self, done, paths):
|
||||
return self.create_job(self._delete_books, done, args=[paths],
|
||||
description=_('Delete books from device'))
|
||||
|
||||
def remove_books_from_metadata(self, paths, booklists):
|
||||
self.device_class.remove_books_from_metadata(paths, booklists)
|
||||
self.device.remove_books_from_metadata(paths, booklists)
|
||||
|
||||
def save_books_func(self):
|
||||
def _save_books(self, paths, target):
|
||||
'''Copy books from device to disk'''
|
||||
def save_books(updater, paths, target):
|
||||
'''Copy books from device to disk'''
|
||||
self.device.set_progress_reporter(updater)
|
||||
for path in paths:
|
||||
name = path.rpartition('/')[2]
|
||||
f = open(os.path.join(target, name), 'wb')
|
||||
self.device.get_file(path, f)
|
||||
f.close()
|
||||
return save_books
|
||||
|
||||
def view_book_func(self):
|
||||
'''Copy book from device to local hdd for viewing'''
|
||||
def view_book(updater, path, target):
|
||||
self.device.set_progress_reporter(updater)
|
||||
f = open(target, 'wb')
|
||||
for path in paths:
|
||||
name = path.rpartition('/')[2]
|
||||
f = open(os.path.join(target, name), 'wb')
|
||||
self.device.get_file(path, f)
|
||||
f.close()
|
||||
return target
|
||||
return view_book
|
||||
|
||||
def save_books(self, done, paths, target):
|
||||
return self.create_job(self._save_books, done, args=[paths, target],
|
||||
description=_('Download books from device'))
|
||||
|
||||
def _view_book(self, path, target):
|
||||
f = open(target, 'wb')
|
||||
self.device.get_file(path, f)
|
||||
f.close()
|
||||
return target
|
||||
|
||||
def view_book(self, done, path, target):
|
||||
return self.create_job(self._view_book, done, args=[path, target],
|
||||
description=_('View book on device'))
|
||||
|
@ -14,17 +14,13 @@ def set_conversion_defaults(window):
|
||||
d.exec_()
|
||||
|
||||
def get_bulk_conversion_options(window):
|
||||
c = config(None)
|
||||
with open(c.config_file_path, 'rb') as f:
|
||||
d = ComicConf(window, config_defaults=f.read())
|
||||
d = ComicConf(window, config_defaults=config(None).as_string())
|
||||
if d.exec_() == QDialog.Accepted:
|
||||
return d.config.parse()
|
||||
|
||||
def get_conversion_options(window, defaults, title, author):
|
||||
if defaults is None:
|
||||
c = config(None)
|
||||
with open(c.config_file_path, 'rb') as f:
|
||||
defaults = f.read()
|
||||
defaults = config(None).as_string()
|
||||
defaults += '\ntitle=%s\nauthor=%s'%(repr(title), repr(author))
|
||||
d = ComicConf(window, config_defaults=defaults, generic=False)
|
||||
if d.exec_() == QDialog.Accepted:
|
||||
@ -61,6 +57,7 @@ class ComicConf(QDialog, Ui_Dialog):
|
||||
self.opt_dont_sharpen.setChecked(opts.dont_sharpen)
|
||||
self.opt_landscape.setChecked(opts.landscape)
|
||||
self.opt_no_sort.setChecked(opts.no_sort)
|
||||
self.opt_right2left.setChecked(opts.right2left)
|
||||
|
||||
for opt in self.config.option_set.preferences:
|
||||
g = getattr(self, 'opt_'+opt.name, False)
|
||||
|
@ -107,14 +107,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0" >
|
||||
<item row="9" column="0" >
|
||||
<widget class="QCheckBox" name="opt_no_sort" >
|
||||
<property name="text" >
|
||||
<string>Dont so&rt</string>
|
||||
<string>Don't so&rt</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1" >
|
||||
<item row="10" column="1" >
|
||||
<widget class="QDialogButtonBox" name="buttonBox" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
@ -124,6 +124,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0" >
|
||||
<widget class="QCheckBox" name="opt_right2left" >
|
||||
<property name="text" >
|
||||
<string>&Right to left</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
|
@ -5,14 +5,14 @@ import os
|
||||
from PyQt4.QtGui import QDialog, QMessageBox, QListWidgetItem, QIcon
|
||||
from PyQt4.QtCore import SIGNAL, QTimer, Qt, QSize, QVariant
|
||||
|
||||
from calibre import islinux, Settings
|
||||
from calibre import islinux
|
||||
from calibre.gui2.dialogs.config_ui import Ui_Dialog
|
||||
from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog
|
||||
from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.gui2.widgets import FilenamePattern
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
|
||||
|
||||
|
||||
class ConfigDialog(QDialog, Ui_Dialog):
|
||||
|
||||
def __init__(self, window, db, columns):
|
||||
@ -24,17 +24,16 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
self.item2 = QListWidgetItem(QIcon(':/images/view.svg'), _('Advanced'), self.category_list)
|
||||
self.db = db
|
||||
self.current_cols = columns
|
||||
settings = Settings()
|
||||
path = settings.get('library path')
|
||||
path = prefs['library_path']
|
||||
self.location.setText(path)
|
||||
self.connect(self.browse_button, SIGNAL('clicked(bool)'), self.browse)
|
||||
self.connect(self.compact_button, SIGNAL('clicked(bool)'), self.compact)
|
||||
|
||||
dirs = settings.get('frequently used directories', [])
|
||||
rn = settings.get('use roman numerals for series number', True)
|
||||
self.timeout.setValue(settings.get('network timeout', 5))
|
||||
dirs = config['frequently_used_directories']
|
||||
rn = config['use_roman_numerals_for_series_number']
|
||||
self.timeout.setValue(prefs['network_timeout'])
|
||||
self.roman_numerals.setChecked(rn)
|
||||
self.new_version_notification.setChecked(settings.get('new version notification', True))
|
||||
self.new_version_notification.setChecked(config['new_version_notification'])
|
||||
self.directory_list.addItems(dirs)
|
||||
self.connect(self.add_button, SIGNAL('clicked(bool)'), self.add_dir)
|
||||
self.connect(self.remove_button, SIGNAL('clicked(bool)'), self.remove_dir)
|
||||
@ -56,17 +55,17 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
self.filename_pattern = FilenamePattern(self)
|
||||
self.metadata_box.layout().insertWidget(0, self.filename_pattern)
|
||||
|
||||
icons = settings.get('toolbar icon size', self.ICON_SIZES[0])
|
||||
icons = config['toolbar_icon_size']
|
||||
self.toolbar_button_size.setCurrentIndex(0 if icons == self.ICON_SIZES[0] else 1 if icons == self.ICON_SIZES[1] else 2)
|
||||
self.show_toolbar_text.setChecked(settings.get('show text in toolbar', True))
|
||||
self.show_toolbar_text.setChecked(config['show_text_in_toolbar'])
|
||||
|
||||
for ext in BOOK_EXTENSIONS:
|
||||
self.single_format.addItem(ext.upper(), QVariant(ext))
|
||||
|
||||
single_format = settings.get('save to disk single format', 'lrf')
|
||||
single_format = config['save_to_disk_single_format']
|
||||
self.single_format.setCurrentIndex(BOOK_EXTENSIONS.index(single_format))
|
||||
self.cover_browse.setValue(settings.get('cover flow queue length', 6))
|
||||
self.confirm_delete.setChecked(settings.get('confirm delete', False))
|
||||
self.cover_browse.setValue(config['cover_flow_queue_length'])
|
||||
self.confirm_delete.setChecked(config['confirm_delete'])
|
||||
|
||||
def compact(self, toggled):
|
||||
d = Vacuum(self, self.db)
|
||||
@ -88,19 +87,18 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
self.directory_list.takeItem(idx)
|
||||
|
||||
def accept(self):
|
||||
settings = Settings()
|
||||
settings.set('use roman numerals for series number', bool(self.roman_numerals.isChecked()))
|
||||
settings.set('new version notification', bool(self.new_version_notification.isChecked()))
|
||||
settings.set('network timeout', int(self.timeout.value()))
|
||||
config['use_roman_numerals_for_series_number'] = bool(self.roman_numerals.isChecked())
|
||||
config['new_version_notification'] = bool(self.new_version_notification.isChecked())
|
||||
prefs['network_timeout'] = int(self.timeout.value())
|
||||
path = qstring_to_unicode(self.location.text())
|
||||
self.final_columns = [self.columns.item(i).checkState() == Qt.Checked for i in range(self.columns.count())]
|
||||
settings.set('toolbar icon size', self.ICON_SIZES[self.toolbar_button_size.currentIndex()])
|
||||
settings.set('show text in toolbar', bool(self.show_toolbar_text.isChecked()))
|
||||
settings.set('confirm delete', bool(self.confirm_delete.isChecked()))
|
||||
config['toolbar_icon_size'] = self.ICON_SIZES[self.toolbar_button_size.currentIndex()]
|
||||
config['show_text_in_toolbar'] = bool(self.show_toolbar_text.isChecked())
|
||||
config['confirm_delete'] = bool(self.confirm_delete.isChecked())
|
||||
pattern = self.filename_pattern.commit()
|
||||
settings.set('filename pattern', pattern)
|
||||
settings.set('save to disk single format', BOOK_EXTENSIONS[self.single_format.currentIndex()])
|
||||
settings.set('cover flow queue length', self.cover_browse.value())
|
||||
prefs['filename_pattern'] = pattern
|
||||
config['save_to_disk_single_format'] = BOOK_EXTENSIONS[self.single_format.currentIndex()]
|
||||
config['cover_flow_queue_length'] = self.cover_browse.value()
|
||||
|
||||
if not path or not os.path.exists(path) or not os.path.isdir(path):
|
||||
d = error_dialog(self, _('Invalid database location'),
|
||||
@ -113,7 +111,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
else:
|
||||
self.database_location = os.path.abspath(path)
|
||||
self.directories = [qstring_to_unicode(self.directory_list.item(i).text()) for i in range(self.directory_list.count())]
|
||||
settings.set('frequently used directories', self.directories)
|
||||
config['frequently_used_directories'] = self.directories
|
||||
QDialog.accept(self)
|
||||
|
||||
class Vacuum(QMessageBox):
|
||||
|
@ -13,7 +13,7 @@ from PyQt4.QtGui import QDialog, QItemSelectionModel
|
||||
from calibre.gui2.dialogs.fetch_metadata_ui import Ui_FetchMetadata
|
||||
from calibre.gui2 import error_dialog, NONE, info_dialog
|
||||
from calibre.ebooks.metadata.isbndb import create_books, option_parser, ISBNDBError
|
||||
from calibre import Settings
|
||||
from calibre.utils.config import prefs
|
||||
|
||||
class Matches(QAbstractTableModel):
|
||||
|
||||
@ -76,7 +76,7 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
|
||||
self.timeout = timeout
|
||||
QObject.connect(self.fetch, SIGNAL('clicked()'), self.fetch_metadata)
|
||||
|
||||
self.key.setText(Settings().get('isbndb.com key', ''))
|
||||
self.key.setText(prefs['isbndb_com_key'])
|
||||
|
||||
self.setWindowTitle(title if title else 'Unknown')
|
||||
self.tlabel.setText(self.tlabel.text().arg(title if title else 'Unknown'))
|
||||
@ -105,7 +105,7 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
|
||||
_('You must specify a valid access key for isbndb.com'))
|
||||
return
|
||||
else:
|
||||
Settings().set('isbndb.com key', key)
|
||||
prefs['isbndb_com_key'] = key
|
||||
|
||||
args = ['isbndb']
|
||||
if self.isbn:
|
||||
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
'''Display active jobs'''
|
||||
|
||||
from PyQt4.QtCore import Qt, QObject, SIGNAL, QSize, QString
|
||||
from PyQt4.QtCore import Qt, QObject, SIGNAL, QSize, QString, QTimer
|
||||
from PyQt4.QtGui import QDialog, QAbstractItemDelegate, QStyleOptionProgressBarV2, \
|
||||
QApplication, QStyle
|
||||
|
||||
@ -45,10 +45,20 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
||||
self.pb_delegate = ProgressBarDelegate(self)
|
||||
self.jobs_view.setItemDelegateForColumn(2, self.pb_delegate)
|
||||
|
||||
self.running_time_timer = QTimer(self)
|
||||
self.connect(self.running_time_timer, SIGNAL('timeout()'), self.update_running_time)
|
||||
self.running_time_timer.start(1000)
|
||||
|
||||
def update_running_time(self):
|
||||
model = self.model
|
||||
for row, job in enumerate(model.jobs):
|
||||
if job.is_running:
|
||||
self.jobs_view.dataChanged(model.index(row, 3), model.index(row, 3))
|
||||
|
||||
def kill_job(self):
|
||||
for index in self.jobs_view.selectedIndexes():
|
||||
row = index.row()
|
||||
self.emit(SIGNAL('kill_job(int, PyQt_PyObject)'), row, self)
|
||||
self.model.kill_job(row, self)
|
||||
return
|
||||
|
||||
def closeEvent(self, e):
|
||||
|
@ -9,11 +9,11 @@ from PyQt4.QtGui import QAbstractSpinBox, QLineEdit, QCheckBox, QDialog, \
|
||||
from calibre.gui2.dialogs.lrf_single_ui import Ui_LRFSingleDialog
|
||||
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
||||
from calibre.gui2 import qstring_to_unicode, error_dialog, \
|
||||
pixmap_to_data, choose_images
|
||||
pixmap_to_data, choose_images, config
|
||||
from calibre.gui2.widgets import FontFamilyModel
|
||||
from calibre.ebooks.lrf import option_parser
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre import __appname__, Settings
|
||||
from calibre.constants import __appname__
|
||||
|
||||
font_family_model = None
|
||||
|
||||
@ -109,7 +109,7 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
|
||||
|
||||
|
||||
def load_saved_global_defaults(self):
|
||||
cmdline = Settings().get('LRF conversion defaults')
|
||||
cmdline = config['LRF_conversion_defaults']
|
||||
if cmdline:
|
||||
self.set_options_from_cmdline(cmdline)
|
||||
|
||||
@ -163,7 +163,7 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
|
||||
|
||||
def select_cover(self, checked):
|
||||
files = choose_images(self, 'change cover dialog',
|
||||
u'Choose cover for ' + qstring_to_unicode(self.gui_title.text()))
|
||||
_('Choose cover for ') + qstring_to_unicode(self.gui_title.text()))
|
||||
if not files:
|
||||
return
|
||||
_file = files[0]
|
||||
@ -385,7 +385,7 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
|
||||
cmdline.extend([u'--cover', self.cover_file.name])
|
||||
self.cmdline = [unicode(i) for i in cmdline]
|
||||
else:
|
||||
Settings().set('LRF conversion defaults', cmdline)
|
||||
config.set('LRF_conversion_defaults', cmdline)
|
||||
QDialog.accept(self)
|
||||
|
||||
class LRFBulkDialog(LRFSingleDialog):
|
||||
|
@ -18,7 +18,8 @@ from calibre.gui2.dialogs.tag_editor import TagEditor
|
||||
from calibre.gui2.dialogs.password import PasswordDialog
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.ebooks.metadata.library_thing import login, cover_from_isbn, LibraryThingError
|
||||
from calibre import Settings, islinux
|
||||
from calibre import islinux
|
||||
from calibre.utils.config import prefs
|
||||
|
||||
class Format(QListWidgetItem):
|
||||
def __init__(self, parent, ext, size, path=None):
|
||||
@ -145,7 +146,7 @@ class MetadataSingleDialog(QDialog, Ui_MetadataSingleDialog):
|
||||
QObject.connect(self.remove_series_button, SIGNAL('clicked()'),
|
||||
self.remove_unused_series)
|
||||
self.connect(self.swap_button, SIGNAL('clicked()'), self.swap_title_author)
|
||||
self.timeout = float(Settings().get('network timeout', 5))
|
||||
self.timeout = float(prefs['network_timeout'])
|
||||
self.title.setText(db.title(row))
|
||||
isbn = db.isbn(self.id, index_is_id=True)
|
||||
if not isbn:
|
||||
|
@ -1,12 +1,11 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import re
|
||||
from PyQt4.QtGui import QDialog, QLineEdit
|
||||
from PyQt4.QtCore import QVariant, SIGNAL, Qt
|
||||
from PyQt4.QtCore import SIGNAL, Qt
|
||||
|
||||
from calibre.gui2.dialogs.password_ui import Ui_Dialog
|
||||
from calibre.gui2 import qstring_to_unicode
|
||||
from calibre import Settings
|
||||
from calibre.gui2 import qstring_to_unicode, dynamic
|
||||
|
||||
class PasswordDialog(QDialog, Ui_Dialog):
|
||||
|
||||
@ -14,10 +13,12 @@ class PasswordDialog(QDialog, Ui_Dialog):
|
||||
QDialog.__init__(self, window)
|
||||
Ui_Dialog.__init__(self)
|
||||
self.setupUi(self)
|
||||
self.cfg_key = re.sub(r'[^0-9a-zA-Z]', '_', name)
|
||||
|
||||
settings = Settings()
|
||||
un = settings.get(name+': un', u'')
|
||||
pw = settings.get(name+': pw', u'')
|
||||
un = dynamic[self.cfg_key+'__un']
|
||||
pw = dynamic[self.cfg_key+'__un']
|
||||
if not un: un = ''
|
||||
if not pw: pw = ''
|
||||
self.gui_username.setText(un)
|
||||
self.gui_password.setText(pw)
|
||||
self.sname = name
|
||||
@ -37,7 +38,6 @@ class PasswordDialog(QDialog, Ui_Dialog):
|
||||
return qstring_to_unicode(self.gui_password.text())
|
||||
|
||||
def accept(self):
|
||||
settings = Settings()
|
||||
settings.set(self.sname+': un', unicode(self.gui_username.text()))
|
||||
settings.set(self.sname+': pw', unicode(self.gui_password.text()))
|
||||
dynamic.set(self.cfg_key+'__un', unicode(self.gui_username.text()))
|
||||
dynamic.set(self.cfg_key+'__pw', unicode(self.gui_password.text()))
|
||||
QDialog.accept(self)
|
||||
|
@ -1,415 +0,0 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import traceback, logging, collections, time
|
||||
|
||||
from PyQt4.QtCore import QAbstractTableModel, QMutex, QObject, SIGNAL, Qt, \
|
||||
QVariant, QThread
|
||||
from PyQt4.QtGui import QIcon, QDialog
|
||||
|
||||
from calibre import detect_ncpus, Settings
|
||||
from calibre.gui2 import NONE, error_dialog
|
||||
from calibre.parallel import Server
|
||||
from calibre.gui2.dialogs.job_view_ui import Ui_Dialog
|
||||
|
||||
class JobException(Exception):
|
||||
pass
|
||||
|
||||
class Job(QThread):
|
||||
''' Class to run a function in a separate thread with optional mutex based locking.'''
|
||||
def __init__(self, id, description, slot, priority, func, *args, **kwargs):
|
||||
'''
|
||||
@param id: Number. Id of this thread.
|
||||
@param description: String. Description of this job.
|
||||
@param slot: The callable that should be called when the job is done.
|
||||
@param priority: The priority with which this thread should be run
|
||||
@param func: A callable that should be executed in this thread.
|
||||
'''
|
||||
QThread.__init__(self)
|
||||
self.id = id
|
||||
self.func = func
|
||||
self.description = description if description else 'Job #' + str(self.id)
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.slot, self._priority = slot, priority
|
||||
self.result = None
|
||||
self.percent_done = 0
|
||||
self.logger = logging.getLogger('Job #'+str(id))
|
||||
self.logger.setLevel(logging.DEBUG)
|
||||
self.is_locked = False
|
||||
self.log = self.exception = self.last_traceback = None
|
||||
self.connect_done_signal()
|
||||
self.start_time = None
|
||||
|
||||
|
||||
def start(self):
|
||||
self.start_time = time.time()
|
||||
QThread.start(self, self._priority)
|
||||
|
||||
def progress_update(self, val):
|
||||
self.percent_done = val
|
||||
self.emit(SIGNAL('status_update(int, int)'), self.id, int(val))
|
||||
|
||||
def formatted_log(self):
|
||||
if self.log is None:
|
||||
return ''
|
||||
return '<h2>Log:</h2><pre>%s</pre>'%self.log
|
||||
|
||||
|
||||
class DeviceJob(Job):
|
||||
''' Jobs that involve communication with the device. '''
|
||||
def run(self):
|
||||
last_traceback, exception = None, None
|
||||
|
||||
try:
|
||||
self.result = self.func(self.progress_update, *self.args, **self.kwargs)
|
||||
except Exception, err:
|
||||
exception = err
|
||||
last_traceback = traceback.format_exc()
|
||||
|
||||
self.exception, self.last_traceback = exception, last_traceback
|
||||
|
||||
def formatted_error(self):
|
||||
if self.exception is None:
|
||||
return ''
|
||||
ans = u'<p><b>%s</b>: %s</p>'%(self.exception.__class__.__name__, self.exception)
|
||||
ans += '<h2>Traceback:</h2><pre>%s</pre>'%self.last_traceback
|
||||
return ans
|
||||
|
||||
def notify(self):
|
||||
self.emit(SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
|
||||
self.id, self.description, self.result, self.exception, self.last_traceback)
|
||||
|
||||
def connect_done_signal(self):
|
||||
if self.slot is not None:
|
||||
self.connect(self, SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
|
||||
self.slot, Qt.QueuedConnection)
|
||||
|
||||
class ConversionJob(Job):
|
||||
''' Jobs that involve conversion of content.'''
|
||||
def __init__(self, *args, **kwdargs):
|
||||
Job.__init__(self, *args, **kwdargs)
|
||||
self.log = ''
|
||||
|
||||
def run(self):
|
||||
result = None
|
||||
self.server.run_job(self.id, self.func, progress=self.progress,
|
||||
args=self.args, kwdargs=self.kwargs,
|
||||
output=self.output)
|
||||
res = None
|
||||
while res is None:
|
||||
time.sleep(2)
|
||||
res = self.server.result(self.id)
|
||||
if res is None:
|
||||
exception, tb = 'UnknownError: This should not have happened', ''
|
||||
else:
|
||||
result, exception, tb = res
|
||||
self.result, self.last_traceback, self.exception = result, tb, exception
|
||||
|
||||
def output(self, msg):
|
||||
if self.log is None:
|
||||
self.log = ''
|
||||
self.log += msg
|
||||
self.emit(SIGNAL('output_received()'))
|
||||
|
||||
def formatted_log(self):
|
||||
return '<h2>Log:</h2><pre>%s</pre>'%self.log
|
||||
|
||||
def notify(self):
|
||||
self.emit(SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
|
||||
self.id, self.description, self.result, self.exception, self.last_traceback, self.log)
|
||||
|
||||
def connect_done_signal(self):
|
||||
if self.slot is not None:
|
||||
self.connect(self, SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
|
||||
self.slot, Qt.QueuedConnection)
|
||||
|
||||
def formatted_error(self):
|
||||
if self.exception is None:
|
||||
return ''
|
||||
ans = u'<p><b>%s</b>:'%repr(self.exception)
|
||||
ans += '<h2>Traceback:</h2><pre>%s</pre>'%self.last_traceback
|
||||
return ans
|
||||
|
||||
def progress(self, percent, msg):
|
||||
self.emit(SIGNAL('update_progress(int, PyQt_PyObject)'), self.id, percent)
|
||||
|
||||
class JobManager(QAbstractTableModel):
|
||||
|
||||
PRIORITY = {'Idle' : QThread.IdlePriority,
|
||||
'Lowest': QThread.LowestPriority,
|
||||
'Low' : QThread.LowPriority,
|
||||
'Normal': QThread.NormalPriority
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
QAbstractTableModel.__init__(self)
|
||||
self.waiting_jobs = collections.deque()
|
||||
self.running_jobs = collections.deque()
|
||||
self.finished_jobs = collections.deque()
|
||||
self.add_queue = collections.deque()
|
||||
self.update_lock = QMutex() # Protects write access to the above dequeues
|
||||
self.next_id = 0
|
||||
self.wait_icon = QVariant(QIcon(':/images/jobs.svg'))
|
||||
self.running_icon = QVariant(QIcon(':/images/exec.svg'))
|
||||
self.error_icon = QVariant(QIcon(':/images/dialog_error.svg'))
|
||||
self.done_icon = QVariant(QIcon(':/images/ok.svg'))
|
||||
|
||||
self.process_server = Server()
|
||||
|
||||
self.ncpus = detect_ncpus()
|
||||
self.timer_id = self.startTimer(500)
|
||||
|
||||
def terminate_device_jobs(self):
|
||||
for job in self.running_jobs:
|
||||
if isinstance(job, DeviceJob):
|
||||
job.terminate()
|
||||
|
||||
def terminate_all_jobs(self):
|
||||
for job in self.running_jobs:
|
||||
try:
|
||||
if isinstance(job, DeviceJob):
|
||||
job.terminate()
|
||||
except:
|
||||
continue
|
||||
self.process_server.killall()
|
||||
|
||||
def timerEvent(self, event):
|
||||
if event.timerId() == self.timer_id:
|
||||
self.update_lock.lock()
|
||||
try:
|
||||
refresh = False
|
||||
|
||||
while self.add_queue:
|
||||
job = self.add_queue.pop()
|
||||
self.waiting_jobs.append(job)
|
||||
self.emit(SIGNAL('job_added(int)'), job.id, Qt.QueuedConnection)
|
||||
refresh = True
|
||||
|
||||
for job in [job for job in self.running_jobs if job.isFinished()]:
|
||||
self.running_jobs.remove(job)
|
||||
self.finished_jobs.appendleft(job)
|
||||
if job.result != self.process_server.KILL_RESULT:
|
||||
job.notify()
|
||||
job.running_time = time.time() - job.start_time
|
||||
self.emit(SIGNAL('job_done(int)'), job.id)
|
||||
refresh = True
|
||||
|
||||
cjs = list(self.running_conversion_jobs())
|
||||
if len(cjs) < self.ncpus:
|
||||
cj = None
|
||||
for job in self.waiting_jobs:
|
||||
if isinstance(job, ConversionJob):
|
||||
cj = job
|
||||
break
|
||||
if cj is not None:
|
||||
self.waiting_jobs.remove(cj)
|
||||
cj.start()
|
||||
self.running_jobs.append(cj)
|
||||
refresh = True
|
||||
|
||||
djs = list(self.running_device_jobs())
|
||||
if len(djs) == 0:
|
||||
dj = None
|
||||
for job in self.waiting_jobs:
|
||||
if isinstance(job, DeviceJob):
|
||||
dj = job
|
||||
break
|
||||
if dj is not None:
|
||||
self.waiting_jobs.remove(dj)
|
||||
dj.start()
|
||||
self.running_jobs.append(dj)
|
||||
refresh = True
|
||||
if refresh:
|
||||
self.reset()
|
||||
if len(self.running_jobs) == 0:
|
||||
self.emit(SIGNAL('no_more_jobs()'))
|
||||
for i in range(len(self.running_jobs)):
|
||||
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), self.index(i, 3), self.index(i, 3))
|
||||
finally:
|
||||
self.update_lock.unlock()
|
||||
|
||||
def has_jobs(self):
|
||||
return len(self.waiting_jobs) + len(self.running_jobs) > 0
|
||||
|
||||
def has_device_jobs(self):
|
||||
return len(tuple(self.running_device_jobs())) > 0
|
||||
|
||||
def running_device_jobs(self):
|
||||
for job in self.running_jobs:
|
||||
if isinstance(job, DeviceJob):
|
||||
yield job
|
||||
|
||||
def running_conversion_jobs(self):
|
||||
for job in self.running_jobs:
|
||||
if isinstance(job, ConversionJob):
|
||||
yield job
|
||||
|
||||
def update_progress(self, id, percent):
|
||||
row = -1
|
||||
for collection in (self.running_jobs, self.waiting_jobs, self.finished_jobs):
|
||||
for job in collection:
|
||||
row += 1
|
||||
if job.id == id:
|
||||
job.percent_done = percent
|
||||
index = self.index(row, 2)
|
||||
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), index, index)
|
||||
return
|
||||
|
||||
|
||||
def create_job(self, job_class, description, slot, priority, *args, **kwargs):
|
||||
self.next_id += 1
|
||||
id = self.next_id
|
||||
job = job_class(id, description, slot, priority, *args, **kwargs)
|
||||
job.server = self.process_server
|
||||
QObject.connect(job, SIGNAL('status_update(int, int)'), self.status_update,
|
||||
Qt.QueuedConnection)
|
||||
self.connect(job, SIGNAL('update_progress(int, PyQt_PyObject)'),
|
||||
self.update_progress, Qt.QueuedConnection)
|
||||
self.update_lock.lock()
|
||||
self.add_queue.append(job)
|
||||
self.update_lock.unlock()
|
||||
return job
|
||||
|
||||
def run_conversion_job(self, slot, callable, args=[], **kwargs):
|
||||
'''
|
||||
Run a conversion job.
|
||||
@param slot: The function to call with the job result.
|
||||
@param callable: The function to call to communicate with the device.
|
||||
@param args: The arguments to pass to callable
|
||||
@param kwargs: The keyword arguments to pass to callable
|
||||
'''
|
||||
desc = kwargs.pop('job_description', '')
|
||||
if args and hasattr(args[0], 'append') and '--verbose' not in args[0]:
|
||||
args[0].append('--verbose')
|
||||
priority = self.PRIORITY[Settings().get('conversion job priority', 'Normal')]
|
||||
job = self.create_job(ConversionJob, desc, slot, priority,
|
||||
callable, *args, **kwargs)
|
||||
return job.id
|
||||
|
||||
def run_device_job(self, slot, callable, *args, **kwargs):
|
||||
'''
|
||||
Run a job to communicate with the device.
|
||||
@param slot: The function to call with the job result.
|
||||
@param callable: The function to call to communicate with the device.
|
||||
@param args: The arguments to pass to callable
|
||||
@param kwargs: The keyword arguments to pass to callable
|
||||
'''
|
||||
desc = callable.__doc__ if callable.__doc__ else ''
|
||||
desc += kwargs.pop('job_extra_description', '')
|
||||
job = self.create_job(DeviceJob, desc, slot, QThread.NormalPriority,
|
||||
callable, *args, **kwargs)
|
||||
return job.id
|
||||
|
||||
def rowCount(self, parent):
|
||||
return len(self.running_jobs) + len(self.waiting_jobs) + len(self.finished_jobs)
|
||||
|
||||
def columnCount(self, parent):
|
||||
return 4
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if role != Qt.DisplayRole:
|
||||
return NONE
|
||||
if orientation == Qt.Horizontal:
|
||||
if section == 0: text = _("Job")
|
||||
elif section == 1: text = _("Status")
|
||||
elif section == 2: text = _("Progress")
|
||||
elif section == 3: text = _('Running time')
|
||||
return QVariant(text)
|
||||
else:
|
||||
return QVariant(section+1)
|
||||
|
||||
def row_to_job(self, row):
|
||||
if row < len(self.running_jobs):
|
||||
return self.running_jobs[row], 0
|
||||
row -= len(self.running_jobs)
|
||||
if row < len(self.waiting_jobs):
|
||||
return self.waiting_jobs[row], 1
|
||||
row -= len(self.running_jobs)
|
||||
return self.finished_jobs[row], 2
|
||||
|
||||
def data(self, index, role):
|
||||
if role not in (Qt.DisplayRole, Qt.DecorationRole):
|
||||
return NONE
|
||||
row, col = index.row(), index.column()
|
||||
try:
|
||||
job, status = self.row_to_job(row)
|
||||
except IndexError:
|
||||
return NONE
|
||||
|
||||
if role == Qt.DisplayRole:
|
||||
if col == 0:
|
||||
return QVariant(job.description)
|
||||
if col == 1:
|
||||
if status == 2:
|
||||
st = _('Finished') if job.exception is None else _('Error')
|
||||
else:
|
||||
st = [_('Working'), _('Waiting')][status]
|
||||
return QVariant(st)
|
||||
if col == 2:
|
||||
return QVariant(int(100*job.percent_done))
|
||||
if col == 3:
|
||||
if job.start_time is None:
|
||||
return NONE
|
||||
rtime = job.running_time if hasattr(job, 'running_time') else time.time() - job.start_time
|
||||
return QVariant('%dm %ds'%(int(rtime)//60, int(rtime)%60))
|
||||
if role == Qt.DecorationRole and col == 0:
|
||||
if status == 1:
|
||||
return self.wait_icon
|
||||
if status == 0:
|
||||
return self.running_icon
|
||||
if status == 2:
|
||||
if job.exception or job.result == self.process_server.KILL_RESULT:
|
||||
return self.error_icon
|
||||
return self.done_icon
|
||||
return NONE
|
||||
|
||||
def status_update(self, id, progress):
|
||||
for i in range(len(self.running_jobs)):
|
||||
job = self.running_jobs[i]
|
||||
if job.id == id:
|
||||
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), self.index(i, 2), self.index(i, 3))
|
||||
break
|
||||
|
||||
def kill_job(self, row, gui_parent):
|
||||
job, status = self.row_to_job(row)
|
||||
if isinstance(job, DeviceJob):
|
||||
error_dialog(gui_parent, _('Cannot kill job'),
|
||||
_('Cannot kill jobs that are communicating with the device as this may cause data corruption.')).exec_()
|
||||
return
|
||||
if status == 2:
|
||||
error_dialog(gui_parent, _('Cannot kill job'),
|
||||
_('Cannot kill already completed jobs.')).exec_()
|
||||
return
|
||||
if status == 1:
|
||||
self.update_lock.lock()
|
||||
try:
|
||||
self.waiting_jobs.remove(job)
|
||||
self.finished_jobs.append(job)
|
||||
self.emit(SIGNAL('job_done(int)'), job.id)
|
||||
job.result = self.process_server.KILL_RESULT
|
||||
finally:
|
||||
self.update_lock.unlock()
|
||||
else:
|
||||
self.process_server.kill(job.id)
|
||||
self.reset()
|
||||
if len(self.running_jobs) + len(self.waiting_jobs) == 0:
|
||||
self.emit(SIGNAL('no_more_jobs()'))
|
||||
|
||||
class DetailView(QDialog, Ui_Dialog):
|
||||
|
||||
def __init__(self, parent, job):
|
||||
QDialog.__init__(self, parent)
|
||||
self.setupUi(self)
|
||||
self.setWindowTitle(job.description)
|
||||
self.job = job
|
||||
self.connect(self.job, SIGNAL('output_received()'), self.update)
|
||||
self.update()
|
||||
|
||||
|
||||
def update(self):
|
||||
txt = self.job.formatted_error() + self.job.formatted_log()
|
||||
if not txt:
|
||||
txt = 'No details available'
|
||||
self.log.setHtml(txt)
|
||||
vbar = self.log.verticalScrollBar()
|
||||
vbar.setValue(vbar.maximum())
|
193
src/calibre/gui2/jobs2.py
Normal file
193
src/calibre/gui2/jobs2.py
Normal file
@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
Job management.
|
||||
'''
|
||||
import time
|
||||
from PyQt4.QtCore import QAbstractTableModel, QVariant, QModelIndex, Qt, SIGNAL
|
||||
from PyQt4.QtGui import QIcon, QDialog
|
||||
|
||||
from calibre.parallel import ParallelJob, Server
|
||||
from calibre.gui2 import Dispatcher, error_dialog
|
||||
from calibre.gui2.device import DeviceJob
|
||||
from calibre.gui2.dialogs.job_view_ui import Ui_Dialog
|
||||
|
||||
NONE = QVariant()
|
||||
|
||||
class JobManager(QAbstractTableModel):
|
||||
|
||||
wait_icon = QVariant(QIcon(':/images/jobs.svg'))
|
||||
running_icon = QVariant(QIcon(':/images/exec.svg'))
|
||||
error_icon = QVariant(QIcon(':/images/dialog_error.svg'))
|
||||
done_icon = QVariant(QIcon(':/images/ok.svg'))
|
||||
|
||||
def __init__(self):
|
||||
QAbstractTableModel.__init__(self)
|
||||
self.jobs = []
|
||||
self.server = Server()
|
||||
self.add_job = Dispatcher(self._add_job)
|
||||
self.status_update = Dispatcher(self._status_update)
|
||||
self.start_work = Dispatcher(self._start_work)
|
||||
self.job_done = Dispatcher(self._job_done)
|
||||
|
||||
def columnCount(self, parent=QModelIndex()):
|
||||
return 4
|
||||
|
||||
def rowCount(self, parent=QModelIndex()):
|
||||
return len(self.jobs)
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if role != Qt.DisplayRole:
|
||||
return NONE
|
||||
if orientation == Qt.Horizontal:
|
||||
if section == 0: text = _("Job")
|
||||
elif section == 1: text = _("Status")
|
||||
elif section == 2: text = _("Progress")
|
||||
elif section == 3: text = _('Running time')
|
||||
return QVariant(text)
|
||||
else:
|
||||
return QVariant(section+1)
|
||||
|
||||
def data(self, index, role):
|
||||
try:
|
||||
if role not in (Qt.DisplayRole, Qt.DecorationRole):
|
||||
return NONE
|
||||
row, col = index.row(), index.column()
|
||||
job = self.jobs[row]
|
||||
|
||||
if role == Qt.DisplayRole:
|
||||
if col == 0:
|
||||
desc = job.description
|
||||
if not desc:
|
||||
desc = _('Unknown job')
|
||||
return QVariant(desc)
|
||||
if col == 1:
|
||||
status = job.status()
|
||||
if status == 'DONE':
|
||||
st = _('Finished')
|
||||
elif status == 'ERROR':
|
||||
st = _('Error')
|
||||
elif status == 'WAITING':
|
||||
st = _('Waiting')
|
||||
else:
|
||||
st = _('Working')
|
||||
return QVariant(st)
|
||||
if col == 2:
|
||||
pc = job.percent
|
||||
if pc <=0:
|
||||
percent = 0
|
||||
else:
|
||||
percent = int(100*pc)
|
||||
return QVariant(percent)
|
||||
if col == 3:
|
||||
if job.start_time is None:
|
||||
return NONE
|
||||
rtime = job.running_time if job.running_time is not None else \
|
||||
time.time() - job.start_time
|
||||
return QVariant('%dm %ds'%(int(rtime)//60, int(rtime)%60))
|
||||
if role == Qt.DecorationRole and col == 0:
|
||||
status = job.status()
|
||||
if status == 'WAITING':
|
||||
return self.wait_icon
|
||||
if status == 'WORKING':
|
||||
return self.running_icon
|
||||
if status == 'ERROR':
|
||||
return self.error_icon
|
||||
if status == 'DONE':
|
||||
return self.done_icon
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return NONE
|
||||
|
||||
def _add_job(self, job):
|
||||
self.emit(SIGNAL('layoutAboutToBeChanged()'))
|
||||
self.jobs.append(job)
|
||||
self.jobs.sort()
|
||||
self.emit(SIGNAL('job_added(int)'), self.rowCount())
|
||||
self.emit(SIGNAL('layoutChanged()'))
|
||||
|
||||
def done_jobs(self):
|
||||
return [j for j in self.jobs if j.status() in ['DONE', 'ERROR']]
|
||||
|
||||
def row_to_job(self, row):
|
||||
return self.jobs[row]
|
||||
|
||||
def _start_work(self, job):
|
||||
self.emit(SIGNAL('layoutAboutToBeChanged()'))
|
||||
self.jobs.sort()
|
||||
self.emit(SIGNAL('layoutChanged()'))
|
||||
|
||||
def _job_done(self, job):
|
||||
self.emit(SIGNAL('layoutAboutToBeChanged()'))
|
||||
self.jobs.sort()
|
||||
self.emit(SIGNAL('job_done(int)'), len(self.jobs) - len(self.done_jobs()))
|
||||
self.emit(SIGNAL('layoutChanged()'))
|
||||
|
||||
def _status_update(self, job):
|
||||
row = self.jobs.index(job)
|
||||
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'),
|
||||
self.index(row, 0), self.index(row, 3))
|
||||
|
||||
|
||||
def has_device_jobs(self):
|
||||
for job in self.jobs:
|
||||
if job.is_running and isinstance(job, DeviceJob):
|
||||
return True
|
||||
return False
|
||||
|
||||
def has_jobs(self):
|
||||
for job in self.jobs:
|
||||
if job.is_running:
|
||||
return True
|
||||
return False
|
||||
|
||||
def run_job(self, done, func, args=[], kwargs={},
|
||||
description=None):
|
||||
job = ParallelJob(func, done, self, args=args, kwargs=kwargs,
|
||||
description=description)
|
||||
self.server.add_job(job)
|
||||
return job
|
||||
|
||||
|
||||
def output(self, job):
|
||||
self.emit(SIGNAL('output_received()'))
|
||||
|
||||
def kill_job(self, row, view):
|
||||
job = self.jobs[row]
|
||||
if isinstance(job, DeviceJob):
|
||||
error_dialog(view, _('Cannot kill job'),
|
||||
_('Cannot kill jobs that communicate with the device')).exec_()
|
||||
return
|
||||
if job.has_run:
|
||||
error_dialog(view, _('Cannot kill job'),
|
||||
_('Job has already run')).exec_()
|
||||
return
|
||||
if not job.is_running:
|
||||
error_dialog(view, _('Cannot kill job'),
|
||||
_('Cannot kill waiting job')).exec_()
|
||||
return
|
||||
|
||||
|
||||
self.server.kill(job)
|
||||
|
||||
def terminate_all_jobs(self):
|
||||
pass
|
||||
|
||||
class DetailView(QDialog, Ui_Dialog):
|
||||
|
||||
def __init__(self, parent, job):
|
||||
QDialog.__init__(self, parent)
|
||||
self.setupUi(self)
|
||||
self.setWindowTitle(job.description)
|
||||
self.job = job
|
||||
self.update()
|
||||
|
||||
|
||||
def update(self):
|
||||
self.log.setHtml(self.job.gui_text())
|
||||
vbar = self.log.verticalScrollBar()
|
||||
vbar.setValue(vbar.maximum())
|
@ -13,10 +13,10 @@ from PyQt4.QtGui import QTableView, QProgressDialog, QAbstractItemView, QColor,
|
||||
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \
|
||||
QCoreApplication, SIGNAL, QObject, QSize, QModelIndex
|
||||
|
||||
from calibre import Settings, preferred_encoding
|
||||
from calibre import preferred_encoding
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.library.database import LibraryDatabase, text_to_tokens
|
||||
from calibre.gui2 import NONE, TableView, qstring_to_unicode
|
||||
from calibre.gui2 import NONE, TableView, qstring_to_unicode, config
|
||||
|
||||
class LibraryDelegate(QItemDelegate):
|
||||
COLOR = QColor("blue")
|
||||
@ -113,7 +113,7 @@ class BooksModel(QAbstractTableModel):
|
||||
self.cover_cache.clear_cache()
|
||||
|
||||
def read_config(self):
|
||||
self.use_roman_numbers = Settings().get('use roman numerals for series number', True)
|
||||
self.use_roman_numbers = config['use_roman_numerals_for_series_number']
|
||||
|
||||
|
||||
def set_database(self, db):
|
||||
|
@ -6,10 +6,11 @@ import sys, logging, os, traceback, time
|
||||
from PyQt4.QtGui import QKeySequence, QPainter, QDialog, QSpinBox, QSlider, QIcon
|
||||
from PyQt4.QtCore import Qt, QObject, SIGNAL, QCoreApplication, QThread
|
||||
|
||||
from calibre import __appname__, setup_cli_handlers, islinux, Settings
|
||||
from calibre import __appname__, setup_cli_handlers, islinux
|
||||
from calibre.ebooks.lrf.lrfparser import LRFDocument
|
||||
|
||||
from calibre.gui2 import ORG_NAME, APP_UID, error_dialog, choose_files, Application
|
||||
from calibre.gui2 import ORG_NAME, APP_UID, error_dialog, \
|
||||
config, choose_files, Application
|
||||
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
|
||||
from calibre.gui2.lrf_renderer.main_ui import Ui_MainWindow
|
||||
from calibre.gui2.lrf_renderer.config_ui import Ui_ViewerConfig
|
||||
@ -102,13 +103,15 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
|
||||
|
||||
def configure(self, triggered):
|
||||
opts = Settings().get('LRF ebook viewer options', self.opts)
|
||||
opts = config['LRF_ebook_viewer_options']
|
||||
if not opts:
|
||||
opts = self.opts
|
||||
d = Config(self, opts)
|
||||
d.exec_()
|
||||
if d.result() == QDialog.Accepted:
|
||||
opts.white_background = bool(d.white_background.isChecked())
|
||||
opts.hyphenate = bool(d.hyphenate.isChecked())
|
||||
Settings().set('LRF ebook viewer options', opts)
|
||||
config['LRF_ebook_viewer_options'] = opts
|
||||
|
||||
def set_ebook(self, stream):
|
||||
self.progress_bar.setMinimum(0)
|
||||
@ -281,7 +284,9 @@ Read the LRF ebook book.lrf
|
||||
return parser
|
||||
|
||||
def normalize_settings(parser, opts):
|
||||
saved_opts = Settings().get('LRF ebook viewer options', opts)
|
||||
saved_opts = config['LRF_ebook_viewer_options']
|
||||
if not saved_opts:
|
||||
saved_opts = opts
|
||||
for opt in parser.option_list:
|
||||
if not opt.dest:
|
||||
continue
|
||||
|
@ -3,31 +3,32 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import os, sys, textwrap, collections, traceback, time
|
||||
from xml.parsers.expat import ExpatError
|
||||
from functools import partial
|
||||
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
|
||||
QVariant, QThread, QUrl, QSize
|
||||
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QUrl
|
||||
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \
|
||||
QToolButton, QDialog, QDesktopServices
|
||||
from PyQt4.QtSvg import QSvgRenderer
|
||||
|
||||
from calibre import __version__, __appname__, islinux, sanitize_file_name, \
|
||||
Settings, iswindows, isosx, preferred_encoding
|
||||
iswindows, isosx, preferred_encoding
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.ebooks.metadata.meta import get_metadata, get_filename_pat, set_filename_pat
|
||||
from calibre.ebooks.metadata.meta import get_metadata
|
||||
from calibre.devices.errors import FreeSpaceError
|
||||
from calibre.devices.interface import Device
|
||||
from calibre.utils.config import prefs, dynamic
|
||||
from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
|
||||
initialize_file_icon_provider, question_dialog,\
|
||||
pixmap_to_data, choose_dir, ORG_NAME, \
|
||||
set_sidebar_directories, \
|
||||
SingleApplication, Application, available_height, max_available_height
|
||||
set_sidebar_directories, Dispatcher, \
|
||||
SingleApplication, Application, available_height, \
|
||||
max_available_height, config
|
||||
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
|
||||
from calibre.library.database import LibraryDatabase
|
||||
from calibre.gui2.update import CheckForUpdates
|
||||
from calibre.gui2.main_window import MainWindow, option_parser
|
||||
from calibre.gui2.main_ui import Ui_MainWindow
|
||||
from calibre.gui2.device import DeviceDetector, DeviceManager
|
||||
from calibre.gui2.device import DeviceManager
|
||||
from calibre.gui2.status import StatusBar
|
||||
from calibre.gui2.jobs import JobManager
|
||||
from calibre.gui2.jobs2 import JobManager
|
||||
from calibre.gui2.news import NewsMenu
|
||||
from calibre.gui2.dialogs.metadata_single import MetadataSingleDialog
|
||||
from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
|
||||
@ -45,6 +46,8 @@ from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.ebooks.lrf import preferred_source_formats as LRF_PREFERRED_SOURCE_FORMATS
|
||||
from calibre.library.database2 import LibraryDatabase2, CoverCache
|
||||
from calibre.parallel import JobKilled
|
||||
from calibre.utils.filenames import ascii_filename
|
||||
|
||||
class Main(MainWindow, Ui_MainWindow):
|
||||
|
||||
@ -73,7 +76,6 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.read_settings()
|
||||
self.job_manager = JobManager()
|
||||
self.jobs_dialog = JobsDialog(self, self.job_manager)
|
||||
self.device_manager = None
|
||||
self.upload_memory = {}
|
||||
self.delete_memory = {}
|
||||
self.conversion_jobs = {}
|
||||
@ -120,12 +122,13 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
sm.addAction(_('Send to storage card by default'))
|
||||
sm.actions()[-1].setCheckable(True)
|
||||
def default_sync(checked):
|
||||
Settings().set('send to device by default', bool(checked))
|
||||
config.set('send_to_device_by_default', bool(checked))
|
||||
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_main_memory)
|
||||
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_card)
|
||||
QObject.connect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_card if checked else self.sync_to_main_memory)
|
||||
QObject.connect(sm.actions()[-1], SIGNAL('toggled(bool)'), default_sync)
|
||||
sm.actions()[-1].setChecked(Settings().get('send to device by default', False))
|
||||
|
||||
sm.actions()[-1].setChecked(config.get('send_to_device_by_default'))
|
||||
default_sync(sm.actions()[-1].isChecked())
|
||||
self.sync_menu = sm # Needed
|
||||
md = QMenu()
|
||||
@ -152,7 +155,7 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.save_menu = QMenu()
|
||||
self.save_menu.addAction(_('Save to disk'))
|
||||
self.save_menu.addAction(_('Save to disk in a single directory'))
|
||||
self.save_menu.addAction(_('Save only %s format to disk')%Settings().get('save to disk single format', 'lrf').upper())
|
||||
self.save_menu.addAction(_('Save only %s format to disk')%config.get('save_to_disk_single_format').upper())
|
||||
|
||||
self.view_menu = QMenu()
|
||||
self.view_menu.addAction(_('View'))
|
||||
@ -252,10 +255,8 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.setMaximumHeight(max_available_height())
|
||||
|
||||
####################### Setup device detection ########################
|
||||
self.detector = DeviceDetector(sleep_time=2000)
|
||||
QObject.connect(self.detector, SIGNAL('connected(PyQt_PyObject, PyQt_PyObject)'),
|
||||
self.device_detected, Qt.QueuedConnection)
|
||||
self.detector.start(QThread.InheritPriority)
|
||||
self.device_manager = DeviceManager(Dispatcher(self.device_detected), self.job_manager)
|
||||
self.device_manager.start()
|
||||
|
||||
self.news_menu.set_custom_feeds(self.library_view.model().db.get_feeds())
|
||||
|
||||
@ -314,22 +315,19 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
|
||||
|
||||
########################## Connect to device ##############################
|
||||
def device_detected(self, device, connected):
|
||||
def device_detected(self, connected):
|
||||
'''
|
||||
Called when a device is connected to the computer.
|
||||
'''
|
||||
if connected:
|
||||
self.device_manager = DeviceManager(device)
|
||||
self.job_manager.run_device_job(self.info_read, self.device_manager.info_func())
|
||||
self.set_default_thumbnail(device.THUMBNAIL_HEIGHT)
|
||||
self.status_bar.showMessage(_('Device: ')+device.__class__.__name__+_(' detected.'), 3000)
|
||||
self.device_manager.get_device_information(Dispatcher(self.info_read))
|
||||
self.set_default_thumbnail(self.device_manager.device.THUMBNAIL_HEIGHT)
|
||||
self.status_bar.showMessage(_('Device: ')+\
|
||||
self.device_manager.device.__class__.__name__+_(' detected.'), 3000)
|
||||
self.action_sync.setEnabled(True)
|
||||
self.device_connected = True
|
||||
else:
|
||||
self.device_connected = False
|
||||
self.job_manager.terminate_device_jobs()
|
||||
if self.device_manager:
|
||||
self.device_manager.device_removed()
|
||||
self.location_view.model().update_devices()
|
||||
self.action_sync.setEnabled(False)
|
||||
self.vanity.setText(self.vanity_template%dict(version=self.latest_version, device=' '))
|
||||
@ -338,27 +336,26 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.status_bar.reset_info()
|
||||
self.location_selected('library')
|
||||
|
||||
def info_read(self, id, description, result, exception, formatted_traceback):
|
||||
def info_read(self, job):
|
||||
'''
|
||||
Called once device information has been read.
|
||||
'''
|
||||
if exception:
|
||||
self.device_job_exception(id, description, exception, formatted_traceback)
|
||||
if job.exception is not None:
|
||||
self.device_job_exception(job)
|
||||
return
|
||||
info, cp, fs = result
|
||||
info, cp, fs = job.result
|
||||
self.location_view.model().update_devices(cp, fs)
|
||||
self.device_info = _('Connected ')+' '.join(info[:-1])
|
||||
self.vanity.setText(self.vanity_template%dict(version=self.latest_version, device=self.device_info))
|
||||
func = self.device_manager.books_func()
|
||||
self.job_manager.run_device_job(self.metadata_downloaded, func)
|
||||
|
||||
def metadata_downloaded(self, id, description, result, exception, formatted_traceback):
|
||||
self.device_manager.books(Dispatcher(self.metadata_downloaded))
|
||||
|
||||
def metadata_downloaded(self, job):
|
||||
'''
|
||||
Called once metadata has been read for all books on the device.
|
||||
'''
|
||||
if exception:
|
||||
print exception, type(exception)
|
||||
if isinstance(exception, ExpatError):
|
||||
if job.exception is not None:
|
||||
if isinstance(job.exception, ExpatError):
|
||||
error_dialog(self, _('Device database corrupted'),
|
||||
_('''
|
||||
<p>The database of books on the reader is corrupted. Try the following:
|
||||
@ -368,9 +365,9 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
</ol>
|
||||
''')%dict(app=__appname__)).exec_()
|
||||
else:
|
||||
self.device_job_exception(id, description, exception, formatted_traceback)
|
||||
self.device_job_exception(job)
|
||||
return
|
||||
mainlist, cardlist = result
|
||||
mainlist, cardlist = job.result
|
||||
self.memory_view.set_database(mainlist)
|
||||
self.card_view.set_database(cardlist)
|
||||
for view in (self.memory_view, self.card_view):
|
||||
@ -387,18 +384,17 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
'''
|
||||
Upload metadata to device.
|
||||
'''
|
||||
self.job_manager.run_device_job(self.metadata_synced,
|
||||
self.device_manager.sync_booklists_func(),
|
||||
self.booklists())
|
||||
self.device_manager.sync_booklists(Dispatcher(self.metadata_synced),
|
||||
self.booklists())
|
||||
|
||||
def metadata_synced(self, id, description, result, exception, formatted_traceback):
|
||||
def metadata_synced(self, job):
|
||||
'''
|
||||
Called once metadata has been uploaded.
|
||||
'''
|
||||
if exception:
|
||||
self.device_job_exception(id, description, exception, formatted_traceback)
|
||||
if job.exception is not None:
|
||||
self.device_job_exception(job)
|
||||
return
|
||||
cp, fs = result
|
||||
cp, fs = job.result
|
||||
self.location_view.model().update_devices(cp, fs)
|
||||
############################################################################
|
||||
|
||||
@ -500,35 +496,34 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
def upload_books(self, files, names, metadata, on_card=False, memory=None):
|
||||
'''
|
||||
Upload books to device.
|
||||
@param files: List of either paths to files or file like objects
|
||||
:param files: List of either paths to files or file like objects
|
||||
'''
|
||||
titles = ', '.join([i['title'] for i in metadata])
|
||||
id = self.job_manager.run_device_job(self.books_uploaded,
|
||||
self.device_manager.upload_books_func(),
|
||||
titles = [i['title'] for i in metadata]
|
||||
job = self.device_manager.upload_books(Dispatcher(self.books_uploaded),
|
||||
files, names, on_card=on_card,
|
||||
job_extra_description=titles
|
||||
titles=titles
|
||||
)
|
||||
self.upload_memory[id] = (metadata, on_card, memory)
|
||||
self.upload_memory[job] = (metadata, on_card, memory)
|
||||
|
||||
def books_uploaded(self, id, description, result, exception, formatted_traceback):
|
||||
def books_uploaded(self, job):
|
||||
'''
|
||||
Called once books have been uploaded.
|
||||
'''
|
||||
metadata, on_card, memory = self.upload_memory.pop(id)
|
||||
metadata, on_card, memory = self.upload_memory.pop(job)
|
||||
|
||||
if exception:
|
||||
if isinstance(exception, FreeSpaceError):
|
||||
where = 'in main memory.' if 'memory' in str(exception) else 'on the storage card.'
|
||||
if job.exception is not None:
|
||||
if isinstance(job.exception, FreeSpaceError):
|
||||
where = 'in main memory.' if 'memory' in str(job.exception) else 'on the storage card.'
|
||||
titles = '\n'.join(['<li>'+mi['title']+'</li>' for mi in metadata])
|
||||
d = error_dialog(self, _('No space on device'),
|
||||
_('<p>Cannot upload books to device there is no more free space available ')+where+
|
||||
'</p>\n<ul>%s</ul>'%(titles,))
|
||||
d.exec_()
|
||||
else:
|
||||
self.device_job_exception(id, description, exception, formatted_traceback)
|
||||
self.device_job_exception(job)
|
||||
return
|
||||
|
||||
self.device_manager.add_books_to_metadata(result, metadata, self.booklists())
|
||||
self.device_manager.add_books_to_metadata(job.result, metadata, self.booklists())
|
||||
|
||||
self.upload_booklists()
|
||||
|
||||
@ -551,7 +546,7 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
rows = view.selectionModel().selectedRows()
|
||||
if not rows or len(rows) == 0:
|
||||
return
|
||||
if Settings().get('confirm delete', False):
|
||||
if config['confirm_delete']:
|
||||
d = question_dialog(self, _('Confirm delete'),
|
||||
_('Are you sure you want to delete these %d books?')%len(rows))
|
||||
if d.exec_() != QMessageBox.Yes:
|
||||
@ -568,22 +563,20 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.status_bar.showMessage(_('Deleting books from device.'), 1000)
|
||||
|
||||
def remove_paths(self, paths):
|
||||
return self.job_manager.run_device_job(self.books_deleted,
|
||||
self.device_manager.delete_books_func(), paths)
|
||||
return self.device_manager.delete_books(Dispatcher(self.books_deleted), paths)
|
||||
|
||||
|
||||
def books_deleted(self, id, description, result, exception, formatted_traceback):
|
||||
def books_deleted(self, job):
|
||||
'''
|
||||
Called once deletion is done on the device
|
||||
'''
|
||||
for view in (self.memory_view, self.card_view):
|
||||
view.model().deletion_done(id, bool(exception))
|
||||
if exception:
|
||||
self.device_job_exception(id, description, exception, formatted_traceback)
|
||||
view.model().deletion_done(id, bool(job.exception))
|
||||
if job.exception is not None:
|
||||
self.device_job_exception(job)
|
||||
return
|
||||
|
||||
if self.delete_memory.has_key(id):
|
||||
paths, model = self.delete_memory.pop(id)
|
||||
if self.delete_memory.has_key(job):
|
||||
paths, model = self.delete_memory.pop(job)
|
||||
self.device_manager.remove_books_from_metadata(paths, self.booklists())
|
||||
model.paths_deleted(paths)
|
||||
self.upload_booklists()
|
||||
@ -686,10 +679,9 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
if not a:
|
||||
a = 'Unknown'
|
||||
prefix = sanitize_file_name(t+' - '+a)
|
||||
if isinstance(prefix, unicode):
|
||||
prefix = prefix.encode('ascii', 'ignore')
|
||||
else:
|
||||
prefix = prefix.decode('ascii', 'ignore').encode('ascii', 'ignore')
|
||||
if not isinstance(prefix, unicode):
|
||||
prefix = prefix.decode(preferred_encoding, 'replace')
|
||||
prefix = ascii_filename(prefix)
|
||||
names.append('%s_%d%s'%(prefix, id, os.path.splitext(f)[1]))
|
||||
remove = [self.library_view.model().id(r) for r in rows] if delete_from_library else []
|
||||
self.upload_books(gf, names, good, on_card, memory=(_files, remove))
|
||||
@ -705,7 +697,7 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
|
||||
############################## Save to disk ################################
|
||||
def save_single_format_to_disk(self, checked):
|
||||
self.save_to_disk(checked, True, Settings().get('save to disk single format', 'lrf'))
|
||||
self.save_to_disk(checked, True, config['save_to_disk_single_format'])
|
||||
|
||||
def save_to_single_dir(self, checked):
|
||||
self.save_to_disk(checked, True)
|
||||
@ -732,12 +724,11 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
QDesktopServices.openUrl(QUrl('file:'+dir))
|
||||
else:
|
||||
paths = self.current_view().model().paths(rows)
|
||||
self.job_manager.run_device_job(self.books_saved,
|
||||
self.device_manager.save_books_func(), paths, dir)
|
||||
self.device_manager.save_books(Dispatcher(self.books_saved), paths, dir)
|
||||
|
||||
def books_saved(self, id, description, result, exception, formatted_traceback):
|
||||
if exception:
|
||||
self.device_job_exception(id, description, exception, formatted_traceback)
|
||||
def books_saved(self, job):
|
||||
if job.exception is not None:
|
||||
self.device_job_exception(job)
|
||||
return
|
||||
|
||||
############################################################################
|
||||
@ -760,15 +751,15 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
if data['password']:
|
||||
args.extend(['--password', data['password']])
|
||||
args.append(data['script'] if data['script'] else data['title'])
|
||||
id = self.job_manager.run_conversion_job(self.news_fetched, 'feeds2lrf', args=[args],
|
||||
job_description=_('Fetch news from ')+data['title'])
|
||||
self.conversion_jobs[id] = (pt, 'lrf')
|
||||
job = self.job_manager.run_job(Dispatcher(self.news_fetched), 'feeds2lrf', args=[args],
|
||||
description=_('Fetch news from ')+data['title'])
|
||||
self.conversion_jobs[job] = (pt, 'lrf')
|
||||
self.status_bar.showMessage(_('Fetching news from ')+data['title'], 2000)
|
||||
|
||||
def news_fetched(self, id, description, result, exception, formatted_traceback, log):
|
||||
pt, fmt = self.conversion_jobs.pop(id)
|
||||
if exception:
|
||||
self.conversion_job_exception(id, description, exception, formatted_traceback, log)
|
||||
def news_fetched(self, job):
|
||||
pt, fmt = self.conversion_jobs.pop(job)
|
||||
if job.exception is not None:
|
||||
self.job_exception(job)
|
||||
return
|
||||
to_device = self.device_connected and fmt in self.device_manager.device_class.FORMATS
|
||||
self._add_books([pt.name], to_device)
|
||||
@ -806,8 +797,9 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
bad_rows = []
|
||||
|
||||
self.status_bar.showMessage(_('Starting Bulk conversion of %d books')%len(rows), 2000)
|
||||
|
||||
for i, row in enumerate([r.row() for r in rows]):
|
||||
if rows and hasattr(rows[0], 'row'):
|
||||
rows = [r.row() for r in rows]
|
||||
for i, row in enumerate(rows):
|
||||
cmdline = list(d.cmdline)
|
||||
mi = self.library_view.model().db.get_metadata(row)
|
||||
if mi.title:
|
||||
@ -841,12 +833,12 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
cmdline.extend(['--cover', cf.name])
|
||||
cmdline.extend(['-o', of.name])
|
||||
cmdline.append(pt.name)
|
||||
id = self.job_manager.run_conversion_job(self.book_converted,
|
||||
job = self.job_manager.run_job(Dispatcher(self.book_converted),
|
||||
'any2lrf', args=[cmdline],
|
||||
job_description=_('Convert book %d of %d (%s)')%(i+1, len(rows), repr(mi.title)))
|
||||
description=_('Convert book %d of %d (%s)')%(i+1, len(rows), repr(mi.title)))
|
||||
|
||||
|
||||
self.conversion_jobs[id] = (d.cover_file, pt, of, d.output_format,
|
||||
self.conversion_jobs[job] = (d.cover_file, pt, of, d.output_format,
|
||||
self.library_view.model().db.id(row))
|
||||
res = []
|
||||
for row in bad_rows:
|
||||
@ -887,10 +879,10 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
setattr(options, 'output', of.name)
|
||||
options.verbose = 1
|
||||
args = [pt.name, options]
|
||||
id = self.job_manager.run_conversion_job(self.book_converted,
|
||||
'comic2lrf', args=args,
|
||||
job_description=_('Convert comic %d of %d (%s)')%(i+1, len(comics), repr(options.title)))
|
||||
self.conversion_jobs[id] = (None, pt, of, 'lrf',
|
||||
job = self.job_manager.run_job(Dispatcher(self.book_converted),
|
||||
'comic2lrf', args=args,
|
||||
description=_('Convert comic %d of %d (%s)')%(i+1, len(comics), repr(options.title)))
|
||||
self.conversion_jobs[job] = (None, pt, of, 'lrf',
|
||||
self.library_view.model().db.id(row))
|
||||
|
||||
|
||||
@ -917,12 +909,12 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
of.close()
|
||||
cmdline.extend(['-o', of.name])
|
||||
cmdline.append(pt.name)
|
||||
id = self.job_manager.run_conversion_job(self.book_converted,
|
||||
'any2lrf', args=[cmdline],
|
||||
job_description=_('Convert book: ')+d.title())
|
||||
job = self.job_manager.run_job(Dispatcher(self.book_converted),
|
||||
'any2lrf', args=[cmdline],
|
||||
description=_('Convert book: ')+d.title())
|
||||
|
||||
|
||||
self.conversion_jobs[id] = (d.cover_file, pt, of, d.output_format, d.id)
|
||||
self.conversion_jobs[job] = (d.cover_file, pt, of, d.output_format, d.id)
|
||||
changed = True
|
||||
if changed:
|
||||
self.library_view.model().resort(reset=False)
|
||||
@ -962,24 +954,24 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
opts.verbose = 1
|
||||
args = [pt.name, opts]
|
||||
changed = True
|
||||
id = self.job_manager.run_conversion_job(self.book_converted,
|
||||
job = self.job_manager.run_job(Dispatcher(self.book_converted),
|
||||
'comic2lrf', args=args,
|
||||
job_description=_('Convert comic: ')+opts.title)
|
||||
self.conversion_jobs[id] = (None, pt, of, 'lrf',
|
||||
description=_('Convert comic: ')+opts.title)
|
||||
self.conversion_jobs[job] = (None, pt, of, 'lrf',
|
||||
self.library_view.model().db.id(row))
|
||||
if changed:
|
||||
self.library_view.model().resort(reset=False)
|
||||
self.library_view.model().research()
|
||||
|
||||
def book_converted(self, id, description, result, exception, formatted_traceback, log):
|
||||
of, fmt, book_id = self.conversion_jobs.pop(id)[2:]
|
||||
if exception:
|
||||
self.conversion_job_exception(id, description, exception, formatted_traceback, log)
|
||||
def book_converted(self, job):
|
||||
of, fmt, book_id = self.conversion_jobs.pop(job)[2:]
|
||||
if job.exception is not None:
|
||||
self.job_exception(job)
|
||||
return
|
||||
data = open(of.name, 'rb')
|
||||
self.library_view.model().db.add_format(book_id, fmt, data, index_is_id=True)
|
||||
data.close()
|
||||
self.status_bar.showMessage(description + (' completed'), 2000)
|
||||
self.status_bar.showMessage(job.description + (' completed'), 2000)
|
||||
|
||||
#############################View book######################################
|
||||
|
||||
@ -990,19 +982,18 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.persistent_files.append(pt)
|
||||
self._view_file(pt.name)
|
||||
|
||||
def book_downloaded_for_viewing(self, id, description, result, exception, formatted_traceback):
|
||||
if exception:
|
||||
self.device_job_exception(id, description, exception, formatted_traceback)
|
||||
def book_downloaded_for_viewing(self, job):
|
||||
if job.exception:
|
||||
self.device_job_exception(job)
|
||||
return
|
||||
print result
|
||||
self._view_file(result)
|
||||
self._view_file(job.result)
|
||||
|
||||
def _view_file(self, name):
|
||||
self.setCursor(Qt.BusyCursor)
|
||||
try:
|
||||
if name.upper().endswith('.LRF'):
|
||||
args = ['lrfviewer', name]
|
||||
self.job_manager.process_server.run_free_job('lrfviewer', kwdargs=dict(args=args))
|
||||
self.job_manager.server.run_free_job('lrfviewer', kwdargs=dict(args=args))
|
||||
else:
|
||||
QDesktopServices.openUrl(QUrl('file:'+name))#launch(name)
|
||||
time.sleep(5) # User feedback
|
||||
@ -1063,8 +1054,8 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
pt = PersistentTemporaryFile('_viewer_'+os.path.splitext(paths[0])[1])
|
||||
self.persistent_files.append(pt)
|
||||
pt.close()
|
||||
self.job_manager.run_device_job(self.book_downloaded_for_viewing,
|
||||
self.device_manager.view_book_func(), paths[0], pt.name)
|
||||
self.device_manager.view_book(Dispatcher(self.book_downloaded_for_viewing),
|
||||
paths[0], pt.name)
|
||||
|
||||
|
||||
|
||||
@ -1093,9 +1084,8 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
d.exec_()
|
||||
if d.result() == d.Accepted:
|
||||
self.library_view.set_visible_columns(d.final_columns)
|
||||
settings = Settings()
|
||||
self.tool_bar.setIconSize(settings.value('toolbar icon size', QVariant(QSize(48, 48))).toSize())
|
||||
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if settings.get('show text in toolbar', True) else Qt.ToolButtonIconOnly)
|
||||
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
||||
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if config['show_text_in_toolbar'] else Qt.ToolButtonIconOnly)
|
||||
|
||||
if self.library_path != d.database_location:
|
||||
try:
|
||||
@ -1116,7 +1106,7 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
_('<p>An invalid database already exists at %s, delete it before trying to move the existing database.<br>Error: %s')%(newloc, str(err)))
|
||||
d.exec_()
|
||||
self.library_path = self.library_view.model().db.library_path
|
||||
Settings().set('library path', self.library_path)
|
||||
prefs['library path'] = self.library_path
|
||||
except Exception, err:
|
||||
traceback.print_exc()
|
||||
d = error_dialog(self, _('Could not move database'), unicode(err))
|
||||
@ -1177,79 +1167,47 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.action_convert.setEnabled(False)
|
||||
self.view_menu.actions()[1].setEnabled(False)
|
||||
|
||||
def device_job_exception(self, id, description, exception, formatted_traceback):
|
||||
def device_job_exception(self, job):
|
||||
'''
|
||||
Handle exceptions in threaded device jobs.
|
||||
'''
|
||||
if 'Could not read 32 bytes on the control bus.' in str(exception):
|
||||
if 'Could not read 32 bytes on the control bus.' in str(job.exception):
|
||||
error_dialog(self, _('Error talking to device'),
|
||||
_('There was a temporary error talking to the device. Please unplug and reconnect the device and or reboot.')).show()
|
||||
return
|
||||
print >>sys.stderr, 'Error in job:', description.encode('utf8')
|
||||
print >>sys.stderr, exception
|
||||
print >>sys.stderr, formatted_traceback.encode('utf8')
|
||||
try:
|
||||
print >>sys.stderr, job.console_text()
|
||||
except:
|
||||
pass
|
||||
if not self.device_error_dialog.isVisible():
|
||||
msg = u'<p><b>%s</b>: '%(exception.__class__.__name__,) + unicode(str(exception), 'utf8', 'replace') + u'</p>'
|
||||
msg += u'<p>Failed to perform <b>job</b>: '+description
|
||||
msg += u'<p>Further device related error messages will not be shown while this message is visible.'
|
||||
msg += u'<p>Detailed <b>traceback</b>:<pre>'
|
||||
if isinstance(formatted_traceback, str):
|
||||
formatted_traceback = unicode(formatted_traceback, 'utf8', 'replace')
|
||||
msg += formatted_traceback
|
||||
self.device_error_dialog.set_message(msg)
|
||||
self.device_error_dialog.set_message(job.gui_text())
|
||||
self.device_error_dialog.show()
|
||||
|
||||
def conversion_job_exception(self, id, description, exception, formatted_traceback, log):
|
||||
def job_exception(self, job):
|
||||
|
||||
def safe_print(msgs, file=sys.stderr):
|
||||
for i, msg in enumerate(msgs):
|
||||
if not msg:
|
||||
msg = ''
|
||||
if isinstance(msg, unicode):
|
||||
msgs[i] = msg.encode(preferred_encoding, 'replace')
|
||||
msg = ' '.join(msgs)
|
||||
print >>file, msg
|
||||
|
||||
def safe_unicode(arg):
|
||||
if not arg:
|
||||
arg = unicode(repr(arg))
|
||||
if isinstance(arg, str):
|
||||
arg = arg.decode(preferred_encoding, 'replace')
|
||||
if not isinstance(arg, unicode):
|
||||
try:
|
||||
arg = unicode(repr(arg))
|
||||
except:
|
||||
arg = u'Could not convert to unicode'
|
||||
return arg
|
||||
|
||||
only_msg = getattr(exception, 'only_msg', False)
|
||||
description, exception, formatted_traceback, log = map(safe_unicode,
|
||||
(description, exception, formatted_traceback, log))
|
||||
only_msg = getattr(job.exception, 'only_msg', False)
|
||||
try:
|
||||
safe_print('Error in job:', description)
|
||||
if log:
|
||||
safe_print(log)
|
||||
safe_print(exception)
|
||||
safe_print(formatted_traceback)
|
||||
print job.console_text()
|
||||
except:
|
||||
pass
|
||||
if only_msg:
|
||||
error_dialog(self, _('Conversion Error'), exception).exec_()
|
||||
try:
|
||||
exc = unicode(job.exception)
|
||||
except:
|
||||
exc = repr(job.exception)
|
||||
error_dialog(self, _('Conversion Error'), exc).exec_()
|
||||
return
|
||||
msg = u'<p><b>%s</b>:'%exception
|
||||
msg += u'<p>Failed to perform <b>job</b>: '+description
|
||||
msg += u'<p>Detailed <b>traceback</b>:<pre>'
|
||||
msg += formatted_traceback + u'</pre>'
|
||||
msg += u'<p><b>Log:</b></p><pre>'
|
||||
msg += log
|
||||
ConversionErrorDialog(self, 'Conversion Error', msg, show=True)
|
||||
if isinstance(job.exception, JobKilled):
|
||||
return
|
||||
ConversionErrorDialog(self, _('Conversion Error'), job.gui_text(),
|
||||
show=True)
|
||||
|
||||
|
||||
def initialize_database(self, settings):
|
||||
self.library_path = settings.get('library path', None)
|
||||
def initialize_database(self):
|
||||
self.library_path = prefs['library path']
|
||||
self.olddb = None
|
||||
if self.library_path is None: # Need to migrate to new database layout
|
||||
self.database_path = settings.get('database path')
|
||||
self.database_path = prefs['database_path']
|
||||
if not os.access(os.path.dirname(self.database_path), os.W_OK):
|
||||
error_dialog(self, _('Database does not exist'), _('The directory in which the database should be: %s no longer exists. Please choose a new database location.')%self.database_path).exec_()
|
||||
self.database_path = choose_dir(self, 'database path dialog', 'Choose new location for database')
|
||||
@ -1272,28 +1230,20 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
|
||||
|
||||
def read_settings(self):
|
||||
settings = Settings()
|
||||
settings.beginGroup('Main Window')
|
||||
geometry = settings.value('main window geometry', QVariant()).toByteArray()
|
||||
self.restoreGeometry(geometry)
|
||||
settings.endGroup()
|
||||
self.initialize_database(settings)
|
||||
self.initialize_database()
|
||||
geometry = config['main_window_geometry']
|
||||
if geometry is not None:
|
||||
self.restoreGeometry(geometry)
|
||||
set_sidebar_directories(None)
|
||||
set_filename_pat(settings.get('filename pattern', get_filename_pat()))
|
||||
self.tool_bar.setIconSize(settings.get('toolbar icon size', QSize(48, 48)))
|
||||
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if settings.get('show text in toolbar', True) else Qt.ToolButtonIconOnly)
|
||||
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
||||
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if config['show_text_in_toolbar'] else Qt.ToolButtonIconOnly)
|
||||
|
||||
|
||||
def write_settings(self):
|
||||
settings = Settings()
|
||||
settings.beginGroup("Main Window")
|
||||
settings.setValue("main window geometry", QVariant(self.saveGeometry()))
|
||||
settings.endGroup()
|
||||
settings.beginGroup('Book Views')
|
||||
config.set('main_window_geometry', self.saveGeometry())
|
||||
self.library_view.write_settings()
|
||||
if self.device_connected:
|
||||
self.memory_view.write_settings()
|
||||
settings.endGroup()
|
||||
|
||||
def closeEvent(self, e):
|
||||
msg = 'There are active jobs. Are you sure you want to quit?'
|
||||
@ -1312,11 +1262,10 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
|
||||
self.job_manager.terminate_all_jobs()
|
||||
self.write_settings()
|
||||
self.detector.keep_going = False
|
||||
self.device_manager.keep_going = False
|
||||
self.cover_cache.stop()
|
||||
self.hide()
|
||||
time.sleep(2)
|
||||
self.detector.terminate()
|
||||
self.cover_cache.terminate()
|
||||
e.accept()
|
||||
|
||||
@ -1327,17 +1276,17 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.vanity.setText(self.vanity_template%(dict(version=self.latest_version,
|
||||
device=self.device_info)))
|
||||
self.vanity.update()
|
||||
s = Settings()
|
||||
if s.get('new version notification', True) and s.get('update to version %s'%version, True):
|
||||
if config.get('new_version_notification') and dynamic.get('update to version %s'%version, True):
|
||||
d = question_dialog(self, _('Update available'), _('%s has been updated to version %s. See the <a href="http://calibre.kovidgoyal.net/wiki/Changelog">new features</a>. Visit the download page?')%(__appname__, version))
|
||||
if d.exec_() == QMessageBox.Yes:
|
||||
url = 'http://calibre.kovidgoyal.net/download_'+('windows' if iswindows else 'osx' if isosx else 'linux')
|
||||
QDesktopServices.openUrl(QUrl(url))
|
||||
s.set('update to version %s'%version, False)
|
||||
dynamic.set('update to version %s'%version, False)
|
||||
|
||||
|
||||
def main(args=sys.argv):
|
||||
from calibre import singleinstance
|
||||
from calibre.utils.lock import singleinstance
|
||||
|
||||
pid = os.fork() if False and islinux else -1
|
||||
if pid <= 0:
|
||||
parser = option_parser('''\
|
||||
|
@ -3,9 +3,9 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import StringIO, traceback, sys
|
||||
|
||||
from PyQt4.Qt import QMainWindow, QString, Qt, QFont
|
||||
from PyQt4.Qt import QMainWindow, QString, Qt, QFont, QCoreApplication, SIGNAL
|
||||
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
|
||||
from calibre import OptionParser
|
||||
from calibre.utils.config import OptionParser
|
||||
|
||||
def option_parser(usage='''\
|
||||
Usage: %prog [options]
|
||||
@ -36,11 +36,17 @@ class MainWindow(QMainWindow):
|
||||
|
||||
def __init__(self, opts, parent=None):
|
||||
QMainWindow.__init__(self, parent)
|
||||
app = QCoreApplication.instance()
|
||||
if app is not None:
|
||||
self.connect(app, SIGNAL('unixSignal(int)'), self.unix_signal)
|
||||
if getattr(opts, 'redirect', False):
|
||||
self.__console_redirect = DebugWindow(self)
|
||||
sys.stdout = sys.stderr = self.__console_redirect
|
||||
self.__console_redirect.show()
|
||||
|
||||
def unix_signal(self, signal):
|
||||
print 'Received signal:', repr(signal)
|
||||
|
||||
def unhandled_exception(self, type, value, tb):
|
||||
try:
|
||||
sio = StringIO.StringIO()
|
||||
|
@ -39,9 +39,14 @@ def build_forms(forms):
|
||||
dat = dat.replace('import images_rc', 'from calibre.gui2 import images_rc')
|
||||
dat = dat.replace('from library import', 'from calibre.gui2.library import')
|
||||
dat = dat.replace('from widgets import', 'from calibre.gui2.widgets import')
|
||||
#dat += '\nfrom calibre.gui2 import TranslatedDialogButtonBox'
|
||||
dat = re.compile(r'QtGui.QApplication.translate\(.+?,\s+"(.+?)(?<!\\)",.+?\)', re.DOTALL).sub(r'_("\1")', dat)
|
||||
#dat = re.compile(r'QtGui.QDialogButtonBox').sub('TranslatedDialogButtonBox', dat)
|
||||
|
||||
# Workaround bug in Qt 4.4 on Windows
|
||||
if form.endswith('dialogs%sconfig.ui'%os.sep) or form.endswith('dialogs%slrf_single.ui'%os.sep):
|
||||
print 'Implementing Workaround for buggy pyuic in form', form
|
||||
dat = re.sub(r'= QtGui\.QTextEdit\(self\..*?\)', '= QtGui.QTextEdit()', dat)
|
||||
dat = re.sub(r'= QtGui\.QListWidget\(self\..*?\)', '= QtGui.QListWidget()', dat)
|
||||
|
||||
open(compiled_form, 'wb').write(dat)
|
||||
|
||||
|
||||
|
@ -1,52 +0,0 @@
|
||||
import os, sys, glob
|
||||
if os.environ.get('PYQT4PATH', None):
|
||||
print os.environ['PYQT4PATH']
|
||||
sys.path.insert(0, os.environ['PYQT4PATH'])
|
||||
from PyQt4 import pyqtconfig
|
||||
|
||||
# The name of the SIP build file generated by SIP and used by the build
|
||||
# system.
|
||||
build_file = "pictureflow.sbf"
|
||||
|
||||
# Get the PyQt configuration information.
|
||||
config = pyqtconfig.Configuration()
|
||||
|
||||
# Run SIP to generate the code. Note that we tell SIP where to find the qt
|
||||
# module's specification files using the -I flag.
|
||||
sip = [config.sip_bin, "-c", ".", "-b", build_file, "-I",
|
||||
config.pyqt_sip_dir, config.pyqt_sip_flags, "../pictureflow.sip"]
|
||||
os.system(" ".join(sip))
|
||||
|
||||
|
||||
|
||||
installs=[]
|
||||
|
||||
# Create the Makefile. The QtModuleMakefile class provided by the
|
||||
# pyqtconfig module takes care of all the extra preprocessor, compiler and
|
||||
# linker flags needed by the Qt library.
|
||||
makefile = pyqtconfig.QtGuiModuleMakefile (
|
||||
configuration=config,
|
||||
build_file=build_file,
|
||||
installs=installs,
|
||||
qt=1,
|
||||
)
|
||||
|
||||
# Setup the platform dependent Makefile parameters
|
||||
d = os.path.dirname
|
||||
if 'darwin' in sys.platform:
|
||||
makefile.extra_cflags += ['-arch i386', '-arch ppc']
|
||||
makefile.extra_lflags += ['-arch i386', '-arch ppc']
|
||||
qtdir = os.path.join(d(d(os.getcwd())), '.build')
|
||||
if 'win32' in sys.platform:
|
||||
qtdir = os.path.join(qtdir, 'release')
|
||||
makefile.extra_lib_dirs += ['C:/Python25/libs']
|
||||
|
||||
# Add the compiled Qt objects
|
||||
qtobjs = map(lambda x:'"'+x+'"', glob.glob(os.path.join(qtdir, '*.o')))
|
||||
makefile.extra_lflags += qtobjs
|
||||
makefile.extra_cxxflags = makefile.extra_cflags
|
||||
|
||||
# Generate the Makefile itself.
|
||||
makefile.generate()
|
||||
|
||||
|
@ -1,6 +0,0 @@
|
||||
TARGET = pictureflow
|
||||
TEMPLATE = lib
|
||||
HEADERS = pictureflow.h
|
||||
SOURCES = pictureflow.cpp
|
||||
VERSION = 1.0.0
|
||||
CONFIG += x86 ppc
|
@ -9,7 +9,7 @@
|
||||
class FlowImages : QObject {
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "../../pictureflow.h"
|
||||
#include <pictureflow.h>
|
||||
%End
|
||||
|
||||
public:
|
||||
@ -22,7 +22,7 @@ public:
|
||||
class PictureFlow : QWidget {
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "../../pictureflow.h"
|
||||
#include <pictureflow.h>
|
||||
%End
|
||||
|
||||
|
@ -163,24 +163,23 @@ class StatusBar(QStatusBar):
|
||||
def show_book_info(self):
|
||||
self.emit(SIGNAL('show_book_info()'))
|
||||
|
||||
def job_added(self, id):
|
||||
def job_added(self, nnum):
|
||||
jobs = self.movie_button.jobs
|
||||
src = qstring_to_unicode(jobs.text())
|
||||
num = self.jobs()
|
||||
nnum = num+1
|
||||
nnum = num + 1
|
||||
text = src.replace(str(num), str(nnum))
|
||||
jobs.setText(text)
|
||||
if self.movie_button.movie.state() == QMovie.Paused:
|
||||
self.movie_button.movie.setPaused(False)
|
||||
|
||||
def job_done(self, id):
|
||||
def job_done(self, running):
|
||||
jobs = self.movie_button.jobs
|
||||
src = qstring_to_unicode(jobs.text())
|
||||
num = self.jobs()
|
||||
nnum = num-1
|
||||
text = src.replace(str(num), str(nnum))
|
||||
text = src.replace(str(num), str(running))
|
||||
jobs.setText(text)
|
||||
if nnum == 0:
|
||||
if running == 0:
|
||||
self.no_more_jobs()
|
||||
|
||||
def no_more_jobs(self):
|
||||
|
@ -9,15 +9,16 @@ from PyQt4.QtGui import QListView, QIcon, QFont, QLabel, QListWidget, \
|
||||
QSyntaxHighlighter, QCursor, QColor, QWidget, \
|
||||
QAbstractItemDelegate, QPixmap, QStyle, QFontMetrics
|
||||
from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, SIGNAL, \
|
||||
QObject, QRegExp, QString
|
||||
QObject, QRegExp, QString, QSettings
|
||||
|
||||
from calibre.gui2.jobs import DetailView
|
||||
from calibre.gui2 import human_readable, NONE, TableView, qstring_to_unicode, error_dialog
|
||||
from calibre.gui2.jobs2 import DetailView
|
||||
from calibre.gui2 import human_readable, NONE, TableView, \
|
||||
qstring_to_unicode, error_dialog
|
||||
from calibre.gui2.filename_pattern_ui import Ui_Form
|
||||
from calibre import fit_image, Settings
|
||||
from calibre import fit_image
|
||||
from calibre.utils.fontconfig import find_font_families
|
||||
from calibre.ebooks.metadata.meta import get_filename_pat, metadata_from_filename, \
|
||||
set_filename_pat
|
||||
from calibre.ebooks.metadata.meta import metadata_from_filename
|
||||
from calibre.utils.config import prefs
|
||||
|
||||
|
||||
|
||||
@ -29,7 +30,7 @@ class FilenamePattern(QWidget, Ui_Form):
|
||||
|
||||
self.connect(self.test_button, SIGNAL('clicked()'), self.do_test)
|
||||
self.connect(self.re, SIGNAL('returnPressed()'), self.do_test)
|
||||
self.re.setText(get_filename_pat())
|
||||
self.re.setText(prefs['filename_pattern'])
|
||||
|
||||
def do_test(self):
|
||||
try:
|
||||
@ -66,9 +67,9 @@ class FilenamePattern(QWidget, Ui_Form):
|
||||
return re.compile(pat)
|
||||
|
||||
def commit(self):
|
||||
pat = self.pattern()
|
||||
set_filename_pat(pat)
|
||||
return pat.pattern
|
||||
pat = self.pattern().pattern
|
||||
prefs['filename_pattern'] = pat
|
||||
return pat
|
||||
|
||||
|
||||
|
||||
@ -235,8 +236,10 @@ class JobsView(TableView):
|
||||
|
||||
def show_details(self, index):
|
||||
row = index.row()
|
||||
job = self.model().row_to_job(row)[0]
|
||||
DetailView(self, job).exec_()
|
||||
job = self.model().row_to_job(row)
|
||||
d = DetailView(self, job)
|
||||
self.connect(self.model(), SIGNAL('output_received()'), d.update)
|
||||
d.exec_()
|
||||
|
||||
|
||||
class FontFamilyModel(QAbstractListModel):
|
||||
@ -365,13 +368,13 @@ class PythonHighlighter(QSyntaxHighlighter):
|
||||
@classmethod
|
||||
def loadConfig(cls):
|
||||
Config = cls.Config
|
||||
settings = QSettings()
|
||||
def setDefaultString(name, default):
|
||||
value = settings.value(name).toString()
|
||||
if value.isEmpty():
|
||||
value = default
|
||||
Config[name] = value
|
||||
|
||||
settings = Settings()
|
||||
for name in ("window", "shell"):
|
||||
Config["%swidth" % name] = settings.value("%swidth" % name,
|
||||
QVariant(QApplication.desktop() \
|
||||
|
@ -10,7 +10,8 @@ Command line interface to the calibre database.
|
||||
import sys, os
|
||||
from textwrap import TextWrapper
|
||||
|
||||
from calibre import OptionParser, Settings, terminal_controller, preferred_encoding
|
||||
from calibre import terminal_controller, preferred_encoding
|
||||
from calibre.utils.config import OptionParser, prefs
|
||||
try:
|
||||
from calibre.utils.single_qt_application import send_message
|
||||
except:
|
||||
@ -26,6 +27,7 @@ def get_parser(usage):
|
||||
parser = OptionParser(usage)
|
||||
go = parser.add_option_group('GLOBAL OPTIONS')
|
||||
go.add_option('--library-path', default=None, help=_('Path to the calibre library. Default is to use the path stored in the settings.'))
|
||||
|
||||
return parser
|
||||
|
||||
def get_db(dbpath, options):
|
||||
@ -44,7 +46,7 @@ def do_list(db, fields, sort_by, ascending, search_text):
|
||||
widths = list(map(lambda x : 0, fields))
|
||||
for i in db.data:
|
||||
for j, field in enumerate(fields):
|
||||
widths[j] = max(widths[j], len(unicode(i[field])))
|
||||
widths[j] = max(widths[j], len(unicode(i[str(field)])))
|
||||
|
||||
screen_width = terminal_controller.COLS
|
||||
if not screen_width:
|
||||
@ -97,8 +99,7 @@ List the books available in the calibre database.
|
||||
parser.add_option('-s', '--search', default=None,
|
||||
help=_('Filter the results by the search query. For the format of the search query, please see the search related documentation in the User Manual. Default is to do no filtering.'))
|
||||
opts, args = parser.parse_args(sys.argv[:1] + args)
|
||||
fields = [f.strip().lower() for f in opts.fields.split(',')]
|
||||
|
||||
fields = [str(f.strip().lower()) for f in opts.fields.split(',')]
|
||||
if not set(fields).issubset(FIELDS):
|
||||
parser.print_help()
|
||||
print
|
||||
@ -424,7 +425,7 @@ For help on an individual command: %%prog command --help
|
||||
return 1
|
||||
|
||||
command = eval('command_'+args[1])
|
||||
dbpath = Settings().get('library path', os.path.expanduser('~'))
|
||||
dbpath = prefs['library_path']
|
||||
|
||||
return command(args[2:], dbpath)
|
||||
|
||||
|
@ -1413,7 +1413,10 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
||||
mi.render(f)
|
||||
f.close()
|
||||
|
||||
for fmt in self.formats(idx, index_is_id=index_is_id).split(','):
|
||||
fmts = self.formats(idx, index_is_id=index_is_id)
|
||||
if not fmts:
|
||||
fmts = ''
|
||||
for fmt in fmts.split(','):
|
||||
data = self.format(idx, fmt, index_is_id=index_is_id)
|
||||
fname = name +'.'+fmt.lower()
|
||||
fname = sanitize_file_name(fname)
|
||||
|
@ -366,12 +366,14 @@ def install_man_pages(fatal_errors):
|
||||
f.write('[see also]\nhttp://%s.kovidgoyal.net\n'%__appname__)
|
||||
f.close()
|
||||
manifest = []
|
||||
os.environ['PATH'] += ':'+os.path.expanduser('~/bin')
|
||||
for src in entry_points['console_scripts']:
|
||||
prog = src[:src.index('=')].strip()
|
||||
if prog in ('prs500', 'pdf-meta', 'epub-meta', 'lit-meta',
|
||||
'markdown-calibre', 'calibre-debug', 'fb2-meta',
|
||||
'calibre-fontconfig', 'calibre-parallel'):
|
||||
continue
|
||||
|
||||
help2man = ('help2man', prog, '--name', 'part of %s'%__appname__,
|
||||
'--section', '1', '--no-info', '--include',
|
||||
f.name, '--manual', __appname__)
|
||||
|
@ -244,6 +244,7 @@ def do_postinstall(destdir):
|
||||
os.chdir(destdir)
|
||||
os.environ['LD_LIBRARY_PATH'] = destdir+':'+os.environ.get('LD_LIBRARY_PATH', '')
|
||||
os.environ['PYTHONPATH'] = destdir
|
||||
os.environ['PYTHONSTARTUP'] = ''
|
||||
subprocess.call((os.path.join(destdir, 'calibre_postinstall'), '--save-manifest-to', t.name))
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
|
@ -17,7 +17,7 @@ E-book Format Conversion
|
||||
|
||||
What formats does |app| support conversion to/from?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|app| supports the conversion of the following formats to LRF: HTML, LIT, MOBI, PRC, EPUB, RTF, TXT, PDF and LRS. It also supports the conversion of LRF to LRS and HTML. Note that calibre does not support the conversion of DRMed ebooks.
|
||||
|app| supports the conversion of the following formats to LRF: HTML, LIT, MOBI, PRC, EPUB, CBR, CBZ, RTF, TXT, PDF and LRS. It also supports the conversion of LRF to LRS and HTML(forthcoming). Note that calibre does not support the conversion of DRMed ebooks.
|
||||
|
||||
What are the best formats to convert to LRF?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -142,7 +142,7 @@ If it still wont launch, start a command prompt (press the windows key and R; th
|
||||
|
||||
calibre-debug -c "from calibre.gui2.main import main; main()"
|
||||
|
||||
Post any output you see in a help message on the `Forums <http://calibre.kovidgoyal.net/discussion`_.
|
||||
Post any output you see in a help message on the `Forums <http://calibre.kovidgoyal.net/discussion>`_.
|
||||
|
||||
|
||||
I want some feature added to |app|. What can I do?
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 9.0 KiB |
@ -27,7 +27,6 @@ is buffered and asynchronous to prevent the job from being IO bound.
|
||||
import sys, os, gc, cPickle, traceback, atexit, cStringIO, time, signal, \
|
||||
subprocess, socket, collections, binascii, re, thread, tempfile
|
||||
from select import select
|
||||
from functools import partial
|
||||
from threading import RLock, Thread, Event
|
||||
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
@ -144,8 +143,10 @@ class WorkerMother(object):
|
||||
self.prefix += 'import sys; sys.frameworks_dir = "%s"; sys.frozen = "macosx_app"; '%fd
|
||||
self.prefix += 'sys.path.insert(0, %s); '%repr(sp)
|
||||
if fd not in os.environ['PATH']:
|
||||
self.env['PATH'] = os.environ['PATH']+':'+fd
|
||||
self.env['PYTHONHOME'] = resources
|
||||
self.env['PATH'] = os.environ['PATH']+':'+fd
|
||||
self.env['PYTHONHOME'] = resources
|
||||
self.env['MAGICK_HOME'] = os.path.join(getattr(sys, 'frameworks_dir'), 'ImageMagick')
|
||||
self.env['DYLD_LIBRARY_PATH'] = os.path.join(getattr(sys, 'frameworks_dir'), 'ImageMagick', 'lib')
|
||||
else:
|
||||
self.executable = os.path.join(getattr(sys, 'frozen_path'), 'calibre-parallel') \
|
||||
if isfrozen else 'calibre-parallel'
|
||||
@ -332,7 +333,7 @@ class Overseer(object):
|
||||
def __init__(self, server, port, timeout=5):
|
||||
self.worker_status = mother.spawn_worker('127.0.0.1:'+str(port))
|
||||
self.socket = server.accept()[0]
|
||||
# Needed if terminate called hwen interpreter is shutting down
|
||||
# Needed if terminate called when interpreter is shutting down
|
||||
self.os = os
|
||||
self.signal = signal
|
||||
self.on_probation = False
|
||||
@ -341,7 +342,6 @@ class Overseer(object):
|
||||
self.working = False
|
||||
self.timeout = timeout
|
||||
self.last_job_time = time.time()
|
||||
self.job_id = None
|
||||
self._stop = False
|
||||
if not select([self.socket], [], [], 120)[0]:
|
||||
raise RuntimeError(_('Could not launch worker process.'))
|
||||
@ -406,16 +406,14 @@ class Overseer(object):
|
||||
|
||||
`job`: An instance of :class:`Job`.
|
||||
'''
|
||||
self.job_id = job.job_id
|
||||
self.working = True
|
||||
self.write('JOB:'+cPickle.dumps((job.func, job.args, job.kwdargs), -1))
|
||||
self.write('JOB:'+cPickle.dumps((job.func, job.args, job.kwargs), -1))
|
||||
msg = self.read()
|
||||
if msg != 'OK':
|
||||
raise ControlError('Failed to initialize job on worker %d:%s'%(self.worker_pid, msg))
|
||||
self.output = job.output if callable(job.output) else sys.stdout.write
|
||||
self.progress = job.progress if callable(job.progress) else None
|
||||
self.job = job
|
||||
self.last_report = time.time()
|
||||
job.start_work()
|
||||
|
||||
def control(self):
|
||||
'''
|
||||
@ -433,7 +431,9 @@ class Overseer(object):
|
||||
else:
|
||||
if self.on_probation:
|
||||
self.terminate()
|
||||
return Result(None, ControlError('Worker process died unexpectedly'), '')
|
||||
self.job.result = None
|
||||
self.job.exception = ControlError('Worker process died unexpectedly')
|
||||
return
|
||||
else:
|
||||
self.on_probation = True
|
||||
return
|
||||
@ -443,13 +443,14 @@ class Overseer(object):
|
||||
return
|
||||
elif word == 'RESULT':
|
||||
self.write('OK')
|
||||
return Result(cPickle.loads(msg), None, None)
|
||||
self.job.result = cPickle.loads(msg)
|
||||
return True
|
||||
elif word == 'OUTPUT':
|
||||
self.write('OK')
|
||||
try:
|
||||
self.output(''.join(cPickle.loads(msg)))
|
||||
self.job.output(''.join(cPickle.loads(msg)))
|
||||
except:
|
||||
self.output('Bad output message: '+ repr(msg))
|
||||
self.job.output('Bad output message: '+ repr(msg))
|
||||
elif word == 'PROGRESS':
|
||||
self.write('OK')
|
||||
percent = None
|
||||
@ -457,45 +458,154 @@ class Overseer(object):
|
||||
percent, msg = cPickle.loads(msg)[-1]
|
||||
except:
|
||||
print 'Bad progress update:', repr(msg)
|
||||
if self.progress and percent is not None:
|
||||
self.progress(percent, msg)
|
||||
if percent is not None:
|
||||
self.job.update_status(percent, msg)
|
||||
elif word == 'ERROR':
|
||||
self.write('OK')
|
||||
return Result(None, *cPickle.loads(msg))
|
||||
self.job.excetion, self.job.traceback = cPickle.loads(msg)
|
||||
return True
|
||||
else:
|
||||
self.terminate()
|
||||
return Result(None, ControlError('Worker sent invalid msg: %s'%repr(msg)), '')
|
||||
self.job.exception = ControlError('Worker sent invalid msg: %s'%repr(msg))
|
||||
return
|
||||
if not self.worker_status.is_alive() or time.time() - self.last_report > 180:
|
||||
self.terminate()
|
||||
return Result(None, ControlError('Worker process died unexpectedly with returncode: %s'%str(self.process.returncode)), '')
|
||||
self.job.exception = ControlError('Worker process died unexpectedly with returncode: %s'%str(self.process.returncode))
|
||||
return
|
||||
|
||||
class JobKilled(Exception):
|
||||
pass
|
||||
|
||||
class Job(object):
|
||||
|
||||
def __init__(self, job_id, func, args, kwdargs, output, progress, done):
|
||||
self.job_id = job_id
|
||||
def __init__(self, job_done, job_manager=None,
|
||||
args=[], kwargs={}, description=None):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self._job_done = job_done
|
||||
self.job_manager = job_manager
|
||||
self.is_running = False
|
||||
self.has_run = False
|
||||
self.percent = -1
|
||||
self.msg = None
|
||||
self.description = description
|
||||
self.start_time = None
|
||||
self.running_time = None
|
||||
|
||||
self.result = self.exception = self.traceback = self.log = None
|
||||
|
||||
def __cmp__(self, other):
|
||||
sstatus, ostatus = self.status(), other.status()
|
||||
if sstatus == ostatus or (self.has_run and other.has_run):
|
||||
if self.start_time == other.start_time:
|
||||
return cmp(id(self), id(other))
|
||||
return cmp(self.start_time, other.start_time)
|
||||
if sstatus == 'WORKING':
|
||||
return -1
|
||||
if ostatus == 'WORKING':
|
||||
return 1
|
||||
if sstatus == 'WAITING':
|
||||
return -1
|
||||
if ostatus == 'WAITING':
|
||||
return 1
|
||||
|
||||
|
||||
def job_done(self):
|
||||
self.is_running, self.has_run = False, True
|
||||
self.running_time = (time.time() - self.start_time) if \
|
||||
self.start_time is not None else 0
|
||||
if self.job_manager is not None:
|
||||
self.job_manager.job_done(self)
|
||||
self._job_done(self)
|
||||
|
||||
def start_work(self):
|
||||
self.is_running = True
|
||||
self.has_run = False
|
||||
self.start_time = time.time()
|
||||
if self.job_manager is not None:
|
||||
self.job_manager.start_work(self)
|
||||
|
||||
def update_status(self, percent, msg=None):
|
||||
self.percent = percent
|
||||
self.msg = msg
|
||||
if self.job_manager is not None:
|
||||
self.job_manager.status_update(self)
|
||||
|
||||
def status(self):
|
||||
if self.is_running:
|
||||
return 'WORKING'
|
||||
if not self.has_run:
|
||||
return 'WAITING'
|
||||
if self.has_run:
|
||||
if self.exception is None:
|
||||
return 'DONE'
|
||||
return 'ERROR'
|
||||
|
||||
def console_text(self):
|
||||
ans = [u'Error in job: ']
|
||||
if self.description:
|
||||
ans[0] += self.description
|
||||
if self.log:
|
||||
if isinstance(self.log, str):
|
||||
self.log = unicode(self.log, 'utf-8', 'replace')
|
||||
ans.append(self.log)
|
||||
header = unicode(self.exception.__class__.__name__) if \
|
||||
hasattr(self.exception, '__class__') else u'Error'
|
||||
header += u': '
|
||||
try:
|
||||
header += unicode(self.exception)
|
||||
except:
|
||||
header += unicode(repr(self.exception))
|
||||
ans.append(header)
|
||||
if self.traceback:
|
||||
ans.append(self.traceback)
|
||||
return (u'\n'.join(ans)).encode('utf-8')
|
||||
|
||||
def gui_text(self):
|
||||
ans = [u'Job: ']
|
||||
if self.description:
|
||||
if not isinstance(self.description, unicode):
|
||||
self.description = self.description.decode('utf-8', 'replace')
|
||||
ans[0] += u'<b>%s</b>'%self.description
|
||||
if self.exception is not None:
|
||||
header = unicode(self.exception.__class__.__name__) if \
|
||||
hasattr(self.exception, '__class__') else u'Error'
|
||||
header = u'<b>%s</b>'%header
|
||||
header += u': '
|
||||
try:
|
||||
header += unicode(self.exception)
|
||||
except:
|
||||
header += unicode(repr(self.exception))
|
||||
ans.append(header)
|
||||
if self.traceback:
|
||||
ans.append(u'<b>Traceback</b>:')
|
||||
ans.extend(self.traceback.split('\n'))
|
||||
if self.log:
|
||||
ans.append(u'<b>Log</b>:')
|
||||
if isinstance(self.log, str):
|
||||
self.log = unicode(self.log, 'utf-8', 'replace')
|
||||
ans.extend(self.log.split('\n'))
|
||||
|
||||
return '<br>'.join(ans)
|
||||
|
||||
|
||||
class ParallelJob(Job):
|
||||
|
||||
def __init__(self, func, *args, **kwargs):
|
||||
Job.__init__(self, *args, **kwargs)
|
||||
self.func = func
|
||||
self.args = args
|
||||
self.kwdargs = kwdargs
|
||||
self.output = output
|
||||
self.progress = progress
|
||||
self.done = done
|
||||
self.done = self.job_done
|
||||
|
||||
class Result(object):
|
||||
def output(self, msg):
|
||||
if not self.log:
|
||||
self.log = u''
|
||||
if not isinstance(msg, unicode):
|
||||
msg = msg.decode('utf-8', 'replace')
|
||||
if msg:
|
||||
self.log += msg
|
||||
if self.job_manager is not None:
|
||||
self.job_manager.output(self)
|
||||
|
||||
def __init__(self, result, exception, traceback):
|
||||
self.result = result
|
||||
self.exception = exception
|
||||
self.traceback = traceback
|
||||
|
||||
def __len__(self):
|
||||
return 3
|
||||
|
||||
def __item__(self, i):
|
||||
return (self.result, self.exception, self.traceback)[i]
|
||||
|
||||
def __iter__(self):
|
||||
return iter((self.result, self.exception, self.traceback))
|
||||
|
||||
def remove_ipc_socket(path):
|
||||
os = __import__('os')
|
||||
@ -525,7 +635,7 @@ class Server(Thread):
|
||||
atexit.register(remove_ipc_socket, self.port)
|
||||
self.server_socket.listen(5)
|
||||
self.number_of_workers = number_of_workers
|
||||
self.pool, self.jobs, self.working, self.results = [], collections.deque(), [], {}
|
||||
self.pool, self.jobs, self.working = [], collections.deque(), []
|
||||
atexit.register(self.killall)
|
||||
atexit.register(self.close)
|
||||
self.job_lock = RLock()
|
||||
@ -544,15 +654,8 @@ class Server(Thread):
|
||||
def add_job(self, job):
|
||||
with self.job_lock:
|
||||
self.jobs.append(job)
|
||||
|
||||
def store_result(self, result, id=None):
|
||||
if id:
|
||||
with self.job_lock:
|
||||
self.results[id] = result
|
||||
|
||||
def result(self, id):
|
||||
with self.result_lock:
|
||||
return self.results.pop(id, None)
|
||||
if job.job_manager is not None:
|
||||
job.job_manager.add_job(job)
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
@ -575,8 +678,9 @@ class Server(Thread):
|
||||
o.initialize_job(job)
|
||||
except Exception, err:
|
||||
o.terminate()
|
||||
res = Result(None, unicode(err), traceback.format_exc())
|
||||
job.done(res)
|
||||
job.exception = err
|
||||
job.traceback = traceback.format_exc()
|
||||
job.done()
|
||||
o = None
|
||||
if o and o.is_viable():
|
||||
with self.working_lock:
|
||||
@ -586,12 +690,14 @@ class Server(Thread):
|
||||
done = []
|
||||
for o in self.working:
|
||||
try:
|
||||
res = o.control()
|
||||
if o.control() is not None or o.job.exception is not None:
|
||||
o.job.done()
|
||||
done.append(o)
|
||||
except Exception, err:
|
||||
res = Result(None, unicode(err), traceback.format_exc())
|
||||
o.job.exception = err
|
||||
o.job.traceback = traceback.format_exc()
|
||||
o.terminate()
|
||||
if isinstance(res, Result):
|
||||
o.job.done(res)
|
||||
o.job.done()
|
||||
done.append(o)
|
||||
for o in done:
|
||||
self.working.remove(o)
|
||||
@ -611,32 +717,23 @@ class Server(Thread):
|
||||
self.pool = []
|
||||
|
||||
|
||||
def kill(self, job_id):
|
||||
def kill(self, job):
|
||||
with self.working_lock:
|
||||
pop = None
|
||||
for o in self.working:
|
||||
if o.job_id == job_id:
|
||||
o.terminate()
|
||||
o.job.done(Result(self.KILL_RESULT, None, ''))
|
||||
if o.job == job or o == job:
|
||||
try:
|
||||
o.terminate()
|
||||
except: pass
|
||||
o.job.exception = JobKilled(_('Job stopped by user'))
|
||||
try:
|
||||
o.job.done()
|
||||
except: pass
|
||||
pop = o
|
||||
break
|
||||
if pop is not None:
|
||||
self.working.remove(pop)
|
||||
|
||||
|
||||
|
||||
def run_job(self, job_id, func, args=[], kwdargs={},
|
||||
output=None, progress=None, done=None):
|
||||
'''
|
||||
Run a job in a separate process. Supports job control, output redirection
|
||||
and progress reporting.
|
||||
'''
|
||||
if done is None:
|
||||
done = partial(self.store_result, id=job_id)
|
||||
job = Job(job_id, func, args, kwdargs, output, progress, done)
|
||||
with self.job_lock:
|
||||
self.jobs.append(job)
|
||||
|
||||
def run_free_job(self, func, args=[], kwdargs={}):
|
||||
pt = PersistentTemporaryFile('.pickle', '_IPC_')
|
||||
pt.write(cPickle.dumps((func, args, kwdargs)))
|
||||
|
149
src/calibre/startup.py
Normal file
149
src/calibre/startup.py
Normal file
@ -0,0 +1,149 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
Perform various initialization tasks.
|
||||
'''
|
||||
|
||||
import locale, sys, os, re, cStringIO
|
||||
from gettext import GNUTranslations
|
||||
|
||||
# Default translation is NOOP
|
||||
import __builtin__
|
||||
__builtin__.__dict__['_'] = lambda s: s
|
||||
|
||||
from calibre.constants import iswindows, isosx, islinux, isfrozen
|
||||
from calibre.translations.msgfmt import make
|
||||
|
||||
_run_once = False
|
||||
if not _run_once:
|
||||
_run_once = True
|
||||
################################################################################
|
||||
# Setup translations
|
||||
|
||||
def get_lang():
|
||||
lang = locale.getdefaultlocale()[0]
|
||||
if lang is None and os.environ.has_key('LANG'): # Needed for OS X
|
||||
try:
|
||||
lang = os.environ['LANG']
|
||||
except:
|
||||
pass
|
||||
if lang:
|
||||
match = re.match('[a-z]{2,3}', lang)
|
||||
if match:
|
||||
lang = match.group()
|
||||
return lang
|
||||
|
||||
def set_translator():
|
||||
# To test different translations invoke as
|
||||
# LC_ALL=de_DE.utf8 program
|
||||
try:
|
||||
from calibre.translations.compiled import translations
|
||||
except:
|
||||
return
|
||||
lang = get_lang()
|
||||
if lang:
|
||||
buf = None
|
||||
if os.access(lang+'.po', os.R_OK):
|
||||
buf = cStringIO.StringIO()
|
||||
make(lang+'.po', buf)
|
||||
buf = cStringIO.StringIO(buf.getvalue())
|
||||
elif translations.has_key(lang):
|
||||
buf = cStringIO.StringIO(translations[lang])
|
||||
if buf is not None:
|
||||
t = GNUTranslations(buf)
|
||||
t.install(unicode=True)
|
||||
|
||||
set_translator()
|
||||
|
||||
################################################################################
|
||||
# Initialize locale
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
except:
|
||||
dl = locale.getdefaultlocale()
|
||||
try:
|
||||
if dl:
|
||||
locale.setlocale(dl[0])
|
||||
except:
|
||||
pass
|
||||
|
||||
################################################################################
|
||||
# Load plugins
|
||||
if isfrozen:
|
||||
if iswindows:
|
||||
plugin_path = os.path.join(os.path.dirname(sys.executable), 'plugins')
|
||||
sys.path.insert(1, os.path.dirname(sys.executable))
|
||||
elif isosx:
|
||||
plugin_path = os.path.join(getattr(sys, 'frameworks_dir'), 'plugins')
|
||||
elif islinux:
|
||||
plugin_path = os.path.join(getattr(sys, 'frozen_path'), 'plugins')
|
||||
sys.path.insert(0, plugin_path)
|
||||
else:
|
||||
import pkg_resources
|
||||
plugins = getattr(pkg_resources, 'resource_filename')('calibre', 'plugins')
|
||||
sys.path.insert(0, plugins)
|
||||
|
||||
plugins = {}
|
||||
for plugin in ['pictureflow', 'lzx', 'msdes'] + \
|
||||
(['winutil'] if iswindows else []) + \
|
||||
(['usbobserver'] if isosx else []):
|
||||
try:
|
||||
p, err = __import__(plugin), ''
|
||||
except Exception, err:
|
||||
p = None
|
||||
err = str(err)
|
||||
plugins[plugin] = (p, err)
|
||||
|
||||
################################################################################
|
||||
# Improve builtin path functions to handle unicode sensibly
|
||||
|
||||
_abspath = os.path.abspath
|
||||
def my_abspath(path, encoding=sys.getfilesystemencoding()):
|
||||
'''
|
||||
Work around for buggy os.path.abspath. This function accepts either byte strings,
|
||||
in which it calls os.path.abspath, or unicode string, in which case it first converts
|
||||
to byte strings using `encoding`, calls abspath and then decodes back to unicode.
|
||||
'''
|
||||
to_unicode = False
|
||||
if isinstance(path, unicode):
|
||||
path = path.encode(encoding)
|
||||
to_unicode = True
|
||||
res = _abspath(path)
|
||||
if to_unicode:
|
||||
res = res.decode(encoding)
|
||||
return res
|
||||
|
||||
os.path.abspath = my_abspath
|
||||
_join = os.path.join
|
||||
def my_join(a, *p):
|
||||
encoding=sys.getfilesystemencoding()
|
||||
p = [a] + list(p)
|
||||
_unicode = False
|
||||
for i in p:
|
||||
if isinstance(i, unicode):
|
||||
_unicode = True
|
||||
break
|
||||
p = [i.encode(encoding) if isinstance(i, unicode) else i for i in p]
|
||||
|
||||
res = _join(*p)
|
||||
if _unicode:
|
||||
res = res.decode(encoding)
|
||||
return res
|
||||
|
||||
os.path.join = my_join
|
||||
|
||||
|
||||
################################################################################
|
||||
# Platform specific modules
|
||||
winutil = winutilerror = None
|
||||
if iswindows:
|
||||
winutil, winutilerror = plugins['winutil']
|
||||
if not winutil:
|
||||
raise RuntimeError('Failed to load the winutil plugin: %s'%winutilerror)
|
||||
if len(sys.argv) > 1:
|
||||
sys.argv[1:] = winutil.argv()[1-len(sys.argv):]
|
||||
|
||||
################################################################################
|
||||
|
@ -35,7 +35,6 @@ class Distribution(object):
|
||||
('ImageMagick', '6.3.5', 'imagemagick', 'imagemagick', 'ImageMagick'),
|
||||
('xdg-utils', '1.0.2', 'xdg-utils', 'xdg-utils', 'xdg-utils'),
|
||||
('dbus-python', '0.82.2', 'dbus-python', 'python-dbus', 'dbus-python'),
|
||||
('convertlit', '1.8', 'convertlit', None, None),
|
||||
('lxml', '1.3.3', 'lxml', 'python-lxml', 'python-lxml'),
|
||||
('help2man', '1.36.4', 'help2man', 'help2man', 'help2man'),
|
||||
]
|
||||
@ -49,10 +48,7 @@ class Distribution(object):
|
||||
'fedora':'Fedora 8', 'debian':'Debian Sid', 'generic': 'Generic Unix'}
|
||||
|
||||
MANUAL_MAP = {
|
||||
'ubuntu' : '<li>You will have to install <a href="">convertlit</a> manually to be able to convert LIT files.</li>',
|
||||
'fedora' : '''<li>You have to upgrade Qt to at least 4.3.1 and PyQt to at least 4.3.1</li>'''\
|
||||
'''<li>You will have to install <a href="">convertlit</a> manually to be able to convert LIT files.</li>''',
|
||||
'debian' : '<li>Add the following to /etc/apt/sources.list<pre class="wiki">deb http://www.debian-multimedia.org sid main</pre>Then<pre class="wiki">apt-get install clit</pre></li>',
|
||||
'fedora' : '''<li>You have to upgrade Qt to at least 4.4.0 and PyQt to at least 4.4.2</li>''',
|
||||
}
|
||||
|
||||
def __init__(self, os):
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -164,8 +164,8 @@ DEFAULTKEYWORDS = ', '.join(default_keywords)
|
||||
|
||||
EMPTYSTRING = ''
|
||||
|
||||
from calibre import __appname__
|
||||
from calibre import __version__ as version
|
||||
from calibre.constants import __appname__
|
||||
from calibre.constants import __version__ as version
|
||||
|
||||
# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's
|
||||
# there.
|
||||
|
@ -6,36 +6,54 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: calibre 0.4.55\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2008-07-21 22:18+0000\n"
|
||||
"PO-Revision-Date: 2008-07-22 05:27+0000\n"
|
||||
"POT-Creation-Date: 2008-08-03 09:01+0000\n"
|
||||
"PO-Revision-Date: 2008-07-22 05:50+0000\n"
|
||||
"Last-Translator: Kovid Goyal <Unknown>\n"
|
||||
"Language-Team: ru\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2008-07-22 05:44+0000\n"
|
||||
"X-Launchpad-Export-Date: 2008-08-04 16:52+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/__init__.py:138
|
||||
#: /home/kovid/work/calibre/src/calibre/__init__.py:178
|
||||
#, fuzzy
|
||||
msgid "%sUsage%s: %s\n"
|
||||
msgstr "%sИспользовано%s: %s\n"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/__init__.py:175
|
||||
#: /home/kovid/work/calibre/src/calibre/__init__.py:215
|
||||
msgid "Created by "
|
||||
msgstr "Сделано "
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:112
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:146
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:174
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:113
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:147
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:175
|
||||
msgid "Unable to detect the %s disk drive. Try rebooting."
|
||||
msgstr "Не удалось определить диск %s. Попробуйте перезагрузиться."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:355
|
||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:356
|
||||
msgid "The reader has no storage card connected."
|
||||
msgstr "К ридеру не подключена карта памяти."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:780
|
||||
msgid "%prog [options] LITFILE"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:783
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:404
|
||||
msgid "Output directory. Defaults to current directory."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:786
|
||||
msgid "Useful for debugging."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:797
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:425
|
||||
msgid "OEB ebook created in"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:71
|
||||
msgid "Set the title. Default: filename."
|
||||
msgstr "Укажите заголовок. По умолчанию: имя файла."
|
||||
@ -50,14 +68,17 @@ msgstr ""
|
||||
"умолчанию: %default"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:74
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:239
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:174
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:314
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:429
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:52
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/library.py:278
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/library.py:685
|
||||
#: /home/kovid/work/calibre/src/calibre/library/database.py:879
|
||||
#: /home/kovid/work/calibre/src/calibre/library/database.py:1387
|
||||
#: /home/kovid/work/calibre/src/calibre/library/database.py:1517
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:926
|
||||
#: /home/kovid/work/calibre/src/calibre/library/database.py:904
|
||||
#: /home/kovid/work/calibre/src/calibre/library/database.py:1412
|
||||
#: /home/kovid/work/calibre/src/calibre/library/database.py:1542
|
||||
msgid "Unknown"
|
||||
msgstr "Неизвестно"
|
||||
|
||||
@ -414,6 +435,88 @@ msgstr ""
|
||||
msgid "No file to convert specified."
|
||||
msgstr "Не указан файл для преобразования."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:200
|
||||
msgid "Rendered %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:230
|
||||
msgid ""
|
||||
"Options to control the conversion of comics (CBR, CBZ) files into ebooks"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:236
|
||||
msgid "Title for generated ebook. Default is to use the filename."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:238
|
||||
msgid ""
|
||||
"Set the author in the metadata of the generated ebook. Default is %default"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:241
|
||||
msgid ""
|
||||
"Path to output LRF file. By default a file is created in the current "
|
||||
"directory."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:243
|
||||
msgid "Number of colors for Grayscale image conversion. Default: %default"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:245
|
||||
msgid ""
|
||||
"Disable normalize (improve contrast) color range for pictures. Default: False"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:247
|
||||
msgid "Maintain picture aspect ratio. Default is to fill the screen."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:249
|
||||
msgid "Disable sharpening."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:251
|
||||
msgid "Don't split landscape images into two portrait images"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:253
|
||||
msgid ""
|
||||
"Don't sort the files found in the comic alphabetically by name. Instead use "
|
||||
"the order they were added to the comic."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:255
|
||||
msgid ""
|
||||
"Choose a profile for the device you are generating this LRF for. The default "
|
||||
"is the SONY PRS-500 with a screen size of 584x754 pixels. Choices are %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:257
|
||||
msgid ""
|
||||
"Be verbose, useful for debugging. Can be specified multiple times for "
|
||||
"greater verbosity."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:259
|
||||
msgid "Don't show progress bar."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:264
|
||||
msgid ""
|
||||
"%prog [options] comic.cb[z|r]\n"
|
||||
"\n"
|
||||
"Convert a comic in a CBZ or CBR file to an LRF ebook. \n"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:328
|
||||
msgid "Rendering comic pages..."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:334
|
||||
msgid "Output written to"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/epub/convert_from.py:17
|
||||
msgid ""
|
||||
"Usage: %prog [options] mybook.epub\n"
|
||||
@ -515,40 +618,40 @@ msgid ""
|
||||
"%s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1749
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1752
|
||||
msgid ""
|
||||
"An error occurred while processing a table: %s. Ignoring table markup."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1751
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1754
|
||||
msgid ""
|
||||
"Bad table:\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1773
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1776
|
||||
msgid "Table has cell that is too large"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1803
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1806
|
||||
msgid ""
|
||||
"You have to save the website %s as an html file first and then run html2lrf "
|
||||
"on it."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1846
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1849
|
||||
msgid "Could not read cover image: %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1849
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1852
|
||||
msgid "Cannot read from: %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1984
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1987
|
||||
msgid "Failed to process opf file"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1990
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1993
|
||||
msgid ""
|
||||
"Usage: %prog [options] mybook.html\n"
|
||||
"\n"
|
||||
@ -559,7 +662,7 @@ msgid ""
|
||||
"convert a whole tree of HTML files."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lit/convert_from.py:21
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lit/convert_from.py:22
|
||||
msgid ""
|
||||
"Usage: %prog [options] mybook.lit\n"
|
||||
"\n"
|
||||
@ -741,11 +844,10 @@ msgstr ""
|
||||
msgid "Set the comment"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:100
|
||||
msgid "mybook.epub"
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:117
|
||||
msgid "A comma separated list of tags to set"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:100
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:50
|
||||
msgid "Usage:"
|
||||
msgstr ""
|
||||
@ -803,11 +905,11 @@ msgid ""
|
||||
"Fetch a cover image for the book identified by ISBN from LibraryThing.com\n"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/lit.py:751
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/lit.py:40
|
||||
msgid "Usage: %s file.lit"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/lit.py:758
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/lit.py:50
|
||||
msgid "Cover saved to"
|
||||
msgstr ""
|
||||
|
||||
@ -819,22 +921,14 @@ msgstr ""
|
||||
msgid "No filename specified."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:391
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:402
|
||||
msgid "%prog [options] myebook.mobi"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:393
|
||||
msgid "Output directory. Defaults to current directory."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:412
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:423
|
||||
msgid "Raw MOBI HTML saved in"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:414
|
||||
msgid "OEB ebook created in"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:22
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:26
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:36
|
||||
@ -855,6 +949,7 @@ msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:55
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:79
|
||||
msgid "Dialog"
|
||||
msgstr ""
|
||||
|
||||
@ -869,6 +964,51 @@ msgstr ""
|
||||
msgid "Choose Format"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:38
|
||||
msgid "Set defaults for conversion of comics (CBR/CBZ files)"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:53
|
||||
msgid "Set options for converting %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:80
|
||||
msgid "&Title:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:81
|
||||
msgid "&Author(s):"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:82
|
||||
msgid "&Number of Colors:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:83
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:552
|
||||
msgid "&Profile:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:84
|
||||
msgid "Disable &normalize"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:85
|
||||
msgid "Keep &aspect ratio"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:86
|
||||
msgid "Disable &Sharpening"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:87
|
||||
msgid "&Landscape"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:88
|
||||
msgid "Dont so&rt"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:23
|
||||
msgid "Basic"
|
||||
msgstr ""
|
||||
@ -877,47 +1017,47 @@ msgstr ""
|
||||
msgid "Advanced"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:105
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:107
|
||||
msgid "<br>Must be a directory."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:105
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:107
|
||||
msgid "Invalid database location "
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:105
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:108
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:107
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:110
|
||||
msgid "Invalid database location"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:108
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:110
|
||||
msgid "Invalid database location.<br>Cannot write to "
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:120
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:122
|
||||
msgid "Compacting database. This may take a while."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:120
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:122
|
||||
msgid "Compacting..."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:216
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:219
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:265
|
||||
msgid "Configuration"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:217
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:220
|
||||
msgid "&Location of books database (library1.db)"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:218
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:221
|
||||
msgid "Browse for the new database location"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:219
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:237
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:239
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:222
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:241
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:243
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:513
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:266
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:285
|
||||
@ -937,89 +1077,93 @@ msgstr ""
|
||||
msgid "..."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:220
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:223
|
||||
msgid "Use &Roman numerals for series number"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:221
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:224
|
||||
msgid "&Number of covers to show in browse mode (after restart):"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:222
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:225
|
||||
msgid "Show notification when &new version is available"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:223
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:226
|
||||
msgid "Ask for &confirmation before deleting files"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:227
|
||||
msgid "Format for &single file save:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:224
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:228
|
||||
msgid "&Priority for conversion jobs:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:225
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:229
|
||||
msgid "Default network &timeout:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:226
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:230
|
||||
msgid ""
|
||||
"Set the default timeout for network fetches (i.e. anytime we go out to the "
|
||||
"internet to get information)"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:227
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:231
|
||||
msgid " seconds"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:228
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:232
|
||||
msgid "Toolbar"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:229
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:233
|
||||
msgid "Large"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:230
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:234
|
||||
msgid "Medium"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:231
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:235
|
||||
msgid "Small"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:232
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:236
|
||||
msgid "&Button size in toolbar"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:233
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:237
|
||||
msgid "Show &text in toolbar buttons"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:234
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:238
|
||||
msgid "Select visible &columns in library view"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:235
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:239
|
||||
msgid "Frequently used directories"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:236
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:240
|
||||
msgid "Add a directory to the frequently used directories list"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:238
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:242
|
||||
msgid "Remove a directory from the frequently used directories list"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:240
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:244
|
||||
msgid "Free unused diskspace from the database"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:241
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:245
|
||||
msgid "&Compact database"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:242
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:246
|
||||
msgid "&Metadata from file name"
|
||||
msgstr ""
|
||||
|
||||
@ -1151,7 +1295,6 @@ msgid "Convert %s to LRF"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:108
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:177
|
||||
msgid "Set conversion defaults"
|
||||
msgstr ""
|
||||
|
||||
@ -1415,10 +1558,6 @@ msgstr ""
|
||||
msgid "Override<br>CSS"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:552
|
||||
msgid "&Profile:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:553
|
||||
msgid "&Left Margin:"
|
||||
msgstr ""
|
||||
@ -2261,23 +2400,31 @@ msgstr ""
|
||||
msgid "Bulk convert"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:309
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:177
|
||||
msgid "Set defaults for conversion to LRF"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:178
|
||||
msgid "Set defaults for conversion of comics"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:311
|
||||
msgid " detected."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:309
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:311
|
||||
msgid "Device: "
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:334
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:336
|
||||
msgid "Connected "
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:346
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:348
|
||||
msgid "Device database corrupted"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:347
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:349
|
||||
msgid ""
|
||||
"\n"
|
||||
" <p>The database of books on the reader is corrupted. Try the "
|
||||
@ -2293,190 +2440,222 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:399
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:473
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:401
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:475
|
||||
msgid ""
|
||||
"<p>Books with the same title as the following already exist in the database. "
|
||||
"Add them anyway?<ul>"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:402
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:476
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:404
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:478
|
||||
msgid "Duplicates found!"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:435
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:448
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:437
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:450
|
||||
msgid "Uploading books to device."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:507
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:509
|
||||
msgid "No space on device"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:508
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:510
|
||||
msgid ""
|
||||
"<p>Cannot upload books to device there is no more free space available "
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:546
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:541
|
||||
msgid "Confirm delete"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:542
|
||||
msgid "Are you sure you want to delete these %d books?"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:554
|
||||
msgid "Deleting books from device."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:578
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:599
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:586
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:607
|
||||
msgid "Cannot edit metadata"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:578
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:599
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:694
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:764
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:834
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:586
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:607
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:702
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:772
|
||||
msgid "No books selected"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:674
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:682
|
||||
msgid "Sending books to device."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:677
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:685
|
||||
msgid "No suitable formats"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:678
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:686
|
||||
msgid ""
|
||||
"Could not upload the following books to the device, as no suitable formats "
|
||||
"were found:<br><ul>%s</ul>"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:694
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:702
|
||||
msgid "Cannot save to disk"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:705
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:713
|
||||
msgid ""
|
||||
"<p>Could not save the following books to disk, because the %s format is not "
|
||||
"available for them:<ul>"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:709
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:717
|
||||
msgid "Could not save some ebooks"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:742
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:750
|
||||
msgid "Fetch news from "
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:744
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:752
|
||||
msgid "Fetching news from "
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:754
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:762
|
||||
msgid "News fetched. Uploading to device."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:764
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:834
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:772
|
||||
msgid "Cannot convert"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:773
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:794
|
||||
msgid "Starting Bulk conversion of %d books"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:905
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:923
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:832
|
||||
msgid "Convert book %d of %d (%s)"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:842
|
||||
msgid ""
|
||||
"<p>Could not convert %d of %d books, because no suitable source format was "
|
||||
"found.<ul>%s</ul>"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:843
|
||||
msgid "Could not convert some books"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:878
|
||||
msgid "Convert comic %d of %d (%s)"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:908
|
||||
msgid "Convert book: "
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:953
|
||||
msgid "Convert comic: "
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1001
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1019
|
||||
msgid "No book selected"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:905
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:923
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:937
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1001
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1019
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1033
|
||||
msgid "Cannot view"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:911
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:942
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1007
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1038
|
||||
msgid "Choose the format to view"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:938
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1034
|
||||
msgid "%s has no available formats."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:976
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1072
|
||||
msgid "Cannot configure"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:976
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1072
|
||||
msgid "Cannot configure while there are running jobs."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:999
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1095
|
||||
msgid "Copying database to "
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1014
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1110
|
||||
msgid "Invalid database"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1015
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1111
|
||||
msgid ""
|
||||
"<p>An invalid database already exists at %s, delete it before trying to move "
|
||||
"the existing database.<br>Error: %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1023
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1119
|
||||
msgid "Could not move database"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1044
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1140
|
||||
msgid "No detailed info available"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1045
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1141
|
||||
msgid "No detailed information is available for books on the device."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1087
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1183
|
||||
msgid "Error talking to device"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1088
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1184
|
||||
msgid ""
|
||||
"There was a temporary error talking to the device. Please unplug and "
|
||||
"reconnect the device and or reboot."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1139
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1235
|
||||
msgid "Conversion Error"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1158
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1254
|
||||
msgid "Database does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1158
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1254
|
||||
msgid ""
|
||||
"The directory in which the database should be: %s no longer exists. Please "
|
||||
"choose a new database location."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1209
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1305
|
||||
msgid ""
|
||||
"<span style=\"color:red; font-weight:bold\">Latest version: <a "
|
||||
"href=\"%s\">%s</a></span>"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1215
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1311
|
||||
msgid ""
|
||||
"%s has been updated to version %s. See the <a "
|
||||
"href=\"http://calibre.kovidgoyal.net/wiki/Changelog\">new features</a>. "
|
||||
"Visit the download page?"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1215
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1311
|
||||
msgid "Update available"
|
||||
msgstr ""
|
||||
|
||||
@ -2589,23 +2768,23 @@ msgstr ""
|
||||
msgid "Custom news sources"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/status.py:95
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/status.py:99
|
||||
msgid "Jobs:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/status.py:104
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/status.py:108
|
||||
msgid "Click to see list of active jobs."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/status.py:133
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/status.py:137
|
||||
msgid "Click to browse books by their covers"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/status.py:133
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/status.py:137
|
||||
msgid "Click to turn off Cover Browsing"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/status.py:138
|
||||
#: /home/kovid/work/calibre/src/calibre/gui2/status.py:142
|
||||
msgid ""
|
||||
"<p>Browsing books by their covers is disabled.<br>Import of pictureflow "
|
||||
"module failed:<br>"
|
||||
@ -2851,11 +3030,11 @@ msgid ""
|
||||
"For help on an individual command: %%prog command --help\n"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/parallel.py:344
|
||||
#: /home/kovid/work/calibre/src/calibre/parallel.py:347
|
||||
msgid "Could not launch worker process."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/fontconfig.py:157
|
||||
#: /home/kovid/work/calibre/src/calibre/utils/fontconfig.py:169
|
||||
msgid "Could not initialize the fontconfig library"
|
||||
msgstr ""
|
||||
|
||||
@ -2964,9 +3143,8 @@ msgid ""
|
||||
"downloads at most 2 feeds."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:84
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:88
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:577
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:70
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:580
|
||||
msgid "Fetching feeds..."
|
||||
msgstr ""
|
||||
|
||||
@ -2995,58 +3173,58 @@ msgstr ""
|
||||
msgid "\tFailed links:"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:559
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:562
|
||||
msgid "Could not fetch article. Run with --debug to see the reason"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:581
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:584
|
||||
msgid "Got feeds from index page"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:585
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:588
|
||||
msgid "Trying to download cover..."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:637
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:640
|
||||
msgid "Starting download [%d thread(s)]..."
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:650
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:653
|
||||
msgid "Feeds downloaded to %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:660
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:663
|
||||
msgid "Could not download cover: %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:665
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:668
|
||||
msgid "Downloading cover from %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:699
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:702
|
||||
msgid "Untitled Article"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:745
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:748
|
||||
msgid ""
|
||||
"\n"
|
||||
"Downloaded article %s from %s\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:751
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:754
|
||||
msgid "Article downloaded: %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:757
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:760
|
||||
msgid "Failed to download article: %s from %s\n"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:762
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:765
|
||||
msgid "Article download failed: %s"
|
||||
msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:777
|
||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:780
|
||||
msgid "Fetching feed"
|
||||
msgstr ""
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -6,10 +6,14 @@ __docformat__ = 'restructuredtext en'
|
||||
'''
|
||||
Manage application-wide preferences.
|
||||
'''
|
||||
import os, re, cPickle
|
||||
import os, re, cPickle, textwrap
|
||||
from copy import deepcopy
|
||||
from optparse import OptionParser as _OptionParser
|
||||
from optparse import IndentedHelpFormatter
|
||||
from PyQt4.QtCore import QString
|
||||
from calibre import iswindows, isosx, OptionParser, ExclusiveFile, LockError
|
||||
from calibre.constants import terminal_controller, iswindows, isosx, \
|
||||
__appname__, __version__, __author__
|
||||
from calibre.utils.lock import LockError, ExclusiveFile
|
||||
from collections import defaultdict
|
||||
|
||||
if iswindows:
|
||||
@ -21,10 +25,129 @@ if iswindows:
|
||||
elif isosx:
|
||||
config_dir = os.path.expanduser('~/Library/Preferences/calibre')
|
||||
else:
|
||||
config_dir = os.path.expanduser('~/.config/calibre')
|
||||
bdir = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME', '~/.config')))
|
||||
config_dir = os.path.join(bdir, 'calibre')
|
||||
|
||||
if not os.path.exists(config_dir):
|
||||
os.makedirs(config_dir)
|
||||
os.makedirs(config_dir, mode=448) # 0700 == 448
|
||||
|
||||
class CustomHelpFormatter(IndentedHelpFormatter):
|
||||
|
||||
def format_usage(self, usage):
|
||||
return _("%sUsage%s: %s\n") % (terminal_controller.BLUE, terminal_controller.NORMAL, usage)
|
||||
|
||||
def format_heading(self, heading):
|
||||
return "%*s%s%s%s:\n" % (self.current_indent, terminal_controller.BLUE,
|
||||
"", heading, terminal_controller.NORMAL)
|
||||
|
||||
def format_option(self, option):
|
||||
result = []
|
||||
opts = self.option_strings[option]
|
||||
opt_width = self.help_position - self.current_indent - 2
|
||||
if len(opts) > opt_width:
|
||||
opts = "%*s%s\n" % (self.current_indent, "",
|
||||
terminal_controller.GREEN+opts+terminal_controller.NORMAL)
|
||||
indent_first = self.help_position
|
||||
else: # start help on same line as opts
|
||||
opts = "%*s%-*s " % (self.current_indent, "", opt_width + len(terminal_controller.GREEN + terminal_controller.NORMAL),
|
||||
terminal_controller.GREEN + opts + terminal_controller.NORMAL)
|
||||
indent_first = 0
|
||||
result.append(opts)
|
||||
if option.help:
|
||||
help_text = self.expand_default(option).split('\n')
|
||||
help_lines = []
|
||||
|
||||
for line in help_text:
|
||||
help_lines.extend(textwrap.wrap(line, self.help_width))
|
||||
result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
|
||||
result.extend(["%*s%s\n" % (self.help_position, "", line)
|
||||
for line in help_lines[1:]])
|
||||
elif opts[-1] != "\n":
|
||||
result.append("\n")
|
||||
return "".join(result)+'\n'
|
||||
|
||||
|
||||
class OptionParser(_OptionParser):
|
||||
|
||||
def __init__(self,
|
||||
usage='%prog [options] filename',
|
||||
version='%%prog (%s %s)'%(__appname__, __version__),
|
||||
epilog=_('Created by ')+terminal_controller.RED+__author__+terminal_controller.NORMAL,
|
||||
gui_mode=False,
|
||||
conflict_handler='resolve',
|
||||
**kwds):
|
||||
usage += '''\n\nWhenever you pass arguments to %prog that have spaces in them, '''\
|
||||
'''enclose the arguments in quotation marks.'''
|
||||
_OptionParser.__init__(self, usage=usage, version=version, epilog=epilog,
|
||||
formatter=CustomHelpFormatter(),
|
||||
conflict_handler=conflict_handler, **kwds)
|
||||
self.gui_mode = gui_mode
|
||||
|
||||
def error(self, msg):
|
||||
if self.gui_mode:
|
||||
raise Exception(msg)
|
||||
_OptionParser.error(self, msg)
|
||||
|
||||
def merge(self, parser):
|
||||
'''
|
||||
Add options from parser to self. In case of conflicts, confilicting options from
|
||||
parser are skipped.
|
||||
'''
|
||||
opts = list(parser.option_list)
|
||||
groups = list(parser.option_groups)
|
||||
|
||||
def merge_options(options, container):
|
||||
for opt in deepcopy(options):
|
||||
if not self.has_option(opt.get_opt_string()):
|
||||
container.add_option(opt)
|
||||
|
||||
merge_options(opts, self)
|
||||
|
||||
for group in groups:
|
||||
g = self.add_option_group(group.title)
|
||||
merge_options(group.option_list, g)
|
||||
|
||||
def subsume(self, group_name, msg=''):
|
||||
'''
|
||||
Move all existing options into a subgroup named
|
||||
C{group_name} with description C{msg}.
|
||||
'''
|
||||
opts = [opt for opt in self.options_iter() if opt.get_opt_string() not in ('--version', '--help')]
|
||||
self.option_groups = []
|
||||
subgroup = self.add_option_group(group_name, msg)
|
||||
for opt in opts:
|
||||
self.remove_option(opt.get_opt_string())
|
||||
subgroup.add_option(opt)
|
||||
|
||||
def options_iter(self):
|
||||
for opt in self.option_list:
|
||||
if str(opt).strip():
|
||||
yield opt
|
||||
for gr in self.option_groups:
|
||||
for opt in gr.option_list:
|
||||
if str(opt).strip():
|
||||
yield opt
|
||||
|
||||
def option_by_dest(self, dest):
|
||||
for opt in self.options_iter():
|
||||
if opt.dest == dest:
|
||||
return opt
|
||||
|
||||
def merge_options(self, lower, upper):
|
||||
'''
|
||||
Merge options in lower and upper option lists into upper.
|
||||
Default values in upper are overridden by
|
||||
non default values in lower.
|
||||
'''
|
||||
for dest in lower.__dict__.keys():
|
||||
if not upper.__dict__.has_key(dest):
|
||||
continue
|
||||
opt = self.option_by_dest(dest)
|
||||
if lower.__dict__[dest] != opt.default and \
|
||||
upper.__dict__[dest] == opt.default:
|
||||
upper.__dict__[dest] = lower.__dict__[dest]
|
||||
|
||||
|
||||
|
||||
class Option(object):
|
||||
|
||||
@ -202,6 +325,15 @@ class Config(object):
|
||||
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
||||
return self.option_set.parse_string(src)
|
||||
|
||||
def as_string(self):
|
||||
if not os.path.exists(self.config_file_path):
|
||||
return ''
|
||||
try:
|
||||
with ExclusiveFile(self.config_file_path) as f:
|
||||
return f.read()
|
||||
except LockError:
|
||||
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
||||
|
||||
def set(self, name, val):
|
||||
if not self.option_set.has_option(name):
|
||||
raise ValueError('The option %s is not defined.'%name)
|
||||
@ -241,7 +373,153 @@ class StringConfig(object):
|
||||
footer = self.option_set.get_override_section(self.src)
|
||||
self.src = self.option_set.serialize(opts)+ '\n\n' + footer + '\n'
|
||||
|
||||
class ConfigProxy(object):
|
||||
'''
|
||||
A Proxy to minimize file reads for widely used config settings
|
||||
'''
|
||||
|
||||
def __init__(self, config):
|
||||
self.__config = config
|
||||
self.__opts = None
|
||||
|
||||
def refresh(self):
|
||||
self.__opts = self.__config.parse()
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.get(key)
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
return self.set(key, val)
|
||||
|
||||
def get(self, key):
|
||||
if self.__opts is None:
|
||||
self.refresh()
|
||||
return getattr(self.__opts, key)
|
||||
|
||||
def set(self, key, val):
|
||||
if self.__opts is None:
|
||||
self.refresh()
|
||||
setattr(self.__opts, key, val)
|
||||
return self.__config.set(key, val)
|
||||
|
||||
class DynamicConfig(dict):
|
||||
'''
|
||||
A replacement for QSettings that supports dynamic config keys.
|
||||
Returns `None` if a config key is not found. Note that the config
|
||||
data is stored in a non human readable pickle file, so only use this
|
||||
class for preferences that you don't intend to have the users edit directly.
|
||||
'''
|
||||
def __init__(self, name='dynamic'):
|
||||
self.name = name
|
||||
self.file_path = os.path.join(config_dir, name+'.pickle')
|
||||
with ExclusiveFile(self.file_path) as f:
|
||||
raw = f.read()
|
||||
d = cPickle.loads(raw) if raw.strip() else {}
|
||||
dict.__init__(self, d)
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return dict.__getitem__(self, key)
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
dict.__setitem__(self, key, val)
|
||||
self.commit()
|
||||
|
||||
def set(self, key, val):
|
||||
self.__setitem__(key, val)
|
||||
|
||||
def commit(self):
|
||||
if hasattr(self, 'file_path') and self.file_path:
|
||||
with ExclusiveFile(self.file_path) as f:
|
||||
raw = cPickle.dumps(self, -1)
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(raw)
|
||||
|
||||
dynamic = DynamicConfig()
|
||||
|
||||
def _prefs():
|
||||
c = Config('global', 'calibre wide preferences')
|
||||
c.add_opt('database_path',
|
||||
default=os.path.expanduser('~/library1.db'),
|
||||
help=_('Path to the database in which books are stored'))
|
||||
c.add_opt('filename_pattern', default=ur'(?P<title>.+) - (?P<author>[^_]+)',
|
||||
help=_('Pattern to guess metadata from filenames'))
|
||||
c.add_opt('isbndb_com_key', default='',
|
||||
help=_('Access key for isbndb.com'))
|
||||
c.add_opt('network_timeout', default=5,
|
||||
help=_('Default timeout for network operations (seconds)'))
|
||||
|
||||
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
|
||||
return c
|
||||
|
||||
prefs = ConfigProxy(_prefs())
|
||||
|
||||
def migrate():
|
||||
p = prefs
|
||||
if p.get('migrated'):
|
||||
return
|
||||
|
||||
from PyQt4.QtCore import QSettings, QVariant
|
||||
class Settings(QSettings):
|
||||
|
||||
def __init__(self, name='calibre2'):
|
||||
QSettings.__init__(self, QSettings.IniFormat, QSettings.UserScope,
|
||||
'kovidgoyal.net', name)
|
||||
|
||||
def get(self, key, default=None):
|
||||
try:
|
||||
key = str(key)
|
||||
if not self.contains(key):
|
||||
return default
|
||||
val = str(self.value(key, QVariant()).toByteArray())
|
||||
if not val:
|
||||
return None
|
||||
return cPickle.loads(val)
|
||||
except:
|
||||
return default
|
||||
|
||||
s, migrated = Settings(), set([])
|
||||
all_keys = set(map(unicode, s.allKeys()))
|
||||
from calibre.gui2 import config, dynamic
|
||||
def _migrate(key, safe=None, from_qvariant=None, p=config):
|
||||
try:
|
||||
if key not in all_keys:
|
||||
return
|
||||
if safe is None:
|
||||
safe = re.sub(r'[^0-9a-zA-Z]', '_', key)
|
||||
val = s.get(key)
|
||||
if from_qvariant is not None:
|
||||
val = getattr(s.value(key), from_qvariant)()
|
||||
p.set(safe, val)
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
migrated.add(key)
|
||||
|
||||
|
||||
_migrate('database path', p=prefs)
|
||||
_migrate('filename pattern', p=prefs)
|
||||
_migrate('network timeout', p=prefs)
|
||||
_migrate('isbndb.com key', p=prefs)
|
||||
|
||||
_migrate('frequently used directories')
|
||||
_migrate('send to device by default')
|
||||
_migrate('save to disk single format')
|
||||
_migrate('confirm delete')
|
||||
_migrate('show text in toolbar')
|
||||
_migrate('new version notification')
|
||||
_migrate('use roman numerals for series number')
|
||||
_migrate('cover flow queue length')
|
||||
_migrate('LRF conversion defaults')
|
||||
_migrate('LRF ebook viewer options')
|
||||
|
||||
for key in all_keys - migrated:
|
||||
if key.endswith(': un') or key.endswith(': pw'):
|
||||
_migrate(key, p=dynamic)
|
||||
p.set('migrated', True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
83
src/calibre/utils/filenames.py
Normal file
83
src/calibre/utils/filenames.py
Normal file
@ -0,0 +1,83 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Make strings safe for use as ASCII filenames, while trying to preserve as much
|
||||
meaning as possible.
|
||||
'''
|
||||
|
||||
import re, string
|
||||
|
||||
|
||||
MAP = {
|
||||
u"‘" : u"'",
|
||||
u"’" : u"'",
|
||||
u"«" : u'"',
|
||||
u"»" : u'"',
|
||||
u"…" : u"...",
|
||||
u"№" : u"#",
|
||||
u"Щ" : u"Sch",
|
||||
u"Щ" : u"SCH",
|
||||
u"Ё" : u"Yo",
|
||||
u"Ё" : u"YO",
|
||||
u"Ж" : u"Zh",
|
||||
u"Ж" : u"ZH",
|
||||
u"Ц" : u"Ts",
|
||||
u"Ц" : u"TS",
|
||||
u"Ч" : u"Ch",
|
||||
u"Ч" : u"CH",
|
||||
u"Ш" : u"Sh",
|
||||
u"Ш" : u"SH",
|
||||
u"Ы" : u"Yi",
|
||||
u"Ы" : u"YI",
|
||||
u"Ю" : u"Yu",
|
||||
u"Ю" : u"YU",
|
||||
u"Я" : u"Ya",
|
||||
u"Я" : u"YA",
|
||||
u"Б" : u"B",
|
||||
u"Г" : u"G",
|
||||
u"Д" : u"D",
|
||||
u"И" : u"I",
|
||||
u"Й" : u"J",
|
||||
u"К" : u"K",
|
||||
u"Л" : u"L",
|
||||
u"П" : u"P",
|
||||
u"Ф" : u"F",
|
||||
u"Э" : u"E",
|
||||
u"Ъ" : u"`",
|
||||
u"Ь" : u"'",
|
||||
u"щ" : u"sch",
|
||||
u"ё" : u"yo",
|
||||
u"ж" : u"zh",
|
||||
u"ц" : u"ts",
|
||||
u"ч" : u"ch",
|
||||
u"ш" : u"sh",
|
||||
u"ы" : u"yi",
|
||||
u"ю" : u"yu",
|
||||
u"я" : u"ya",
|
||||
u"б" : u"b",
|
||||
u"в" : u"v",
|
||||
u"г" : u"g",
|
||||
u"д" : u"d",
|
||||
u"з" : u"z",
|
||||
u"и" : u"i",
|
||||
u"й" : u"j",
|
||||
u"к" : u"k",
|
||||
u"л" : u"l",
|
||||
u"м" : u"m",
|
||||
u"н" : u"n",
|
||||
u"о" : u"o",
|
||||
u"п" : u"p",
|
||||
u"т" : u"t",
|
||||
u"ф" : u"f",
|
||||
u"э" : u"e",
|
||||
u"ъ" : u"`",
|
||||
u"ь" : u"'",
|
||||
} #: Translation table
|
||||
|
||||
def ascii_filename(orig):
|
||||
orig = PAT.sub(lambda m:MAP[m.group()], orig)
|
||||
buf = []
|
||||
for i in range(len(orig)):
|
||||
val = ord(orig[i])
|
||||
buf.append('_' if val < 33 or val > 126 else orig[i])
|
||||
return (''.join(buf)).encode('ascii')
|
||||
|
86
src/calibre/utils/lock.py
Normal file
86
src/calibre/utils/lock.py
Normal file
@ -0,0 +1,86 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
Secure access to locked files from multiple processes.
|
||||
'''
|
||||
|
||||
from calibre.constants import iswindows, __appname__, \
|
||||
win32api, win32event, winerror, fcntl
|
||||
import time, atexit, os
|
||||
|
||||
class LockError(Exception):
|
||||
pass
|
||||
|
||||
class ExclusiveFile(object):
|
||||
|
||||
def __init__(self, path, timeout=10):
|
||||
self.path = path
|
||||
self.timeout = timeout
|
||||
|
||||
def __enter__(self):
|
||||
self.file = open(self.path, 'a+b')
|
||||
self.file.seek(0)
|
||||
timeout = self.timeout
|
||||
if iswindows:
|
||||
name = ('Local\\'+(__appname__+self.file.name).replace('\\', '_'))[:201]
|
||||
while self.timeout < 0 or timeout >= 0:
|
||||
self.mutex = win32event.CreateMutex(None, False, name)
|
||||
if win32api.GetLastError() != winerror.ERROR_ALREADY_EXISTS: break
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
else:
|
||||
while self.timeout < 0 or timeout >= 0:
|
||||
try:
|
||||
fcntl.lockf(self.file.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
|
||||
break
|
||||
except IOError:
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
if timeout < 0 and self.timeout >= 0:
|
||||
self.file.close()
|
||||
raise LockError
|
||||
return self.file
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.file.close()
|
||||
if iswindows:
|
||||
win32api.CloseHandle(self.mutex)
|
||||
|
||||
|
||||
def _clean_lock_file(file):
|
||||
try:
|
||||
file.close()
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
os.remove(file.name)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def singleinstance(name):
|
||||
'''
|
||||
Return True if no other instance of the application identified by name is running,
|
||||
False otherwise.
|
||||
@param name: The name to lock.
|
||||
@type name: string
|
||||
'''
|
||||
if iswindows:
|
||||
mutexname = 'mutexforsingleinstanceof'+__appname__+name
|
||||
mutex = win32event.CreateMutex(None, False, mutexname)
|
||||
if mutex:
|
||||
atexit.register(win32api.CloseHandle, mutex)
|
||||
return not win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS
|
||||
else:
|
||||
path = os.path.expanduser('~/.'+__appname__+'_'+name+'.lock')
|
||||
try:
|
||||
f = open(path, 'w')
|
||||
fcntl.lockf(f.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
|
||||
atexit.register(_clean_lock_file, f)
|
||||
return True
|
||||
except IOError:
|
||||
return False
|
||||
|
||||
return False
|
@ -49,7 +49,7 @@ wherever possible in this module.
|
||||
Get command line arguments as unicode objects. Note that the
|
||||
first argument will be the path to the interpreter, *not* the
|
||||
script being run. So to replace sys.argv, you should use
|
||||
sys.argv[1:] = winutil.argv()[1:].
|
||||
`if len(sys.argv) > 1: sys.argv[1:] = winutil.argv()[1-len(sys.argv):]`
|
||||
|
||||
*/
|
||||
|
||||
|
@ -1028,11 +1028,13 @@ class ZipFile:
|
||||
|
||||
# Create all upper directories if necessary.
|
||||
upperdirs = os.path.dirname(targetpath)
|
||||
if upperdirs and os.path.exists(upperdirs) and not os.path.isdir(upperdirs):
|
||||
os.unlink(upperdirs)
|
||||
if upperdirs and not os.path.exists(upperdirs):
|
||||
os.makedirs(upperdirs)
|
||||
|
||||
source = self.open(member, pwd=pwd)
|
||||
target = file(targetpath, "wb")
|
||||
target = open(targetpath, "wb")
|
||||
shutil.copyfileobj(source, target)
|
||||
source.close()
|
||||
target.close()
|
||||
|
@ -12,9 +12,10 @@ from urllib import url2pathname
|
||||
from httplib import responses
|
||||
|
||||
from calibre import setup_cli_handlers, browser, sanitize_file_name, \
|
||||
OptionParser, relpath, LoggingInterface
|
||||
relpath, LoggingInterface
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag
|
||||
from calibre.ebooks.chardet import xml_to_unicode
|
||||
from calibre.utils.config import OptionParser
|
||||
|
||||
class FetchError(Exception):
|
||||
pass
|
||||
|
14
upload.py
14
upload.py
@ -28,9 +28,8 @@ MOBILEREAD = 'ftp://dev.mobileread.com/calibre/'
|
||||
BUILD_SCRIPT ='''\
|
||||
#!/bin/bash
|
||||
cd ~/build && \
|
||||
rsync -avz --exclude src/calibre/plugins --exclude docs --exclude .bzr --exclude .build --exclude build --exclude dist --exclude "*.pyc" --exclude "*.pyo" rsync://%(host)s/work/%(project)s . && \
|
||||
cd %(project)s && rm -rf build dist src/calibre/plugins && \
|
||||
mkdir -p build dist src/calibre/plugins && \
|
||||
rsync -avz --exclude src/calibre/plugins --exclude calibre/src/calibre.egg-info --exclude docs --exclude .bzr --exclude .build --exclude build --exclude dist --exclude "*.pyc" --exclude "*.pyo" rsync://%(host)s/work/%(project)s . && \
|
||||
cd %(project)s && \
|
||||
%%s && \
|
||||
rm -rf build/* && \
|
||||
%%s %%s
|
||||
@ -76,6 +75,8 @@ def build_windows(shutdown=True):
|
||||
installer = installer_name('exe')
|
||||
vm = '/vmware/Windows XP/Windows XP Professional.vmx'
|
||||
start_vm(vm, 'windows', BUILD_SCRIPT%('python setup.py develop', 'python','installer\\\\windows\\\\freeze.py'))
|
||||
if os.path.exists('build/py2exe'):
|
||||
shutil.rmtree('build/py2exe')
|
||||
subprocess.check_call(('scp', '-rp', 'windows:build/%s/build/py2exe'%PROJECT, 'build'))
|
||||
if not os.path.exists('build/py2exe'):
|
||||
raise Exception('Failed to run py2exe')
|
||||
@ -217,12 +218,12 @@ def upload_src_tarball():
|
||||
check_call('scp dist/calibre-*.tar.bz2 divok:%s/'%DOWNLOADS)
|
||||
|
||||
def stage_one():
|
||||
shutil.rmtree('build')
|
||||
check_call('sudo rm -rf build', shell=True)
|
||||
os.mkdir('build')
|
||||
shutil.rmtree('docs')
|
||||
os.mkdir('docs')
|
||||
check_call(['python', 'setup.py', 'build'])
|
||||
check_call('sudo rm -f src/%s/gui2/images_rc.pyc'%__appname__, shell=True)
|
||||
check_call('python setup.py build', shell=True)
|
||||
check_call('sudo python setup.py develop', shell=True)
|
||||
check_call('make', shell=True)
|
||||
tag_release()
|
||||
upload_demo()
|
||||
@ -241,6 +242,7 @@ def stage_three():
|
||||
upload_user_manual()
|
||||
check_call('python setup.py register bdist_egg --exclude-source-files upload')
|
||||
check_call('''rm -rf dist/* build/*''')
|
||||
check_call('''ssh divok bzr update /var/www/calibre.kovidgoyal.net/calibre/''')
|
||||
|
||||
def main(args=sys.argv):
|
||||
print 'Starting stage one...'
|
||||
|
Loading…
x
Reference in New Issue
Block a user