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'
|
HOME = '/home/kovid'
|
||||||
PYINSTALLER = os.path.expanduser('~/build/pyinstaller')
|
PYINSTALLER = os.path.expanduser('~/build/pyinstaller')
|
||||||
CALIBREPREFIX = '___'
|
CALIBREPREFIX = '___'
|
||||||
CLIT = '/usr/bin/clit'
|
|
||||||
PDFTOHTML = '/usr/bin/pdftohtml'
|
PDFTOHTML = '/usr/bin/pdftohtml'
|
||||||
LIBUNRAR = '/usr/lib/libunrar.so'
|
LIBUNRAR = '/usr/lib/libunrar.so'
|
||||||
QTDIR = '/usr/lib/qt4'
|
QTDIR = '/usr/lib/qt4'
|
||||||
@ -20,7 +19,9 @@ SQLITE = '/usr/lib/libsqlite3.so.0'
|
|||||||
DBUS = '/usr/lib/libdbus-1.so.3'
|
DBUS = '/usr/lib/libdbus-1.so.3'
|
||||||
LIBMNG = '/usr/lib/libmng.so.1'
|
LIBMNG = '/usr/lib/libmng.so.1'
|
||||||
LIBZ = '/lib/libz.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')
|
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')]
|
binaries += [(os.path.basename(f), f, 'BINARY')]
|
||||||
|
|
||||||
print 'Adding external programs...'
|
print 'Adding external programs...'
|
||||||
binaries += [('clit', CLIT, 'BINARY'), ('pdftohtml', PDFTOHTML, 'BINARY'),
|
binaries += [('pdftohtml', PDFTOHTML, 'BINARY'),
|
||||||
('libunrar.so', LIBUNRAR, 'BINARY')]
|
('libunrar.so', LIBUNRAR, 'BINARY')]
|
||||||
|
|
||||||
print 'Adding external libraries...'
|
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 = []
|
qt = []
|
||||||
|
@ -244,12 +244,6 @@ _check_symlinks_prescript()
|
|||||||
|
|
||||||
|
|
||||||
print
|
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'
|
print 'Adding pdftohtml'
|
||||||
os.link(os.path.expanduser('~/pdftohtml'), os.path.join(frameworks_dir, 'pdftohtml'))
|
os.link(os.path.expanduser('~/pdftohtml'), os.path.join(frameworks_dir, 'pdftohtml'))
|
||||||
print 'Adding plugins'
|
print 'Adding plugins'
|
||||||
|
@ -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 ::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 ::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 ::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 ::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 ::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 ::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 ::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 ::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
|
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 ::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 ::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 ::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 ::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 ::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 ::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 ::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 ::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 ::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
|
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
|
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'
|
DEVCON = 'C:\\devcon\\i386\\devcon.exe'
|
||||||
LIBUSB_DIR = 'C:\\libusb'
|
LIBUSB_DIR = 'C:\\libusb'
|
||||||
LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
|
LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
|
||||||
CLIT = 'C:\\clit\\clit.exe'
|
|
||||||
PDFTOHTML = 'C:\\pdftohtml\\pdftohtml.exe'
|
PDFTOHTML = 'C:\\pdftohtml\\pdftohtml.exe'
|
||||||
IMAGEMAGICK_DIR = 'C:\\ImageMagick'
|
IMAGEMAGICK_DIR = 'C:\\ImageMagick'
|
||||||
FONTCONFIG_DIR = 'C:\\fontconfig'
|
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)))
|
shutil.copyfile(DEVCON, os.path.join(tdir, os.path.basename(DEVCON)))
|
||||||
print '\tAdding unrar'
|
print '\tAdding unrar'
|
||||||
shutil.copyfile(LIBUNRAR, os.path.join(PY2EXE_DIR, os.path.basename(LIBUNRAR)))
|
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'
|
print '\tAdding pdftohtml'
|
||||||
shutil.copyfile(PDFTOHTML, os.path.join(PY2EXE_DIR, os.path.basename(PDFTOHTML)))
|
shutil.copyfile(PDFTOHTML, os.path.join(PY2EXE_DIR, os.path.basename(PDFTOHTML)))
|
||||||
print '\tAdding ImageMagick'
|
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
|
||||||
|
|
||||||
|
|
57
setup.py
57
setup.py
@ -7,7 +7,7 @@ sys.path.append('src')
|
|||||||
iswindows = re.search('win(32|64)', sys.platform)
|
iswindows = re.search('win(32|64)', sys.platform)
|
||||||
isosx = 'darwin' in sys.platform
|
isosx = 'darwin' in sys.platform
|
||||||
islinux = not isosx and not iswindows
|
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)
|
VERSION = re.search(r'__version__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
|
||||||
APPNAME = re.search(r'__appname__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
|
APPNAME = re.search(r'__appname__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
|
||||||
print 'Setup', APPNAME, 'version:', VERSION
|
print 'Setup', APPNAME, 'version:', VERSION
|
||||||
@ -47,17 +47,25 @@ main_functions = {
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from setuptools import setup, find_packages, Extension
|
from setuptools import setup, find_packages, Extension
|
||||||
|
from pyqtdistutils import PyQtExtension, build_ext
|
||||||
import subprocess, glob
|
import subprocess, glob
|
||||||
|
|
||||||
entry_points['console_scripts'].append('calibre_postinstall = calibre.linux:post_install')
|
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',
|
sources=['src/calibre/utils/lzx/lzxmodule.c',
|
||||||
'src/calibre/utils/lzx/lzxd.c'],
|
'src/calibre/utils/lzx/lzxd.c'],
|
||||||
include_dirs=['src/calibre/utils/lzx']),
|
include_dirs=['src/calibre/utils/lzx']),
|
||||||
Extension('calibre.plugins.msdes',
|
Extension('calibre.plugins.msdes',
|
||||||
sources=['src/calibre/utils/msdes/msdesmodule.c',
|
sources=['src/calibre/utils/msdes/msdesmodule.c',
|
||||||
'src/calibre/utils/msdes/des.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:
|
if iswindows:
|
||||||
ext_modules.append(Extension('calibre.plugins.winutil',
|
ext_modules.append(Extension('calibre.plugins.winutil',
|
||||||
sources=['src/calibre/utils/windows/winutil.c'],
|
sources=['src/calibre/utils/windows/winutil.c'],
|
||||||
@ -68,43 +76,7 @@ if __name__ == '__main__':
|
|||||||
ext_modules.append(Extension('calibre.plugins.usbobserver',
|
ext_modules.append(Extension('calibre.plugins.usbobserver',
|
||||||
sources=['src/calibre/devices/usbobserver/usbobserver.c'])
|
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(
|
setup(
|
||||||
name=APPNAME,
|
name=APPNAME,
|
||||||
packages = find_packages('src'),
|
packages = find_packages('src'),
|
||||||
@ -152,7 +124,8 @@ if __name__ == '__main__':
|
|||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
'Topic :: System :: Hardware :: Hardware Drivers'
|
'Topic :: System :: Hardware :: Hardware Drivers'
|
||||||
]
|
],
|
||||||
|
cmdclass = {'build_ext': build_ext},
|
||||||
)
|
)
|
||||||
|
|
||||||
if 'develop' in ' '.join(sys.argv) and islinux:
|
if 'develop' in ' '.join(sys.argv) and islinux:
|
||||||
|
@ -1,122 +1,28 @@
|
|||||||
''' E-book management software'''
|
''' E-book management software'''
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__version__ = '0.4.80'
|
__docformat__ = 'restructuredtext en'
|
||||||
__docformat__ = "epytext"
|
|
||||||
__author__ = "Kovid Goyal <kovid at kovidgoyal.net>"
|
|
||||||
__appname__ = 'calibre'
|
|
||||||
|
|
||||||
import sys, os, logging, mechanize, locale, copy, cStringIO, re, subprocess, \
|
import sys, os, re, logging, time, subprocess, mechanize, atexit
|
||||||
textwrap, atexit, cPickle, codecs, time
|
|
||||||
from gettext import GNUTranslations
|
|
||||||
from htmlentitydefs import name2codepoint
|
from htmlentitydefs import name2codepoint
|
||||||
from math import floor
|
from math import floor
|
||||||
from optparse import OptionParser as _OptionParser
|
|
||||||
from optparse import IndentedHelpFormatter
|
|
||||||
from logging import Formatter
|
from logging import Formatter
|
||||||
|
|
||||||
from PyQt4.QtCore import QSettings, QVariant, QUrl, QByteArray, QString
|
from PyQt4.QtCore import QUrl
|
||||||
from PyQt4.QtGui import QDesktopServices
|
from PyQt4.QtGui import QDesktopServices
|
||||||
|
from calibre.startup import plugins, winutil, winutilerror
|
||||||
from calibre.translations.msgfmt import make
|
from calibre.constants import iswindows, isosx, islinux, isfrozen, \
|
||||||
from calibre.ebooks.chardet import detect
|
terminal_controller, preferred_encoding, \
|
||||||
from calibre.utils.terminfo import TerminalController
|
__appname__, __version__, __author__, \
|
||||||
|
win32event, win32api, winerror, fcntl
|
||||||
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))
|
|
||||||
|
|
||||||
|
|
||||||
plugins = {}
|
def unicode_path(path, abs=False):
|
||||||
for plugin in ['pictureflow', 'lzx', 'msdes'] + \
|
if not isinstance(path, unicode):
|
||||||
(['winutil'] if iswindows else []) + \
|
path = path.decode(sys.getfilesystemencoding())
|
||||||
(['usbobserver'] if isosx else []):
|
if abs:
|
||||||
try:
|
path = os.path.abspath(path)
|
||||||
p, err = __import__(plugin), ''
|
return path
|
||||||
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 osx_version():
|
def osx_version():
|
||||||
if isosx:
|
if isosx:
|
||||||
@ -127,10 +33,6 @@ def osx_version():
|
|||||||
return int(m.group(1)), int(m.group(2)), int(m.group(3))
|
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):
|
class CommandLineError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -172,121 +74,6 @@ def setup_cli_handlers(logger, level):
|
|||||||
|
|
||||||
logger.addHandler(handler)
|
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):
|
def load_library(name, cdll):
|
||||||
if iswindows:
|
if iswindows:
|
||||||
@ -319,36 +106,37 @@ def extract(path, dir):
|
|||||||
extractor(path, dir)
|
extractor(path, dir)
|
||||||
|
|
||||||
def get_proxies():
|
def get_proxies():
|
||||||
proxies = {}
|
proxies = {}
|
||||||
if iswindows:
|
|
||||||
try:
|
for q in ('http', 'ftp'):
|
||||||
winreg = __import__('_winreg')
|
proxy = os.environ.get(q+'_proxy', None)
|
||||||
settings = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
|
if not proxy: continue
|
||||||
'Software\\Microsoft\\Windows'
|
if proxy.startswith(q+'://'):
|
||||||
'\\CurrentVersion\\Internet Settings')
|
proxy = proxy[7:]
|
||||||
proxy = winreg.QueryValueEx(settings, "ProxyEnable")[0]
|
proxies[q] = proxy
|
||||||
if proxy:
|
|
||||||
server = str(winreg.QueryValueEx(settings, 'ProxyServer')[0])
|
if iswindows:
|
||||||
if ';' in server:
|
try:
|
||||||
for p in server.split(';'):
|
winreg = __import__('_winreg')
|
||||||
protocol, address = p.split('=')
|
settings = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
|
||||||
proxies[protocol] = address
|
'Software\\Microsoft\\Windows'
|
||||||
else:
|
'\\CurrentVersion\\Internet Settings')
|
||||||
proxies['http'] = server
|
proxy = winreg.QueryValueEx(settings, "ProxyEnable")[0]
|
||||||
proxies['ftp'] = server
|
if proxy:
|
||||||
settings.Close()
|
server = str(winreg.QueryValueEx(settings, 'ProxyServer')[0])
|
||||||
except Exception, e:
|
if ';' in server:
|
||||||
print('Unable to detect proxy settings: %s' % str(e))
|
for p in server.split(';'):
|
||||||
if proxies:
|
protocol, address = p.split('=')
|
||||||
print('Using proxies: %s' % proxies)
|
proxies[protocol] = address
|
||||||
else:
|
else:
|
||||||
for q in ('http', 'ftp'):
|
proxies['http'] = server
|
||||||
proxy = os.environ.get(q+'_proxy', None)
|
proxies['ftp'] = server
|
||||||
if not proxy: continue
|
settings.Close()
|
||||||
if proxy.startswith(q+'://'):
|
except Exception, e:
|
||||||
proxy = proxy[7:]
|
print('Unable to detect proxy settings: %s' % str(e))
|
||||||
proxies[q] = proxy
|
if proxies:
|
||||||
return proxies
|
print('Using proxies: %s' % proxies)
|
||||||
|
return proxies
|
||||||
|
|
||||||
|
|
||||||
def browser(honor_time=False):
|
def browser(honor_time=False):
|
||||||
@ -383,40 +171,6 @@ def fit_image(width, height, pwidth, pheight):
|
|||||||
|
|
||||||
return scaled, int(width), int(height)
|
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):
|
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:]
|
rel_list = [os.pardir] * (len(base_list)-i) + target_list[i:]
|
||||||
return os.path.join(*rel_list)
|
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)
|
_spat = re.compile(r'^the\s+|^a\s+|^an\s+', re.IGNORECASE)
|
||||||
def english_sort(x, y):
|
def english_sort(x, y):
|
||||||
'''
|
'''
|
||||||
@ -662,11 +302,8 @@ def strftime(fmt, t=time.localtime()):
|
|||||||
A version of strtime that returns unicode strings.
|
A version of strtime that returns unicode strings.
|
||||||
'''
|
'''
|
||||||
result = time.strftime(fmt, t)
|
result = time.strftime(fmt, t)
|
||||||
try:
|
return unicode(result, preferred_encoding, 'replace')
|
||||||
return unicode(result, locale.getpreferredencoding(), 'replace')
|
|
||||||
except:
|
|
||||||
return unicode(result, 'utf-8', 'replace')
|
|
||||||
|
|
||||||
def entity_to_unicode(match, exceptions=[], encoding='cp1252'):
|
def entity_to_unicode(match, exceptions=[], encoding='cp1252'):
|
||||||
'''
|
'''
|
||||||
@param match: A match object such that '&'+match.group(1)';' is the entity.
|
@param match: A match object such that '&'+match.group(1)';' is the entity.
|
||||||
@ -707,5 +344,10 @@ if isosx:
|
|||||||
if not os.path.exists(os.path.join(fdir, 'LiberationSans_Regular.ttf')):
|
if not os.path.exists(os.path.join(fdir, 'LiberationSans_Regular.ttf')):
|
||||||
from calibre.ebooks.lrf.fonts.liberation import __all__ as fonts
|
from calibre.ebooks.lrf.fonts.liberation import __all__ as fonts
|
||||||
for font in fonts:
|
for font in fonts:
|
||||||
exec 'from calibre.ebooks.lrf.fonts.liberation.'+font+' import font_data'
|
l = {}
|
||||||
open(os.path.join(fdir, font+'.ttf'), 'wb').write(font_data)
|
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
|
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
|
from calibre.libunzip import update
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
@ -25,18 +26,23 @@ Run an embedded python interpreter.
|
|||||||
def update_zipfile(zipfile, mod, path):
|
def update_zipfile(zipfile, mod, path):
|
||||||
if 'win32' in sys.platform:
|
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 '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()
|
raw_input()
|
||||||
pat = re.compile(mod.replace('.', '/')+r'\.py[co]*')
|
pat = re.compile(mod.replace('.', '/')+r'\.py[co]*')
|
||||||
name = mod.replace('.', '/') + os.path.splitext(path)[-1]
|
name = mod.replace('.', '/') + os.path.splitext(path)[-1]
|
||||||
update(zipfile, [pat], [path], [name])
|
update(zipfile, [pat], [path], [name])
|
||||||
|
|
||||||
|
|
||||||
def update_module(mod, path):
|
def update_module(mod, path):
|
||||||
if not hasattr(sys, 'frozen'):
|
if not hasattr(sys, 'frozen'):
|
||||||
raise RuntimeError('Modules can only be updated in frozen installs.')
|
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')
|
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)
|
update_zipfile(zp, mod, path)
|
||||||
else:
|
else:
|
||||||
raise ValueError('Updating modules is not supported on this platform.')
|
raise ValueError('Updating modules is not supported on this platform.')
|
||||||
@ -53,10 +59,10 @@ def main(args=sys.argv):
|
|||||||
from IPython.Shell import IPShellEmbed
|
from IPython.Shell import IPShellEmbed
|
||||||
ipshell = IPShellEmbed()
|
ipshell = IPShellEmbed()
|
||||||
ipshell()
|
ipshell()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
@ -11,7 +11,7 @@ from optparse import OptionParser
|
|||||||
|
|
||||||
from calibre import __version__, iswindows, __appname__
|
from calibre import __version__, iswindows, __appname__
|
||||||
from calibre.devices.errors import PathError
|
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.errors import ArgumentError, DeviceError, DeviceLocked
|
||||||
from calibre.devices import devices
|
from calibre.devices import devices
|
||||||
from calibre.devices.scanner import DeviceScanner
|
from calibre.devices.scanner import DeviceScanner
|
||||||
|
@ -135,7 +135,7 @@ class PRS505(Device):
|
|||||||
if 'PRS-505/UC&' in device_id:
|
if 'PRS-505/UC&' in device_id:
|
||||||
main = volumes[device_id]+':\\'
|
main = volumes[device_id]+':\\'
|
||||||
if not main:
|
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
|
self._main_prefix = main
|
||||||
card = self._card_prefix = None
|
card = self._card_prefix = None
|
||||||
win32api = __import__('win32api')
|
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 sys, struct, cStringIO, os
|
||||||
import functools
|
import functools
|
||||||
import re
|
import re
|
||||||
|
from lxml import etree
|
||||||
from calibre.ebooks.lit import LitError
|
from calibre.ebooks.lit import LitError
|
||||||
from calibre.ebooks.lit.maps import OPF_MAP, HTML_MAP
|
from calibre.ebooks.lit.maps import OPF_MAP, HTML_MAP
|
||||||
import calibre.ebooks.lit.mssha1 as mssha1
|
import calibre.ebooks.lit.mssha1 as mssha1
|
||||||
@ -17,6 +18,8 @@ from calibre import plugins
|
|||||||
lzx, lxzerror = plugins['lzx']
|
lzx, lxzerror = plugins['lzx']
|
||||||
msdes, msdeserror = plugins['msdes']
|
msdes, msdeserror = plugins['msdes']
|
||||||
|
|
||||||
|
XML_DECL = """<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
"""
|
||||||
OPF_DECL = """<?xml version="1.0" encoding="UTF-8" ?>
|
OPF_DECL = """<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<!DOCTYPE package
|
<!DOCTYPE package
|
||||||
PUBLIC "+//ISBN 0-9673008-1-9//DTD OEB 1.0.1 Package//EN"
|
PUBLIC "+//ISBN 0-9673008-1-9//DTD OEB 1.0.1 Package//EN"
|
||||||
@ -107,11 +110,12 @@ class UnBinary(object):
|
|||||||
AMPERSAND_RE = re.compile(
|
AMPERSAND_RE = re.compile(
|
||||||
r'&(?!(?:#[0-9]+|#x[0-9a-fA-F]+|[a-zA-Z_:][a-zA-Z0-9.-_:]+);)')
|
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.manifest = manifest
|
||||||
self.tag_map, self.attr_map, self.tag_to_attr_map = map
|
self.tag_map, self.attr_map, self.tag_to_attr_map = map
|
||||||
self.opf = map is OPF_MAP
|
self.opf = map is OPF_MAP
|
||||||
self.bin = bin
|
self.bin = bin
|
||||||
|
self.dir = os.path.dirname(path)
|
||||||
self.buf = cStringIO.StringIO()
|
self.buf = cStringIO.StringIO()
|
||||||
self.binary_to_text()
|
self.binary_to_text()
|
||||||
self.raw = self.buf.getvalue().lstrip().decode('utf-8')
|
self.raw = self.buf.getvalue().lstrip().decode('utf-8')
|
||||||
@ -122,9 +126,19 @@ class UnBinary(object):
|
|||||||
|
|
||||||
def item_path(self, internal_id):
|
def item_path(self, internal_id):
|
||||||
try:
|
try:
|
||||||
return self.manifest[internal_id].path
|
target = self.manifest[internal_id].path
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return internal_id
|
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):
|
def __unicode__(self):
|
||||||
return self.raw
|
return self.raw
|
||||||
@ -147,7 +161,7 @@ class UnBinary(object):
|
|||||||
continue
|
continue
|
||||||
elif c == '\v':
|
elif c == '\v':
|
||||||
c = '\n'
|
c = '\n'
|
||||||
self.buf.write(c.encode('utf-8'))
|
self.buf.write(c.encode('ascii', 'xmlcharrefreplace'))
|
||||||
|
|
||||||
elif state == 'get flags':
|
elif state == 'get flags':
|
||||||
if oc == 0:
|
if oc == 0:
|
||||||
@ -206,7 +220,7 @@ class UnBinary(object):
|
|||||||
state = 'get attr length'
|
state = 'get attr length'
|
||||||
continue
|
continue
|
||||||
attr = None
|
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]
|
attr = current_map[oc]
|
||||||
elif oc in self.attr_map:
|
elif oc in self.attr_map:
|
||||||
attr = self.attr_map[oc]
|
attr = self.attr_map[oc]
|
||||||
@ -247,7 +261,8 @@ class UnBinary(object):
|
|||||||
state = 'get attr'
|
state = 'get attr'
|
||||||
elif count > 0:
|
elif count > 0:
|
||||||
if not in_censorship:
|
if not in_censorship:
|
||||||
self.buf.write(unicode(c).encode('utf-8'))
|
self.buf.write(c.encode(
|
||||||
|
'ascii', 'xmlcharrefreplace'))
|
||||||
count -= 1
|
count -= 1
|
||||||
if count == 0:
|
if count == 0:
|
||||||
if not in_censorship:
|
if not in_censorship:
|
||||||
@ -299,7 +314,8 @@ class UnBinary(object):
|
|||||||
path = self.item_path(doc)
|
path = self.item_path(doc)
|
||||||
if m and frag:
|
if m and frag:
|
||||||
path += m + 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'
|
state = 'get attr'
|
||||||
return index
|
return index
|
||||||
|
|
||||||
@ -354,6 +370,8 @@ def preserve(function):
|
|||||||
|
|
||||||
class LitReader(object):
|
class LitReader(object):
|
||||||
PIECE_SIZE = 16
|
PIECE_SIZE = 16
|
||||||
|
XML_PARSER = etree.XMLParser(
|
||||||
|
remove_blank_text=True, resolve_entities=False)
|
||||||
|
|
||||||
def magic():
|
def magic():
|
||||||
@preserve
|
@preserve
|
||||||
@ -596,16 +614,23 @@ class LitReader(object):
|
|||||||
if item.path[0] == '/':
|
if item.path[0] == '/':
|
||||||
item.path = os.path.basename(item.path)
|
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):
|
def _read_meta(self):
|
||||||
|
path = 'content.opf'
|
||||||
raw = self.get_file('/meta')
|
raw = self.get_file('/meta')
|
||||||
try:
|
try:
|
||||||
xml = OPF_DECL + unicode(UnBinary(raw, self.manifest, OPF_MAP))
|
xml = OPF_DECL + unicode(UnBinary(raw, path, self.manifest, OPF_MAP))
|
||||||
except LitError:
|
except LitError:
|
||||||
if 'PENGUIN group' not in raw: raise
|
if 'PENGUIN group' not in raw: raise
|
||||||
print "WARNING: attempting PENGUIN malformed OPF fix"
|
print "WARNING: attempting PENGUIN malformed OPF fix"
|
||||||
raw = raw.replace(
|
raw = raw.replace(
|
||||||
'PENGUIN group', '\x00\x01\x18\x00PENGUIN group', 1)
|
'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
|
self.meta = xml
|
||||||
|
|
||||||
def _read_drm(self):
|
def _read_drm(self):
|
||||||
@ -645,13 +670,6 @@ class LitReader(object):
|
|||||||
key[i % 8] ^= ord(digest[i])
|
key[i % 8] ^= ord(digest[i])
|
||||||
return ''.join(chr(x) for x in key)
|
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):
|
def get_file(self, name):
|
||||||
entry = self.entries[name]
|
entry = self.entries[name]
|
||||||
if entry.section == 0:
|
if entry.section == 0:
|
||||||
@ -748,7 +766,23 @@ class LitReader(object):
|
|||||||
raise LitError("Failed to completely decompress section")
|
raise LitError("Failed to completely decompress section")
|
||||||
return ''.join(result)
|
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)
|
output_dir = os.path.abspath(output_dir)
|
||||||
try:
|
try:
|
||||||
opf_path = os.path.splitext(
|
opf_path = os.path.splitext(
|
||||||
@ -758,17 +792,15 @@ class LitReader(object):
|
|||||||
opf_path = os.path.join(output_dir, opf_path)
|
opf_path = os.path.join(output_dir, opf_path)
|
||||||
self._ensure_dir(opf_path)
|
self._ensure_dir(opf_path)
|
||||||
with open(opf_path, 'wb') as f:
|
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():
|
for entry in self.manifest.values():
|
||||||
path = os.path.join(output_dir, entry.path)
|
path = os.path.join(output_dir, entry.path)
|
||||||
self._ensure_dir(path)
|
self._ensure_dir(path)
|
||||||
with open(path, 'wb') as f:
|
with open(path, 'wb') as f:
|
||||||
if 'spine' in entry.state:
|
f.write(self.get_entry_content(entry, pretty_print))
|
||||||
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))
|
|
||||||
|
|
||||||
def _ensure_dir(self, path):
|
def _ensure_dir(self, path):
|
||||||
dir = os.path.dirname(path)
|
dir = os.path.dirname(path)
|
||||||
@ -776,11 +808,14 @@ class LitReader(object):
|
|||||||
os.makedirs(dir)
|
os.makedirs(dir)
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
from calibre import OptionParser
|
from calibre.utils.config import OptionParser
|
||||||
parser = OptionParser(usage=_('%prog [options] LITFILE'))
|
parser = OptionParser(usage=_('%prog [options] LITFILE'))
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
'-o', '--output-dir', default='.',
|
'-o', '--output-dir', default='.',
|
||||||
help=_('Output directory. Defaults to current directory.'))
|
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(
|
parser.add_option(
|
||||||
'--verbose', default=False, action='store_true',
|
'--verbose', default=False, action='store_true',
|
||||||
help=_('Useful for debugging.'))
|
help=_('Useful for debugging.'))
|
||||||
@ -793,7 +828,7 @@ def main(args=sys.argv):
|
|||||||
parser.print_help()
|
parser.print_help()
|
||||||
return 1
|
return 1
|
||||||
lr = LitReader(args[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
|
print _('OEB ebook created in'), opts.output_dir
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@ -14,7 +14,8 @@ from calibre.ebooks.lrf.pylrs.pylrs import TextBlock, Header, PutObj, \
|
|||||||
Paragraph, TextStyle, BlockStyle
|
Paragraph, TextStyle, BlockStyle
|
||||||
from calibre.ebooks.lrf.fonts import FONT_FILE_MAP
|
from calibre.ebooks.lrf.fonts import FONT_FILE_MAP
|
||||||
from calibre.ebooks import ConversionError
|
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"
|
__docformat__ = "epytext"
|
||||||
|
|
||||||
|
@ -121,8 +121,8 @@ class PageProcessor(list):
|
|||||||
DestroyMagickWand(img)
|
DestroyMagickWand(img)
|
||||||
MagickCropImage(split1, (width/2)-1, height, 0, 0)
|
MagickCropImage(split1, (width/2)-1, height, 0, 0)
|
||||||
MagickCropImage(split2, (width/2)-1, height, width/2, 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()
|
self.process_pages()
|
||||||
except Exception, err:
|
except Exception, err:
|
||||||
print 'Failed to process page: %s'%os.path.basename(self.path_to_page)
|
print 'Failed to process page: %s'%os.path.basename(self.path_to_page)
|
||||||
@ -180,7 +180,7 @@ class PageProcessor(list):
|
|||||||
|
|
||||||
MagickSetImageType(wand, GrayscaleType)
|
MagickSetImageType(wand, GrayscaleType)
|
||||||
MagickQuantizeImage(wand, self.opts.colors, RGBColorspace, 0, 1, 0)
|
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)
|
dest = os.path.join(self.dest, dest)
|
||||||
MagickWriteImage(wand, dest)
|
MagickWriteImage(wand, dest)
|
||||||
self.append(dest)
|
self.append(dest)
|
||||||
@ -219,7 +219,7 @@ def process_pages(pages, opts, update):
|
|||||||
|
|
||||||
for pp in processed_pages:
|
for pp in processed_pages:
|
||||||
if len(pp) == 0:
|
if len(pp) == 0:
|
||||||
failures.append(os.path.basename(pp.path_to_page()))
|
failures.append(os.path.basename(pp.path_to_page))
|
||||||
else:
|
else:
|
||||||
ans += pp
|
ans += pp
|
||||||
return ans, failures, tdir
|
return ans, failures, tdir
|
||||||
@ -238,7 +238,7 @@ def config(defaults=None):
|
|||||||
c.add_opt('output', ['-o', '--output'],
|
c.add_opt('output', ['-o', '--output'],
|
||||||
help=_('Path to output LRF file. By default a file is created in the current directory.'))
|
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,
|
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,
|
c.add_opt('dont_normalize', ['-n', '--disable-normalize'], default=False,
|
||||||
help=_('Disable normalize (improve contrast) color range for pictures. 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,
|
c.add_opt('keep_aspect_ratio', ['-r', '--keep-aspect-ratio'], default=False,
|
||||||
@ -247,6 +247,8 @@ def config(defaults=None):
|
|||||||
help=_('Disable sharpening.'))
|
help=_('Disable sharpening.'))
|
||||||
c.add_opt('landscape', ['-l', '--landscape'], default=False,
|
c.add_opt('landscape', ['-l', '--landscape'], default=False,
|
||||||
help=_("Don't split landscape images into two portrait images"))
|
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,
|
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."))
|
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(),
|
c.add_opt('profile', ['-p', '--profile'], default='prs500', choices=PROFILES.keys(),
|
||||||
@ -333,4 +335,4 @@ def main(args=sys.argv, notification=None):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
@ -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.lrf.html.convert_from import process_file as html_process_file
|
||||||
from calibre.ebooks.metadata.opf import OPF
|
from calibre.ebooks.metadata.opf import OPF
|
||||||
from calibre.ebooks.metadata.epub import OCFDirReader
|
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
|
from calibre import __appname__, setup_cli_handlers
|
||||||
|
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ def generate_html(pathtoepub, logger):
|
|||||||
tdir = mkdtemp(prefix=__appname__+'_')
|
tdir = mkdtemp(prefix=__appname__+'_')
|
||||||
os.rmdir(tdir)
|
os.rmdir(tdir)
|
||||||
try:
|
try:
|
||||||
zip_extract(pathtoepub, tdir)
|
ZipFile(pathtoepub).extractall(tdir)
|
||||||
except:
|
except:
|
||||||
if os.path.exists(tdir) and os.path.isdir(tdir):
|
if os.path.exists(tdir) and os.path.isdir(tdir):
|
||||||
shutil.rmtree(tdir)
|
shutil.rmtree(tdir)
|
||||||
|
@ -2,7 +2,8 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
import sys, logging, os
|
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 import ConversionError
|
||||||
from calibre.ebooks.lrf.meta import get_metadata
|
from calibre.ebooks.lrf.meta import get_metadata
|
||||||
from calibre.ebooks.lrf.lrfparser import LRFDocument
|
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, \
|
from PyQt4.Qt import QWebPage, QUrl, QApplication, QSize, \
|
||||||
SIGNAL, QPainter, QImage, QObject, Qt
|
SIGNAL, QPainter, QImage, QObject, Qt
|
||||||
|
|
||||||
|
from calibre.parallel import ParallelJob
|
||||||
|
|
||||||
__app = None
|
__app = None
|
||||||
|
|
||||||
class HTMLTableRenderer(QObject):
|
class HTMLTableRenderer(QObject):
|
||||||
@ -80,18 +82,17 @@ def render_table(server, soup, table, css, base_dir, width, height, dpi, factor=
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
'''%(head, width-10, style, unicode(table))
|
'''%(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])
|
args=[html, base_dir, width, height, dpi, factor])
|
||||||
res = None
|
server.add_job(job)
|
||||||
while res is None:
|
while not job.has_run:
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
res = server.result(1)
|
|
||||||
result, exception, traceback = res
|
if job.exception is not None:
|
||||||
if exception:
|
|
||||||
print 'Failed to render table'
|
print 'Failed to render table'
|
||||||
print exception
|
print job.exception
|
||||||
print traceback
|
print job.traceback
|
||||||
images, tdir = result
|
images, tdir = job.result
|
||||||
atexit.register(shutil.rmtree, tdir)
|
atexit.register(shutil.rmtree, tdir)
|
||||||
return images
|
return images
|
||||||
|
|
||||||
|
@ -3,19 +3,12 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
import os, sys, shutil, glob, logging
|
import os, sys, shutil, glob, logging
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from subprocess import Popen, PIPE
|
|
||||||
from calibre.ebooks.lrf import option_parser as lrf_option_parser
|
from calibre.ebooks.lrf import option_parser as lrf_option_parser
|
||||||
from calibre.ebooks.lit.reader import LitReader
|
from calibre.ebooks.lit.reader import LitReader
|
||||||
from calibre.ebooks import ConversionError
|
from calibre.ebooks import ConversionError
|
||||||
from calibre.ebooks.lrf.html.convert_from import process_file as html_process_file
|
from calibre.ebooks.lrf.html.convert_from import process_file as html_process_file
|
||||||
from calibre.ebooks.metadata.opf import OPFReader
|
from calibre.ebooks.metadata.opf import OPFReader
|
||||||
from calibre import isosx, __appname__, setup_cli_handlers, islinux
|
from calibre import __appname__, setup_cli_handlers
|
||||||
|
|
||||||
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')
|
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
parser = lrf_option_parser(
|
parser = lrf_option_parser(
|
||||||
@ -24,37 +17,15 @@ _('''Usage: %prog [options] mybook.lit
|
|||||||
|
|
||||||
%prog converts mybook.lit to mybook.lrf''')
|
%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
|
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):
|
def generate_html(pathtolit, logger):
|
||||||
if not os.access(pathtolit, os.R_OK):
|
if not os.access(pathtolit, os.R_OK):
|
||||||
raise ConversionError, 'Cannot read from ' + pathtolit
|
raise ConversionError, 'Cannot read from ' + pathtolit
|
||||||
tdir = mkdtemp(prefix=__appname__+'_')
|
tdir = mkdtemp(prefix=__appname__+'_'+'lit2oeb_')
|
||||||
os.rmdir(tdir)
|
lr = LitReader(pathtolit)
|
||||||
cmd = [CLIT, pathtolit, '%s'%(tdir+os.sep)]
|
print 'Extracting LIT file to', tdir
|
||||||
logger.debug(repr(cmd))
|
lr.extract_content(tdir)
|
||||||
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
|
|
||||||
return tdir
|
return tdir
|
||||||
|
|
||||||
def process_file(path, options, logger=None):
|
def process_file(path, options, logger=None):
|
||||||
@ -63,8 +34,7 @@ def process_file(path, options, logger=None):
|
|||||||
logger = logging.getLogger('lit2lrf')
|
logger = logging.getLogger('lit2lrf')
|
||||||
setup_cli_handlers(logger, level)
|
setup_cli_handlers(logger, level)
|
||||||
lit = os.path.abspath(os.path.expanduser(path))
|
lit = os.path.abspath(os.path.expanduser(path))
|
||||||
tdir = generate_html2(lit, logger) if getattr(options, 'lit2oeb', False) \
|
tdir = generate_html(lit, logger)
|
||||||
else generate_html(lit, logger)
|
|
||||||
try:
|
try:
|
||||||
opf = glob.glob(os.path.join(tdir, '*.opf'))
|
opf = glob.glob(os.path.join(tdir, '*.opf'))
|
||||||
if 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.basename(os.path.splitext(path)[0]) + ext)
|
||||||
options.output = os.path.abspath(os.path.expanduser(options.output))
|
options.output = os.path.abspath(os.path.expanduser(options.output))
|
||||||
options.use_spine = True
|
options.use_spine = True
|
||||||
|
|
||||||
html_process_file(htmlfile, options, logger=logger)
|
html_process_file(htmlfile, options, logger=logger)
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
@ -108,7 +77,7 @@ def process_file(path, options, logger=None):
|
|||||||
def main(args=sys.argv, logger=None):
|
def main(args=sys.argv, logger=None):
|
||||||
parser = option_parser()
|
parser = option_parser()
|
||||||
options, args = parser.parse_args(args)
|
options, args = parser.parse_args(args)
|
||||||
if len(args) != 2:
|
if len(args) != 2:
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
print
|
print
|
||||||
print 'No lit file specified'
|
print 'No lit file specified'
|
||||||
|
@ -4,7 +4,8 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
import sys, array, os, re, codecs, logging
|
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.meta import LRFMetaFile
|
||||||
from calibre.ebooks.lrf.objects import get_object, PageTree, StyleObject, \
|
from calibre.ebooks.lrf.objects import get_object, PageTree, StyleObject, \
|
||||||
Font, Text, TOCObject, BookAttr, ruby_tags
|
Font, Text, TOCObject, BookAttr, ruby_tags
|
||||||
|
@ -6,7 +6,8 @@ Compile a LRS file into a LRF file.
|
|||||||
|
|
||||||
import sys, os, logging
|
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, \
|
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, NavigableString, \
|
||||||
CData, Tag
|
CData, Tag
|
||||||
from calibre.ebooks.lrf.pylrs.pylrs import Book, PageStyle, TextStyle, \
|
from calibre.ebooks.lrf.pylrs.pylrs import Book, PageStyle, TextStyle, \
|
||||||
|
@ -574,8 +574,8 @@ class LRFMetaFile(object):
|
|||||||
|
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
from optparse import OptionParser
|
from calibre.utils.config import OptionParser
|
||||||
from calibre import __appname__, __version__
|
from calibre.constants import __appname__, __version__
|
||||||
parser = OptionParser(usage = \
|
parser = OptionParser(usage = \
|
||||||
_('''%prog [options] mybook.lrf
|
_('''%prog [options] mybook.lrf
|
||||||
|
|
||||||
|
@ -25,6 +25,8 @@ def generate_html(pathtopdf, logger):
|
|||||||
Convert the pdf into html.
|
Convert the pdf into html.
|
||||||
@return: Path to a temporary file containing the 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):
|
if not os.access(pathtopdf, os.R_OK):
|
||||||
raise ConversionError, 'Cannot read from ' + pathtopdf
|
raise ConversionError, 'Cannot read from ' + pathtopdf
|
||||||
tdir = PersistentTemporaryDirectory('pdftohtml')
|
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
|
import sys, os, re, tempfile, subprocess, atexit, shutil, logging, xml.parsers.expat
|
||||||
from xml.etree.ElementTree import parse
|
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
|
from calibre.ebooks import ConversionError
|
||||||
|
|
||||||
PDFTOXML = 'pdftoxml.exe'
|
PDFTOXML = 'pdftoxml.exe'
|
||||||
|
@ -11,8 +11,9 @@ from urllib import unquote, quote
|
|||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
|
||||||
|
|
||||||
from calibre import __version__ as VERSION, relpath
|
from calibre.constants import __version__ as VERSION
|
||||||
from calibre import OptionParser
|
from calibre import relpath
|
||||||
|
from calibre.utils.config import OptionParser
|
||||||
|
|
||||||
def get_parser(extension):
|
def get_parser(extension):
|
||||||
''' Return an option parser with the basic metadata options already setup'''
|
''' 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
|
import sys, logging, re, socket
|
||||||
from urllib import urlopen, quote
|
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.metadata import MetaInformation
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup
|
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
|
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
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||||
browser = None
|
browser = None
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
import os, re, collections
|
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.rtf import get_metadata as rtf_metadata
|
||||||
from calibre.ebooks.metadata.fb2 import get_metadata as fb2_metadata
|
from calibre.ebooks.metadata.fb2 import get_metadata as fb2_metadata
|
||||||
from calibre.ebooks.lrf.meta import get_metadata as lrf_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':
|
elif stream_type == 'rtf':
|
||||||
set_rtf_metadata(stream, mi)
|
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):
|
def metadata_from_filename(name, pat=None):
|
||||||
name = os.path.splitext(name)[0]
|
name = os.path.splitext(name)[0]
|
||||||
mi = MetaInformation(None, None)
|
mi = MetaInformation(None, None)
|
||||||
if pat is None:
|
if pat is None:
|
||||||
pat = _filename_pat
|
pat = re.compile(prefs.get('filename_pattern'))
|
||||||
match = pat.search(name)
|
match = pat.search(name)
|
||||||
if match:
|
if match:
|
||||||
try:
|
try:
|
||||||
@ -134,12 +126,12 @@ def metadata_from_filename(name, pat=None):
|
|||||||
try:
|
try:
|
||||||
si = match.group('series_index')
|
si = match.group('series_index')
|
||||||
mi.series_index = int(si)
|
mi.series_index = int(si)
|
||||||
except IndexError, ValueError:
|
except (IndexError, ValueError):
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
si = match.group('isbn')
|
si = match.group('isbn')
|
||||||
mi.isbn = si
|
mi.isbn = si
|
||||||
except IndexError, ValueError:
|
except (IndexError, ValueError):
|
||||||
pass
|
pass
|
||||||
if not mi.title:
|
if not mi.title:
|
||||||
mi.title = name
|
mi.title = name
|
||||||
|
@ -93,7 +93,7 @@ class BookHeader(object):
|
|||||||
1252 : 'cp1252',
|
1252 : 'cp1252',
|
||||||
65001 : 'utf-8',
|
65001 : 'utf-8',
|
||||||
}[self.codepage]
|
}[self.codepage]
|
||||||
except IndexError, KeyError:
|
except (IndexError, KeyError):
|
||||||
print '[WARNING] Unknown codepage %d. Assuming cp-1252'%self.codepage
|
print '[WARNING] Unknown codepage %d. Assuming cp-1252'%self.codepage
|
||||||
self.codec = 'cp1252'
|
self.codec = 'cp1252'
|
||||||
|
|
||||||
@ -408,7 +408,7 @@ def get_metadata(stream):
|
|||||||
return mi
|
return mi
|
||||||
|
|
||||||
def option_parser():
|
def option_parser():
|
||||||
from calibre import OptionParser
|
from calibre.utils.config import OptionParser
|
||||||
parser = OptionParser(usage=_('%prog [options] myebook.mobi'))
|
parser = OptionParser(usage=_('%prog [options] myebook.mobi'))
|
||||||
parser.add_option('-o', '--output-dir', default='.',
|
parser.add_option('-o', '--output-dir', default='.',
|
||||||
help=_('Output directory. Defaults to current directory.'))
|
help=_('Output directory. Defaults to current directory.'))
|
||||||
|
@ -2,19 +2,49 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
""" The GUI """
|
""" The GUI """
|
||||||
import sys, os, re, StringIO, traceback
|
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
|
QByteArray, QLocale, QUrl, QTranslator, QCoreApplication
|
||||||
from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
|
from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
|
||||||
QIcon, QTableView, QDialogButtonBox, QApplication
|
QIcon, QTableView, QDialogButtonBox, QApplication
|
||||||
|
|
||||||
ORG_NAME = 'KovidsBrain'
|
ORG_NAME = 'KovidsBrain'
|
||||||
APP_UID = 'libprs500'
|
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
|
import calibre.resources as resources
|
||||||
|
|
||||||
NONE = QVariant() #: Null value to return from the data function of item models
|
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
|
# Turn off DeprecationWarnings in windows GUI
|
||||||
if iswindows:
|
if iswindows:
|
||||||
import warnings
|
import warnings
|
||||||
@ -84,20 +114,33 @@ def human_readable(size):
|
|||||||
size = size[:-2]
|
size = size[:-2]
|
||||||
return size + " " + suffix
|
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):
|
class TableView(QTableView):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
QTableView.__init__(self, parent)
|
QTableView.__init__(self, parent)
|
||||||
self.read_settings()
|
self.read_settings()
|
||||||
|
|
||||||
|
|
||||||
def read_settings(self):
|
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):
|
def write_settings(self):
|
||||||
settings = Settings()
|
dynamic[self.__class__.__name__+'column widths'] = \
|
||||||
settings.set(self.__class__.__name__ + ' column widths',
|
tuple([int(self.columnWidth(i)) for i in range(self.model().columnCount(None))])
|
||||||
tuple([int(self.columnWidth(i)) for i in range(self.model().columnCount(None))]))
|
|
||||||
|
|
||||||
def restore_column_widths(self):
|
def restore_column_widths(self):
|
||||||
if self.cw and len(self.cw):
|
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
|
@param cols: A list of booleans or None. If an entry is False the corresponding column
|
||||||
is hidden, if True it is shown.
|
is hidden, if True it is shown.
|
||||||
'''
|
'''
|
||||||
|
key = self.__class__.__name__+'visible columns'
|
||||||
if cols:
|
if cols:
|
||||||
Settings().set(self.__class__.__name__ + ' visible columns', cols)
|
dynamic[key] = cols
|
||||||
else:
|
else:
|
||||||
cols = Settings().get(self.__class__.__name__ + ' visible columns')
|
cols = dynamic[key]
|
||||||
if not cols:
|
if not cols:
|
||||||
cols = [True for i in range(self.model().columnCount(self))]
|
cols = [True for i in range(self.model().columnCount(self))]
|
||||||
|
|
||||||
@ -217,7 +261,7 @@ _sidebar_directories = []
|
|||||||
def set_sidebar_directories(dirs):
|
def set_sidebar_directories(dirs):
|
||||||
global _sidebar_directories
|
global _sidebar_directories
|
||||||
if dirs is None:
|
if dirs is None:
|
||||||
dirs = Settings().get('frequently used directories', [])
|
dirs = config['frequently_used_directories']
|
||||||
_sidebar_directories = [QUrl.fromLocalFile(i) for i in dirs]
|
_sidebar_directories = [QUrl.fromLocalFile(i) for i in dirs]
|
||||||
|
|
||||||
class FileDialog(QObject):
|
class FileDialog(QObject):
|
||||||
@ -240,10 +284,10 @@ class FileDialog(QObject):
|
|||||||
if add_all_files_filter or not ftext:
|
if add_all_files_filter or not ftext:
|
||||||
ftext += 'All files (*)'
|
ftext += 'All files (*)'
|
||||||
|
|
||||||
settings = Settings()
|
|
||||||
self.dialog_name = name if name else 'dialog_' + title
|
self.dialog_name = name if name else 'dialog_' + title
|
||||||
self.selected_files = None
|
self.selected_files = None
|
||||||
self.fd = None
|
self.fd = None
|
||||||
|
|
||||||
if islinux:
|
if islinux:
|
||||||
self.fd = QFileDialog(parent)
|
self.fd = QFileDialog(parent)
|
||||||
self.fd.setFileMode(mode)
|
self.fd.setFileMode(mode)
|
||||||
@ -251,15 +295,15 @@ class FileDialog(QObject):
|
|||||||
self.fd.setModal(modal)
|
self.fd.setModal(modal)
|
||||||
self.fd.setFilter(ftext)
|
self.fd.setFilter(ftext)
|
||||||
self.fd.setWindowTitle(title)
|
self.fd.setWindowTitle(title)
|
||||||
state = settings.get(self.dialog_name, QByteArray())
|
state = dynamic[self.dialog_name]
|
||||||
if not self.fd.restoreState(state):
|
if not state or not self.fd.restoreState(state):
|
||||||
self.fd.setDirectory(os.path.expanduser('~'))
|
self.fd.setDirectory(os.path.expanduser('~'))
|
||||||
osu = [i for i in self.fd.sidebarUrls()]
|
osu = [i for i in self.fd.sidebarUrls()]
|
||||||
self.fd.setSidebarUrls(osu + _sidebar_directories)
|
self.fd.setSidebarUrls(osu + _sidebar_directories)
|
||||||
QObject.connect(self.fd, SIGNAL('accepted()'), self.save_dir)
|
QObject.connect(self.fd, SIGNAL('accepted()'), self.save_dir)
|
||||||
self.accepted = self.fd.exec_() == QFileDialog.Accepted
|
self.accepted = self.fd.exec_() == QFileDialog.Accepted
|
||||||
else:
|
else:
|
||||||
dir = settings.get(self.dialog_name, os.path.expanduser('~'))
|
dir = dynamic.get(self.dialog_name, default=os.path.expanduser('~'))
|
||||||
self.selected_files = []
|
self.selected_files = []
|
||||||
if mode == QFileDialog.AnyFile:
|
if mode == QFileDialog.AnyFile:
|
||||||
f = qstring_to_unicode(
|
f = qstring_to_unicode(
|
||||||
@ -284,7 +328,7 @@ class FileDialog(QObject):
|
|||||||
self.selected_files.append(f)
|
self.selected_files.append(f)
|
||||||
if self.selected_files:
|
if self.selected_files:
|
||||||
self.selected_files = [qstring_to_unicode(q) for q in 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)
|
self.accepted = bool(self.selected_files)
|
||||||
|
|
||||||
|
|
||||||
@ -298,8 +342,7 @@ class FileDialog(QObject):
|
|||||||
|
|
||||||
def save_dir(self):
|
def save_dir(self):
|
||||||
if self.fd:
|
if self.fd:
|
||||||
settings = Settings()
|
dynamic[self.dialog_name] = self.fd.saveState()
|
||||||
settings.set(self.dialog_name, self.fd.saveState())
|
|
||||||
|
|
||||||
|
|
||||||
def choose_dir(window, name, title):
|
def choose_dir(window, name, title):
|
||||||
|
@ -12,7 +12,8 @@ import sys, os
|
|||||||
from PyQt4.QtGui import QImage, QSizePolicy
|
from PyQt4.QtGui import QImage, QSizePolicy
|
||||||
from PyQt4.QtCore import Qt, QSize, SIGNAL, QObject
|
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']
|
pictureflow, pictureflowerror = plugins['pictureflow']
|
||||||
|
|
||||||
if pictureflow is not None:
|
if pictureflow is not None:
|
||||||
@ -70,7 +71,7 @@ if pictureflow is not None:
|
|||||||
|
|
||||||
def __init__(self, height=300, parent=None):
|
def __init__(self, height=300, parent=None):
|
||||||
pictureflow.PictureFlow.__init__(self, parent,
|
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.setSlideSize(QSize(int(2/3. * height), height))
|
||||||
self.setMinimumSize(QSize(int(2.35*0.67*height), (5/3.)*height+25))
|
self.setMinimumSize(QSize(int(2.35*0.67*height), (5/3.)*height+25))
|
||||||
self.setFocusPolicy(Qt.WheelFocus)
|
self.setFocusPolicy(Qt.WheelFocus)
|
||||||
|
@ -1,131 +1,185 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__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.devices import devices
|
||||||
|
from calibre.parallel import Job
|
||||||
from calibre.devices.scanner import DeviceScanner
|
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
|
Worker thread that polls the USB ports for devices. Emits the
|
||||||
signal connected(PyQt_PyObject, PyQt_PyObject) on connection and
|
signal connected(PyQt_PyObject, PyQt_PyObject) on connection and
|
||||||
disconnection events.
|
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
|
@param sleep_time: Time to sleep between device probes in millisecs
|
||||||
@type sleep_time: integer
|
@type sleep_time: integer
|
||||||
'''
|
'''
|
||||||
self.devices = [[d, False] for d in devices()]
|
Thread.__init__(self)
|
||||||
self.sleep_time = sleep_time
|
self.setDaemon(True)
|
||||||
QThread.__init__(self)
|
self.devices = [[d, False] for d in devices()]
|
||||||
self.keep_going = True
|
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 run(self):
|
def detect_device(self):
|
||||||
scanner = DeviceScanner()
|
self.scanner.scan()
|
||||||
while self.keep_going:
|
for device in self.devices:
|
||||||
scanner.scan()
|
connected = self.scanner.is_device_connected(device[0])
|
||||||
for device in self.devices:
|
if connected and not device[1]:
|
||||||
connected = scanner.is_device_connected(device[0])
|
try:
|
||||||
if connected and not device[1]:
|
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:
|
try:
|
||||||
dev = device[0]()
|
job = self.jobs.get_nowait()
|
||||||
dev.open()
|
job.abort(Exception(_('Device no longer connected.')))
|
||||||
self.emit(SIGNAL('connected(PyQt_PyObject, PyQt_PyObject)'), dev, True)
|
except Queue.Empty:
|
||||||
except:
|
break
|
||||||
print 'Unable to open device'
|
self.device = None
|
||||||
traceback.print_exc()
|
self.connected_slot(False)
|
||||||
finally:
|
device[1] ^= True
|
||||||
device[1] = True
|
|
||||||
elif not connected and device[1]:
|
def next(self):
|
||||||
self.emit(SIGNAL('connected(PyQt_PyObject, PyQt_PyObject)'), device[0], False)
|
if not self.jobs.empty():
|
||||||
device[1] ^= True
|
try:
|
||||||
self.msleep(self.sleep_time)
|
return self.jobs.get_nowait()
|
||||||
|
except Queue.Empty:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while self.keep_going:
|
||||||
|
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,
|
||||||
class DeviceManager(QObject):
|
args=args, kwargs=kwargs, description=description)
|
||||||
def __init__(self, device):
|
self.job_manager.add_job(job)
|
||||||
QObject.__init__(self)
|
self.jobs.put(job)
|
||||||
self.device_class = device.__class__
|
return job
|
||||||
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 _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)
|
||||||
|
|
||||||
|
def books(self, done):
|
||||||
'''Return callable that returns the list of books on device as two booklists'''
|
'''Return callable that returns the list of books on device as two booklists'''
|
||||||
def books(updater):
|
return self.create_job(self._books, done, description=_('Get list of books on device'))
|
||||||
'''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
|
|
||||||
|
|
||||||
def sync_booklists_func(self):
|
def _sync_booklists(self, booklists):
|
||||||
'''Upload booklists to device'''
|
'''Sync metadata to device'''
|
||||||
def sync_booklists(updater, booklists):
|
self.device.sync_booklists(booklists, end_session=False)
|
||||||
'''Sync metadata to device'''
|
return self.device.card_prefix(end_session=False), self.device.free_space()
|
||||||
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 upload_books_func(self):
|
def sync_booklists(self, done, booklists):
|
||||||
'''Upload books to device'''
|
return self.create_job(self._sync_booklists, done, args=[booklists],
|
||||||
def upload_books(updater, files, names, on_card=False):
|
description=_('Send metadata to device'))
|
||||||
'''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 _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):
|
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'''
|
'''Remove books from device'''
|
||||||
def delete_books(updater, paths):
|
self.device.delete_books(paths, end_session=True)
|
||||||
'''Remove books from device'''
|
|
||||||
self.device.delete_books(paths, end_session=True)
|
def delete_books(self, done, paths):
|
||||||
return delete_books
|
return self.create_job(self._delete_books, done, args=[paths],
|
||||||
|
description=_('Delete books from device'))
|
||||||
|
|
||||||
def remove_books_from_metadata(self, paths, booklists):
|
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'''
|
'''Copy books from device to disk'''
|
||||||
def save_books(updater, paths, target):
|
for path in paths:
|
||||||
'''Copy books from device to disk'''
|
name = path.rpartition('/')[2]
|
||||||
self.device.set_progress_reporter(updater)
|
f = open(os.path.join(target, name), '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 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')
|
|
||||||
self.device.get_file(path, f)
|
self.device.get_file(path, f)
|
||||||
f.close()
|
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_()
|
d.exec_()
|
||||||
|
|
||||||
def get_bulk_conversion_options(window):
|
def get_bulk_conversion_options(window):
|
||||||
c = config(None)
|
d = ComicConf(window, config_defaults=config(None).as_string())
|
||||||
with open(c.config_file_path, 'rb') as f:
|
|
||||||
d = ComicConf(window, config_defaults=f.read())
|
|
||||||
if d.exec_() == QDialog.Accepted:
|
if d.exec_() == QDialog.Accepted:
|
||||||
return d.config.parse()
|
return d.config.parse()
|
||||||
|
|
||||||
def get_conversion_options(window, defaults, title, author):
|
def get_conversion_options(window, defaults, title, author):
|
||||||
if defaults is None:
|
if defaults is None:
|
||||||
c = config(None)
|
defaults = config(None).as_string()
|
||||||
with open(c.config_file_path, 'rb') as f:
|
|
||||||
defaults = f.read()
|
|
||||||
defaults += '\ntitle=%s\nauthor=%s'%(repr(title), repr(author))
|
defaults += '\ntitle=%s\nauthor=%s'%(repr(title), repr(author))
|
||||||
d = ComicConf(window, config_defaults=defaults, generic=False)
|
d = ComicConf(window, config_defaults=defaults, generic=False)
|
||||||
if d.exec_() == QDialog.Accepted:
|
if d.exec_() == QDialog.Accepted:
|
||||||
@ -61,6 +57,7 @@ class ComicConf(QDialog, Ui_Dialog):
|
|||||||
self.opt_dont_sharpen.setChecked(opts.dont_sharpen)
|
self.opt_dont_sharpen.setChecked(opts.dont_sharpen)
|
||||||
self.opt_landscape.setChecked(opts.landscape)
|
self.opt_landscape.setChecked(opts.landscape)
|
||||||
self.opt_no_sort.setChecked(opts.no_sort)
|
self.opt_no_sort.setChecked(opts.no_sort)
|
||||||
|
self.opt_right2left.setChecked(opts.right2left)
|
||||||
|
|
||||||
for opt in self.config.option_set.preferences:
|
for opt in self.config.option_set.preferences:
|
||||||
g = getattr(self, 'opt_'+opt.name, False)
|
g = getattr(self, 'opt_'+opt.name, False)
|
||||||
|
@ -107,14 +107,14 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="0" >
|
<item row="9" column="0" >
|
||||||
<widget class="QCheckBox" name="opt_no_sort" >
|
<widget class="QCheckBox" name="opt_no_sort" >
|
||||||
<property name="text" >
|
<property name="text" >
|
||||||
<string>Dont so&rt</string>
|
<string>Don't so&rt</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="9" column="1" >
|
<item row="10" column="1" >
|
||||||
<widget class="QDialogButtonBox" name="buttonBox" >
|
<widget class="QDialogButtonBox" name="buttonBox" >
|
||||||
<property name="orientation" >
|
<property name="orientation" >
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
@ -124,6 +124,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="8" column="0" >
|
||||||
|
<widget class="QCheckBox" name="opt_right2left" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>&Right to left</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
|
@ -5,14 +5,14 @@ import os
|
|||||||
from PyQt4.QtGui import QDialog, QMessageBox, QListWidgetItem, QIcon
|
from PyQt4.QtGui import QDialog, QMessageBox, QListWidgetItem, QIcon
|
||||||
from PyQt4.QtCore import SIGNAL, QTimer, Qt, QSize, QVariant
|
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.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.gui2.widgets import FilenamePattern
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigDialog(QDialog, Ui_Dialog):
|
class ConfigDialog(QDialog, Ui_Dialog):
|
||||||
|
|
||||||
def __init__(self, window, db, columns):
|
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.item2 = QListWidgetItem(QIcon(':/images/view.svg'), _('Advanced'), self.category_list)
|
||||||
self.db = db
|
self.db = db
|
||||||
self.current_cols = columns
|
self.current_cols = columns
|
||||||
settings = Settings()
|
path = prefs['library_path']
|
||||||
path = settings.get('library path')
|
|
||||||
self.location.setText(path)
|
self.location.setText(path)
|
||||||
self.connect(self.browse_button, SIGNAL('clicked(bool)'), self.browse)
|
self.connect(self.browse_button, SIGNAL('clicked(bool)'), self.browse)
|
||||||
self.connect(self.compact_button, SIGNAL('clicked(bool)'), self.compact)
|
self.connect(self.compact_button, SIGNAL('clicked(bool)'), self.compact)
|
||||||
|
|
||||||
dirs = settings.get('frequently used directories', [])
|
dirs = config['frequently_used_directories']
|
||||||
rn = settings.get('use roman numerals for series number', True)
|
rn = config['use_roman_numerals_for_series_number']
|
||||||
self.timeout.setValue(settings.get('network timeout', 5))
|
self.timeout.setValue(prefs['network_timeout'])
|
||||||
self.roman_numerals.setChecked(rn)
|
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.directory_list.addItems(dirs)
|
||||||
self.connect(self.add_button, SIGNAL('clicked(bool)'), self.add_dir)
|
self.connect(self.add_button, SIGNAL('clicked(bool)'), self.add_dir)
|
||||||
self.connect(self.remove_button, SIGNAL('clicked(bool)'), self.remove_dir)
|
self.connect(self.remove_button, SIGNAL('clicked(bool)'), self.remove_dir)
|
||||||
@ -55,18 +54,18 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
|
|
||||||
self.filename_pattern = FilenamePattern(self)
|
self.filename_pattern = FilenamePattern(self)
|
||||||
self.metadata_box.layout().insertWidget(0, self.filename_pattern)
|
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.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:
|
for ext in BOOK_EXTENSIONS:
|
||||||
self.single_format.addItem(ext.upper(), QVariant(ext))
|
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.single_format.setCurrentIndex(BOOK_EXTENSIONS.index(single_format))
|
||||||
self.cover_browse.setValue(settings.get('cover flow queue length', 6))
|
self.cover_browse.setValue(config['cover_flow_queue_length'])
|
||||||
self.confirm_delete.setChecked(settings.get('confirm delete', False))
|
self.confirm_delete.setChecked(config['confirm_delete'])
|
||||||
|
|
||||||
def compact(self, toggled):
|
def compact(self, toggled):
|
||||||
d = Vacuum(self, self.db)
|
d = Vacuum(self, self.db)
|
||||||
@ -88,20 +87,19 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
self.directory_list.takeItem(idx)
|
self.directory_list.takeItem(idx)
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
settings = Settings()
|
config['use_roman_numerals_for_series_number'] = bool(self.roman_numerals.isChecked())
|
||||||
settings.set('use roman numerals for series number', bool(self.roman_numerals.isChecked()))
|
config['new_version_notification'] = bool(self.new_version_notification.isChecked())
|
||||||
settings.set('new version notification', bool(self.new_version_notification.isChecked()))
|
prefs['network_timeout'] = int(self.timeout.value())
|
||||||
settings.set('network timeout', int(self.timeout.value()))
|
|
||||||
path = qstring_to_unicode(self.location.text())
|
path = qstring_to_unicode(self.location.text())
|
||||||
self.final_columns = [self.columns.item(i).checkState() == Qt.Checked for i in range(self.columns.count())]
|
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()])
|
config['toolbar_icon_size'] = self.ICON_SIZES[self.toolbar_button_size.currentIndex()]
|
||||||
settings.set('show text in toolbar', bool(self.show_toolbar_text.isChecked()))
|
config['show_text_in_toolbar'] = bool(self.show_toolbar_text.isChecked())
|
||||||
settings.set('confirm delete', bool(self.confirm_delete.isChecked()))
|
config['confirm_delete'] = bool(self.confirm_delete.isChecked())
|
||||||
pattern = self.filename_pattern.commit()
|
pattern = self.filename_pattern.commit()
|
||||||
settings.set('filename pattern', pattern)
|
prefs['filename_pattern'] = pattern
|
||||||
settings.set('save to disk single format', BOOK_EXTENSIONS[self.single_format.currentIndex()])
|
config['save_to_disk_single_format'] = BOOK_EXTENSIONS[self.single_format.currentIndex()]
|
||||||
settings.set('cover flow queue length', self.cover_browse.value())
|
config['cover_flow_queue_length'] = self.cover_browse.value()
|
||||||
|
|
||||||
if not path or not os.path.exists(path) or not os.path.isdir(path):
|
if not path or not os.path.exists(path) or not os.path.isdir(path):
|
||||||
d = error_dialog(self, _('Invalid database location'),
|
d = error_dialog(self, _('Invalid database location'),
|
||||||
_('Invalid database location ')+path+_('<br>Must be a directory.'))
|
_('Invalid database location ')+path+_('<br>Must be a directory.'))
|
||||||
@ -113,7 +111,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
else:
|
else:
|
||||||
self.database_location = os.path.abspath(path)
|
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())]
|
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)
|
QDialog.accept(self)
|
||||||
|
|
||||||
class Vacuum(QMessageBox):
|
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.dialogs.fetch_metadata_ui import Ui_FetchMetadata
|
||||||
from calibre.gui2 import error_dialog, NONE, info_dialog
|
from calibre.gui2 import error_dialog, NONE, info_dialog
|
||||||
from calibre.ebooks.metadata.isbndb import create_books, option_parser, ISBNDBError
|
from calibre.ebooks.metadata.isbndb import create_books, option_parser, ISBNDBError
|
||||||
from calibre import Settings
|
from calibre.utils.config import prefs
|
||||||
|
|
||||||
class Matches(QAbstractTableModel):
|
class Matches(QAbstractTableModel):
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ class FetchMetadata(QDialog, Ui_FetchMetadata):
|
|||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
QObject.connect(self.fetch, SIGNAL('clicked()'), self.fetch_metadata)
|
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.setWindowTitle(title if title else 'Unknown')
|
||||||
self.tlabel.setText(self.tlabel.text().arg(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'))
|
_('You must specify a valid access key for isbndb.com'))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
Settings().set('isbndb.com key', key)
|
prefs['isbndb_com_key'] = key
|
||||||
|
|
||||||
args = ['isbndb']
|
args = ['isbndb']
|
||||||
if self.isbn:
|
if self.isbn:
|
||||||
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
'''Display active jobs'''
|
'''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, \
|
from PyQt4.QtGui import QDialog, QAbstractItemDelegate, QStyleOptionProgressBarV2, \
|
||||||
QApplication, QStyle
|
QApplication, QStyle
|
||||||
|
|
||||||
@ -44,11 +44,21 @@ class JobsDialog(QDialog, Ui_JobsDialog):
|
|||||||
self.jobs_view.model().kill_job)
|
self.jobs_view.model().kill_job)
|
||||||
self.pb_delegate = ProgressBarDelegate(self)
|
self.pb_delegate = ProgressBarDelegate(self)
|
||||||
self.jobs_view.setItemDelegateForColumn(2, self.pb_delegate)
|
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):
|
def kill_job(self):
|
||||||
for index in self.jobs_view.selectedIndexes():
|
for index in self.jobs_view.selectedIndexes():
|
||||||
row = index.row()
|
row = index.row()
|
||||||
self.emit(SIGNAL('kill_job(int, PyQt_PyObject)'), row, self)
|
self.model.kill_job(row, self)
|
||||||
return
|
return
|
||||||
|
|
||||||
def closeEvent(self, e):
|
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.lrf_single_ui import Ui_LRFSingleDialog
|
||||||
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
||||||
from calibre.gui2 import qstring_to_unicode, error_dialog, \
|
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.gui2.widgets import FontFamilyModel
|
||||||
from calibre.ebooks.lrf import option_parser
|
from calibre.ebooks.lrf import option_parser
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre import __appname__, Settings
|
from calibre.constants import __appname__
|
||||||
|
|
||||||
font_family_model = None
|
font_family_model = None
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
|
|||||||
|
|
||||||
|
|
||||||
def load_saved_global_defaults(self):
|
def load_saved_global_defaults(self):
|
||||||
cmdline = Settings().get('LRF conversion defaults')
|
cmdline = config['LRF_conversion_defaults']
|
||||||
if cmdline:
|
if cmdline:
|
||||||
self.set_options_from_cmdline(cmdline)
|
self.set_options_from_cmdline(cmdline)
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
|
|||||||
|
|
||||||
def select_cover(self, checked):
|
def select_cover(self, checked):
|
||||||
files = choose_images(self, 'change cover dialog',
|
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:
|
if not files:
|
||||||
return
|
return
|
||||||
_file = files[0]
|
_file = files[0]
|
||||||
@ -385,7 +385,7 @@ class LRFSingleDialog(QDialog, Ui_LRFSingleDialog):
|
|||||||
cmdline.extend([u'--cover', self.cover_file.name])
|
cmdline.extend([u'--cover', self.cover_file.name])
|
||||||
self.cmdline = [unicode(i) for i in cmdline]
|
self.cmdline = [unicode(i) for i in cmdline]
|
||||||
else:
|
else:
|
||||||
Settings().set('LRF conversion defaults', cmdline)
|
config.set('LRF_conversion_defaults', cmdline)
|
||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
class LRFBulkDialog(LRFSingleDialog):
|
class LRFBulkDialog(LRFSingleDialog):
|
||||||
|
@ -18,7 +18,8 @@ from calibre.gui2.dialogs.tag_editor import TagEditor
|
|||||||
from calibre.gui2.dialogs.password import PasswordDialog
|
from calibre.gui2.dialogs.password import PasswordDialog
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
from calibre.ebooks import BOOK_EXTENSIONS
|
||||||
from calibre.ebooks.metadata.library_thing import login, cover_from_isbn, LibraryThingError
|
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):
|
class Format(QListWidgetItem):
|
||||||
def __init__(self, parent, ext, size, path=None):
|
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()'),
|
QObject.connect(self.remove_series_button, SIGNAL('clicked()'),
|
||||||
self.remove_unused_series)
|
self.remove_unused_series)
|
||||||
self.connect(self.swap_button, SIGNAL('clicked()'), self.swap_title_author)
|
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))
|
self.title.setText(db.title(row))
|
||||||
isbn = db.isbn(self.id, index_is_id=True)
|
isbn = db.isbn(self.id, index_is_id=True)
|
||||||
if not isbn:
|
if not isbn:
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
import re
|
||||||
from PyQt4.QtGui import QDialog, QLineEdit
|
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.dialogs.password_ui import Ui_Dialog
|
||||||
from calibre.gui2 import qstring_to_unicode
|
from calibre.gui2 import qstring_to_unicode, dynamic
|
||||||
from calibre import Settings
|
|
||||||
|
|
||||||
class PasswordDialog(QDialog, Ui_Dialog):
|
class PasswordDialog(QDialog, Ui_Dialog):
|
||||||
|
|
||||||
@ -14,10 +13,12 @@ class PasswordDialog(QDialog, Ui_Dialog):
|
|||||||
QDialog.__init__(self, window)
|
QDialog.__init__(self, window)
|
||||||
Ui_Dialog.__init__(self)
|
Ui_Dialog.__init__(self)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
self.cfg_key = re.sub(r'[^0-9a-zA-Z]', '_', name)
|
||||||
|
|
||||||
settings = Settings()
|
un = dynamic[self.cfg_key+'__un']
|
||||||
un = settings.get(name+': un', u'')
|
pw = dynamic[self.cfg_key+'__un']
|
||||||
pw = settings.get(name+': pw', u'')
|
if not un: un = ''
|
||||||
|
if not pw: pw = ''
|
||||||
self.gui_username.setText(un)
|
self.gui_username.setText(un)
|
||||||
self.gui_password.setText(pw)
|
self.gui_password.setText(pw)
|
||||||
self.sname = name
|
self.sname = name
|
||||||
@ -37,7 +38,6 @@ class PasswordDialog(QDialog, Ui_Dialog):
|
|||||||
return qstring_to_unicode(self.gui_password.text())
|
return qstring_to_unicode(self.gui_password.text())
|
||||||
|
|
||||||
def accept(self):
|
def accept(self):
|
||||||
settings = Settings()
|
dynamic.set(self.cfg_key+'__un', unicode(self.gui_username.text()))
|
||||||
settings.set(self.sname+': un', unicode(self.gui_username.text()))
|
dynamic.set(self.cfg_key+'__pw', unicode(self.gui_password.text()))
|
||||||
settings.set(self.sname+': pw', unicode(self.gui_password.text()))
|
|
||||||
QDialog.accept(self)
|
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, \
|
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \
|
||||||
QCoreApplication, SIGNAL, QObject, QSize, QModelIndex
|
QCoreApplication, SIGNAL, QObject, QSize, QModelIndex
|
||||||
|
|
||||||
from calibre import Settings, preferred_encoding
|
from calibre import preferred_encoding
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.library.database import LibraryDatabase, text_to_tokens
|
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):
|
class LibraryDelegate(QItemDelegate):
|
||||||
COLOR = QColor("blue")
|
COLOR = QColor("blue")
|
||||||
@ -113,9 +113,9 @@ class BooksModel(QAbstractTableModel):
|
|||||||
self.cover_cache.clear_cache()
|
self.cover_cache.clear_cache()
|
||||||
|
|
||||||
def read_config(self):
|
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):
|
def set_database(self, db):
|
||||||
if isinstance(db, (QString, basestring)):
|
if isinstance(db, (QString, basestring)):
|
||||||
if isinstance(db, QString):
|
if isinstance(db, QString):
|
||||||
|
@ -6,10 +6,11 @@ import sys, logging, os, traceback, time
|
|||||||
from PyQt4.QtGui import QKeySequence, QPainter, QDialog, QSpinBox, QSlider, QIcon
|
from PyQt4.QtGui import QKeySequence, QPainter, QDialog, QSpinBox, QSlider, QIcon
|
||||||
from PyQt4.QtCore import Qt, QObject, SIGNAL, QCoreApplication, QThread
|
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.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.dialogs.conversion_error import ConversionErrorDialog
|
||||||
from calibre.gui2.lrf_renderer.main_ui import Ui_MainWindow
|
from calibre.gui2.lrf_renderer.main_ui import Ui_MainWindow
|
||||||
from calibre.gui2.lrf_renderer.config_ui import Ui_ViewerConfig
|
from calibre.gui2.lrf_renderer.config_ui import Ui_ViewerConfig
|
||||||
@ -102,13 +103,15 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
|
|
||||||
def configure(self, triggered):
|
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 = Config(self, opts)
|
||||||
d.exec_()
|
d.exec_()
|
||||||
if d.result() == QDialog.Accepted:
|
if d.result() == QDialog.Accepted:
|
||||||
opts.white_background = bool(d.white_background.isChecked())
|
opts.white_background = bool(d.white_background.isChecked())
|
||||||
opts.hyphenate = bool(d.hyphenate.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):
|
def set_ebook(self, stream):
|
||||||
self.progress_bar.setMinimum(0)
|
self.progress_bar.setMinimum(0)
|
||||||
@ -281,7 +284,9 @@ Read the LRF ebook book.lrf
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
def normalize_settings(parser, opts):
|
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:
|
for opt in parser.option_list:
|
||||||
if not opt.dest:
|
if not opt.dest:
|
||||||
continue
|
continue
|
||||||
|
@ -3,31 +3,32 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
import os, sys, textwrap, collections, traceback, time
|
import os, sys, textwrap, collections, traceback, time
|
||||||
from xml.parsers.expat import ExpatError
|
from xml.parsers.expat import ExpatError
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
|
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QUrl
|
||||||
QVariant, QThread, QUrl, QSize
|
|
||||||
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \
|
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \
|
||||||
QToolButton, QDialog, QDesktopServices
|
QToolButton, QDialog, QDesktopServices
|
||||||
from PyQt4.QtSvg import QSvgRenderer
|
from PyQt4.QtSvg import QSvgRenderer
|
||||||
|
|
||||||
from calibre import __version__, __appname__, islinux, sanitize_file_name, \
|
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.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.errors import FreeSpaceError
|
||||||
from calibre.devices.interface import Device
|
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, \
|
from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
|
||||||
initialize_file_icon_provider, question_dialog,\
|
initialize_file_icon_provider, question_dialog,\
|
||||||
pixmap_to_data, choose_dir, ORG_NAME, \
|
pixmap_to_data, choose_dir, ORG_NAME, \
|
||||||
set_sidebar_directories, \
|
set_sidebar_directories, Dispatcher, \
|
||||||
SingleApplication, Application, available_height, max_available_height
|
SingleApplication, Application, available_height, \
|
||||||
|
max_available_height, config
|
||||||
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
|
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
|
||||||
from calibre.library.database import LibraryDatabase
|
from calibre.library.database import LibraryDatabase
|
||||||
from calibre.gui2.update import CheckForUpdates
|
from calibre.gui2.update import CheckForUpdates
|
||||||
from calibre.gui2.main_window import MainWindow, option_parser
|
from calibre.gui2.main_window import MainWindow, option_parser
|
||||||
from calibre.gui2.main_ui import Ui_MainWindow
|
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.status import StatusBar
|
||||||
from calibre.gui2.jobs import JobManager
|
from calibre.gui2.jobs2 import JobManager
|
||||||
from calibre.gui2.news import NewsMenu
|
from calibre.gui2.news import NewsMenu
|
||||||
from calibre.gui2.dialogs.metadata_single import MetadataSingleDialog
|
from calibre.gui2.dialogs.metadata_single import MetadataSingleDialog
|
||||||
from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
|
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 import BOOK_EXTENSIONS
|
||||||
from calibre.ebooks.lrf import preferred_source_formats as LRF_PREFERRED_SOURCE_FORMATS
|
from calibre.ebooks.lrf import preferred_source_formats as LRF_PREFERRED_SOURCE_FORMATS
|
||||||
from calibre.library.database2 import LibraryDatabase2, CoverCache
|
from calibre.library.database2 import LibraryDatabase2, CoverCache
|
||||||
|
from calibre.parallel import JobKilled
|
||||||
|
from calibre.utils.filenames import ascii_filename
|
||||||
|
|
||||||
class Main(MainWindow, Ui_MainWindow):
|
class Main(MainWindow, Ui_MainWindow):
|
||||||
|
|
||||||
@ -73,7 +76,6 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.read_settings()
|
self.read_settings()
|
||||||
self.job_manager = JobManager()
|
self.job_manager = JobManager()
|
||||||
self.jobs_dialog = JobsDialog(self, self.job_manager)
|
self.jobs_dialog = JobsDialog(self, self.job_manager)
|
||||||
self.device_manager = None
|
|
||||||
self.upload_memory = {}
|
self.upload_memory = {}
|
||||||
self.delete_memory = {}
|
self.delete_memory = {}
|
||||||
self.conversion_jobs = {}
|
self.conversion_jobs = {}
|
||||||
@ -120,13 +122,14 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
sm.addAction(_('Send to storage card by default'))
|
sm.addAction(_('Send to storage card by default'))
|
||||||
sm.actions()[-1].setCheckable(True)
|
sm.actions()[-1].setCheckable(True)
|
||||||
def default_sync(checked):
|
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_main_memory)
|
||||||
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_card)
|
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(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)
|
QObject.connect(sm.actions()[-1], SIGNAL('toggled(bool)'), default_sync)
|
||||||
sm.actions()[-1].setChecked(Settings().get('send to device by default', False))
|
|
||||||
default_sync(sm.actions()[-1].isChecked())
|
sm.actions()[-1].setChecked(config.get('send_to_device_by_default'))
|
||||||
|
default_sync(sm.actions()[-1].isChecked())
|
||||||
self.sync_menu = sm # Needed
|
self.sync_menu = sm # Needed
|
||||||
md = QMenu()
|
md = QMenu()
|
||||||
md.addAction(_('Edit metadata individually'))
|
md.addAction(_('Edit metadata individually'))
|
||||||
@ -152,8 +155,8 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.save_menu = QMenu()
|
self.save_menu = QMenu()
|
||||||
self.save_menu.addAction(_('Save to disk'))
|
self.save_menu.addAction(_('Save to disk'))
|
||||||
self.save_menu.addAction(_('Save to disk in a single directory'))
|
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 = QMenu()
|
||||||
self.view_menu.addAction(_('View'))
|
self.view_menu.addAction(_('View'))
|
||||||
self.view_menu.addAction(_('View specific format'))
|
self.view_menu.addAction(_('View specific format'))
|
||||||
@ -252,11 +255,9 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.setMaximumHeight(max_available_height())
|
self.setMaximumHeight(max_available_height())
|
||||||
|
|
||||||
####################### Setup device detection ########################
|
####################### Setup device detection ########################
|
||||||
self.detector = DeviceDetector(sleep_time=2000)
|
self.device_manager = DeviceManager(Dispatcher(self.device_detected), self.job_manager)
|
||||||
QObject.connect(self.detector, SIGNAL('connected(PyQt_PyObject, PyQt_PyObject)'),
|
self.device_manager.start()
|
||||||
self.device_detected, Qt.QueuedConnection)
|
|
||||||
self.detector.start(QThread.InheritPriority)
|
|
||||||
|
|
||||||
self.news_menu.set_custom_feeds(self.library_view.model().db.get_feeds())
|
self.news_menu.set_custom_feeds(self.library_view.model().db.get_feeds())
|
||||||
|
|
||||||
def toggle_cover_flow(self, show):
|
def toggle_cover_flow(self, show):
|
||||||
@ -314,22 +315,19 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
|
|
||||||
########################## Connect to device ##############################
|
########################## Connect to device ##############################
|
||||||
def device_detected(self, device, connected):
|
def device_detected(self, connected):
|
||||||
'''
|
'''
|
||||||
Called when a device is connected to the computer.
|
Called when a device is connected to the computer.
|
||||||
'''
|
'''
|
||||||
if connected:
|
if connected:
|
||||||
self.device_manager = DeviceManager(device)
|
self.device_manager.get_device_information(Dispatcher(self.info_read))
|
||||||
self.job_manager.run_device_job(self.info_read, self.device_manager.info_func())
|
self.set_default_thumbnail(self.device_manager.device.THUMBNAIL_HEIGHT)
|
||||||
self.set_default_thumbnail(device.THUMBNAIL_HEIGHT)
|
self.status_bar.showMessage(_('Device: ')+\
|
||||||
self.status_bar.showMessage(_('Device: ')+device.__class__.__name__+_(' detected.'), 3000)
|
self.device_manager.device.__class__.__name__+_(' detected.'), 3000)
|
||||||
self.action_sync.setEnabled(True)
|
self.action_sync.setEnabled(True)
|
||||||
self.device_connected = True
|
self.device_connected = True
|
||||||
else:
|
else:
|
||||||
self.device_connected = False
|
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.location_view.model().update_devices()
|
||||||
self.action_sync.setEnabled(False)
|
self.action_sync.setEnabled(False)
|
||||||
self.vanity.setText(self.vanity_template%dict(version=self.latest_version, device=' '))
|
self.vanity.setText(self.vanity_template%dict(version=self.latest_version, device=' '))
|
||||||
@ -337,28 +335,27 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
if self.current_view() != self.library_view:
|
if self.current_view() != self.library_view:
|
||||||
self.status_bar.reset_info()
|
self.status_bar.reset_info()
|
||||||
self.location_selected('library')
|
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.
|
Called once device information has been read.
|
||||||
'''
|
'''
|
||||||
if exception:
|
if job.exception is not None:
|
||||||
self.device_job_exception(id, description, exception, formatted_traceback)
|
self.device_job_exception(job)
|
||||||
return
|
return
|
||||||
info, cp, fs = result
|
info, cp, fs = job.result
|
||||||
self.location_view.model().update_devices(cp, fs)
|
self.location_view.model().update_devices(cp, fs)
|
||||||
self.device_info = _('Connected ')+' '.join(info[:-1])
|
self.device_info = _('Connected ')+' '.join(info[:-1])
|
||||||
self.vanity.setText(self.vanity_template%dict(version=self.latest_version, device=self.device_info))
|
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.
|
Called once metadata has been read for all books on the device.
|
||||||
'''
|
'''
|
||||||
if exception:
|
if job.exception is not None:
|
||||||
print exception, type(exception)
|
if isinstance(job.exception, ExpatError):
|
||||||
if isinstance(exception, ExpatError):
|
|
||||||
error_dialog(self, _('Device database corrupted'),
|
error_dialog(self, _('Device database corrupted'),
|
||||||
_('''
|
_('''
|
||||||
<p>The database of books on the reader is corrupted. Try the following:
|
<p>The database of books on the reader is corrupted. Try the following:
|
||||||
@ -368,9 +365,9 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
</ol>
|
</ol>
|
||||||
''')%dict(app=__appname__)).exec_()
|
''')%dict(app=__appname__)).exec_()
|
||||||
else:
|
else:
|
||||||
self.device_job_exception(id, description, exception, formatted_traceback)
|
self.device_job_exception(job)
|
||||||
return
|
return
|
||||||
mainlist, cardlist = result
|
mainlist, cardlist = job.result
|
||||||
self.memory_view.set_database(mainlist)
|
self.memory_view.set_database(mainlist)
|
||||||
self.card_view.set_database(cardlist)
|
self.card_view.set_database(cardlist)
|
||||||
for view in (self.memory_view, self.card_view):
|
for view in (self.memory_view, self.card_view):
|
||||||
@ -387,18 +384,17 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
'''
|
'''
|
||||||
Upload metadata to device.
|
Upload metadata to device.
|
||||||
'''
|
'''
|
||||||
self.job_manager.run_device_job(self.metadata_synced,
|
self.device_manager.sync_booklists(Dispatcher(self.metadata_synced),
|
||||||
self.device_manager.sync_booklists_func(),
|
self.booklists())
|
||||||
self.booklists())
|
|
||||||
|
def metadata_synced(self, job):
|
||||||
def metadata_synced(self, id, description, result, exception, formatted_traceback):
|
|
||||||
'''
|
'''
|
||||||
Called once metadata has been uploaded.
|
Called once metadata has been uploaded.
|
||||||
'''
|
'''
|
||||||
if exception:
|
if job.exception is not None:
|
||||||
self.device_job_exception(id, description, exception, formatted_traceback)
|
self.device_job_exception(job)
|
||||||
return
|
return
|
||||||
cp, fs = result
|
cp, fs = job.result
|
||||||
self.location_view.model().update_devices(cp, fs)
|
self.location_view.model().update_devices(cp, fs)
|
||||||
############################################################################
|
############################################################################
|
||||||
|
|
||||||
@ -500,36 +496,35 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
def upload_books(self, files, names, metadata, on_card=False, memory=None):
|
def upload_books(self, files, names, metadata, on_card=False, memory=None):
|
||||||
'''
|
'''
|
||||||
Upload books to device.
|
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])
|
titles = [i['title'] for i in metadata]
|
||||||
id = self.job_manager.run_device_job(self.books_uploaded,
|
job = self.device_manager.upload_books(Dispatcher(self.books_uploaded),
|
||||||
self.device_manager.upload_books_func(),
|
|
||||||
files, names, on_card=on_card,
|
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.
|
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 job.exception is not None:
|
||||||
if isinstance(exception, FreeSpaceError):
|
if isinstance(job.exception, FreeSpaceError):
|
||||||
where = 'in main memory.' if 'memory' in str(exception) else 'on the storage card.'
|
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])
|
titles = '\n'.join(['<li>'+mi['title']+'</li>' for mi in metadata])
|
||||||
d = error_dialog(self, _('No space on device'),
|
d = error_dialog(self, _('No space on device'),
|
||||||
_('<p>Cannot upload books to device there is no more free space available ')+where+
|
_('<p>Cannot upload books to device there is no more free space available ')+where+
|
||||||
'</p>\n<ul>%s</ul>'%(titles,))
|
'</p>\n<ul>%s</ul>'%(titles,))
|
||||||
d.exec_()
|
d.exec_()
|
||||||
else:
|
else:
|
||||||
self.device_job_exception(id, description, exception, formatted_traceback)
|
self.device_job_exception(job)
|
||||||
return
|
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()
|
self.upload_booklists()
|
||||||
|
|
||||||
view = self.card_view if on_card else self.memory_view
|
view = self.card_view if on_card else self.memory_view
|
||||||
@ -551,7 +546,7 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
rows = view.selectionModel().selectedRows()
|
rows = view.selectionModel().selectedRows()
|
||||||
if not rows or len(rows) == 0:
|
if not rows or len(rows) == 0:
|
||||||
return
|
return
|
||||||
if Settings().get('confirm delete', False):
|
if config['confirm_delete']:
|
||||||
d = question_dialog(self, _('Confirm delete'),
|
d = question_dialog(self, _('Confirm delete'),
|
||||||
_('Are you sure you want to delete these %d books?')%len(rows))
|
_('Are you sure you want to delete these %d books?')%len(rows))
|
||||||
if d.exec_() != QMessageBox.Yes:
|
if d.exec_() != QMessageBox.Yes:
|
||||||
@ -568,22 +563,20 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.status_bar.showMessage(_('Deleting books from device.'), 1000)
|
self.status_bar.showMessage(_('Deleting books from device.'), 1000)
|
||||||
|
|
||||||
def remove_paths(self, paths):
|
def remove_paths(self, paths):
|
||||||
return self.job_manager.run_device_job(self.books_deleted,
|
return self.device_manager.delete_books(Dispatcher(self.books_deleted), paths)
|
||||||
self.device_manager.delete_books_func(), paths)
|
|
||||||
|
def books_deleted(self, job):
|
||||||
|
|
||||||
def books_deleted(self, id, description, result, exception, formatted_traceback):
|
|
||||||
'''
|
'''
|
||||||
Called once deletion is done on the device
|
Called once deletion is done on the device
|
||||||
'''
|
'''
|
||||||
for view in (self.memory_view, self.card_view):
|
for view in (self.memory_view, self.card_view):
|
||||||
view.model().deletion_done(id, bool(exception))
|
view.model().deletion_done(id, bool(job.exception))
|
||||||
if exception:
|
if job.exception is not None:
|
||||||
self.device_job_exception(id, description, exception, formatted_traceback)
|
self.device_job_exception(job)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.delete_memory.has_key(id):
|
if self.delete_memory.has_key(job):
|
||||||
paths, model = self.delete_memory.pop(id)
|
paths, model = self.delete_memory.pop(job)
|
||||||
self.device_manager.remove_books_from_metadata(paths, self.booklists())
|
self.device_manager.remove_books_from_metadata(paths, self.booklists())
|
||||||
model.paths_deleted(paths)
|
model.paths_deleted(paths)
|
||||||
self.upload_booklists()
|
self.upload_booklists()
|
||||||
@ -686,10 +679,9 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
if not a:
|
if not a:
|
||||||
a = 'Unknown'
|
a = 'Unknown'
|
||||||
prefix = sanitize_file_name(t+' - '+a)
|
prefix = sanitize_file_name(t+' - '+a)
|
||||||
if isinstance(prefix, unicode):
|
if not isinstance(prefix, unicode):
|
||||||
prefix = prefix.encode('ascii', 'ignore')
|
prefix = prefix.decode(preferred_encoding, 'replace')
|
||||||
else:
|
prefix = ascii_filename(prefix)
|
||||||
prefix = prefix.decode('ascii', 'ignore').encode('ascii', 'ignore')
|
|
||||||
names.append('%s_%d%s'%(prefix, id, os.path.splitext(f)[1]))
|
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 []
|
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))
|
self.upload_books(gf, names, good, on_card, memory=(_files, remove))
|
||||||
@ -705,8 +697,8 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
############################## Save to disk ################################
|
############################## Save to disk ################################
|
||||||
def save_single_format_to_disk(self, checked):
|
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):
|
def save_to_single_dir(self, checked):
|
||||||
self.save_to_disk(checked, True)
|
self.save_to_disk(checked, True)
|
||||||
|
|
||||||
@ -732,12 +724,11 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
QDesktopServices.openUrl(QUrl('file:'+dir))
|
QDesktopServices.openUrl(QUrl('file:'+dir))
|
||||||
else:
|
else:
|
||||||
paths = self.current_view().model().paths(rows)
|
paths = self.current_view().model().paths(rows)
|
||||||
self.job_manager.run_device_job(self.books_saved,
|
self.device_manager.save_books(Dispatcher(self.books_saved), paths, dir)
|
||||||
self.device_manager.save_books_func(), paths, dir)
|
|
||||||
|
def books_saved(self, job):
|
||||||
def books_saved(self, id, description, result, exception, formatted_traceback):
|
if job.exception is not None:
|
||||||
if exception:
|
self.device_job_exception(job)
|
||||||
self.device_job_exception(id, description, exception, formatted_traceback)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
############################################################################
|
############################################################################
|
||||||
@ -760,15 +751,15 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
if data['password']:
|
if data['password']:
|
||||||
args.extend(['--password', data['password']])
|
args.extend(['--password', data['password']])
|
||||||
args.append(data['script'] if data['script'] else data['title'])
|
args.append(data['script'] if data['script'] else data['title'])
|
||||||
id = self.job_manager.run_conversion_job(self.news_fetched, 'feeds2lrf', args=[args],
|
job = self.job_manager.run_job(Dispatcher(self.news_fetched), 'feeds2lrf', args=[args],
|
||||||
job_description=_('Fetch news from ')+data['title'])
|
description=_('Fetch news from ')+data['title'])
|
||||||
self.conversion_jobs[id] = (pt, 'lrf')
|
self.conversion_jobs[job] = (pt, 'lrf')
|
||||||
self.status_bar.showMessage(_('Fetching news from ')+data['title'], 2000)
|
self.status_bar.showMessage(_('Fetching news from ')+data['title'], 2000)
|
||||||
|
|
||||||
def news_fetched(self, id, description, result, exception, formatted_traceback, log):
|
def news_fetched(self, job):
|
||||||
pt, fmt = self.conversion_jobs.pop(id)
|
pt, fmt = self.conversion_jobs.pop(job)
|
||||||
if exception:
|
if job.exception is not None:
|
||||||
self.conversion_job_exception(id, description, exception, formatted_traceback, log)
|
self.job_exception(job)
|
||||||
return
|
return
|
||||||
to_device = self.device_connected and fmt in self.device_manager.device_class.FORMATS
|
to_device = self.device_connected and fmt in self.device_manager.device_class.FORMATS
|
||||||
self._add_books([pt.name], to_device)
|
self._add_books([pt.name], to_device)
|
||||||
@ -806,8 +797,9 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
bad_rows = []
|
bad_rows = []
|
||||||
|
|
||||||
self.status_bar.showMessage(_('Starting Bulk conversion of %d books')%len(rows), 2000)
|
self.status_bar.showMessage(_('Starting Bulk conversion of %d books')%len(rows), 2000)
|
||||||
|
if rows and hasattr(rows[0], 'row'):
|
||||||
for i, row in enumerate([r.row() for r in rows]):
|
rows = [r.row() for r in rows]
|
||||||
|
for i, row in enumerate(rows):
|
||||||
cmdline = list(d.cmdline)
|
cmdline = list(d.cmdline)
|
||||||
mi = self.library_view.model().db.get_metadata(row)
|
mi = self.library_view.model().db.get_metadata(row)
|
||||||
if mi.title:
|
if mi.title:
|
||||||
@ -841,12 +833,12 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
cmdline.extend(['--cover', cf.name])
|
cmdline.extend(['--cover', cf.name])
|
||||||
cmdline.extend(['-o', of.name])
|
cmdline.extend(['-o', of.name])
|
||||||
cmdline.append(pt.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],
|
'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))
|
self.library_view.model().db.id(row))
|
||||||
res = []
|
res = []
|
||||||
for row in bad_rows:
|
for row in bad_rows:
|
||||||
@ -887,10 +879,10 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
setattr(options, 'output', of.name)
|
setattr(options, 'output', of.name)
|
||||||
options.verbose = 1
|
options.verbose = 1
|
||||||
args = [pt.name, options]
|
args = [pt.name, options]
|
||||||
id = self.job_manager.run_conversion_job(self.book_converted,
|
job = self.job_manager.run_job(Dispatcher(self.book_converted),
|
||||||
'comic2lrf', args=args,
|
'comic2lrf', args=args,
|
||||||
job_description=_('Convert comic %d of %d (%s)')%(i+1, len(comics), repr(options.title)))
|
description=_('Convert comic %d of %d (%s)')%(i+1, len(comics), repr(options.title)))
|
||||||
self.conversion_jobs[id] = (None, pt, of, 'lrf',
|
self.conversion_jobs[job] = (None, pt, of, 'lrf',
|
||||||
self.library_view.model().db.id(row))
|
self.library_view.model().db.id(row))
|
||||||
|
|
||||||
|
|
||||||
@ -917,12 +909,12 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
of.close()
|
of.close()
|
||||||
cmdline.extend(['-o', of.name])
|
cmdline.extend(['-o', of.name])
|
||||||
cmdline.append(pt.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],
|
'any2lrf', args=[cmdline],
|
||||||
job_description=_('Convert book: ')+d.title())
|
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
|
changed = True
|
||||||
if changed:
|
if changed:
|
||||||
self.library_view.model().resort(reset=False)
|
self.library_view.model().resort(reset=False)
|
||||||
@ -962,25 +954,25 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
opts.verbose = 1
|
opts.verbose = 1
|
||||||
args = [pt.name, opts]
|
args = [pt.name, opts]
|
||||||
changed = True
|
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,
|
'comic2lrf', args=args,
|
||||||
job_description=_('Convert comic: ')+opts.title)
|
description=_('Convert comic: ')+opts.title)
|
||||||
self.conversion_jobs[id] = (None, pt, of, 'lrf',
|
self.conversion_jobs[job] = (None, pt, of, 'lrf',
|
||||||
self.library_view.model().db.id(row))
|
self.library_view.model().db.id(row))
|
||||||
if changed:
|
if changed:
|
||||||
self.library_view.model().resort(reset=False)
|
self.library_view.model().resort(reset=False)
|
||||||
self.library_view.model().research()
|
self.library_view.model().research()
|
||||||
|
|
||||||
def book_converted(self, id, description, result, exception, formatted_traceback, log):
|
def book_converted(self, job):
|
||||||
of, fmt, book_id = self.conversion_jobs.pop(id)[2:]
|
of, fmt, book_id = self.conversion_jobs.pop(job)[2:]
|
||||||
if exception:
|
if job.exception is not None:
|
||||||
self.conversion_job_exception(id, description, exception, formatted_traceback, log)
|
self.job_exception(job)
|
||||||
return
|
return
|
||||||
data = open(of.name, 'rb')
|
data = open(of.name, 'rb')
|
||||||
self.library_view.model().db.add_format(book_id, fmt, data, index_is_id=True)
|
self.library_view.model().db.add_format(book_id, fmt, data, index_is_id=True)
|
||||||
data.close()
|
data.close()
|
||||||
self.status_bar.showMessage(description + (' completed'), 2000)
|
self.status_bar.showMessage(job.description + (' completed'), 2000)
|
||||||
|
|
||||||
#############################View book######################################
|
#############################View book######################################
|
||||||
|
|
||||||
def view_format(self, row, format):
|
def view_format(self, row, format):
|
||||||
@ -989,20 +981,19 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
pt.close()
|
pt.close()
|
||||||
self.persistent_files.append(pt)
|
self.persistent_files.append(pt)
|
||||||
self._view_file(pt.name)
|
self._view_file(pt.name)
|
||||||
|
|
||||||
def book_downloaded_for_viewing(self, id, description, result, exception, formatted_traceback):
|
def book_downloaded_for_viewing(self, job):
|
||||||
if exception:
|
if job.exception:
|
||||||
self.device_job_exception(id, description, exception, formatted_traceback)
|
self.device_job_exception(job)
|
||||||
return
|
return
|
||||||
print result
|
self._view_file(job.result)
|
||||||
self._view_file(result)
|
|
||||||
|
|
||||||
def _view_file(self, name):
|
def _view_file(self, name):
|
||||||
self.setCursor(Qt.BusyCursor)
|
self.setCursor(Qt.BusyCursor)
|
||||||
try:
|
try:
|
||||||
if name.upper().endswith('.LRF'):
|
if name.upper().endswith('.LRF'):
|
||||||
args = ['lrfviewer', name]
|
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:
|
else:
|
||||||
QDesktopServices.openUrl(QUrl('file:'+name))#launch(name)
|
QDesktopServices.openUrl(QUrl('file:'+name))#launch(name)
|
||||||
time.sleep(5) # User feedback
|
time.sleep(5) # User feedback
|
||||||
@ -1063,11 +1054,11 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
pt = PersistentTemporaryFile('_viewer_'+os.path.splitext(paths[0])[1])
|
pt = PersistentTemporaryFile('_viewer_'+os.path.splitext(paths[0])[1])
|
||||||
self.persistent_files.append(pt)
|
self.persistent_files.append(pt)
|
||||||
pt.close()
|
pt.close()
|
||||||
self.job_manager.run_device_job(self.book_downloaded_for_viewing,
|
self.device_manager.view_book(Dispatcher(self.book_downloaded_for_viewing),
|
||||||
self.device_manager.view_book_func(), paths[0], pt.name)
|
paths[0], pt.name)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
############################################################################
|
############################################################################
|
||||||
|
|
||||||
########################### Do advanced search #############################
|
########################### Do advanced search #############################
|
||||||
@ -1093,10 +1084,9 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
d.exec_()
|
d.exec_()
|
||||||
if d.result() == d.Accepted:
|
if d.result() == d.Accepted:
|
||||||
self.library_view.set_visible_columns(d.final_columns)
|
self.library_view.set_visible_columns(d.final_columns)
|
||||||
settings = Settings()
|
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
||||||
self.tool_bar.setIconSize(settings.value('toolbar icon size', QVariant(QSize(48, 48))).toSize())
|
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if config['show_text_in_toolbar'] else Qt.ToolButtonIconOnly)
|
||||||
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if settings.get('show text in toolbar', True) else Qt.ToolButtonIconOnly)
|
|
||||||
|
|
||||||
if self.library_path != d.database_location:
|
if self.library_path != d.database_location:
|
||||||
try:
|
try:
|
||||||
newloc = d.database_location
|
newloc = d.database_location
|
||||||
@ -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)))
|
_('<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_()
|
d.exec_()
|
||||||
self.library_path = self.library_view.model().db.library_path
|
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:
|
except Exception, err:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
d = error_dialog(self, _('Could not move database'), unicode(err))
|
d = error_dialog(self, _('Could not move database'), unicode(err))
|
||||||
@ -1176,80 +1166,48 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.action_edit.setEnabled(False)
|
self.action_edit.setEnabled(False)
|
||||||
self.action_convert.setEnabled(False)
|
self.action_convert.setEnabled(False)
|
||||||
self.view_menu.actions()[1].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.
|
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'),
|
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()
|
_('There was a temporary error talking to the device. Please unplug and reconnect the device and or reboot.')).show()
|
||||||
return
|
return
|
||||||
print >>sys.stderr, 'Error in job:', description.encode('utf8')
|
|
||||||
print >>sys.stderr, exception
|
|
||||||
print >>sys.stderr, formatted_traceback.encode('utf8')
|
|
||||||
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.show()
|
|
||||||
|
|
||||||
def conversion_job_exception(self, id, description, exception, formatted_traceback, log):
|
|
||||||
|
|
||||||
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))
|
|
||||||
try:
|
try:
|
||||||
safe_print('Error in job:', description)
|
print >>sys.stderr, job.console_text()
|
||||||
if log:
|
except:
|
||||||
safe_print(log)
|
pass
|
||||||
safe_print(exception)
|
if not self.device_error_dialog.isVisible():
|
||||||
safe_print(formatted_traceback)
|
self.device_error_dialog.set_message(job.gui_text())
|
||||||
|
self.device_error_dialog.show()
|
||||||
|
|
||||||
|
def job_exception(self, job):
|
||||||
|
|
||||||
|
only_msg = getattr(job.exception, 'only_msg', False)
|
||||||
|
try:
|
||||||
|
print job.console_text()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
if only_msg:
|
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
|
return
|
||||||
msg = u'<p><b>%s</b>:'%exception
|
if isinstance(job.exception, JobKilled):
|
||||||
msg += u'<p>Failed to perform <b>job</b>: '+description
|
return
|
||||||
msg += u'<p>Detailed <b>traceback</b>:<pre>'
|
ConversionErrorDialog(self, _('Conversion Error'), job.gui_text(),
|
||||||
msg += formatted_traceback + u'</pre>'
|
show=True)
|
||||||
msg += u'<p><b>Log:</b></p><pre>'
|
|
||||||
msg += log
|
|
||||||
ConversionErrorDialog(self, 'Conversion Error', msg, show=True)
|
|
||||||
|
|
||||||
|
|
||||||
def initialize_database(self, settings):
|
def initialize_database(self):
|
||||||
self.library_path = settings.get('library path', None)
|
self.library_path = prefs['library path']
|
||||||
self.olddb = None
|
self.olddb = None
|
||||||
if self.library_path is None: # Need to migrate to new database layout
|
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):
|
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_()
|
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')
|
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):
|
def read_settings(self):
|
||||||
settings = Settings()
|
self.initialize_database()
|
||||||
settings.beginGroup('Main Window')
|
geometry = config['main_window_geometry']
|
||||||
geometry = settings.value('main window geometry', QVariant()).toByteArray()
|
if geometry is not None:
|
||||||
self.restoreGeometry(geometry)
|
self.restoreGeometry(geometry)
|
||||||
settings.endGroup()
|
|
||||||
self.initialize_database(settings)
|
|
||||||
set_sidebar_directories(None)
|
set_sidebar_directories(None)
|
||||||
set_filename_pat(settings.get('filename pattern', get_filename_pat()))
|
self.tool_bar.setIconSize(config['toolbar_icon_size'])
|
||||||
self.tool_bar.setIconSize(settings.get('toolbar icon size', QSize(48, 48)))
|
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if config['show_text_in_toolbar'] else Qt.ToolButtonIconOnly)
|
||||||
self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if settings.get('show text in toolbar', True) else Qt.ToolButtonIconOnly)
|
|
||||||
|
|
||||||
|
|
||||||
def write_settings(self):
|
def write_settings(self):
|
||||||
settings = Settings()
|
config.set('main_window_geometry', self.saveGeometry())
|
||||||
settings.beginGroup("Main Window")
|
|
||||||
settings.setValue("main window geometry", QVariant(self.saveGeometry()))
|
|
||||||
settings.endGroup()
|
|
||||||
settings.beginGroup('Book Views')
|
|
||||||
self.library_view.write_settings()
|
self.library_view.write_settings()
|
||||||
if self.device_connected:
|
if self.device_connected:
|
||||||
self.memory_view.write_settings()
|
self.memory_view.write_settings()
|
||||||
settings.endGroup()
|
|
||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
msg = 'There are active jobs. Are you sure you want to quit?'
|
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.job_manager.terminate_all_jobs()
|
||||||
self.write_settings()
|
self.write_settings()
|
||||||
self.detector.keep_going = False
|
self.device_manager.keep_going = False
|
||||||
self.cover_cache.stop()
|
self.cover_cache.stop()
|
||||||
self.hide()
|
self.hide()
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
self.detector.terminate()
|
|
||||||
self.cover_cache.terminate()
|
self.cover_cache.terminate()
|
||||||
e.accept()
|
e.accept()
|
||||||
|
|
||||||
@ -1327,17 +1276,17 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.vanity.setText(self.vanity_template%(dict(version=self.latest_version,
|
self.vanity.setText(self.vanity_template%(dict(version=self.latest_version,
|
||||||
device=self.device_info)))
|
device=self.device_info)))
|
||||||
self.vanity.update()
|
self.vanity.update()
|
||||||
s = Settings()
|
if config.get('new_version_notification') and dynamic.get('update to version %s'%version, True):
|
||||||
if s.get('new version notification', True) and s.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))
|
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:
|
if d.exec_() == QMessageBox.Yes:
|
||||||
url = 'http://calibre.kovidgoyal.net/download_'+('windows' if iswindows else 'osx' if isosx else 'linux')
|
url = 'http://calibre.kovidgoyal.net/download_'+('windows' if iswindows else 'osx' if isosx else 'linux')
|
||||||
QDesktopServices.openUrl(QUrl(url))
|
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):
|
def main(args=sys.argv):
|
||||||
from calibre import singleinstance
|
from calibre.utils.lock import singleinstance
|
||||||
|
|
||||||
pid = os.fork() if False and islinux else -1
|
pid = os.fork() if False and islinux else -1
|
||||||
if pid <= 0:
|
if pid <= 0:
|
||||||
parser = option_parser('''\
|
parser = option_parser('''\
|
||||||
|
@ -3,9 +3,9 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
|
|
||||||
import StringIO, traceback, sys
|
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.gui2.dialogs.conversion_error import ConversionErrorDialog
|
||||||
from calibre import OptionParser
|
from calibre.utils.config import OptionParser
|
||||||
|
|
||||||
def option_parser(usage='''\
|
def option_parser(usage='''\
|
||||||
Usage: %prog [options]
|
Usage: %prog [options]
|
||||||
@ -36,11 +36,17 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def __init__(self, opts, parent=None):
|
def __init__(self, opts, parent=None):
|
||||||
QMainWindow.__init__(self, parent)
|
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):
|
if getattr(opts, 'redirect', False):
|
||||||
self.__console_redirect = DebugWindow(self)
|
self.__console_redirect = DebugWindow(self)
|
||||||
sys.stdout = sys.stderr = self.__console_redirect
|
sys.stdout = sys.stderr = self.__console_redirect
|
||||||
self.__console_redirect.show()
|
self.__console_redirect.show()
|
||||||
|
|
||||||
|
def unix_signal(self, signal):
|
||||||
|
print 'Received signal:', repr(signal)
|
||||||
|
|
||||||
def unhandled_exception(self, type, value, tb):
|
def unhandled_exception(self, type, value, tb):
|
||||||
try:
|
try:
|
||||||
sio = StringIO.StringIO()
|
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('import images_rc', 'from calibre.gui2 import images_rc')
|
||||||
dat = dat.replace('from library import', 'from calibre.gui2.library import')
|
dat = dat.replace('from library import', 'from calibre.gui2.library import')
|
||||||
dat = dat.replace('from widgets import', 'from calibre.gui2.widgets 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.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)
|
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 {
|
class FlowImages : QObject {
|
||||||
|
|
||||||
%TypeHeaderCode
|
%TypeHeaderCode
|
||||||
#include "../../pictureflow.h"
|
#include <pictureflow.h>
|
||||||
%End
|
%End
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -22,7 +22,7 @@ public:
|
|||||||
class PictureFlow : QWidget {
|
class PictureFlow : QWidget {
|
||||||
|
|
||||||
%TypeHeaderCode
|
%TypeHeaderCode
|
||||||
#include "../../pictureflow.h"
|
#include <pictureflow.h>
|
||||||
%End
|
%End
|
||||||
|
|
||||||
|
|
@ -163,24 +163,23 @@ class StatusBar(QStatusBar):
|
|||||||
def show_book_info(self):
|
def show_book_info(self):
|
||||||
self.emit(SIGNAL('show_book_info()'))
|
self.emit(SIGNAL('show_book_info()'))
|
||||||
|
|
||||||
def job_added(self, id):
|
def job_added(self, nnum):
|
||||||
jobs = self.movie_button.jobs
|
jobs = self.movie_button.jobs
|
||||||
src = qstring_to_unicode(jobs.text())
|
src = qstring_to_unicode(jobs.text())
|
||||||
num = self.jobs()
|
num = self.jobs()
|
||||||
nnum = num+1
|
nnum = num + 1
|
||||||
text = src.replace(str(num), str(nnum))
|
text = src.replace(str(num), str(nnum))
|
||||||
jobs.setText(text)
|
jobs.setText(text)
|
||||||
if self.movie_button.movie.state() == QMovie.Paused:
|
if self.movie_button.movie.state() == QMovie.Paused:
|
||||||
self.movie_button.movie.setPaused(False)
|
self.movie_button.movie.setPaused(False)
|
||||||
|
|
||||||
def job_done(self, id):
|
def job_done(self, running):
|
||||||
jobs = self.movie_button.jobs
|
jobs = self.movie_button.jobs
|
||||||
src = qstring_to_unicode(jobs.text())
|
src = qstring_to_unicode(jobs.text())
|
||||||
num = self.jobs()
|
num = self.jobs()
|
||||||
nnum = num-1
|
text = src.replace(str(num), str(running))
|
||||||
text = src.replace(str(num), str(nnum))
|
|
||||||
jobs.setText(text)
|
jobs.setText(text)
|
||||||
if nnum == 0:
|
if running == 0:
|
||||||
self.no_more_jobs()
|
self.no_more_jobs()
|
||||||
|
|
||||||
def no_more_jobs(self):
|
def no_more_jobs(self):
|
||||||
|
@ -9,15 +9,16 @@ from PyQt4.QtGui import QListView, QIcon, QFont, QLabel, QListWidget, \
|
|||||||
QSyntaxHighlighter, QCursor, QColor, QWidget, \
|
QSyntaxHighlighter, QCursor, QColor, QWidget, \
|
||||||
QAbstractItemDelegate, QPixmap, QStyle, QFontMetrics
|
QAbstractItemDelegate, QPixmap, QStyle, QFontMetrics
|
||||||
from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, SIGNAL, \
|
from PyQt4.QtCore import QAbstractListModel, QVariant, Qt, SIGNAL, \
|
||||||
QObject, QRegExp, QString
|
QObject, QRegExp, QString, QSettings
|
||||||
|
|
||||||
from calibre.gui2.jobs import DetailView
|
from calibre.gui2.jobs2 import DetailView
|
||||||
from calibre.gui2 import human_readable, NONE, TableView, qstring_to_unicode, error_dialog
|
from calibre.gui2 import human_readable, NONE, TableView, \
|
||||||
|
qstring_to_unicode, error_dialog
|
||||||
from calibre.gui2.filename_pattern_ui import Ui_Form
|
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.utils.fontconfig import find_font_families
|
||||||
from calibre.ebooks.metadata.meta import get_filename_pat, metadata_from_filename, \
|
from calibre.ebooks.metadata.meta import metadata_from_filename
|
||||||
set_filename_pat
|
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.test_button, SIGNAL('clicked()'), self.do_test)
|
||||||
self.connect(self.re, SIGNAL('returnPressed()'), 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):
|
def do_test(self):
|
||||||
try:
|
try:
|
||||||
@ -66,9 +67,9 @@ class FilenamePattern(QWidget, Ui_Form):
|
|||||||
return re.compile(pat)
|
return re.compile(pat)
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
pat = self.pattern()
|
pat = self.pattern().pattern
|
||||||
set_filename_pat(pat)
|
prefs['filename_pattern'] = pat
|
||||||
return pat.pattern
|
return pat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -235,8 +236,10 @@ class JobsView(TableView):
|
|||||||
|
|
||||||
def show_details(self, index):
|
def show_details(self, index):
|
||||||
row = index.row()
|
row = index.row()
|
||||||
job = self.model().row_to_job(row)[0]
|
job = self.model().row_to_job(row)
|
||||||
DetailView(self, job).exec_()
|
d = DetailView(self, job)
|
||||||
|
self.connect(self.model(), SIGNAL('output_received()'), d.update)
|
||||||
|
d.exec_()
|
||||||
|
|
||||||
|
|
||||||
class FontFamilyModel(QAbstractListModel):
|
class FontFamilyModel(QAbstractListModel):
|
||||||
@ -365,13 +368,13 @@ class PythonHighlighter(QSyntaxHighlighter):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def loadConfig(cls):
|
def loadConfig(cls):
|
||||||
Config = cls.Config
|
Config = cls.Config
|
||||||
|
settings = QSettings()
|
||||||
def setDefaultString(name, default):
|
def setDefaultString(name, default):
|
||||||
value = settings.value(name).toString()
|
value = settings.value(name).toString()
|
||||||
if value.isEmpty():
|
if value.isEmpty():
|
||||||
value = default
|
value = default
|
||||||
Config[name] = value
|
Config[name] = value
|
||||||
|
|
||||||
settings = Settings()
|
|
||||||
for name in ("window", "shell"):
|
for name in ("window", "shell"):
|
||||||
Config["%swidth" % name] = settings.value("%swidth" % name,
|
Config["%swidth" % name] = settings.value("%swidth" % name,
|
||||||
QVariant(QApplication.desktop() \
|
QVariant(QApplication.desktop() \
|
||||||
|
@ -10,7 +10,8 @@ Command line interface to the calibre database.
|
|||||||
import sys, os
|
import sys, os
|
||||||
from textwrap import TextWrapper
|
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:
|
try:
|
||||||
from calibre.utils.single_qt_application import send_message
|
from calibre.utils.single_qt_application import send_message
|
||||||
except:
|
except:
|
||||||
@ -26,6 +27,7 @@ def get_parser(usage):
|
|||||||
parser = OptionParser(usage)
|
parser = OptionParser(usage)
|
||||||
go = parser.add_option_group('GLOBAL OPTIONS')
|
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.'))
|
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
|
return parser
|
||||||
|
|
||||||
def get_db(dbpath, options):
|
def get_db(dbpath, options):
|
||||||
@ -44,8 +46,8 @@ def do_list(db, fields, sort_by, ascending, search_text):
|
|||||||
widths = list(map(lambda x : 0, fields))
|
widths = list(map(lambda x : 0, fields))
|
||||||
for i in db.data:
|
for i in db.data:
|
||||||
for j, field in enumerate(fields):
|
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
|
screen_width = terminal_controller.COLS
|
||||||
if not screen_width:
|
if not screen_width:
|
||||||
screen_width = 80
|
screen_width = 80
|
||||||
@ -97,8 +99,7 @@ List the books available in the calibre database.
|
|||||||
parser.add_option('-s', '--search', default=None,
|
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.'))
|
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)
|
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):
|
if not set(fields).issubset(FIELDS):
|
||||||
parser.print_help()
|
parser.print_help()
|
||||||
print
|
print
|
||||||
@ -424,7 +425,7 @@ For help on an individual command: %%prog command --help
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
command = eval('command_'+args[1])
|
command = eval('command_'+args[1])
|
||||||
dbpath = Settings().get('library path', os.path.expanduser('~'))
|
dbpath = prefs['library_path']
|
||||||
|
|
||||||
return command(args[2:], dbpath)
|
return command(args[2:], dbpath)
|
||||||
|
|
||||||
|
@ -1412,8 +1412,11 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
mi.authors = [_('Unknown')]
|
mi.authors = [_('Unknown')]
|
||||||
mi.render(f)
|
mi.render(f)
|
||||||
f.close()
|
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)
|
data = self.format(idx, fmt, index_is_id=index_is_id)
|
||||||
fname = name +'.'+fmt.lower()
|
fname = name +'.'+fmt.lower()
|
||||||
fname = sanitize_file_name(fname)
|
fname = sanitize_file_name(fname)
|
||||||
|
@ -16,14 +16,14 @@ if os.environ.has_key('DESTDIR'):
|
|||||||
|
|
||||||
entry_points = {
|
entry_points = {
|
||||||
'console_scripts': [ \
|
'console_scripts': [ \
|
||||||
'prs500 = calibre.devices.prs500.cli.main:main',
|
'prs500 = calibre.devices.prs500.cli.main:main',
|
||||||
'lrf-meta = calibre.ebooks.lrf.meta:main',
|
'lrf-meta = calibre.ebooks.lrf.meta:main',
|
||||||
'rtf-meta = calibre.ebooks.metadata.rtf:main',
|
'rtf-meta = calibre.ebooks.metadata.rtf:main',
|
||||||
'pdf-meta = calibre.ebooks.metadata.pdf:main',
|
'pdf-meta = calibre.ebooks.metadata.pdf:main',
|
||||||
'lit-meta = calibre.ebooks.metadata.lit:main',
|
'lit-meta = calibre.ebooks.metadata.lit:main',
|
||||||
'opf-meta = calibre.ebooks.metadata.opf:main',
|
'opf-meta = calibre.ebooks.metadata.opf:main',
|
||||||
'epub-meta = calibre.ebooks.metadata.epub:main',
|
'epub-meta = calibre.ebooks.metadata.epub:main',
|
||||||
'txt2lrf = calibre.ebooks.lrf.txt.convert_from:main',
|
'txt2lrf = calibre.ebooks.lrf.txt.convert_from:main',
|
||||||
'html2lrf = calibre.ebooks.lrf.html.convert_from:main',
|
'html2lrf = calibre.ebooks.lrf.html.convert_from:main',
|
||||||
'markdown-calibre = calibre.ebooks.markdown.markdown:main',
|
'markdown-calibre = calibre.ebooks.markdown.markdown:main',
|
||||||
'lit2lrf = calibre.ebooks.lrf.lit.convert_from:main',
|
'lit2lrf = calibre.ebooks.lrf.lit.convert_from:main',
|
||||||
@ -51,8 +51,8 @@ entry_points = {
|
|||||||
'calibredb = calibre.library.cli:main',
|
'calibredb = calibre.library.cli:main',
|
||||||
'calibre-fontconfig = calibre.utils.fontconfig:main',
|
'calibre-fontconfig = calibre.utils.fontconfig:main',
|
||||||
'calibre-parallel = calibre.parallel:main',
|
'calibre-parallel = calibre.parallel:main',
|
||||||
],
|
],
|
||||||
'gui_scripts' : [
|
'gui_scripts' : [
|
||||||
__appname__+' = calibre.gui2.main:main',
|
__appname__+' = calibre.gui2.main:main',
|
||||||
'lrfviewer = calibre.gui2.lrf_renderer.main:main',
|
'lrfviewer = calibre.gui2.lrf_renderer.main:main',
|
||||||
],
|
],
|
||||||
@ -60,7 +60,7 @@ entry_points = {
|
|||||||
|
|
||||||
|
|
||||||
def options(option_parser):
|
def options(option_parser):
|
||||||
parser = option_parser()
|
parser = option_parser()
|
||||||
options = parser.option_list
|
options = parser.option_list
|
||||||
for group in parser.option_groups:
|
for group in parser.option_groups:
|
||||||
options += group.option_list
|
options += group.option_list
|
||||||
@ -72,7 +72,7 @@ def options(option_parser):
|
|||||||
|
|
||||||
def opts_and_words(name, op, words):
|
def opts_and_words(name, op, words):
|
||||||
opts = '|'.join(options(op))
|
opts = '|'.join(options(op))
|
||||||
words = '|'.join([w.replace("'", "\\'") for w in words])
|
words = '|'.join([w.replace("'", "\\'") for w in words])
|
||||||
return '_'+name+'()'+\
|
return '_'+name+'()'+\
|
||||||
'''
|
'''
|
||||||
{
|
{
|
||||||
@ -82,13 +82,13 @@ def opts_and_words(name, op, words):
|
|||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
opts="%s"
|
opts="%s"
|
||||||
words="%s"
|
words="%s"
|
||||||
|
|
||||||
case "${cur}" in
|
case "${cur}" in
|
||||||
-* )
|
-* )
|
||||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||||
COMPREPLY=( $( echo ${COMPREPLY[@]} | sed 's/ /\\\\ /g' | tr '\\n' '\\t' ) )
|
COMPREPLY=( $( echo ${COMPREPLY[@]} | sed 's/ /\\\\ /g' | tr '\\n' '\\t' ) )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
* )
|
* )
|
||||||
COMPREPLY=( $(compgen -W "${words}" -- ${cur}) )
|
COMPREPLY=( $(compgen -W "${words}" -- ${cur}) )
|
||||||
COMPREPLY=( $( echo ${COMPREPLY[@]} | sed 's/ /\\\\ /g' | tr '\\n' '\\t' ) )
|
COMPREPLY=( $( echo ${COMPREPLY[@]} | sed 's/ /\\\\ /g' | tr '\\n' '\\t' ) )
|
||||||
@ -167,16 +167,16 @@ def setup_completion(fatal_errors):
|
|||||||
from calibre.ebooks.lrf.feeds.convert_from import option_parser as feeds2lrf
|
from calibre.ebooks.lrf.feeds.convert_from import option_parser as feeds2lrf
|
||||||
from calibre.ebooks.metadata.epub import option_parser as epub_meta
|
from calibre.ebooks.metadata.epub import option_parser as epub_meta
|
||||||
from calibre.ebooks.lrf.comic.convert_from import option_parser as comicop
|
from calibre.ebooks.lrf.comic.convert_from import option_parser as comicop
|
||||||
|
|
||||||
f = open_file('/etc/bash_completion.d/libprs500')
|
f = open_file('/etc/bash_completion.d/libprs500')
|
||||||
f.close()
|
f.close()
|
||||||
os.remove(f.name)
|
os.remove(f.name)
|
||||||
manifest = []
|
manifest = []
|
||||||
f = open_file('/etc/bash_completion.d/calibre')
|
f = open_file('/etc/bash_completion.d/calibre')
|
||||||
manifest.append(f.name)
|
manifest.append(f.name)
|
||||||
|
|
||||||
f.write('# calibre Bash Shell Completion\n')
|
f.write('# calibre Bash Shell Completion\n')
|
||||||
f.write(opts_and_exts('html2lrf', htmlop,
|
f.write(opts_and_exts('html2lrf', htmlop,
|
||||||
['htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip', 'php']))
|
['htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip', 'php']))
|
||||||
f.write(opts_and_exts('txt2lrf', txtop, ['txt']))
|
f.write(opts_and_exts('txt2lrf', txtop, ['txt']))
|
||||||
f.write(opts_and_exts('lit2lrf', htmlop, ['lit']))
|
f.write(opts_and_exts('lit2lrf', htmlop, ['lit']))
|
||||||
@ -185,8 +185,8 @@ def setup_completion(fatal_errors):
|
|||||||
f.write(opts_and_exts('mobi2lrf', htmlop, ['mobi', 'prc']))
|
f.write(opts_and_exts('mobi2lrf', htmlop, ['mobi', 'prc']))
|
||||||
f.write(opts_and_exts('fb22lrf', htmlop, ['fb2']))
|
f.write(opts_and_exts('fb22lrf', htmlop, ['fb2']))
|
||||||
f.write(opts_and_exts('pdf2lrf', htmlop, ['pdf']))
|
f.write(opts_and_exts('pdf2lrf', htmlop, ['pdf']))
|
||||||
f.write(opts_and_exts('any2lrf', htmlop,
|
f.write(opts_and_exts('any2lrf', htmlop,
|
||||||
['epub', 'htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip',
|
['epub', 'htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip',
|
||||||
'txt', 'lit', 'rtf', 'pdf', 'prc', 'mobi', 'fb2']))
|
'txt', 'lit', 'rtf', 'pdf', 'prc', 'mobi', 'fb2']))
|
||||||
f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf']))
|
f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf']))
|
||||||
f.write(opts_and_exts('lrf-meta', metaop, ['lrf']))
|
f.write(opts_and_exts('lrf-meta', metaop, ['lrf']))
|
||||||
@ -228,17 +228,17 @@ _prs500_ls()
|
|||||||
prefix="${prefix}/"
|
prefix="${prefix}/"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo $(compgen -P "${prefix}" -W "${listing}" "${pattern}")
|
echo $(compgen -P "${prefix}" -W "${listing}" "${pattern}")
|
||||||
}
|
}
|
||||||
|
|
||||||
_prs500()
|
_prs500()
|
||||||
{
|
{
|
||||||
local cur prev
|
local cur prev
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
case "${prev}" in
|
case "${prev}" in
|
||||||
ls|rm|mkdir|touch|cat )
|
ls|rm|mkdir|touch|cat )
|
||||||
COMPREPLY=( $(_prs500_ls "${cur}") )
|
COMPREPLY=( $(_prs500_ls "${cur}") )
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
@ -284,7 +284,7 @@ complete -o nospace -F _prs500 prs500
|
|||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return manifest
|
return manifest
|
||||||
|
|
||||||
def setup_udev_rules(group_file, reload, fatal_errors):
|
def setup_udev_rules(group_file, reload, fatal_errors):
|
||||||
print 'Trying to setup udev rules...'
|
print 'Trying to setup udev rules...'
|
||||||
manifest = []
|
manifest = []
|
||||||
@ -325,7 +325,7 @@ def setup_udev_rules(group_file, reload, fatal_errors):
|
|||||||
break
|
break
|
||||||
if not called and os.access('/etc/rc.d/rc.hald', os.X_OK):
|
if not called and os.access('/etc/rc.d/rc.hald', os.X_OK):
|
||||||
call(('/etc/rc.d/rc.hald', 'restart'))
|
call(('/etc/rc.d/rc.hald', 'restart'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
check_call('udevadm control --reload_rules', shell=True)
|
check_call('udevadm control --reload_rules', shell=True)
|
||||||
except:
|
except:
|
||||||
@ -351,9 +351,9 @@ def option_parser():
|
|||||||
help='File from which to read group information. Default: %default')
|
help='File from which to read group information. Default: %default')
|
||||||
parser.add_option('--dont-check-root', action='store_true', default=False, dest='no_root',
|
parser.add_option('--dont-check-root', action='store_true', default=False, dest='no_root',
|
||||||
help='If set, do not check if we are root.')
|
help='If set, do not check if we are root.')
|
||||||
parser.add_option('--make-errors-fatal', action='store_true', default=False,
|
parser.add_option('--make-errors-fatal', action='store_true', default=False,
|
||||||
dest='fatal_errors', help='If set die on errors.')
|
dest='fatal_errors', help='If set die on errors.')
|
||||||
parser.add_option('--save-manifest-to', default=None,
|
parser.add_option('--save-manifest-to', default=None,
|
||||||
help='Save a manifest of all installed files to the specified location')
|
help='Save a manifest of all installed files to the specified location')
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
@ -366,12 +366,14 @@ def install_man_pages(fatal_errors):
|
|||||||
f.write('[see also]\nhttp://%s.kovidgoyal.net\n'%__appname__)
|
f.write('[see also]\nhttp://%s.kovidgoyal.net\n'%__appname__)
|
||||||
f.close()
|
f.close()
|
||||||
manifest = []
|
manifest = []
|
||||||
|
os.environ['PATH'] += ':'+os.path.expanduser('~/bin')
|
||||||
for src in entry_points['console_scripts']:
|
for src in entry_points['console_scripts']:
|
||||||
prog = src[:src.index('=')].strip()
|
prog = src[:src.index('=')].strip()
|
||||||
if prog in ('prs500', 'pdf-meta', 'epub-meta', 'lit-meta',
|
if prog in ('prs500', 'pdf-meta', 'epub-meta', 'lit-meta',
|
||||||
'markdown-calibre', 'calibre-debug', 'fb2-meta',
|
'markdown-calibre', 'calibre-debug', 'fb2-meta',
|
||||||
'calibre-fontconfig', 'calibre-parallel'):
|
'calibre-fontconfig', 'calibre-parallel'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
help2man = ('help2man', prog, '--name', 'part of %s'%__appname__,
|
help2man = ('help2man', prog, '--name', 'part of %s'%__appname__,
|
||||||
'--section', '1', '--no-info', '--include',
|
'--section', '1', '--no-info', '--include',
|
||||||
f.name, '--manual', __appname__)
|
f.name, '--manual', __appname__)
|
||||||
@ -390,18 +392,18 @@ def install_man_pages(fatal_errors):
|
|||||||
print 'Unable to create MAN page for', prog
|
print 'Unable to create MAN page for', prog
|
||||||
continue
|
continue
|
||||||
f2 = open_file(manfile)
|
f2 = open_file(manfile)
|
||||||
manifest.append(f2.name)
|
manifest.append(f2.name)
|
||||||
f2.write(compress(raw))
|
f2.write(compress(raw))
|
||||||
return manifest
|
return manifest
|
||||||
|
|
||||||
def post_install():
|
def post_install():
|
||||||
parser = option_parser()
|
parser = option_parser()
|
||||||
opts = parser.parse_args()[0]
|
opts = parser.parse_args()[0]
|
||||||
|
|
||||||
if not opts.no_root and os.geteuid() != 0:
|
if not opts.no_root and os.geteuid() != 0:
|
||||||
print >> sys.stderr, 'You must be root to run this command.'
|
print >> sys.stderr, 'You must be root to run this command.'
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
global use_destdir
|
global use_destdir
|
||||||
use_destdir = opts.destdir
|
use_destdir = opts.destdir
|
||||||
manifest = []
|
manifest = []
|
||||||
@ -409,18 +411,18 @@ def post_install():
|
|||||||
manifest += setup_completion(opts.fatal_errors)
|
manifest += setup_completion(opts.fatal_errors)
|
||||||
setup_desktop_integration(opts.fatal_errors)
|
setup_desktop_integration(opts.fatal_errors)
|
||||||
manifest += install_man_pages(opts.fatal_errors)
|
manifest += install_man_pages(opts.fatal_errors)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PyQt4 import Qt
|
from PyQt4 import Qt
|
||||||
if Qt.PYQT_VERSION < int('0x40402', 16):
|
if Qt.PYQT_VERSION < int('0x40402', 16):
|
||||||
print 'WARNING: You need PyQt >= 4.4.2 for the GUI. You have', Qt.PYQT_VERSION_STR, '\nYou may experience crashes or other strange behavior.'
|
print 'WARNING: You need PyQt >= 4.4.2 for the GUI. You have', Qt.PYQT_VERSION_STR, '\nYou may experience crashes or other strange behavior.'
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print 'WARNING: You do not have PyQt4 installed. The GUI will not work.'
|
print 'WARNING: You do not have PyQt4 installed. The GUI will not work.'
|
||||||
|
|
||||||
if opts.save_manifest_to:
|
if opts.save_manifest_to:
|
||||||
open(opts.save_manifest_to, 'wb').write('\n'.join(manifest)+'\n')
|
open(opts.save_manifest_to, 'wb').write('\n'.join(manifest)+'\n')
|
||||||
|
|
||||||
|
|
||||||
VIEWER = '''\
|
VIEWER = '''\
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Version=%s
|
Version=%s
|
||||||
@ -478,10 +480,10 @@ def setup_desktop_integration(fatal_errors):
|
|||||||
from PyQt4.QtCore import QFile
|
from PyQt4.QtCore import QFile
|
||||||
from calibre.gui2 import images_rc # Load images
|
from calibre.gui2 import images_rc # Load images
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
|
|
||||||
print 'Setting up desktop integration...'
|
print 'Setting up desktop integration...'
|
||||||
|
|
||||||
|
|
||||||
tdir = mkdtemp()
|
tdir = mkdtemp()
|
||||||
cwd = os.getcwdu()
|
cwd = os.getcwdu()
|
||||||
try:
|
try:
|
||||||
@ -493,7 +495,7 @@ def setup_desktop_integration(fatal_errors):
|
|||||||
check_call('xdg-icon-resource install --size 128 calibre-gui.png calibre-gui', shell=True)
|
check_call('xdg-icon-resource install --size 128 calibre-gui.png calibre-gui', shell=True)
|
||||||
render_svg(QFile(':/images/viewer.svg'), os.path.join(tdir, 'calibre-viewer.png'))
|
render_svg(QFile(':/images/viewer.svg'), os.path.join(tdir, 'calibre-viewer.png'))
|
||||||
check_call('xdg-icon-resource install --size 128 calibre-viewer.png calibre-viewer', shell=True)
|
check_call('xdg-icon-resource install --size 128 calibre-viewer.png calibre-viewer', shell=True)
|
||||||
|
|
||||||
f = open('calibre-lrfviewer.desktop', 'wb')
|
f = open('calibre-lrfviewer.desktop', 'wb')
|
||||||
f.write(VIEWER)
|
f.write(VIEWER)
|
||||||
f.close()
|
f.close()
|
||||||
@ -513,11 +515,11 @@ def setup_desktop_integration(fatal_errors):
|
|||||||
raise
|
raise
|
||||||
print >>sys.stderr, 'Could not setup desktop integration. Error:'
|
print >>sys.stderr, 'Could not setup desktop integration. Error:'
|
||||||
print err
|
print err
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
post_install()
|
post_install()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -244,6 +244,7 @@ def do_postinstall(destdir):
|
|||||||
os.chdir(destdir)
|
os.chdir(destdir)
|
||||||
os.environ['LD_LIBRARY_PATH'] = destdir+':'+os.environ.get('LD_LIBRARY_PATH', '')
|
os.environ['LD_LIBRARY_PATH'] = destdir+':'+os.environ.get('LD_LIBRARY_PATH', '')
|
||||||
os.environ['PYTHONPATH'] = destdir
|
os.environ['PYTHONPATH'] = destdir
|
||||||
|
os.environ['PYTHONSTARTUP'] = ''
|
||||||
subprocess.call((os.path.join(destdir, 'calibre_postinstall'), '--save-manifest-to', t.name))
|
subprocess.call((os.path.join(destdir, 'calibre_postinstall'), '--save-manifest-to', t.name))
|
||||||
finally:
|
finally:
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
|
@ -17,7 +17,7 @@ E-book Format Conversion
|
|||||||
|
|
||||||
What formats does |app| support conversion to/from?
|
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?
|
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()"
|
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?
|
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 |
@ -13,7 +13,7 @@ has the environment variable :envvar:`CALIBRE_WORKER` defined.
|
|||||||
|
|
||||||
The worker control protocol has two modes of operation. In the first mode, the
|
The worker control protocol has two modes of operation. In the first mode, the
|
||||||
worker process listens for commands from the controller process. The controller
|
worker process listens for commands from the controller process. The controller
|
||||||
process can either hand off a job to the worker or tell the worker to die.
|
process can either hand off a job to the worker or tell the worker to die.
|
||||||
Once a job is handed off to the worker, the protocol enters the second mode, where
|
Once a job is handed off to the worker, the protocol enters the second mode, where
|
||||||
the controller listens for messages from the worker. The worker can send progress updates
|
the controller listens for messages from the worker. The worker can send progress updates
|
||||||
as well as console output (i.e. text that would normally have been written to stdout
|
as well as console output (i.e. text that would normally have been written to stdout
|
||||||
@ -22,12 +22,11 @@ returns the result (or exception) to the controller and the protocol reverts to
|
|||||||
|
|
||||||
In the second mode, the controller can also send the worker STOP messages, in which case
|
In the second mode, the controller can also send the worker STOP messages, in which case
|
||||||
the worker interrupts the job and dies. The sending of progress and console output messages
|
the worker interrupts the job and dies. The sending of progress and console output messages
|
||||||
is buffered and asynchronous to prevent the job from being IO bound.
|
is buffered and asynchronous to prevent the job from being IO bound.
|
||||||
'''
|
'''
|
||||||
import sys, os, gc, cPickle, traceback, atexit, cStringIO, time, signal, \
|
import sys, os, gc, cPickle, traceback, atexit, cStringIO, time, signal, \
|
||||||
subprocess, socket, collections, binascii, re, thread, tempfile
|
subprocess, socket, collections, binascii, re, thread, tempfile
|
||||||
from select import select
|
from select import select
|
||||||
from functools import partial
|
|
||||||
from threading import RLock, Thread, Event
|
from threading import RLock, Thread, Event
|
||||||
|
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
@ -37,20 +36,20 @@ DEBUG = False
|
|||||||
|
|
||||||
#: A mapping from job names to functions that perform the jobs
|
#: A mapping from job names to functions that perform the jobs
|
||||||
PARALLEL_FUNCS = {
|
PARALLEL_FUNCS = {
|
||||||
'any2lrf' :
|
'any2lrf' :
|
||||||
('calibre.ebooks.lrf.any.convert_from', 'main', dict(gui_mode=True), None),
|
('calibre.ebooks.lrf.any.convert_from', 'main', dict(gui_mode=True), None),
|
||||||
|
|
||||||
'lrfviewer' :
|
'lrfviewer' :
|
||||||
('calibre.gui2.lrf_renderer.main', 'main', {}, None),
|
('calibre.gui2.lrf_renderer.main', 'main', {}, None),
|
||||||
|
|
||||||
'feeds2lrf' :
|
'feeds2lrf' :
|
||||||
('calibre.ebooks.lrf.feeds.convert_from', 'main', {}, 'notification'),
|
('calibre.ebooks.lrf.feeds.convert_from', 'main', {}, 'notification'),
|
||||||
|
|
||||||
'render_table' :
|
'render_table' :
|
||||||
('calibre.ebooks.lrf.html.table_as_image', 'do_render', {}, None),
|
('calibre.ebooks.lrf.html.table_as_image', 'do_render', {}, None),
|
||||||
|
|
||||||
'comic2lrf' :
|
'comic2lrf' :
|
||||||
('calibre.ebooks.lrf.comic.convert_from', 'do_convert', {}, 'notification'),
|
('calibre.ebooks.lrf.comic.convert_from', 'do_convert', {}, 'notification'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -67,20 +66,20 @@ class WorkerStatus(object):
|
|||||||
'''
|
'''
|
||||||
A platform independent class to control child processes. Provides the
|
A platform independent class to control child processes. Provides the
|
||||||
methods:
|
methods:
|
||||||
|
|
||||||
.. method:: WorkerStatus.is_alive()
|
.. method:: WorkerStatus.is_alive()
|
||||||
|
|
||||||
Return True is the child process is alive (i.e. it hasn't exited and returned a return code).
|
Return True is the child process is alive (i.e. it hasn't exited and returned a return code).
|
||||||
|
|
||||||
.. method:: WorkerStatus.returncode()
|
.. method:: WorkerStatus.returncode()
|
||||||
|
|
||||||
Wait for the child process to exit and return its return code (blocks until child returns).
|
Wait for the child process to exit and return its return code (blocks until child returns).
|
||||||
|
|
||||||
.. method:: WorkerStatus.kill()
|
.. method:: WorkerStatus.kill()
|
||||||
|
|
||||||
Forcibly terminates child process using operating system specific semantics.
|
Forcibly terminates child process using operating system specific semantics.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
'''
|
'''
|
||||||
`obj`: On windows a process handle, on unix a subprocess.Popen object.
|
`obj`: On windows a process handle, on unix a subprocess.Popen object.
|
||||||
@ -92,22 +91,22 @@ class WorkerStatus(object):
|
|||||||
ext = 'windows' if iswindows else 'unix'
|
ext = 'windows' if iswindows else 'unix'
|
||||||
for func in ('is_alive', 'returncode', 'kill'):
|
for func in ('is_alive', 'returncode', 'kill'):
|
||||||
setattr(self, func, getattr(self, func+'_'+ext))
|
setattr(self, func, getattr(self, func+'_'+ext))
|
||||||
|
|
||||||
def is_alive_unix(self):
|
def is_alive_unix(self):
|
||||||
return self.obj.poll() == None
|
return self.obj.poll() == None
|
||||||
|
|
||||||
def returncode_unix(self):
|
def returncode_unix(self):
|
||||||
return self.obj.wait()
|
return self.obj.wait()
|
||||||
|
|
||||||
def kill_unix(self):
|
def kill_unix(self):
|
||||||
os.kill(self.obj.pid, self.signal.SIGKILL)
|
os.kill(self.obj.pid, self.signal.SIGKILL)
|
||||||
|
|
||||||
def is_alive_windows(self):
|
def is_alive_windows(self):
|
||||||
return win32event.WaitForSingleObject(self.obj, 0) != win32event.WAIT_OBJECT_0
|
return win32event.WaitForSingleObject(self.obj, 0) != win32event.WAIT_OBJECT_0
|
||||||
|
|
||||||
def returncode_windows(self):
|
def returncode_windows(self):
|
||||||
return win32process.GetExitCodeProcess(self.obj)
|
return win32process.GetExitCodeProcess(self.obj)
|
||||||
|
|
||||||
def kill_windows(self, returncode=-1):
|
def kill_windows(self, returncode=-1):
|
||||||
self.win32process.TerminateProcess(self.obj, returncode)
|
self.win32process.TerminateProcess(self.obj, returncode)
|
||||||
|
|
||||||
@ -115,16 +114,16 @@ class WorkerMother(object):
|
|||||||
'''
|
'''
|
||||||
Platform independent object for launching child processes. All processes
|
Platform independent object for launching child processes. All processes
|
||||||
have the environment variable :envvar:`CALIBRE_WORKER` set.
|
have the environment variable :envvar:`CALIBRE_WORKER` set.
|
||||||
|
|
||||||
..method:: WorkerMother.spawn_free_spirit(arg)
|
..method:: WorkerMother.spawn_free_spirit(arg)
|
||||||
|
|
||||||
Launch a non monitored process with argument `arg`.
|
Launch a non monitored process with argument `arg`.
|
||||||
|
|
||||||
..method:: WorkerMother.spawn_worker(arg)
|
..method:: WorkerMother.spawn_worker(arg)
|
||||||
|
|
||||||
Launch a monitored and controllable process with argument `arg`.
|
Launch a monitored and controllable process with argument `arg`.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
ext = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
ext = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
||||||
self.os = os # Needed incase cleanup called when interpreter is shutting down
|
self.os = os # Needed incase cleanup called when interpreter is shutting down
|
||||||
@ -140,26 +139,28 @@ class WorkerMother(object):
|
|||||||
contents = os.path.dirname(fd)
|
contents = os.path.dirname(fd)
|
||||||
resources = os.path.join(contents, 'Resources')
|
resources = os.path.join(contents, 'Resources')
|
||||||
sp = os.path.join(resources, 'lib', 'python'+sys.version[:3], 'site-packages.zip')
|
sp = os.path.join(resources, 'lib', 'python'+sys.version[:3], 'site-packages.zip')
|
||||||
|
|
||||||
self.prefix += 'import sys; sys.frameworks_dir = "%s"; sys.frozen = "macosx_app"; '%fd
|
self.prefix += 'import sys; sys.frameworks_dir = "%s"; sys.frozen = "macosx_app"; '%fd
|
||||||
self.prefix += 'sys.path.insert(0, %s); '%repr(sp)
|
self.prefix += 'sys.path.insert(0, %s); '%repr(sp)
|
||||||
if fd not in os.environ['PATH']:
|
if fd not in os.environ['PATH']:
|
||||||
self.env['PATH'] = os.environ['PATH']+':'+fd
|
self.env['PATH'] = os.environ['PATH']+':'+fd
|
||||||
self.env['PYTHONHOME'] = resources
|
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:
|
else:
|
||||||
self.executable = os.path.join(getattr(sys, 'frozen_path'), 'calibre-parallel') \
|
self.executable = os.path.join(getattr(sys, 'frozen_path'), 'calibre-parallel') \
|
||||||
if isfrozen else 'calibre-parallel'
|
if isfrozen else 'calibre-parallel'
|
||||||
if isfrozen:
|
if isfrozen:
|
||||||
self.env['LD_LIBRARY_PATH'] = getattr(sys, 'frozen_path') + ':' + os.environ.get('LD_LIBRARY_PATH', '')
|
self.env['LD_LIBRARY_PATH'] = getattr(sys, 'frozen_path') + ':' + os.environ.get('LD_LIBRARY_PATH', '')
|
||||||
|
|
||||||
self.spawn_worker_windows = lambda arg : self.spawn_free_spirit_windows(arg, type='worker')
|
self.spawn_worker_windows = lambda arg : self.spawn_free_spirit_windows(arg, type='worker')
|
||||||
self.spawn_worker_linux = lambda arg : self.spawn_free_spirit_linux(arg, type='worker')
|
self.spawn_worker_linux = lambda arg : self.spawn_free_spirit_linux(arg, type='worker')
|
||||||
self.spawn_worker_osx = lambda arg : self.spawn_free_spirit_osx(arg, type='worker')
|
self.spawn_worker_osx = lambda arg : self.spawn_free_spirit_osx(arg, type='worker')
|
||||||
|
|
||||||
for func in ('spawn_free_spirit', 'spawn_worker'):
|
for func in ('spawn_free_spirit', 'spawn_worker'):
|
||||||
setattr(self, func, getattr(self, func+'_'+ext))
|
setattr(self, func, getattr(self, func+'_'+ext))
|
||||||
|
|
||||||
|
|
||||||
def cleanup_child_windows(self, child, name=None, fd=None):
|
def cleanup_child_windows(self, child, name=None, fd=None):
|
||||||
try:
|
try:
|
||||||
child.kill()
|
child.kill()
|
||||||
@ -175,13 +176,13 @@ class WorkerMother(object):
|
|||||||
self.os.unlink(name)
|
self.os.unlink(name)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def cleanup_child_linux(self, child):
|
def cleanup_child_linux(self, child):
|
||||||
try:
|
try:
|
||||||
child.kill()
|
child.kill()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_env(self):
|
def get_env(self):
|
||||||
env = dict(os.environ)
|
env = dict(os.environ)
|
||||||
env['CALIBRE_WORKER'] = '1'
|
env['CALIBRE_WORKER'] = '1'
|
||||||
@ -189,21 +190,21 @@ class WorkerMother(object):
|
|||||||
if hasattr(self, 'env'):
|
if hasattr(self, 'env'):
|
||||||
env.update(self.env)
|
env.update(self.env)
|
||||||
return env
|
return env
|
||||||
|
|
||||||
def spawn_free_spirit_osx(self, arg, type='free_spirit'):
|
def spawn_free_spirit_osx(self, arg, type='free_spirit'):
|
||||||
script = 'from calibre.parallel import main; main(args=["calibre-parallel", %s]);'%repr(arg)
|
script = 'from calibre.parallel import main; main(args=["calibre-parallel", %s]);'%repr(arg)
|
||||||
cmdline = [self.executable, '-c', self.prefix+script]
|
cmdline = [self.executable, '-c', self.prefix+script]
|
||||||
child = WorkerStatus(subprocess.Popen(cmdline, env=self.get_env()))
|
child = WorkerStatus(subprocess.Popen(cmdline, env=self.get_env()))
|
||||||
atexit.register(self.cleanup_child_linux, child)
|
atexit.register(self.cleanup_child_linux, child)
|
||||||
return child
|
return child
|
||||||
|
|
||||||
def spawn_free_spirit_linux(self, arg, type='free_spirit'):
|
def spawn_free_spirit_linux(self, arg, type='free_spirit'):
|
||||||
cmdline = [self.executable, arg]
|
cmdline = [self.executable, arg]
|
||||||
child = WorkerStatus(subprocess.Popen(cmdline,
|
child = WorkerStatus(subprocess.Popen(cmdline,
|
||||||
env=self.get_env(), cwd=getattr(sys, 'frozen_path', None)))
|
env=self.get_env(), cwd=getattr(sys, 'frozen_path', None)))
|
||||||
atexit.register(self.cleanup_child_linux, child)
|
atexit.register(self.cleanup_child_linux, child)
|
||||||
return child
|
return child
|
||||||
|
|
||||||
def spawn_free_spirit_windows(self, arg, type='free_spirit'):
|
def spawn_free_spirit_windows(self, arg, type='free_spirit'):
|
||||||
fd, name = tempfile.mkstemp('.log', 'calibre_'+type+'_')
|
fd, name = tempfile.mkstemp('.log', 'calibre_'+type+'_')
|
||||||
handle = msvcrt.get_osfhandle(fd)
|
handle = msvcrt.get_osfhandle(fd)
|
||||||
@ -226,9 +227,9 @@ class WorkerMother(object):
|
|||||||
child = WorkerStatus(hProcess)
|
child = WorkerStatus(hProcess)
|
||||||
atexit.register(self.cleanup_child_windows, child, name, fd)
|
atexit.register(self.cleanup_child_windows, child, name, fd)
|
||||||
return child
|
return child
|
||||||
|
|
||||||
|
|
||||||
mother = WorkerMother()
|
mother = WorkerMother()
|
||||||
|
|
||||||
_comm_lock = RLock()
|
_comm_lock = RLock()
|
||||||
def write(socket, msg, timeout=5):
|
def write(socket, msg, timeout=5):
|
||||||
@ -236,14 +237,14 @@ def write(socket, msg, timeout=5):
|
|||||||
Write a message on socket. If `msg` is unicode, it is encoded in utf-8.
|
Write a message on socket. If `msg` is unicode, it is encoded in utf-8.
|
||||||
Raises a `RuntimeError` if the socket is not ready for writing or the writing fails.
|
Raises a `RuntimeError` if the socket is not ready for writing or the writing fails.
|
||||||
`msg` is broken into chunks of size 4096 and sent. The :function:`read` function
|
`msg` is broken into chunks of size 4096 and sent. The :function:`read` function
|
||||||
automatically re-assembles the chunks into whole message.
|
automatically re-assembles the chunks into whole message.
|
||||||
'''
|
'''
|
||||||
if isworker:
|
if isworker:
|
||||||
_comm_lock.acquire()
|
_comm_lock.acquire()
|
||||||
try:
|
try:
|
||||||
if isinstance(msg, unicode):
|
if isinstance(msg, unicode):
|
||||||
msg = msg.encode('utf-8')
|
msg = msg.encode('utf-8')
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
print >>sys.__stdout__, 'write(%s):'%('worker' if isworker else 'overseer'), repr(msg)
|
print >>sys.__stdout__, 'write(%s):'%('worker' if isworker else 'overseer'), repr(msg)
|
||||||
length = None
|
length = None
|
||||||
while len(msg) > 0:
|
while len(msg) > 0:
|
||||||
@ -260,12 +261,12 @@ def write(socket, msg, timeout=5):
|
|||||||
raise RuntimeError('Failed to write chunk to socket')
|
raise RuntimeError('Failed to write chunk to socket')
|
||||||
finally:
|
finally:
|
||||||
if isworker:
|
if isworker:
|
||||||
_comm_lock.release()
|
_comm_lock.release()
|
||||||
|
|
||||||
def read(socket, timeout=5):
|
def read(socket, timeout=5):
|
||||||
'''
|
'''
|
||||||
Read a message from `socket`. The message must have been sent with the :function:`write`
|
Read a message from `socket`. The message must have been sent with the :function:`write`
|
||||||
function. Raises a `RuntimeError` if the message is corrpted. Can return an
|
function. Raises a `RuntimeError` if the message is corrpted. Can return an
|
||||||
empty string.
|
empty string.
|
||||||
'''
|
'''
|
||||||
if isworker:
|
if isworker:
|
||||||
@ -298,25 +299,25 @@ def read(socket, timeout=5):
|
|||||||
|
|
||||||
class RepeatingTimer(Thread):
|
class RepeatingTimer(Thread):
|
||||||
'''
|
'''
|
||||||
Calls a specified function repeatedly at a specified interval. Runs in a
|
Calls a specified function repeatedly at a specified interval. Runs in a
|
||||||
daemon thread (i.e. the interpreter can exit while it is still running).
|
daemon thread (i.e. the interpreter can exit while it is still running).
|
||||||
Call :meth:`start()` to start it.
|
Call :meth:`start()` to start it.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def repeat(self):
|
def repeat(self):
|
||||||
while True:
|
while True:
|
||||||
self.event.wait(self.interval)
|
self.event.wait(self.interval)
|
||||||
if self.event.isSet():
|
if self.event.isSet():
|
||||||
break
|
break
|
||||||
self.action()
|
self.action()
|
||||||
|
|
||||||
def __init__(self, interval, func, name):
|
def __init__(self, interval, func, name):
|
||||||
self.event = Event()
|
self.event = Event()
|
||||||
self.interval = interval
|
self.interval = interval
|
||||||
self.action = func
|
self.action = func
|
||||||
Thread.__init__(self, target=self.repeat, name=name)
|
Thread.__init__(self, target=self.repeat, name=name)
|
||||||
self.setDaemon(True)
|
self.setDaemon(True)
|
||||||
|
|
||||||
class ControlError(Exception):
|
class ControlError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -325,34 +326,33 @@ class Overseer(object):
|
|||||||
Responsible for controlling worker processes. The main interface is the
|
Responsible for controlling worker processes. The main interface is the
|
||||||
methods, :meth:`initialize_job`, :meth:`control`.
|
methods, :meth:`initialize_job`, :meth:`control`.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
KILL_RESULT = 'Server: job killed by user|||#@#$%&*)*(*$#$%#$@&'
|
KILL_RESULT = 'Server: job killed by user|||#@#$%&*)*(*$#$%#$@&'
|
||||||
INTERVAL = 0.1
|
INTERVAL = 0.1
|
||||||
|
|
||||||
def __init__(self, server, port, timeout=5):
|
def __init__(self, server, port, timeout=5):
|
||||||
self.worker_status = mother.spawn_worker('127.0.0.1:'+str(port))
|
self.worker_status = mother.spawn_worker('127.0.0.1:'+str(port))
|
||||||
self.socket = server.accept()[0]
|
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.os = os
|
||||||
self.signal = signal
|
self.signal = signal
|
||||||
self.on_probation = False
|
self.on_probation = False
|
||||||
self.terminated = False
|
self.terminated = False
|
||||||
|
|
||||||
self.working = False
|
self.working = False
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.last_job_time = time.time()
|
self.last_job_time = time.time()
|
||||||
self.job_id = None
|
|
||||||
self._stop = False
|
self._stop = False
|
||||||
if not select([self.socket], [], [], 120)[0]:
|
if not select([self.socket], [], [], 120)[0]:
|
||||||
raise RuntimeError(_('Could not launch worker process.'))
|
raise RuntimeError(_('Could not launch worker process.'))
|
||||||
ID = self.read().split(':')
|
ID = self.read().split(':')
|
||||||
if ID[0] != 'CALIBRE_WORKER':
|
if ID[0] != 'CALIBRE_WORKER':
|
||||||
raise RuntimeError('Impostor')
|
raise RuntimeError('Impostor')
|
||||||
self.worker_pid = int(ID[1])
|
self.worker_pid = int(ID[1])
|
||||||
self.write('OK')
|
self.write('OK')
|
||||||
if self.read() != 'WAITING':
|
if self.read() != 'WAITING':
|
||||||
raise RuntimeError('Worker sulking')
|
raise RuntimeError('Worker sulking')
|
||||||
|
|
||||||
def terminate(self):
|
def terminate(self):
|
||||||
'Kill worker process.'
|
'Kill worker process.'
|
||||||
self.terminated = True
|
self.terminated = True
|
||||||
@ -379,50 +379,48 @@ class Overseer(object):
|
|||||||
self.worker_status.kill()
|
self.worker_status.kill()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def write(self, msg, timeout=None):
|
def write(self, msg, timeout=None):
|
||||||
write(self.socket, msg, timeout=self.timeout if timeout is None else timeout)
|
write(self.socket, msg, timeout=self.timeout if timeout is None else timeout)
|
||||||
|
|
||||||
def read(self, timeout=None):
|
def read(self, timeout=None):
|
||||||
return read(self.socket, timeout=self.timeout if timeout is None else timeout)
|
return read(self.socket, timeout=self.timeout if timeout is None else timeout)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return hasattr(other, 'process') and hasattr(other, 'worker_pid') and self.worker_pid == other.worker_pid
|
return hasattr(other, 'process') and hasattr(other, 'worker_pid') and self.worker_pid == other.worker_pid
|
||||||
|
|
||||||
def is_viable(self):
|
def is_viable(self):
|
||||||
if self.terminated:
|
if self.terminated:
|
||||||
return False
|
return False
|
||||||
return self.worker_status.is_alive()
|
return self.worker_status.is_alive()
|
||||||
|
|
||||||
def select(self, timeout=0):
|
def select(self, timeout=0):
|
||||||
return select([self.socket], [self.socket], [self.socket], timeout)
|
return select([self.socket], [self.socket], [self.socket], timeout)
|
||||||
|
|
||||||
def initialize_job(self, job):
|
def initialize_job(self, job):
|
||||||
'''
|
'''
|
||||||
Sends `job` to worker process. Can raise `ControlError` if worker process
|
Sends `job` to worker process. Can raise `ControlError` if worker process
|
||||||
does not respond appropriately. In this case, this Overseer is useless
|
does not respond appropriately. In this case, this Overseer is useless
|
||||||
and should be discarded.
|
and should be discarded.
|
||||||
|
|
||||||
`job`: An instance of :class:`Job`.
|
`job`: An instance of :class:`Job`.
|
||||||
'''
|
'''
|
||||||
self.job_id = job.job_id
|
|
||||||
self.working = True
|
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()
|
msg = self.read()
|
||||||
if msg != 'OK':
|
if msg != 'OK':
|
||||||
raise ControlError('Failed to initialize job on worker %d:%s'%(self.worker_pid, msg))
|
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.job = job
|
||||||
self.last_report = time.time()
|
self.last_report = time.time()
|
||||||
|
job.start_work()
|
||||||
|
|
||||||
def control(self):
|
def control(self):
|
||||||
'''
|
'''
|
||||||
Listens for messages from the worker process and dispatches them
|
Listens for messages from the worker process and dispatches them
|
||||||
appropriately. If the worker process dies unexpectedly, returns a result
|
appropriately. If the worker process dies unexpectedly, returns a result
|
||||||
of None with a ControlError indicating the worker died.
|
of None with a ControlError indicating the worker died.
|
||||||
|
|
||||||
Returns a :class:`Result` instance or None, if the worker is still working.
|
Returns a :class:`Result` instance or None, if the worker is still working.
|
||||||
'''
|
'''
|
||||||
if select([self.socket],[],[],0)[0]:
|
if select([self.socket],[],[],0)[0]:
|
||||||
@ -433,7 +431,9 @@ class Overseer(object):
|
|||||||
else:
|
else:
|
||||||
if self.on_probation:
|
if self.on_probation:
|
||||||
self.terminate()
|
self.terminate()
|
||||||
return Result(None, ControlError('Worker process died unexpectedly'), '')
|
self.job.result = None
|
||||||
|
self.job.exception = ControlError('Worker process died unexpectedly')
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
self.on_probation = True
|
self.on_probation = True
|
||||||
return
|
return
|
||||||
@ -443,13 +443,14 @@ class Overseer(object):
|
|||||||
return
|
return
|
||||||
elif word == 'RESULT':
|
elif word == 'RESULT':
|
||||||
self.write('OK')
|
self.write('OK')
|
||||||
return Result(cPickle.loads(msg), None, None)
|
self.job.result = cPickle.loads(msg)
|
||||||
|
return True
|
||||||
elif word == 'OUTPUT':
|
elif word == 'OUTPUT':
|
||||||
self.write('OK')
|
self.write('OK')
|
||||||
try:
|
try:
|
||||||
self.output(''.join(cPickle.loads(msg)))
|
self.job.output(''.join(cPickle.loads(msg)))
|
||||||
except:
|
except:
|
||||||
self.output('Bad output message: '+ repr(msg))
|
self.job.output('Bad output message: '+ repr(msg))
|
||||||
elif word == 'PROGRESS':
|
elif word == 'PROGRESS':
|
||||||
self.write('OK')
|
self.write('OK')
|
||||||
percent = None
|
percent = None
|
||||||
@ -457,45 +458,154 @@ class Overseer(object):
|
|||||||
percent, msg = cPickle.loads(msg)[-1]
|
percent, msg = cPickle.loads(msg)[-1]
|
||||||
except:
|
except:
|
||||||
print 'Bad progress update:', repr(msg)
|
print 'Bad progress update:', repr(msg)
|
||||||
if self.progress and percent is not None:
|
if percent is not None:
|
||||||
self.progress(percent, msg)
|
self.job.update_status(percent, msg)
|
||||||
elif word == 'ERROR':
|
elif word == 'ERROR':
|
||||||
self.write('OK')
|
self.write('OK')
|
||||||
return Result(None, *cPickle.loads(msg))
|
self.job.excetion, self.job.traceback = cPickle.loads(msg)
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
self.terminate()
|
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:
|
if not self.worker_status.is_alive() or time.time() - self.last_report > 180:
|
||||||
self.terminate()
|
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):
|
class Job(object):
|
||||||
|
|
||||||
def __init__(self, job_id, func, args, kwdargs, output, progress, done):
|
def __init__(self, job_done, job_manager=None,
|
||||||
self.job_id = job_id
|
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.func = func
|
||||||
self.args = args
|
self.done = self.job_done
|
||||||
self.kwdargs = kwdargs
|
|
||||||
self.output = output
|
|
||||||
self.progress = progress
|
|
||||||
self.done = 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):
|
def remove_ipc_socket(path):
|
||||||
os = __import__('os')
|
os = __import__('os')
|
||||||
@ -503,12 +613,12 @@ def remove_ipc_socket(path):
|
|||||||
os.unlink(path)
|
os.unlink(path)
|
||||||
|
|
||||||
class Server(Thread):
|
class Server(Thread):
|
||||||
|
|
||||||
KILL_RESULT = Overseer.KILL_RESULT
|
KILL_RESULT = Overseer.KILL_RESULT
|
||||||
START_PORT = 10013
|
START_PORT = 10013
|
||||||
PID = os.getpid()
|
PID = os.getpid()
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, number_of_workers=detect_ncpus()):
|
def __init__(self, number_of_workers=detect_ncpus()):
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.setDaemon(True)
|
self.setDaemon(True)
|
||||||
@ -524,8 +634,8 @@ class Server(Thread):
|
|||||||
if not iswindows:
|
if not iswindows:
|
||||||
atexit.register(remove_ipc_socket, self.port)
|
atexit.register(remove_ipc_socket, self.port)
|
||||||
self.server_socket.listen(5)
|
self.server_socket.listen(5)
|
||||||
self.number_of_workers = number_of_workers
|
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.killall)
|
||||||
atexit.register(self.close)
|
atexit.register(self.close)
|
||||||
self.job_lock = RLock()
|
self.job_lock = RLock()
|
||||||
@ -534,26 +644,19 @@ class Server(Thread):
|
|||||||
self.result_lock = RLock()
|
self.result_lock = RLock()
|
||||||
self.pool_lock = RLock()
|
self.pool_lock = RLock()
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
try:
|
try:
|
||||||
self.server_socket.shutdown(socket.SHUT_RDWR)
|
self.server_socket.shutdown(socket.SHUT_RDWR)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def add_job(self, job):
|
def add_job(self, job):
|
||||||
with self.job_lock:
|
with self.job_lock:
|
||||||
self.jobs.append(job)
|
self.jobs.append(job)
|
||||||
|
if job.job_manager is not None:
|
||||||
|
job.job_manager.add_job(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)
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while True:
|
while True:
|
||||||
job = None
|
job = None
|
||||||
@ -575,81 +678,75 @@ class Server(Thread):
|
|||||||
o.initialize_job(job)
|
o.initialize_job(job)
|
||||||
except Exception, err:
|
except Exception, err:
|
||||||
o.terminate()
|
o.terminate()
|
||||||
res = Result(None, unicode(err), traceback.format_exc())
|
job.exception = err
|
||||||
job.done(res)
|
job.traceback = traceback.format_exc()
|
||||||
|
job.done()
|
||||||
o = None
|
o = None
|
||||||
if o and o.is_viable():
|
if o and o.is_viable():
|
||||||
with self.working_lock:
|
with self.working_lock:
|
||||||
self.working.append(o)
|
self.working.append(o)
|
||||||
|
|
||||||
with self.working_lock:
|
with self.working_lock:
|
||||||
done = []
|
done = []
|
||||||
for o in self.working:
|
for o in self.working:
|
||||||
try:
|
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:
|
except Exception, err:
|
||||||
res = Result(None, unicode(err), traceback.format_exc())
|
o.job.exception = err
|
||||||
|
o.job.traceback = traceback.format_exc()
|
||||||
o.terminate()
|
o.terminate()
|
||||||
if isinstance(res, Result):
|
o.job.done()
|
||||||
o.job.done(res)
|
|
||||||
done.append(o)
|
done.append(o)
|
||||||
for o in done:
|
for o in done:
|
||||||
self.working.remove(o)
|
self.working.remove(o)
|
||||||
if o and o.is_viable():
|
if o and o.is_viable():
|
||||||
with self.pool_lock:
|
with self.pool_lock:
|
||||||
self.pool.append(o)
|
self.pool.append(o)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
except:
|
except:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def killall(self):
|
def killall(self):
|
||||||
with self.pool_lock:
|
with self.pool_lock:
|
||||||
map(lambda x: x.terminate(), self.pool)
|
map(lambda x: x.terminate(), self.pool)
|
||||||
self.pool = []
|
self.pool = []
|
||||||
|
|
||||||
|
|
||||||
def kill(self, job_id):
|
def kill(self, job):
|
||||||
with self.working_lock:
|
with self.working_lock:
|
||||||
pop = None
|
pop = None
|
||||||
for o in self.working:
|
for o in self.working:
|
||||||
if o.job_id == job_id:
|
if o.job == job or o == job:
|
||||||
o.terminate()
|
try:
|
||||||
o.job.done(Result(self.KILL_RESULT, None, ''))
|
o.terminate()
|
||||||
|
except: pass
|
||||||
|
o.job.exception = JobKilled(_('Job stopped by user'))
|
||||||
|
try:
|
||||||
|
o.job.done()
|
||||||
|
except: pass
|
||||||
pop = o
|
pop = o
|
||||||
break
|
break
|
||||||
if pop is not None:
|
if pop is not None:
|
||||||
self.working.remove(pop)
|
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={}):
|
def run_free_job(self, func, args=[], kwdargs={}):
|
||||||
pt = PersistentTemporaryFile('.pickle', '_IPC_')
|
pt = PersistentTemporaryFile('.pickle', '_IPC_')
|
||||||
pt.write(cPickle.dumps((func, args, kwdargs)))
|
pt.write(cPickle.dumps((func, args, kwdargs)))
|
||||||
pt.close()
|
pt.close()
|
||||||
mother.spawn_free_spirit(binascii.hexlify(pt.name))
|
mother.spawn_free_spirit(binascii.hexlify(pt.name))
|
||||||
|
|
||||||
|
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
##################################### CLIENT CODE #####################################
|
##################################### CLIENT CODE #####################################
|
||||||
##########################################################################################
|
##########################################################################################
|
||||||
|
|
||||||
class BufferedSender(object):
|
class BufferedSender(object):
|
||||||
|
|
||||||
def __init__(self, socket):
|
def __init__(self, socket):
|
||||||
self.socket = socket
|
self.socket = socket
|
||||||
self.wbuf, self.pbuf = [], []
|
self.wbuf, self.pbuf = [], []
|
||||||
@ -657,14 +754,14 @@ class BufferedSender(object):
|
|||||||
self.last_report = None
|
self.last_report = None
|
||||||
self.timer = RepeatingTimer(0.5, self.send, 'BufferedSender')
|
self.timer = RepeatingTimer(0.5, self.send, 'BufferedSender')
|
||||||
self.timer.start()
|
self.timer.start()
|
||||||
|
|
||||||
|
|
||||||
def write(self, msg):
|
def write(self, msg):
|
||||||
if not isinstance(msg, basestring):
|
if not isinstance(msg, basestring):
|
||||||
msg = unicode(msg)
|
msg = unicode(msg)
|
||||||
with self.wlock:
|
with self.wlock:
|
||||||
self.wbuf.append(msg)
|
self.wbuf.append(msg)
|
||||||
|
|
||||||
def send(self):
|
def send(self):
|
||||||
if callable(select) and select([self.socket], [], [], 0)[0]:
|
if callable(select) and select([self.socket], [], [], 0)[0]:
|
||||||
msg = read(self.socket)
|
msg = read(self.socket)
|
||||||
@ -687,7 +784,7 @@ class BufferedSender(object):
|
|||||||
write(self.socket, 'OUTPUT:'+msg)
|
write(self.socket, 'OUTPUT:'+msg)
|
||||||
read(self.socket, 10)
|
read(self.socket, 10)
|
||||||
reported = True
|
reported = True
|
||||||
|
|
||||||
with self.plock:
|
with self.plock:
|
||||||
if self.pbuf:
|
if self.pbuf:
|
||||||
msg = cPickle.dumps(self.pbuf, -1)
|
msg = cPickle.dumps(self.pbuf, -1)
|
||||||
@ -703,11 +800,11 @@ class BufferedSender(object):
|
|||||||
write(self.socket, 'PING:')
|
write(self.socket, 'PING:')
|
||||||
read(self.socket, 10)
|
read(self.socket, 10)
|
||||||
self.last_report = time.time()
|
self.last_report = time.time()
|
||||||
|
|
||||||
def notify(self, percent, msg=''):
|
def notify(self, percent, msg=''):
|
||||||
with self.plock:
|
with self.plock:
|
||||||
self.pbuf.append((percent, msg))
|
self.pbuf.append((percent, msg))
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -725,13 +822,13 @@ def work(client_socket, func, args, kwdargs):
|
|||||||
kargs[notification] = sys.stdout.notify
|
kargs[notification] = sys.stdout.notify
|
||||||
kargs.update(kwdargs)
|
kargs.update(kwdargs)
|
||||||
res = func(*args, **kargs)
|
res = func(*args, **kargs)
|
||||||
if hasattr(sys.stdout, 'send'):
|
if hasattr(sys.stdout, 'send'):
|
||||||
sys.stdout.send()
|
sys.stdout.send()
|
||||||
return res
|
return res
|
||||||
finally:
|
finally:
|
||||||
sys.stdout.last_report = None
|
sys.stdout.last_report = None
|
||||||
time.sleep(5) # Give any in progress BufferedSend time to complete
|
time.sleep(5) # Give any in progress BufferedSend time to complete
|
||||||
|
|
||||||
|
|
||||||
def worker(host, port):
|
def worker(host, port):
|
||||||
client_socket = socket.socket(SOCKET_TYPE, socket.SOCK_STREAM)
|
client_socket = socket.socket(SOCKET_TYPE, socket.SOCK_STREAM)
|
||||||
@ -745,7 +842,7 @@ def worker(host, port):
|
|||||||
|
|
||||||
sys.stdout = BufferedSender(client_socket)
|
sys.stdout = BufferedSender(client_socket)
|
||||||
sys.stderr = sys.stdout
|
sys.stderr = sys.stdout
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if not select([client_socket], [], [], 60)[0]:
|
if not select([client_socket], [], [], 60)[0]:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
@ -766,7 +863,7 @@ def worker(host, port):
|
|||||||
break
|
break
|
||||||
gc.collect()
|
gc.collect()
|
||||||
elif msg == 'PING:':
|
elif msg == 'PING:':
|
||||||
write(client_socket, 'OK')
|
write(client_socket, 'OK')
|
||||||
elif msg == 'STOP:':
|
elif msg == 'STOP:':
|
||||||
client_socket.shutdown(socket.SHUT_RDWR)
|
client_socket.shutdown(socket.SHUT_RDWR)
|
||||||
return 0
|
return 0
|
||||||
@ -775,7 +872,7 @@ def worker(host, port):
|
|||||||
else:
|
else:
|
||||||
print >>sys.__stderr__, 'Invalid protocols message', msg
|
print >>sys.__stderr__, 'Invalid protocols message', msg
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def free_spirit(path):
|
def free_spirit(path):
|
||||||
func, args, kwdargs = cPickle.load(open(path, 'rb'))
|
func, args, kwdargs = cPickle.load(open(path, 'rb'))
|
||||||
try:
|
try:
|
||||||
@ -785,7 +882,7 @@ def free_spirit(path):
|
|||||||
func, kargs = get_func(func)[:2]
|
func, kargs = get_func(func)[:2]
|
||||||
kargs.update(kwdargs)
|
kargs.update(kwdargs)
|
||||||
func(*args, **kargs)
|
func(*args, **kargs)
|
||||||
|
|
||||||
def main(args=sys.argv):
|
def main(args=sys.argv):
|
||||||
global isworker
|
global isworker
|
||||||
isworker = True
|
isworker = True
|
||||||
@ -793,9 +890,9 @@ def main(args=sys.argv):
|
|||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
free_spirit(binascii.unhexlify(re.sub(r'[^a-f0-9A-F]', '', args[0])))
|
free_spirit(binascii.unhexlify(re.sub(r'[^a-f0-9A-F]', '', args[0])))
|
||||||
else:
|
else:
|
||||||
worker(args[0].replace("'", ''), int(args[1]) if iswindows else args[1])
|
worker(args[0].replace("'", ''), int(args[1]) if iswindows else args[1])
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|
||||||
|
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'),
|
('ImageMagick', '6.3.5', 'imagemagick', 'imagemagick', 'ImageMagick'),
|
||||||
('xdg-utils', '1.0.2', 'xdg-utils', 'xdg-utils', 'xdg-utils'),
|
('xdg-utils', '1.0.2', 'xdg-utils', 'xdg-utils', 'xdg-utils'),
|
||||||
('dbus-python', '0.82.2', 'dbus-python', 'python-dbus', 'dbus-python'),
|
('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'),
|
('lxml', '1.3.3', 'lxml', 'python-lxml', 'python-lxml'),
|
||||||
('help2man', '1.36.4', 'help2man', 'help2man', 'help2man'),
|
('help2man', '1.36.4', 'help2man', 'help2man', 'help2man'),
|
||||||
]
|
]
|
||||||
@ -49,10 +48,7 @@ class Distribution(object):
|
|||||||
'fedora':'Fedora 8', 'debian':'Debian Sid', 'generic': 'Generic Unix'}
|
'fedora':'Fedora 8', 'debian':'Debian Sid', 'generic': 'Generic Unix'}
|
||||||
|
|
||||||
MANUAL_MAP = {
|
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.4.0 and PyQt to at least 4.4.2</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>',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, os):
|
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 = ''
|
EMPTYSTRING = ''
|
||||||
|
|
||||||
from calibre import __appname__
|
from calibre.constants import __appname__
|
||||||
from calibre import __version__ as version
|
from calibre.constants import __version__ as version
|
||||||
|
|
||||||
# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's
|
# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's
|
||||||
# there.
|
# there.
|
||||||
@ -641,4 +641,4 @@ def main(outfile, args=sys.argv[1:]):
|
|||||||
eater.write(outfile)
|
eater.write(outfile)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main(sys.stdout)
|
main(sys.stdout)
|
||||||
|
@ -6,36 +6,54 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: calibre 0.4.55\n"
|
"Project-Id-Version: calibre 0.4.55\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2008-07-21 22:18+0000\n"
|
"POT-Creation-Date: 2008-08-03 09:01+0000\n"
|
||||||
"PO-Revision-Date: 2008-07-22 05:27+0000\n"
|
"PO-Revision-Date: 2008-07-22 05:50+0000\n"
|
||||||
"Last-Translator: Kovid Goyal <Unknown>\n"
|
"Last-Translator: Kovid Goyal <Unknown>\n"
|
||||||
"Language-Team: ru\n"
|
"Language-Team: ru\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\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"
|
"X-Generator: Launchpad (build Unknown)\n"
|
||||||
"Generated-By: pygettext.py 1.5\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
|
#, fuzzy
|
||||||
msgid "%sUsage%s: %s\n"
|
msgid "%sUsage%s: %s\n"
|
||||||
msgstr "%sИспользовано%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 "
|
msgid "Created by "
|
||||||
msgstr "Сделано "
|
msgstr "Сделано "
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:112
|
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:113
|
||||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:146
|
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:147
|
||||||
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:174
|
#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:175
|
||||||
msgid "Unable to detect the %s disk drive. Try rebooting."
|
msgid "Unable to detect the %s disk drive. Try rebooting."
|
||||||
msgstr "Не удалось определить диск %s. Попробуйте перезагрузиться."
|
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."
|
msgid "The reader has no storage card connected."
|
||||||
msgstr "К ридеру не подключена карта памяти."
|
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
|
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:71
|
||||||
msgid "Set the title. Default: filename."
|
msgid "Set the title. Default: filename."
|
||||||
msgstr "Укажите заголовок. По умолчанию: имя файла."
|
msgstr "Укажите заголовок. По умолчанию: имя файла."
|
||||||
@ -50,14 +68,17 @@ msgstr ""
|
|||||||
"умолчанию: %default"
|
"умолчанию: %default"
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:74
|
#: /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/__init__.py:174
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:314
|
#: /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/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:278
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/library.py:685
|
#: /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/gui2/main.py:926
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database.py:1387
|
#: /home/kovid/work/calibre/src/calibre/library/database.py:904
|
||||||
#: /home/kovid/work/calibre/src/calibre/library/database.py:1517
|
#: /home/kovid/work/calibre/src/calibre/library/database.py:1412
|
||||||
|
#: /home/kovid/work/calibre/src/calibre/library/database.py:1542
|
||||||
msgid "Unknown"
|
msgid "Unknown"
|
||||||
msgstr "Неизвестно"
|
msgstr "Неизвестно"
|
||||||
|
|
||||||
@ -414,6 +435,88 @@ msgstr ""
|
|||||||
msgid "No file to convert specified."
|
msgid "No file to convert specified."
|
||||||
msgstr "Не указан файл для преобразования."
|
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
|
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/epub/convert_from.py:17
|
||||||
msgid ""
|
msgid ""
|
||||||
"Usage: %prog [options] mybook.epub\n"
|
"Usage: %prog [options] mybook.epub\n"
|
||||||
@ -515,40 +618,40 @@ msgid ""
|
|||||||
"%s"
|
"%s"
|
||||||
msgstr ""
|
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 ""
|
msgid ""
|
||||||
"An error occurred while processing a table: %s. Ignoring table markup."
|
"An error occurred while processing a table: %s. Ignoring table markup."
|
||||||
msgstr ""
|
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 ""
|
msgid ""
|
||||||
"Bad table:\n"
|
"Bad table:\n"
|
||||||
"%s"
|
"%s"
|
||||||
msgstr ""
|
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"
|
msgid "Table has cell that is too large"
|
||||||
msgstr ""
|
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 ""
|
msgid ""
|
||||||
"You have to save the website %s as an html file first and then run html2lrf "
|
"You have to save the website %s as an html file first and then run html2lrf "
|
||||||
"on it."
|
"on it."
|
||||||
msgstr ""
|
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"
|
msgid "Could not read cover image: %s"
|
||||||
msgstr ""
|
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"
|
msgid "Cannot read from: %s"
|
||||||
msgstr ""
|
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"
|
msgid "Failed to process opf file"
|
||||||
msgstr ""
|
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 ""
|
msgid ""
|
||||||
"Usage: %prog [options] mybook.html\n"
|
"Usage: %prog [options] mybook.html\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -559,7 +662,7 @@ msgid ""
|
|||||||
"convert a whole tree of HTML files."
|
"convert a whole tree of HTML files."
|
||||||
msgstr ""
|
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 ""
|
msgid ""
|
||||||
"Usage: %prog [options] mybook.lit\n"
|
"Usage: %prog [options] mybook.lit\n"
|
||||||
"\n"
|
"\n"
|
||||||
@ -741,11 +844,10 @@ msgstr ""
|
|||||||
msgid "Set the comment"
|
msgid "Set the comment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:100
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:117
|
||||||
msgid "mybook.epub"
|
msgid "A comma separated list of tags to set"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:100
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:50
|
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/fb2.py:50
|
||||||
msgid "Usage:"
|
msgid "Usage:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -803,11 +905,11 @@ msgid ""
|
|||||||
"Fetch a cover image for the book identified by ISBN from LibraryThing.com\n"
|
"Fetch a cover image for the book identified by ISBN from LibraryThing.com\n"
|
||||||
msgstr ""
|
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"
|
msgid "Usage: %s file.lit"
|
||||||
msgstr ""
|
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"
|
msgid "Cover saved to"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -819,22 +921,14 @@ msgstr ""
|
|||||||
msgid "No filename specified."
|
msgid "No filename specified."
|
||||||
msgstr ""
|
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"
|
msgid "%prog [options] myebook.mobi"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:393
|
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:423
|
||||||
msgid "Output directory. Defaults to current directory."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:412
|
|
||||||
msgid "Raw MOBI HTML saved in"
|
msgid "Raw MOBI HTML saved in"
|
||||||
msgstr ""
|
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:22
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:26
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:26
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:36
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:36
|
||||||
@ -855,6 +949,7 @@ msgid "Comments"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:55
|
#: /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"
|
msgid "Dialog"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -869,6 +964,51 @@ msgstr ""
|
|||||||
msgid "Choose Format"
|
msgid "Choose Format"
|
||||||
msgstr ""
|
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
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:23
|
||||||
msgid "Basic"
|
msgid "Basic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -877,47 +1017,47 @@ msgstr ""
|
|||||||
msgid "Advanced"
|
msgid "Advanced"
|
||||||
msgstr ""
|
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."
|
msgid "<br>Must be a directory."
|
||||||
msgstr ""
|
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 "
|
msgid "Invalid database location "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:105
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:107
|
||||||
#: /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"
|
msgid "Invalid database location"
|
||||||
msgstr ""
|
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 "
|
msgid "Invalid database location.<br>Cannot write to "
|
||||||
msgstr ""
|
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."
|
msgid "Compacting database. This may take a while."
|
||||||
msgstr ""
|
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..."
|
msgid "Compacting..."
|
||||||
msgstr ""
|
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
|
#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:265
|
||||||
msgid "Configuration"
|
msgid "Configuration"
|
||||||
msgstr ""
|
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)"
|
msgid "&Location of books database (library1.db)"
|
||||||
msgstr ""
|
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"
|
msgid "Browse for the new database location"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:219
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:222
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:237
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:241
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:239
|
#: /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/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:266
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:285
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:285
|
||||||
@ -937,89 +1077,93 @@ msgstr ""
|
|||||||
msgid "..."
|
msgid "..."
|
||||||
msgstr ""
|
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"
|
msgid "Use &Roman numerals for series number"
|
||||||
msgstr ""
|
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):"
|
msgid "&Number of covers to show in browse mode (after restart):"
|
||||||
msgstr ""
|
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"
|
msgid "Show notification when &new version is available"
|
||||||
msgstr ""
|
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:"
|
msgid "Format for &single file save:"
|
||||||
msgstr ""
|
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:"
|
msgid "&Priority for conversion jobs:"
|
||||||
msgstr ""
|
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:"
|
msgid "Default network &timeout:"
|
||||||
msgstr ""
|
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 ""
|
msgid ""
|
||||||
"Set the default timeout for network fetches (i.e. anytime we go out to the "
|
"Set the default timeout for network fetches (i.e. anytime we go out to the "
|
||||||
"internet to get information)"
|
"internet to get information)"
|
||||||
msgstr ""
|
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"
|
msgid " seconds"
|
||||||
msgstr ""
|
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"
|
msgid "Toolbar"
|
||||||
msgstr ""
|
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"
|
msgid "Large"
|
||||||
msgstr ""
|
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"
|
msgid "Medium"
|
||||||
msgstr ""
|
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"
|
msgid "Small"
|
||||||
msgstr ""
|
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"
|
msgid "&Button size in toolbar"
|
||||||
msgstr ""
|
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"
|
msgid "Show &text in toolbar buttons"
|
||||||
msgstr ""
|
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"
|
msgid "Select visible &columns in library view"
|
||||||
msgstr ""
|
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"
|
msgid "Frequently used directories"
|
||||||
msgstr ""
|
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"
|
msgid "Add a directory to the frequently used directories list"
|
||||||
msgstr ""
|
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"
|
msgid "Remove a directory from the frequently used directories list"
|
||||||
msgstr ""
|
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"
|
msgid "Free unused diskspace from the database"
|
||||||
msgstr ""
|
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"
|
msgid "&Compact database"
|
||||||
msgstr ""
|
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"
|
msgid "&Metadata from file name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -1151,7 +1295,6 @@ msgid "Convert %s to LRF"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:108
|
#: /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"
|
msgid "Set conversion defaults"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -1415,10 +1558,6 @@ msgstr ""
|
|||||||
msgid "Override<br>CSS"
|
msgid "Override<br>CSS"
|
||||||
msgstr ""
|
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
|
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:553
|
||||||
msgid "&Left Margin:"
|
msgid "&Left Margin:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2261,23 +2400,31 @@ msgstr ""
|
|||||||
msgid "Bulk convert"
|
msgid "Bulk convert"
|
||||||
msgstr ""
|
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."
|
msgid " detected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:309
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:311
|
||||||
msgid "Device: "
|
msgid "Device: "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:334
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:336
|
||||||
msgid "Connected "
|
msgid "Connected "
|
||||||
msgstr ""
|
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"
|
msgid "Device database corrupted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:347
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:349
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
" <p>The database of books on the reader is corrupted. Try the "
|
" <p>The database of books on the reader is corrupted. Try the "
|
||||||
@ -2293,190 +2440,222 @@ msgid ""
|
|||||||
" "
|
" "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:399
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:401
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:473
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:475
|
||||||
msgid ""
|
msgid ""
|
||||||
"<p>Books with the same title as the following already exist in the database. "
|
"<p>Books with the same title as the following already exist in the database. "
|
||||||
"Add them anyway?<ul>"
|
"Add them anyway?<ul>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:402
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:404
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:476
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:478
|
||||||
msgid "Duplicates found!"
|
msgid "Duplicates found!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:435
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:437
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:448
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:450
|
||||||
msgid "Uploading books to device."
|
msgid "Uploading books to device."
|
||||||
msgstr ""
|
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"
|
msgid "No space on device"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:508
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:510
|
||||||
msgid ""
|
msgid ""
|
||||||
"<p>Cannot upload books to device there is no more free space available "
|
"<p>Cannot upload books to device there is no more free space available "
|
||||||
msgstr ""
|
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."
|
msgid "Deleting books from device."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:578
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:586
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:599
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:607
|
||||||
msgid "Cannot edit metadata"
|
msgid "Cannot edit metadata"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:578
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:586
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:599
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:607
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:694
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:702
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:764
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:772
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:834
|
|
||||||
msgid "No books selected"
|
msgid "No books selected"
|
||||||
msgstr ""
|
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."
|
msgid "Sending books to device."
|
||||||
msgstr ""
|
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"
|
msgid "No suitable formats"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:678
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:686
|
||||||
msgid ""
|
msgid ""
|
||||||
"Could not upload the following books to the device, as no suitable formats "
|
"Could not upload the following books to the device, as no suitable formats "
|
||||||
"were found:<br><ul>%s</ul>"
|
"were found:<br><ul>%s</ul>"
|
||||||
msgstr ""
|
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"
|
msgid "Cannot save to disk"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:705
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:713
|
||||||
msgid ""
|
msgid ""
|
||||||
"<p>Could not save the following books to disk, because the %s format is not "
|
"<p>Could not save the following books to disk, because the %s format is not "
|
||||||
"available for them:<ul>"
|
"available for them:<ul>"
|
||||||
msgstr ""
|
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"
|
msgid "Could not save some ebooks"
|
||||||
msgstr ""
|
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 "
|
msgid "Fetch news from "
|
||||||
msgstr ""
|
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 "
|
msgid "Fetching news from "
|
||||||
msgstr ""
|
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."
|
msgid "News fetched. Uploading to device."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:764
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:772
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:834
|
|
||||||
msgid "Cannot convert"
|
msgid "Cannot convert"
|
||||||
msgstr ""
|
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"
|
msgid "Starting Bulk conversion of %d books"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:905
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:832
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:923
|
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"
|
msgid "No book selected"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:905
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1001
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:923
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1019
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:937
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1033
|
||||||
msgid "Cannot view"
|
msgid "Cannot view"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:911
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1007
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:942
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1038
|
||||||
msgid "Choose the format to view"
|
msgid "Choose the format to view"
|
||||||
msgstr ""
|
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."
|
msgid "%s has no available formats."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:976
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1072
|
||||||
msgid "Cannot configure"
|
msgid "Cannot configure"
|
||||||
msgstr ""
|
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."
|
msgid "Cannot configure while there are running jobs."
|
||||||
msgstr ""
|
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 "
|
msgid "Copying database to "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1014
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1110
|
||||||
msgid "Invalid database"
|
msgid "Invalid database"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1015
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1111
|
||||||
msgid ""
|
msgid ""
|
||||||
"<p>An invalid database already exists at %s, delete it before trying to move "
|
"<p>An invalid database already exists at %s, delete it before trying to move "
|
||||||
"the existing database.<br>Error: %s"
|
"the existing database.<br>Error: %s"
|
||||||
msgstr ""
|
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"
|
msgid "Could not move database"
|
||||||
msgstr ""
|
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"
|
msgid "No detailed info available"
|
||||||
msgstr ""
|
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."
|
msgid "No detailed information is available for books on the device."
|
||||||
msgstr ""
|
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"
|
msgid "Error talking to device"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1088
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1184
|
||||||
msgid ""
|
msgid ""
|
||||||
"There was a temporary error talking to the device. Please unplug and "
|
"There was a temporary error talking to the device. Please unplug and "
|
||||||
"reconnect the device and or reboot."
|
"reconnect the device and or reboot."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1139
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1235
|
||||||
msgid "Conversion Error"
|
msgid "Conversion Error"
|
||||||
msgstr ""
|
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"
|
msgid "Database does not exist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1158
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1254
|
||||||
msgid ""
|
msgid ""
|
||||||
"The directory in which the database should be: %s no longer exists. Please "
|
"The directory in which the database should be: %s no longer exists. Please "
|
||||||
"choose a new database location."
|
"choose a new database location."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1209
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1305
|
||||||
msgid ""
|
msgid ""
|
||||||
"<span style=\"color:red; font-weight:bold\">Latest version: <a "
|
"<span style=\"color:red; font-weight:bold\">Latest version: <a "
|
||||||
"href=\"%s\">%s</a></span>"
|
"href=\"%s\">%s</a></span>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1215
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1311
|
||||||
msgid ""
|
msgid ""
|
||||||
"%s has been updated to version %s. See the <a "
|
"%s has been updated to version %s. See the <a "
|
||||||
"href=\"http://calibre.kovidgoyal.net/wiki/Changelog\">new features</a>. "
|
"href=\"http://calibre.kovidgoyal.net/wiki/Changelog\">new features</a>. "
|
||||||
"Visit the download page?"
|
"Visit the download page?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1215
|
#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1311
|
||||||
msgid "Update available"
|
msgid "Update available"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -2589,23 +2768,23 @@ msgstr ""
|
|||||||
msgid "Custom news sources"
|
msgid "Custom news sources"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/status.py:95
|
#: /home/kovid/work/calibre/src/calibre/gui2/status.py:99
|
||||||
msgid "Jobs:"
|
msgid "Jobs:"
|
||||||
msgstr ""
|
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."
|
msgid "Click to see list of active jobs."
|
||||||
msgstr ""
|
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"
|
msgid "Click to browse books by their covers"
|
||||||
msgstr ""
|
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"
|
msgid "Click to turn off Cover Browsing"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/gui2/status.py:138
|
#: /home/kovid/work/calibre/src/calibre/gui2/status.py:142
|
||||||
msgid ""
|
msgid ""
|
||||||
"<p>Browsing books by their covers is disabled.<br>Import of pictureflow "
|
"<p>Browsing books by their covers is disabled.<br>Import of pictureflow "
|
||||||
"module failed:<br>"
|
"module failed:<br>"
|
||||||
@ -2851,11 +3030,11 @@ msgid ""
|
|||||||
"For help on an individual command: %%prog command --help\n"
|
"For help on an individual command: %%prog command --help\n"
|
||||||
msgstr ""
|
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."
|
msgid "Could not launch worker process."
|
||||||
msgstr ""
|
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"
|
msgid "Could not initialize the fontconfig library"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -2964,9 +3143,8 @@ msgid ""
|
|||||||
"downloads at most 2 feeds."
|
"downloads at most 2 feeds."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:84
|
#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:70
|
||||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:88
|
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:580
|
||||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:577
|
|
||||||
msgid "Fetching feeds..."
|
msgid "Fetching feeds..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -2995,58 +3173,58 @@ msgstr ""
|
|||||||
msgid "\tFailed links:"
|
msgid "\tFailed links:"
|
||||||
msgstr ""
|
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"
|
msgid "Could not fetch article. Run with --debug to see the reason"
|
||||||
msgstr ""
|
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"
|
msgid "Got feeds from index page"
|
||||||
msgstr ""
|
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..."
|
msgid "Trying to download cover..."
|
||||||
msgstr ""
|
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)]..."
|
msgid "Starting download [%d thread(s)]..."
|
||||||
msgstr ""
|
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"
|
msgid "Feeds downloaded to %s"
|
||||||
msgstr ""
|
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"
|
msgid "Could not download cover: %s"
|
||||||
msgstr ""
|
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"
|
msgid "Downloading cover from %s"
|
||||||
msgstr ""
|
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"
|
msgid "Untitled Article"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:745
|
#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:748
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
"Downloaded article %s from %s\n"
|
"Downloaded article %s from %s\n"
|
||||||
"%s"
|
"%s"
|
||||||
msgstr ""
|
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"
|
msgid "Article downloaded: %s"
|
||||||
msgstr ""
|
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"
|
msgid "Failed to download article: %s from %s\n"
|
||||||
msgstr ""
|
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"
|
msgid "Article download failed: %s"
|
||||||
msgstr ""
|
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"
|
msgid "Fetching feed"
|
||||||
msgstr ""
|
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.
|
Manage application-wide preferences.
|
||||||
'''
|
'''
|
||||||
import os, re, cPickle
|
import os, re, cPickle, textwrap
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from optparse import OptionParser as _OptionParser
|
||||||
|
from optparse import IndentedHelpFormatter
|
||||||
from PyQt4.QtCore import QString
|
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
|
from collections import defaultdict
|
||||||
|
|
||||||
if iswindows:
|
if iswindows:
|
||||||
@ -21,11 +25,130 @@ if iswindows:
|
|||||||
elif isosx:
|
elif isosx:
|
||||||
config_dir = os.path.expanduser('~/Library/Preferences/calibre')
|
config_dir = os.path.expanduser('~/Library/Preferences/calibre')
|
||||||
else:
|
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):
|
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):
|
class Option(object):
|
||||||
|
|
||||||
def __init__(self, name, switches=[], help='', type=None, choices=None,
|
def __init__(self, name, switches=[], help='', type=None, choices=None,
|
||||||
@ -202,6 +325,15 @@ class Config(object):
|
|||||||
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
||||||
return self.option_set.parse_string(src)
|
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):
|
def set(self, name, val):
|
||||||
if not self.option_set.has_option(name):
|
if not self.option_set.has_option(name):
|
||||||
raise ValueError('The option %s is not defined.'%name)
|
raise ValueError('The option %s is not defined.'%name)
|
||||||
@ -240,8 +372,154 @@ class StringConfig(object):
|
|||||||
setattr(opts, name, val)
|
setattr(opts, name, val)
|
||||||
footer = self.option_set.get_override_section(self.src)
|
footer = self.option_set.get_override_section(self.src)
|
||||||
self.src = self.option_set.serialize(opts)+ '\n\n' + footer + '\n'
|
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__':
|
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
|
Get command line arguments as unicode objects. Note that the
|
||||||
first argument will be the path to the interpreter, *not* the
|
first argument will be the path to the interpreter, *not* the
|
||||||
script being run. So to replace sys.argv, you should use
|
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.
|
# Create all upper directories if necessary.
|
||||||
upperdirs = os.path.dirname(targetpath)
|
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):
|
if upperdirs and not os.path.exists(upperdirs):
|
||||||
os.makedirs(upperdirs)
|
os.makedirs(upperdirs)
|
||||||
|
|
||||||
source = self.open(member, pwd=pwd)
|
source = self.open(member, pwd=pwd)
|
||||||
target = file(targetpath, "wb")
|
target = open(targetpath, "wb")
|
||||||
shutil.copyfileobj(source, target)
|
shutil.copyfileobj(source, target)
|
||||||
source.close()
|
source.close()
|
||||||
target.close()
|
target.close()
|
||||||
|
@ -573,7 +573,7 @@ class BasicNewsRecipe(object, LoggingInterface):
|
|||||||
open(pt.name, 'wb').write(raw)
|
open(pt.name, 'wb').write(raw)
|
||||||
pt.close()
|
pt.close()
|
||||||
url = ('file:'+pt.name) if iswindows else ('file://'+pt.name)
|
url = ('file:'+pt.name) if iswindows else ('file://'+pt.name)
|
||||||
return self._fetch_article(url, dir, logger, f, a, num_of_feeds)
|
return self._fetch_article(url, dir, logger, f, a, num_of_feeds)
|
||||||
|
|
||||||
|
|
||||||
def build_index(self):
|
def build_index(self):
|
||||||
@ -586,7 +586,7 @@ class BasicNewsRecipe(object, LoggingInterface):
|
|||||||
feeds = self.parse_feeds()
|
feeds = self.parse_feeds()
|
||||||
|
|
||||||
self.report_progress(0, _('Trying to download cover...'))
|
self.report_progress(0, _('Trying to download cover...'))
|
||||||
self.download_cover()
|
self.download_cover()
|
||||||
if self.test:
|
if self.test:
|
||||||
feeds = feeds[:2]
|
feeds = feeds[:2]
|
||||||
self.has_single_feed = len(feeds) == 1
|
self.has_single_feed = len(feeds) == 1
|
||||||
|
@ -12,9 +12,10 @@ from urllib import url2pathname
|
|||||||
from httplib import responses
|
from httplib import responses
|
||||||
|
|
||||||
from calibre import setup_cli_handlers, browser, sanitize_file_name, \
|
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.BeautifulSoup import BeautifulSoup, Tag
|
||||||
from calibre.ebooks.chardet import xml_to_unicode
|
from calibre.ebooks.chardet import xml_to_unicode
|
||||||
|
from calibre.utils.config import OptionParser
|
||||||
|
|
||||||
class FetchError(Exception):
|
class FetchError(Exception):
|
||||||
pass
|
pass
|
||||||
|
50
upload.py
50
upload.py
@ -12,7 +12,7 @@ def get_ip_address(ifname):
|
|||||||
0x8915, # SIOCGIFADDR
|
0x8915, # SIOCGIFADDR
|
||||||
struct.pack('256s', ifname[:15])
|
struct.pack('256s', ifname[:15])
|
||||||
)[20:24])
|
)[20:24])
|
||||||
|
|
||||||
HOST=get_ip_address('eth0')
|
HOST=get_ip_address('eth0')
|
||||||
PROJECT=os.path.basename(os.getcwd())
|
PROJECT=os.path.basename(os.getcwd())
|
||||||
|
|
||||||
@ -28,13 +28,12 @@ MOBILEREAD = 'ftp://dev.mobileread.com/calibre/'
|
|||||||
BUILD_SCRIPT ='''\
|
BUILD_SCRIPT ='''\
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
cd ~/build && \
|
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 . && \
|
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 && rm -rf build dist src/calibre/plugins && \
|
cd %(project)s && \
|
||||||
mkdir -p build dist src/calibre/plugins && \
|
|
||||||
%%s && \
|
%%s && \
|
||||||
rm -rf build/* && \
|
rm -rf build/* && \
|
||||||
%%s %%s
|
%%s %%s
|
||||||
'''%dict(host=HOST, project=PROJECT)
|
'''%dict(host=HOST, project=PROJECT)
|
||||||
check_call = partial(_check_call, shell=True)
|
check_call = partial(_check_call, shell=True)
|
||||||
#h = Host(hostType=VIX_SERVICEPROVIDER_VMWARE_WORKSTATION)
|
#h = Host(hostType=VIX_SERVICEPROVIDER_VMWARE_WORKSTATION)
|
||||||
|
|
||||||
@ -43,7 +42,7 @@ def tag_release():
|
|||||||
print 'Tagging release'
|
print 'Tagging release'
|
||||||
check_call('bzr tag '+__version__)
|
check_call('bzr tag '+__version__)
|
||||||
check_call('bzr commit --unchanged -m "IGN:Tag release"')
|
check_call('bzr commit --unchanged -m "IGN:Tag release"')
|
||||||
|
|
||||||
def installer_name(ext):
|
def installer_name(ext):
|
||||||
if ext in ('exe', 'dmg'):
|
if ext in ('exe', 'dmg'):
|
||||||
return 'dist/%s-%s.%s'%(__appname__, __version__, ext)
|
return 'dist/%s-%s.%s'%(__appname__, __version__, ext)
|
||||||
@ -76,6 +75,8 @@ def build_windows(shutdown=True):
|
|||||||
installer = installer_name('exe')
|
installer = installer_name('exe')
|
||||||
vm = '/vmware/Windows XP/Windows XP Professional.vmx'
|
vm = '/vmware/Windows XP/Windows XP Professional.vmx'
|
||||||
start_vm(vm, 'windows', BUILD_SCRIPT%('python setup.py develop', 'python','installer\\\\windows\\\\freeze.py'))
|
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'))
|
subprocess.check_call(('scp', '-rp', 'windows:build/%s/build/py2exe'%PROJECT, 'build'))
|
||||||
if not os.path.exists('build/py2exe'):
|
if not os.path.exists('build/py2exe'):
|
||||||
raise Exception('Failed to run py2exe')
|
raise Exception('Failed to run py2exe')
|
||||||
@ -87,7 +88,7 @@ def build_windows(shutdown=True):
|
|||||||
def build_osx(shutdown=True):
|
def build_osx(shutdown=True):
|
||||||
installer = installer_name('dmg')
|
installer = installer_name('dmg')
|
||||||
vm = '/vmware/Mac OSX/Mac OSX.vmx'
|
vm = '/vmware/Mac OSX/Mac OSX.vmx'
|
||||||
python = '/Library/Frameworks/Python.framework/Versions/Current/bin/python'
|
python = '/Library/Frameworks/Python.framework/Versions/Current/bin/python'
|
||||||
start_vm(vm, 'osx', (BUILD_SCRIPT%('sudo %s setup.py develop'%python, python, 'installer/osx/freeze.py')).replace('rm ', 'sudo rm '))
|
start_vm(vm, 'osx', (BUILD_SCRIPT%('sudo %s setup.py develop'%python, python, 'installer/osx/freeze.py')).replace('rm ', 'sudo rm '))
|
||||||
subprocess.check_call(('scp', 'osx:build/%s/dist/*.dmg'%PROJECT, 'dist'))
|
subprocess.check_call(('scp', 'osx:build/%s/dist/*.dmg'%PROJECT, 'dist'))
|
||||||
if not os.path.exists(installer):
|
if not os.path.exists(installer):
|
||||||
@ -95,7 +96,7 @@ def build_osx(shutdown=True):
|
|||||||
if shutdown:
|
if shutdown:
|
||||||
subprocess.Popen(('ssh', 'osx', 'sudo', '/sbin/shutdown', '-h', 'now'))
|
subprocess.Popen(('ssh', 'osx', 'sudo', '/sbin/shutdown', '-h', 'now'))
|
||||||
return os.path.basename(installer)
|
return os.path.basename(installer)
|
||||||
|
|
||||||
|
|
||||||
def build_linux(shutdown=True):
|
def build_linux(shutdown=True):
|
||||||
installer = installer_name('tar.bz2')
|
installer = installer_name('tar.bz2')
|
||||||
@ -146,7 +147,7 @@ def curl_delete_file(path, url=MOBILEREAD):
|
|||||||
c.setopt(c.QUOTE, ['dele '+ path])
|
c.setopt(c.QUOTE, ['dele '+ path])
|
||||||
c.perform()
|
c.perform()
|
||||||
c.close()
|
c.close()
|
||||||
|
|
||||||
|
|
||||||
def curl_upload_file(stream, url):
|
def curl_upload_file(stream, url):
|
||||||
c = pycurl.Curl()
|
c = pycurl.Curl()
|
||||||
@ -173,9 +174,9 @@ def curl_upload_file(stream, url):
|
|||||||
stream.seek(0,2)
|
stream.seek(0,2)
|
||||||
if size != stream.tell():
|
if size != stream.tell():
|
||||||
raise RuntimeError('curl failed to upload %s correctly'%getattr(stream, 'name', ''))
|
raise RuntimeError('curl failed to upload %s correctly'%getattr(stream, 'name', ''))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def upload_installer(name):
|
def upload_installer(name):
|
||||||
if not os.path.exists(name):
|
if not os.path.exists(name):
|
||||||
return
|
return
|
||||||
@ -189,10 +190,10 @@ def upload_installer(name):
|
|||||||
def upload_installers():
|
def upload_installers():
|
||||||
for i in ('dmg', 'exe', 'tar.bz2'):
|
for i in ('dmg', 'exe', 'tar.bz2'):
|
||||||
upload_installer(installer_name(i))
|
upload_installer(installer_name(i))
|
||||||
|
|
||||||
check_call('''ssh divok echo %s \\> %s/latest_version'''%(__version__, DOWNLOADS))
|
check_call('''ssh divok echo %s \\> %s/latest_version'''%(__version__, DOWNLOADS))
|
||||||
|
|
||||||
|
|
||||||
def upload_docs():
|
def upload_docs():
|
||||||
check_call('''epydoc --config epydoc.conf''')
|
check_call('''epydoc --config epydoc.conf''')
|
||||||
check_call('''scp -r docs/html divok:%s/'''%(DOCS,))
|
check_call('''scp -r docs/html divok:%s/'''%(DOCS,))
|
||||||
@ -208,25 +209,25 @@ def upload_user_manual():
|
|||||||
check_call('scp -r .build/html/* divok:%s'%USER_MANUAL)
|
check_call('scp -r .build/html/* divok:%s'%USER_MANUAL)
|
||||||
finally:
|
finally:
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
|
|
||||||
def build_src_tarball():
|
def build_src_tarball():
|
||||||
check_call('bzr export dist/calibre-%s.tar.bz2'%__version__)
|
check_call('bzr export dist/calibre-%s.tar.bz2'%__version__)
|
||||||
|
|
||||||
def upload_src_tarball():
|
def upload_src_tarball():
|
||||||
check_call('ssh divok rm -f %s/calibre-\*.tar.bz2'%DOWNLOADS)
|
check_call('ssh divok rm -f %s/calibre-\*.tar.bz2'%DOWNLOADS)
|
||||||
check_call('scp dist/calibre-*.tar.bz2 divok:%s/'%DOWNLOADS)
|
check_call('scp dist/calibre-*.tar.bz2 divok:%s/'%DOWNLOADS)
|
||||||
|
|
||||||
def stage_one():
|
def stage_one():
|
||||||
shutil.rmtree('build')
|
check_call('sudo rm -rf build', shell=True)
|
||||||
os.mkdir('build')
|
os.mkdir('build')
|
||||||
shutil.rmtree('docs')
|
shutil.rmtree('docs')
|
||||||
os.mkdir('docs')
|
os.mkdir('docs')
|
||||||
check_call(['python', 'setup.py', 'build'])
|
check_call('python setup.py build', shell=True)
|
||||||
check_call('sudo rm -f src/%s/gui2/images_rc.pyc'%__appname__, shell=True)
|
check_call('sudo python setup.py develop', shell=True)
|
||||||
check_call('make', shell=True)
|
check_call('make', shell=True)
|
||||||
tag_release()
|
tag_release()
|
||||||
upload_demo()
|
upload_demo()
|
||||||
|
|
||||||
def stage_two():
|
def stage_two():
|
||||||
subprocess.check_call('rm -rf dist/*', shell=True)
|
subprocess.check_call('rm -rf dist/*', shell=True)
|
||||||
build_installers()
|
build_installers()
|
||||||
@ -241,6 +242,7 @@ def stage_three():
|
|||||||
upload_user_manual()
|
upload_user_manual()
|
||||||
check_call('python setup.py register bdist_egg --exclude-source-files upload')
|
check_call('python setup.py register bdist_egg --exclude-source-files upload')
|
||||||
check_call('''rm -rf dist/* build/*''')
|
check_call('''rm -rf dist/* build/*''')
|
||||||
|
check_call('''ssh divok bzr update /var/www/calibre.kovidgoyal.net/calibre/''')
|
||||||
|
|
||||||
def main(args=sys.argv):
|
def main(args=sys.argv):
|
||||||
print 'Starting stage one...'
|
print 'Starting stage one...'
|
||||||
@ -250,8 +252,8 @@ def main(args=sys.argv):
|
|||||||
print 'Starting stage three...'
|
print 'Starting stage three...'
|
||||||
stage_three()
|
stage_three()
|
||||||
print 'Finished'
|
print 'Finished'
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user