Merge from trunk

This commit is contained in:
Sengian 2010-07-26 22:51:10 +02:00
commit c00c253d65
35 changed files with 1474 additions and 4714 deletions

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) --> <!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/" xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -10,106 +11,573 @@
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0" version="1.0"
width="48" width="128"
height="48" height="128"
id="svg2160" id="svg4486"
sodipodi:version="0.32" inkscape:version="0.47 r22583"
inkscape:version="0.45.1" sodipodi:docname="epub.svg">
sodipodi:docname="mobi.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
sodipodi:docbase="/home/kovid/temp">
<sodipodi:namedview
inkscape:window-height="674"
inkscape:window-width="791"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
guidetolerance="10.0"
gridtolerance="10.0"
objecttolerance="10.0"
borderopacity="1.0"
bordercolor="#666666"
pagecolor="#ffffff"
id="base"
inkscape:zoom="9.6458333"
inkscape:cx="24"
inkscape:cy="24"
inkscape:window-x="0"
inkscape:window-y="31"
inkscape:current-layer="svg2160" />
<metadata <metadata
id="metadata7"> id="metadata52">
<rdf:RDF> <rdf:RDF>
<cc:Work <cc:Work
rdf:about=""> rdf:about="">
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1366"
inkscape:window-height="692"
id="namedview50"
showgrid="false"
inkscape:snap-bbox="true"
inkscape:zoom="2.3256144"
inkscape:cx="73.759964"
inkscape:cy="13.023094"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:current-layer="svg4486" />
<defs <defs
id="defs2162" /> id="defs4488">
<linearGradient
id="linearGradient3672">
<stop
style="stop-color:#2b2b2b;stop-opacity:1;"
offset="0"
id="stop3674" />
<stop
id="stop3685"
offset="0.5"
style="stop-color:#242424;stop-opacity:1;" />
<stop
style="stop-color:#363636;stop-opacity:1;"
offset="0.75"
id="stop3689" />
<stop
style="stop-color:#2b2b2b;stop-opacity:1;"
offset="1"
id="stop3676" />
</linearGradient>
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 64 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="128 : 64 : 1"
inkscape:persp3d-origin="64 : 42.666667 : 1"
id="perspective54" />
<linearGradient
id="linearGradient5048">
<stop
id="stop5050"
style="stop-color:#000000;stop-opacity:0"
offset="0" />
<stop
id="stop5056"
style="stop-color:#000000;stop-opacity:1"
offset="0.5" />
<stop
id="stop5052"
style="stop-color:#000000;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient5060">
<stop
id="stop5062"
style="stop-color:#000000;stop-opacity:1"
offset="0" />
<stop
id="stop5064"
style="stop-color:#000000;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3104">
<stop
id="stop3106"
style="stop-color:#aaaaaa;stop-opacity:1"
offset="0" />
<stop
id="stop3108"
style="stop-color:#c8c8c8;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient3600">
<stop
id="stop3602"
style="stop-color:#f4f4f4;stop-opacity:1"
offset="0" />
<stop
id="stop3604"
style="stop-color:#dbdbdb;stop-opacity:1"
offset="1" />
</linearGradient>
<radialGradient
cx="102"
cy="112.3047"
r="139.55859"
id="XMLID_8_"
gradientUnits="userSpaceOnUse">
<stop
id="stop41"
style="stop-color:#b7b8b9;stop-opacity:1"
offset="0" />
<stop
id="stop47"
style="stop-color:#ececec;stop-opacity:1"
offset="0.18851049" />
<stop
id="stop49"
style="stop-color:#fafafa;stop-opacity:0"
offset="0.25718147" />
<stop
id="stop51"
style="stop-color:#ffffff;stop-opacity:0"
offset="0.30111277" />
<stop
id="stop53"
style="stop-color:#fafafa;stop-opacity:0"
offset="0.53130001" />
<stop
id="stop55"
style="stop-color:#ebecec;stop-opacity:0"
offset="0.84490001" />
<stop
id="stop57"
style="stop-color:#e1e2e3;stop-opacity:0"
offset="1" />
</radialGradient>
<linearGradient
id="linearGradient3211">
<stop
id="stop3213"
style="stop-color:#ffffff;stop-opacity:1"
offset="0" />
<stop
id="stop3215"
style="stop-color:#ffffff;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
id="linearGradient8589">
<stop
id="stop8591"
style="stop-color:#fefefe;stop-opacity:1"
offset="0" />
<stop
id="stop8593"
style="stop-color:#cbcbcb;stop-opacity:1"
offset="1" />
</linearGradient>
<filter
x="-0.14846256"
y="-0.16434373"
width="1.2969251"
height="1.3286875"
color-interpolation-filters="sRGB"
id="filter3212">
<feGaussianBlur
stdDeviation="0.77391625"
id="feGaussianBlur3214" />
</filter>
<linearGradient
x1="32.892288"
y1="8.0590115"
x2="36.358372"
y2="5.4565363"
id="linearGradient2455"
xlink:href="#linearGradient8589"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.6605294,0,0,2.7751643,0.7455334,-3.5662351)" />
<linearGradient
x1="24"
y1="1.9999999"
x2="24"
y2="46.01725"
id="linearGradient2459"
xlink:href="#linearGradient3211"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.7575715,0,0,2.6744155,-2.1817183,-4.1859717)" />
<radialGradient
cx="102"
cy="112.3047"
r="139.55859"
id="radialGradient2462"
xlink:href="#XMLID_8_"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.9787237,0,0,-1.0535153,1.3617012,127.48164)" />
<linearGradient
x1="25.132275"
y1="0.98520643"
x2="25.132275"
y2="47.013336"
id="linearGradient2465"
xlink:href="#linearGradient3600"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.657139,0,0,2.5422197,0.2286615,-4.9132741)" />
<linearGradient
x1="-51.786404"
y1="50.786446"
x2="-51.786404"
y2="2.9062471"
id="linearGradient2467"
xlink:href="#linearGradient3104"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.1456312,0,0,2.3791388,158.0899,-7.7465258)" />
<linearGradient
x1="302.85715"
y1="366.64789"
x2="302.85715"
y2="609.50507"
id="linearGradient2483"
xlink:href="#linearGradient5048"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.1725148,0,0,0.03920058,0.6482238,98.773724)" />
<radialGradient
cx="605.71429"
cy="486.64789"
r="117.14286"
fx="605.71429"
fy="486.64789"
id="radialGradient2485"
xlink:href="#linearGradient5060"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.05903807,0,0,0.03920058,56.929907,98.773804)" />
<radialGradient
cx="605.71429"
cy="486.64789"
r="117.14286"
fx="605.71429"
fy="486.64789"
id="radialGradient2487"
xlink:href="#linearGradient5060"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.05903808,0,0,0.03920058,69.07008,98.773804)" />
<inkscape:perspective
id="perspective2878"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94"
id="linearGradient2989"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.7993221,0,0,1.0036506,40.855793,-1.5607197)"
x1="-22.539846"
y1="11.109024"
x2="-22.539846"
y2="46.263954" />
<linearGradient
id="linearGradient7012-661-145-733-759-865-745-661-970-94">
<stop
offset="0"
style="stop-color:#f0c178;stop-opacity:1"
id="stop3618" />
<stop
offset="0.5"
style="stop-color:#e18941;stop-opacity:1"
id="stop3270" />
<stop
offset="1"
style="stop-color:#ec4f18;stop-opacity:1"
id="stop3620" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3390-178-986-453"
id="linearGradient2991"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.9110051,0,0,0.9769973,0.6027366,-0.94793564)"
x1="9.4919996"
y1="46.314064"
x2="9.4919996"
y2="1.7164899" />
<linearGradient
id="linearGradient3390-178-986-453">
<stop
offset="0"
style="stop-color:#c92e13;stop-opacity:1;"
id="stop3624" />
<stop
offset="1"
style="stop-color:#dea176;stop-opacity:1;"
id="stop3626" />
</linearGradient>
<linearGradient
y2="46.263954"
x2="-22.539846"
y1="11.109024"
x1="-22.539846"
gradientTransform="matrix(1.2084176,0,0,2.666214,69.448297,-3.8858475)"
gradientUnits="userSpaceOnUse"
id="linearGradient2892"
xlink:href="#linearGradient7012-661-145-733-759-865-745-661-970-94"
inkscape:collect="always" />
<linearGradient
y2="1.7164899"
x2="9.4919996"
y1="46.314064"
x1="9.4919996"
gradientTransform="matrix(1.3772603,0,0,2.595409,8.5935979,-2.257977)"
gradientUnits="userSpaceOnUse"
id="linearGradient2894"
xlink:href="#linearGradient3390-178-986-453"
inkscape:collect="always" />
<inkscape:perspective
id="perspective3666"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<linearGradient
x1="302.85715"
y1="366.64789"
x2="302.85715"
y2="609.50507"
id="linearGradient19619"
xlink:href="#linearGradient5048-1"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.08449704,0,0,0.01235294,-6.5396456,38.470822)" />
<linearGradient
id="linearGradient5048-1">
<stop
id="stop5050-2"
style="stop-color:black;stop-opacity:0"
offset="0" />
<stop
id="stop5056-0"
style="stop-color:black;stop-opacity:1"
offset="0.5" />
<stop
id="stop5052-7"
style="stop-color:black;stop-opacity:0"
offset="1" />
</linearGradient>
<radialGradient
cx="605.71429"
cy="486.64789"
r="117.14286"
fx="605.71429"
fy="486.64789"
id="radialGradient19616"
xlink:href="#linearGradient5060-3"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-0.0289166,0,0,0.01235294,21.026894,38.470848)" />
<linearGradient
id="linearGradient5060-3">
<stop
id="stop5062-1"
style="stop-color:black;stop-opacity:1"
offset="0" />
<stop
id="stop5064-1"
style="stop-color:black;stop-opacity:0"
offset="1" />
</linearGradient>
<radialGradient
cx="605.71429"
cy="486.64789"
r="117.14286"
fx="605.71429"
fy="486.64789"
id="radialGradient19613"
xlink:href="#linearGradient5060-3"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.02891661,0,0,0.01235294,26.973101,38.470848)" />
<linearGradient
id="linearGradient3680">
<stop
id="stop3682"
style="stop-color:black;stop-opacity:1"
offset="0" />
<stop
id="stop3684"
style="stop-color:black;stop-opacity:0"
offset="1" />
</linearGradient>
<linearGradient
x1="108.26451"
y1="110.28094"
x2="25.817675"
y2="14.029031"
id="linearGradient19610"
xlink:href="#linearGradient259-942"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.5066363,0,0,0.3512482,-58.338079,-49.085986)" />
<linearGradient
id="linearGradient259-942">
<stop
id="stop3802"
style="stop-color:white;stop-opacity:1"
offset="0" />
<stop
id="stop3804"
style="stop-color:#e0e0e0;stop-opacity:1"
offset="1" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3650"
id="linearGradient3656"
x1="-44.02877"
y1="-26.590452"
x2="-4.1013746"
y2="-26.590452"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3650">
<stop
style="stop-color:#73d216;stop-opacity:1;"
offset="0"
id="stop3652" />
<stop
style="stop-color:#8ae234;stop-opacity:1;"
offset="1"
id="stop3654" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3672"
id="linearGradient3682"
x1="32.254131"
y1="57.967407"
x2="98.357651"
y2="57.967407"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(19.439951,41.709408)" />
<inkscape:perspective
id="perspective2970"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective2984"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3890"
inkscape:persp3d-origin="150 : 23.333333 : 1"
inkscape:vp_z="300 : 35 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 35 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3915"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective4057"
inkscape:persp3d-origin="150 : 23.333333 : 1"
inkscape:vp_z="300 : 35 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 35 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective4082"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
</defs>
<g <g
id="layer1"> transform="matrix(1.0408163,0,0,0.6302428,-1.5714269,43.690218)"
<image id="g2478">
id="image2226" <rect
width="48" width="83.299995"
y="0" height="9.5201406"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABmJLR0QA/wD/AP+gvaeTAAAACXBI x="21.349998"
WXMAAABIAAAASABGyWs+AAAACXZwQWcAAAAwAAAAMADO7oxXAAALJUlEQVRo3u2ae3DTVRbHP3k1 y="113.14653"
v6ZJk4a+W1NalAJSy4qtvKS4iCKOYtcHvvCxDuuMozu62vVRdGcRfOzsODoq7o4jyoyy49aFqYoO id="rect2879"
7rJjaREsKi2F0sC2CNImado82ib5/fJo9o+0vzS2pe2i4B97/rq553fu7/u9597zu+fcKB56+OFo style="opacity:0.3;fill:url(#linearGradient2483);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
bnY2fr8fKRgkIIpIosiwaAUBAK/HA8A0s1nW6fV6RsrGTZsUnGNRPL9pU/T3TzyZ0OnxuEe0PUiS <path
hBiIkero6MDb50UMBJCCQex2OwFRRK1UyjY+vx+320NamglRFDGaTLz+2ms/CTn1WJ0mU9qYbYDS d="m 21.35,113.14694 c 0,0 0,9.51962 0,9.51962 -3.040314,0.0179 -7.35,-2.13287 -7.35,-4.76043 0,-2.62755 3.392762,-4.75919 7.35,-4.75919 z"
efPGHWyY+GPbtuNtP8ZTa29HCkp8unMn66uroz+Fh9RnP0RcPm5u5s26Iyj1M3nghhky2dbWVg63 id="path2881"
tPzY2M+ewMleD0fajnGk8yQ72rpR6meSlrsU6/F/smzWHT8J4EkR2NnUwvYvGznaI57RWKmfiV6n style="opacity:0.3;fill:url(#radialGradient2485);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
J90wl5yCKGIogj8cpaLAiEGrPX8ENn20h7WrbuayEX2u/hAAPilMMDRIIDSIFB5EDEUIhOPgHQMe <path
VllSzwl4AOVkHposeF84QtD5NeuuvurnQ2Aq4AfEfioKjOcM/IQEpgLeGx7E2dPOQ1cv+3kQmDL4 d="m 104.64999,113.14694 c 0,0 0,9.51962 0,9.51962 3.04032,0.0179 7.35001,-2.13287 7.35001,-4.76043 0,-2.62755 -3.39277,-4.75919 -7.35001,-4.75919 z"
cJQbLIMUppsn/fKflMBUwfedbuaxFVeeU/BnJDAl8JFB7pkJuRnxr7YoipMCcNYE/H7/mIqpgKdj id="path2883"
d8LsnyvwcAYPTBq8388Lyy9khiX350VgUuCBctURfrv6+kRbSTr/BCYDPs19hDfuWDnaNvAz8MBE style="opacity:0.3;fill:url(#radialGradient2487);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
4LG18PK1hcy5IG+Uba+r9/wTmAj8O5UzuHXBglF2nqHM7bwTONOyeadyBmsrlo1p5/V4zymBcfOB
8Wb+hpmMCx6g025HSNJM+OL11dVRKRiUfw977q233ppS1jYugVHgxTCkzWbrKRtsfpu3H7x/TLtu
uw2LxTKmrqqqKhoQRfqkKKecMU8lZZoJdrvQAnapi0WLFkUBcnPz+PDDmgnJjEtgFPhhSc5h6ylg
89tsvv/OBBuPx4MYCAAgDFUzACwWC3V1dSiSjcxetoQ8IJixEJ8UJq3vAMyBJFsns5iF7cFaQnXT
cLZFKCsrixYVXcQHH2wbl8i4e2BM8AkkTPx9//6E7tY2KwAqTTwbe/fdd/j6wAHmXHsdV66pRF+0
FE3uLMwGDSlaNe7UMvlZ2+JacvauRlPRS+4DPVTeeCP9Of9hxYoV0SkTGBf8CBKP7mhM6DpubUOt
UsneePyx3wGx2lJawWwGUmYP/c7E1R+SSTiS5+PTGcjZuxrb4lrMu68BwLV8F8svWMOcq/QsWrFo
TBLjEzgT+CHxCDNkL9hsNux2u6x78smnmFtSAsCMOSUUqsKsKtSywDQAQJJzX8JYUr9IS84qnI13
oRUEzLuvQWfVYltci0W9hMsrBXl/TN4DE0lyDl8caQdg/4jltGP7hyxcuACLxYKqsBCtUsWg1yPr
NREnIW8fAx178ElhkjRKfBddS7SnHk/KQnw6A67luwDI2bsaSRTJcC/HVB5mzZo7EkicHQGQKxf7
v/oKgPYTJ0gWBMzz5tHd3U1+RhYhQYc0GKHl22843Pk9IVUGutnl6IuWkqJVk6KNxRJF+hUkqxX0
/eI9efxDGgNWQwau5bu4NGUFfn//j+iBIdm7dy92W2z5dPa0U1xcTF4kIuu7jx/h+/YTlFw6n7l5
FwCxfSCJ3UAs9xiWS07Xjhp/n/8iAHQ6HWk5BVRVVcleOGsClbMyqampAWIbN2NWFEEQ6FSpEEUR
x6EmLBYLupRkXO5e9nviBeFQV5vcDoRiUc9qyEC752YAPnG/gFW0cCwi8NEXf+Rd1SLasmcmHFeU
I7+GU5aAjYHT3TQ1NQFgt9spmXYdmspdRE6cAKAgP3bYi0aCCeABHMnzRw3pSVlIUsgvk2gdTCNV
peRYRGCh7jhrjCF84fhhUR04i+RjevQk1sZ4KDWka2Ifsr9VIAgCzp5eGrocZGdl0OnoIY9Pcer0
6IuW4uoPkaRR4h6ITaAUHsQdCGPy7aPDYKZDWYZXjBUWKnXNmAKfoSnvhb2r0RjjS04pjUFgdrrA
hBKwcYmqg4aGehTKWOyfe+F8hj2qqdyFRqUk5O0jcuIEeVnpGG6vw9DrlCseAFmBb+TlM1KKBg9w
Z0Y1v1lWRYGxBk1FfNb7uuLPqQEkSUSrjYNeYdaxtbkOcivGBU/DmzR+d3RMteH2OrldfHe93HZu
Kad/WgYQLxpYKYWh2R+WAmMN/mKJsbP1RFECuN3uhM7bbrudu1V26KqLgR0JvKsOGt5E/XU9fp+P
oqIiysvLWHPrrfg0hnFf5NxSji0QxZ1allDxEEMRxFAsYomhiAxeZ51ccVgN4LA7yM7OSVBs2fwq
S999h+0fb+PL7xspzb2MFJWSJeVlZC68k+IXn2XhkqUJNk0vv4EroiF/6LcUDOLcUo5LrcWVfyVZ
gW9wDAGH2LoPhGMR0RWY+Muvs2pRqhLPn2qAg00Hx7x5uffe+7j33vtG9dfVN1C/t4Gc/AuYPr1Q
7p97URG1fdMx/yWEbXEtLD7JaetmjMkaGF4uQ0CHc24AX8ALpNAlhviSP7DI+keMPSvxF9eis2o5
6b2Fot0u8rLS6cdHOBD/GCuzMjNpam4eBdLjcVNX30BdfUNC/2uvv85gfx+P5inY8kDicdqkSyYQ
jnJIY8DZeBc6q5YZjn9j7w/iDYRwB8JEe+pxB8IEwlFcgbA8811iCGc4SuOAhg2nN1ATuZg/736J
Dac3ANBhMOPU6Wk9cpjs7Ow4gY2bNim8Hg92uy0B/MbnniNJMcjsDY/zp5delHXdtk6WuFpwb91B
yqGTCQTL5pdi8sUOaR3KMow98YrF8Gx3KucghiIycF84gissYFQr6YsMysf44fh/jyF2NaVIv4Ik
Wyd2uz0ha1NCLPlobW2VX7bxued45u5fcem+f6A63Y9vTxxkkuTDvXUHqtOxM0lpycVxD5jSWGHW
AaDFx2fayzGrQph8++TZlkghO9iIFh9afJQovyUp2ENSsEceJ1WlpFLXTIZaQYeyDMm4CK1aSVtb
W8LsywRSdDpaRlzCGRVhhM9rGNjyr1FLK9TytQw+eOdNo24xV65cSb4ocrn/W1xhAVdEQ1G/Cy0+
IBbfi/udzLvsaeZd9jQZ5e+xZOnzFAunmKkSWaCJpZpW0YJRrcScrCY7VUvk0PtoBWFUzqwGeOXV
VxVr166Vd0aXL8jAlnj8LrzlJrn967++z9sffEBmVhbP/GCDezxuTn53EoXXweeqLErSv2U3Myk2
nOIKbwN6QYNtcS0uwLz7GsT8L2TbfFHkWESAiECqCvQ6PSnJWuYH9yEdF/E5bLw+RsIvd6xbty76
yCOPUFw8i+amJrZXrycadqH/5Q388CL8h4APNh2kqblZvs03mkwkCwI1g5lUFBjZ2l/Cs/nPAuAv
jpcddVatHPNfsW0kGz92dJTqk8gzaCjVHKa9tRWFwzZutSIhqB48eJDi4lmUzptH6c5PRj1st9uw
Hm/n6OFDtB07lgDYlJo66ja+qqoqum3/UcovFIk4rkda+qEMeiSJYbHoNRSqtWQHG8ELrQfqY7f8
Zyi1yIr11dVRR3c3TzzxBNOnF2K323DYHRxtO0rb0aM4urvlqvM0sxm9Xj+p/0asr66OnnJ6sSUL
5AREpi+OJf5eux5NRa9MIOK4HkdfD6HMfHwH95GmVpOVmTnhOxKU69atiw6DFAQBURQRBGFSA02G
iKevD6/HQ7u9HU2GREawiIhSIkmbRCSgIC3NhFYQMKWmnpc/jvxf/hf5L875Zqc0KDqlAAAAAElF
TkSuQmCC
"
x="0"
height="48" />
</g> </g>
<path
d="m 17.499961,1.499962 c 21.311039,0 42.622077,0 63.933118,0 3.738416,1.2619859 23.822451,15.639336 29.066961,25.594247 0,30.468614 0,60.937223 0,91.405831 -31.000026,0 -62.000051,0 -93.000079,0 0,-39.000023 0,-78.000052 0,-117.000078 z"
id="path4160"
style="fill:url(#linearGradient2465);fill-opacity:1;stroke:url(#linearGradient2467);stroke-width:0.99992192;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;display:inline" />
<path
d="M 18.978723,118 C 18.439449,118 18,117.52697 18,116.94648 L 18,3.1668773 C 18,2.5853392 18.439449,2.1133645 18.978723,2.1133645 39.22709,2.4045657 61.66587,1.6777678 81.889633,2.1858707 L 109.71323,26.088148 110,116.94648 C 110,117.52697 109.56154,118 109.02128,118 l -90.042557,0 z"
id="path4191"
style="fill:url(#radialGradient2462);fill-opacity:1" />
<path
d="m 109.50003,26.517935 c 0,29.94778 0,61.034321 0,90.982105 -30.333353,0 -60.666712,0 -91.00007,0 0,-38.333364 0,-76.666721 0,-115.0000784 20.852737,0 42.202796,0 63.055535,0"
id="path2435"
style="opacity:0.6;fill:none;stroke:url(#linearGradient2459);stroke-width:0.99992174;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;display:inline" />
<path
d="m 28.617256,0.92125832 c 4.282522,0 2.15325,8.48317128 2.15325,8.48317128 0,0 10.357642,-1.8023467 10.357642,2.8187444 0,-2.6097233 -11.302304,-10.7285956 -12.510892,-11.30191568 z"
transform="matrix(2.6666667,0,0,2.6666667,0.3087257,-0.6174513)"
id="path12038"
style="opacity:0.4;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;display:inline;filter:url(#filter3212)" />
<path
d="m 76.62141,1.8392376 c 8.497314,0 6.228693,20.4316764 6.228693,20.4316764 0,0 27.133687,-2.616144 27.133687,9.706766 0,-3.002504 0.2291,-5.152653 -0.35674,-6.089581 C 105.41822,19.156956 87.239007,4.052365 80.67425,2.0726634 80.182991,1.9245177 79.093706,1.8392376 76.62141,1.8392376 z"
id="path4474"
style="fill:url(#linearGradient2455);fill-opacity:1;fill-rule:evenodd;stroke:none;display:inline" />
<path
style="fill:url(#linearGradient2892);fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient2894);stroke-width:1.00930357;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:block;overflow:visible"
id="path4530"
d="m 25.816692,118.46233 c -2.652245,0 -5.304508,0 -7.956769,0 -0.64465,-1.3837 -0.154446,-4.13089 -0.307623,-6.08978 0,-36.699903 0,-73.399803 0,-110.0996953 l 0.08995,-0.4752429 0.217716,-0.1962367 0,0 c 2.76046,0 5.088125,0 7.848554,0" />
<text
xml:space="preserve"
style="font-size:72px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
x="38.310501"
y="64.757271"
id="text2892"><tspan
sodipodi:role="line"
id="tspan2894" /></text>
<text
xml:space="preserve"
style="font-size:28px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:FreeSans;-inkscape-font-specification:FreeSans"
x="35.259502"
y="109.51025"
id="text3772"><tspan
sodipodi:role="line"
id="tspan3774"
x="35.259502"
y="109.51025"
style="font-size:28px;fill:#000000;fill-opacity:1">mobi</tspan></text>
<path
id="path3858"
d="m 100.22334,68.053513 c -8.716894,6.432161 -21.357191,9.853809 -32.243032,9.853809 -15.251419,0 -28.989193,-5.636741 -39.38419,-15.021522 -0.815557,-0.738364 -0.08726,-1.746902 0.89191,-1.173831 11.217267,6.527812 25.085933,10.456247 39.410201,10.456247 9.664184,0 20.284886,-2.004491 30.058986,-6.151079 1.474215,-0.623415 2.709285,0.97246 1.266125,2.036376 z"
style="fill:#ff9201;fill-rule:evenodd" />
<path
id="path3860"
d="m 103.85056,63.911959 c -1.11342,-1.426386 -7.371902,-0.676274 -10.179364,-0.337298 -0.853315,0.09817 -0.984206,-0.641874 -0.217315,-1.184739 4.990672,-3.505554 13.168059,-2.491141 14.119539,-1.318987 0.95736,1.187256 -0.25087,9.384779 -4.92858,13.293915 -0.71907,0.604117 -1.40373,0.286117 -1.08573,-0.510142 1.05133,-2.631263 3.40906,-8.515524 2.29145,-9.942749 z"
style="fill:#ff9201;fill-rule:evenodd" />
<path
id="path4047"
d="m 69.299213,48.156661 c 0,2.157 0.052,3.954 -1.035,5.874 -0.88,1.561 -2.279,2.517 -3.833,2.517 -2.121,0 -3.366,-1.62 -3.366,-4.015 0,-4.714 4.232,-5.571 8.233,-5.571 v 1.195 z m 5.579,13.496 c -0.365,0.332 -0.896,0.352 -1.307,0.132 -1.838,-1.528 -2.167,-2.231 -3.174,-3.69 -3.035,3.094 -5.188,4.023 -9.123,4.023 -4.663,0 -8.284,-2.876 -8.284,-8.629 0,-4.49 2.433,-7.544 5.899,-9.045 3.005,-1.317 7.202,-1.556 10.41,-1.914 v -0.723 c 0,-1.313 0.104,-2.875 -0.671,-4.012 -0.674,-1.021 -1.968,-1.437 -3.106,-1.437 -2.111,0 -3.99,1.078 -4.45,3.321 -0.097,0.498 -0.46,0.991 -0.962,1.017 l -5.364,-0.581 c -0.456,-0.102 -0.958,-0.463 -0.828,-1.155 1.233,-6.511 7.109,-8.475 12.378,-8.475 2.693,0 6.215,0.719 8.335,2.757 2.693,2.515 2.432,5.869 2.432,9.524 v 8.623 c 0,2.596 1.081,3.732 2.091,5.128 0.354,0.503 0.434,1.103 -0.018,1.473 -1.131,0.949 -3.139,2.693 -4.244,3.676 l -0.014,-0.013 z"
style="fill-rule:evenodd" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,13 +1,13 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
''' '''
sp.rian.ru sp.rian.ru
''' '''
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class Ria_eng(BasicNewsRecipe): class Ria_esp(BasicNewsRecipe):
title = 'Ria Novosti' title = 'Ria Novosti'
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic'
description = 'Noticias desde Russia en Castellano' description = 'Noticias desde Russia en Castellano'
@ -28,14 +28,10 @@ class Ria_eng(BasicNewsRecipe):
} }
keep_only_tags = [dict(name='div', attrs={'class':'articletxt'})] keep_only_tags = [dict(name='div', attrs={'class':['mainnewsrubric','titleblock','mainnewstxt']})]
remove_tags = [dict(name=['object','link','iframe','base'])] remove_tags = [dict(name=['object','link','iframe','base'])]
remove_tags_after = dict(name='div',attrs={'class':'text'})
feeds = [(u'Noticias', u'http://sp.rian.ru/export/rss2/index.xml')] feeds = [(u'Noticias', u'http://rss.feedsportal.com/c/860/fe.ed/sp.rian.ru/export/rss2/index.xml')]
def print_version(self, url):
return url.replace('.html','-print.html')

View File

@ -10,7 +10,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
class TagesspiegelRSS(BasicNewsRecipe): class TagesspiegelRSS(BasicNewsRecipe):
title = u'Der Tagesspiegel' title = u'Der Tagesspiegel'
__author__ = 'ipaschke' __author__ = 'Ingo Paschke'
language = 'de' language = 'de'
oldest_article = 7 oldest_article = 7
max_articles_per_feed = 100 max_articles_per_feed = 100
@ -39,25 +39,30 @@ class TagesspiegelRSS(BasicNewsRecipe):
dict(name='link'), dict(name='iframe'),dict(name='style'),dict(name='meta'),dict(name='button'), dict(name='link'), dict(name='iframe'),dict(name='style'),dict(name='meta'),dict(name='button'),
dict(name='div', attrs={'class':["hcf-jump-to-comments","hcf-clear","hcf-magnify hcf-media-control"] }), dict(name='div', attrs={'class':["hcf-jump-to-comments","hcf-clear","hcf-magnify hcf-media-control"] }),
dict(name='span', attrs={'class':["hcf-mainsearch",] }), dict(name='span', attrs={'class':["hcf-mainsearch",] }),
dict(name='ul', attrs={'class':["hcf-tools"] }), dict(name='ul', attrs={'class':["hcf-tools"]}),
dict(name='ul', attrs={'class': re.compile('hcf-services')})
] ]
def parse_index(self): def parse_index(self):
soup = self.index_to_soup('http://www.tagesspiegel.de/zeitung/') soup = self.index_to_soup('http://www.tagesspiegel.de/zeitung/')
def feed_title(div): def feed_title(div):
return ''.join(div.findAll(text=True, recursive=False)).strip() return ''.join(div.findAll(text=True, recursive=False)).strip() if div is not None else None
articles = {} articles = {}
key = None key = None
ans = [] ans = []
maincol = soup.find('div', attrs={'class':re.compile('hcf-main-col')})
for div in soup.findAll(True, attrs={'class':['hcf-teaser', 'hcf-header', 'story headline']}): for div in maincol.findAll(True, attrs={'class':['hcf-teaser', 'hcf-header', 'story headline']}):
if div['class'] == 'hcf-header': if div['class'] == 'hcf-header':
try:
key = string.capwords(feed_title(div.em.a)) key = string.capwords(feed_title(div.em.a))
articles[key] = [] articles[key] = []
ans.append(key) ans.append(key)
except:
continue
elif div['class'] == 'hcf-teaser' and getattr(div.contents[0],'name','') == 'h2': elif div['class'] == 'hcf-teaser' and getattr(div.contents[0],'name','') == 'h2':
a = div.find('a', href=True) a = div.find('a', href=True)
@ -84,3 +89,4 @@ class TagesspiegelRSS(BasicNewsRecipe):
return ans return ans

View File

@ -50,12 +50,14 @@ class cdnet(BasicNewsRecipe):
dict(name='div', attrs={'class':'greyBoxR clearfix'}), dict(name='div', attrs={'class':'greyBoxR clearfix'}),
dict(name='div', attrs={'class':'greyBoxL clearfix'}), dict(name='div', attrs={'class':'greyBoxL clearfix'}),
dict(name='div', attrs={'class':'greyBox clearfix'}), dict(name='div', attrs={'class':'greyBox clearfix'}),
dict(name='div', attrs={'class':'labelized'}),
dict(id='')] dict(id='')]
#remove_tags_before = [dict(id='header-news-title')] #remove_tags_before = [dict(id='header-news-title')]
remove_tags_after = [dict(name='div', attrs={'class':'btmGreyTables'})] remove_tags_after = [dict(name='div', attrs={'class':'labelized'})]
#remove_tags_after = [dict(name='div', attrs={'class':'intelliTXT'})] #remove_tags_after = [dict(name='div', attrs={'class':'intelliTXT'})]
feeds = [ ('tomshardware', 'http://www.tomshardware.com/de/feeds/rss2/tom-s-hardware-de,12-1.xml') ] feeds = [ ('tomshardware', 'http://www.tomshardware.com/de/feeds/rss2/tom-s-hardware-de,12-1.xml') ]

View File

@ -36,7 +36,7 @@ class Vijesti(BasicNewsRecipe):
keep_only_tags = [dict(name='div', attrs={'id':'mainnews'})] keep_only_tags = [dict(name='div', attrs={'id':'mainnews'})]
remove_tags = [dict(name=['object','link','embed'])] remove_tags = [dict(name=['object','link','embed','form'])]
feeds = [(u'Sve vijesti', u'http://www.vijesti.me/rss.php' )] feeds = [(u'Sve vijesti', u'http://www.vijesti.me/rss.php' )]

View File

@ -22,7 +22,7 @@ class weltDe(BasicNewsRecipe):
remove_stylesheets = True remove_stylesheets = True
remove_javascript = True remove_javascript = True
encoding = 'utf-8' encoding = 'utf-8'
html2epub_options = 'linearize_tables = True\nbase_font_size2=10' html2epub_options = 'base_font_size=10'
BasicNewsRecipe.summary_length = 100 BasicNewsRecipe.summary_length = 100
@ -83,10 +83,9 @@ class weltDe(BasicNewsRecipe):
dict(name='div', attrs={'class':'articleOptions clear'}), dict(name='div', attrs={'class':'articleOptions clear'}),
dict(name='div', attrs={'class':'noPrint galleryIndex'}), dict(name='div', attrs={'class':'noPrint galleryIndex'}),
dict(name='div', attrs={'class':'inlineBox inlineTagCloud'}), dict(name='div', attrs={'class':'inlineBox inlineTagCloud'}),
dict(name='div', attrs={'class':'clear module imageGalleryBig bgColor1'}),
dict(name='div', attrs={'class':'clear module writeComment bgColor1'}), dict(name='div', attrs={'class':'clear module writeComment bgColor1'}),
dict(name='div', attrs={'class':'clear module textGallery bgColor1'}), dict(name='div', attrs={'class':'clear module textGallery bgColor1'}),
dict(name='div', attrs={'class':'clear module socialMedia bgColor1'}),
dict(name='div', attrs={'class':'clear module continuativeLinks'}),
dict(name='div', attrs={'class':'moreArtH3'}), dict(name='div', attrs={'class':'moreArtH3'}),
dict(name='div', attrs={'class':'jqmWindow'}), dict(name='div', attrs={'class':'jqmWindow'}),
dict(name='div', attrs={'class':'clear gap4'}), dict(name='div', attrs={'class':'clear gap4'}),
@ -99,7 +98,7 @@ class weltDe(BasicNewsRecipe):
dict(name='div', attrs={'class':'headLineH3'}), dict(name='div', attrs={'class':'headLineH3'}),
dict(name='div', attrs={'class':'print'}), dict(name='div', attrs={'class':'print'}),
dict(name='div', attrs={'class':'clear menu'}), dict(name='div', attrs={'class':'clear menu'}),
dict(name='div', attrs={'class':'clear galleryContent'}), dict(name='div', attrs={'class':'themenalarm'}),
dict(name='p', attrs={'class':'jump'}), dict(name='p', attrs={'class':'jump'}),
dict(name='a', attrs={'class':'commentLink'}), dict(name='a', attrs={'class':'commentLink'}),
dict(name='h2', attrs={'class':'jumpHeading'}), dict(name='h2', attrs={'class':'jumpHeading'}),
@ -110,7 +109,7 @@ class weltDe(BasicNewsRecipe):
dict(name='table', attrs={'class':'textGallery'}), dict(name='table', attrs={'class':'textGallery'}),
dict(name='li', attrs={'class':'active'})] dict(name='li', attrs={'class':'active'})]
remove_tags_after = [dict(name='div', attrs={'class':'clear departmentLine'})] remove_tags_after = [dict(name='div', attrs={'class':'themenalarm'})]
extra_css = ''' extra_css = '''
h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small; color: #003399;} h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small; color: #003399;}
@ -122,6 +121,7 @@ class weltDe(BasicNewsRecipe):
.photo {font-family:Arial,Helvetica,sans-serif; font-size: x-small; color: #666666;} ''' .photo {font-family:Arial,Helvetica,sans-serif; font-size: x-small; color: #666666;} '''
feeds = [ ('Politik', 'http://welt.de/politik/?service=Rss'), feeds = [ ('Politik', 'http://welt.de/politik/?service=Rss'),
('Deutsche Dinge', 'http://www.welt.de/deutsche-dinge/?service=Rss'),
('Wirtschaft', 'http://welt.de/wirtschaft/?service=Rss'), ('Wirtschaft', 'http://welt.de/wirtschaft/?service=Rss'),
('Finanzen', 'http://welt.de/finanzen/?service=Rss'), ('Finanzen', 'http://welt.de/finanzen/?service=Rss'),
('Sport', 'http://welt.de/sport/?service=Rss'), ('Sport', 'http://welt.de/sport/?service=Rss'),
@ -137,3 +137,4 @@ class weltDe(BasicNewsRecipe):
def print_version(self, url): def print_version(self, url):
return url.replace ('.html', '.html?print=true') return url.replace ('.html', '.html?print=true')

View File

@ -6,88 +6,105 @@ Fetch Die Zeit.
''' '''
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag
class ZeitDe(BasicNewsRecipe): class ZeitDe(BasicNewsRecipe):
title = 'Die Zeit Nachrichten' title = 'ZEIT Online Reader Edition'
description = 'Die Zeit - Online Nachrichten' description = 'ZEIT Online'
language = 'de' language = 'de'
lang = 'de_DE' lang = 'de_DE'
__author__ = 'Martin Pitt and Sujata Raman' __author__ = 'Martin Pitt, Sujata Raman and Ingo Paschke'
use_embedded_content = False use_embedded_content = False
max_articles_per_feed = 40 max_articles_per_feed = 100
remove_empty_feeds = True remove_empty_feeds = True
no_stylesheets = True no_stylesheets = True
no_javascript = True
encoding = 'utf-8' encoding = 'utf-8'
delay = 0
feeds = [ feeds = [
('Politik', 'http://newsfeed.zeit.de/politik/index'), ('Seite 1', 'http://newsfeed.zeit.de/index'),
('Wirtschaft', 'http://newsfeed.zeit.de/wirtschaft/index'), ('Politik', 'http://www.zeit.de/solr/select/?q=ressort:%22Politik%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'),
('Meinung', 'http://newsfeed.zeit.de/meinung/index'), ('Wirtschaft', 'http://www.zeit.de/solr/select/?q=ressort:%22Wirtschaft%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'),
('Gesellschaft', 'http://newsfeed.zeit.de/gesellschaft/index'), ('Meinung', 'http://www.zeit.de/solr/select/?q=ressort:%22Meinung%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'),
('Kultur', 'http://newsfeed.zeit.de/kultur/index'), ('Gesellschaft', 'http://www.zeit.de/solr/select/?q=ressort:%22Gesellschaft%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'),
('Wissen', 'http://newsfeed.zeit.de/wissen/index'), ('Kultur', 'http://www.zeit.de/solr/select/?q=ressort:%22Kultur%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'),
('Wissen', 'http://www.zeit.de/solr/select/?q=ressort:%22Wissen%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'),
('Digital', 'http://www.zeit.de/solr/select/?q=ressort:%22Digital%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'),
('Studium', 'http://www.zeit.de/solr/select/?q=ressort:%22Studium%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'),
('Karriere', 'http://www.zeit.de/solr/select/?q=ressort:%22Karriere%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'),
('Lebensart', 'http://www.zeit.de/solr/select/?q=ressort:%22Lebensart%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'),
('Reisen', 'http://www.zeit.de/solr/select/?q=ressort:%22Reisen%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'),
('Auto', 'http://www.zeit.de/solr/select/?q=ressort:%22Auto%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'),
('Sport', 'http://www.zeit.de/solr/select/?q=ressort:%22Sport%22%20type:article&version=2.2&start=0&rows=50&sort=date-first-released%20desc&indent=on&wt=xslt&tr=solr2rss.xsl'),
] ]
extra_css = ''' extra_css = '''
.supertitle{color:#990000; font-family:Arial,Helvetica,sans-serif;font-size:xx-small;} .supertitle{color:#990000; font-family:Arial,Helvetica,sans-serif;font-size:xx-small;}
.excerpt{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:large;} .excerpt{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:small;}
.title{font-family:Arial,Helvetica,sans-serif;font-size:large} .title{font-family:Arial,Helvetica,sans-serif;font-size:large;clear:right;}
.caption{color:#666666; font-family:Arial,Helvetica,sans-serif;font-size:xx-small;} .caption{color:#666666; font-family:Arial,Helvetica,sans-serif;font-size:xx-small;}
.copyright{color:#666666; font-family:Arial,Helvetica,sans-serif;font-size:xx-small;} .copyright{color:#666666; font-family:Arial,Helvetica,sans-serif;font-size:xx-small;}
.article{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:x-small} .article{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:x-small}
.quote{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:x-small}
.quote .cite{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:xx-small}
.headline iconportrait_inline{font-family:Arial,Helvetica,sans-serif;font-size:x-small} .headline iconportrait_inline{font-family:Arial,Helvetica,sans-serif;font-size:x-small}
.inline{float:left;margin-top:0;margin-right:15px;position:relative;width:180px; }
img.inline{float:none}
.intertitle{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:x-small;font-weight:700}
.ebinfobox{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:xx-small;list-style-type:none;float:right;margin-top:0;border-left-style:solid;border-left-width:1px;padding-left:10px;}
.infobox {border-style: solid; border-width: 1px;padding:8px;}
.infobox dt {font-weight:700;}
''' '''
#filter_regexps = [r'ad.de.doubleclick.net/'] #filter_regexps = [r'ad.de.doubleclick.net/']
keep_only_tags = [ keep_only_tags = [
dict(name='div', attrs={'class':["article"]}) , dict(name='div', attrs={'class':["article"]}) ,
dict(name='ul', attrs={'class':["tools"]}) ,
] ]
remove_tags = [ remove_tags = [
dict(name='link'), dict(name='iframe'),dict(name='style'), dict(name='link'), dict(name='iframe'),dict(name='style'),dict(name='meta'),
dict(name='div', attrs={'class':["pagination block","pagenav","inline link"] }), dict(name='div', attrs={'class':["pagination block","pagenav","inline link", "copyright"] }),
dict(name='div', attrs={'id':["place_5","place_4"]}) dict(name='p', attrs={'class':["ressortbacklink", "copyright"] }),
dict(name='div', attrs={'id':["place_5","place_4","comments"]})
] ]
remove_attributes = ['style', 'font']
def get_article_url(self, article): def get_article_url(self, article):
ans = article.get('link',None)
ans += "?page=all"
ans = article.get('guid',None) if 'video' in ans or 'quiz' or 'blog.zeit.de/' in ans :
try:
self.log('Looking for full story link in', ans)
soup = self.index_to_soup(ans)
x = soup.find(text="Auf einer Seite lesen")
if x is not None:
a = x.parent
if a and a.has_key('href'):
ans = a['href']
self.log('Found full story link', ans)
except:
pass
if 'video' in ans or 'quiz' in ans :
ans = None ans = None
return ans return ans
def get_cover_url(self):
try:
inhalt = self.index_to_soup('http://www.zeit.de/inhalt')
return inhalt.find('div', attrs={'class':'singlearchive clearfix'}).img['src'].replace('icon_','')
except:
return 'http://images.zeit.de/bilder/titelseiten_zeit/1946/001_001.jpg'
def preprocess_html(self, soup): def preprocess_html(self, soup):
soup.html['xml:lang'] = self.lang soup.html['xml:lang'] = self.lang
soup.html['lang'] = self.lang soup.html['lang'] = self.lang
mtag = '<meta http-equiv="Content-Type" content="text/html; charset=' + self.encoding + '">' mtag = '<meta http-equiv="Content-Type" content="text/html; charset=' + self.encoding + '">'
soup.head.insert(0,mtag) soup.head.insert(0,mtag)
title = soup.find('h2', attrs={'class':'title'})
if title is None:
print "no title"
return soup
info = Tag(soup,'ul',[('class','ebinfobox')])
tools = soup.find('ul', attrs={'class':'tools'})
author = tools.find('li','author first')
for tag in ['author first', 'date', 'date first', 'author', 'source']:
line = tools.find('li', tag)
if line:
info.insert(0,line)
title.parent.insert(0,info)
tools.extract()
return soup return soup
#def print_version(self,url):
# return url.replace('http://www.zeit.de/', 'http://images.zeit.de/text/').replace('?from=rss', '')

View File

@ -73,11 +73,11 @@ class Manual(Command):
os.makedirs(d) os.makedirs(d)
if not os.path.exists('.build'+os.sep+'html'): if not os.path.exists('.build'+os.sep+'html'):
os.makedirs('.build'+os.sep+'html') os.makedirs('.build'+os.sep+'html')
os.environ['__appname__']= __appname__ os.environ['__appname__'] = __appname__
os.environ['__version__']= __version__ os.environ['__version__'] = __version__
subprocess.check_call(['sphinx-build', '-b', 'custom', '-t', 'online', subprocess.check_call(['sphinx-build', '-b', 'html', '-t', 'online',
'-d', '.build/doctrees', '.', '.build/html']) '-d', '.build/doctrees', '.', '.build/html'])
subprocess.check_call(['sphinx-build', '-b', 'epub', '-d', subprocess.check_call(['sphinx-build', '-b', 'myepub', '-d',
'.build/doctrees', '.', '.build/epub']) '.build/doctrees', '.', '.build/epub'])
shutil.copyfile(self.j('.build', 'epub', 'calibre.epub'), self.j('.build', shutil.copyfile(self.j('.build', 'epub', 'calibre.epub'), self.j('.build',
'html', 'calibre.epub')) 'html', 'calibre.epub'))

View File

@ -262,31 +262,21 @@ class CatalogPlugin(Plugin):
type = _('Catalog generator') type = _('Catalog generator')
#: CLI parser options specific to this plugin, declared as namedtuple Option #: CLI parser options specific to this plugin, declared as namedtuple Option::
#: #:
#: from collections import namedtuple #: from collections import namedtuple
#: Option = namedtuple('Option', 'option, default, dest, help') #: Option = namedtuple('Option', 'option, default, dest, help')
#: cli_options = [Option('--catalog-title', #: cli_options = [Option('--catalog-title',
#: default = 'My Catalog', #: default = 'My Catalog',
#: dest = 'catalog_title', #: dest = 'catalog_title',
#: help = (_('Title of generated catalog. \nDefault:') + " '" + #: help = (_('Title of generated catalog. \nDefault:') + " '" +
#: '%default' + "'"))] #: '%default' + "'"))]
#: cli_options parsed in library.cli:catalog_option_parser() #: cli_options parsed in library.cli:catalog_option_parser()
cli_options = [] cli_options = []
def search_sort_db(self, db, opts): def search_sort_db(self, db, opts):
'''
# Don't add Catalogs to the generated Catalogs
cat = _('Catalog')
if opts.search_text:
opts.search_text += ' not tag:'+cat
else:
opts.search_text = 'not tag:'+cat
'''
db.search(opts.search_text) db.search(opts.search_text)
if opts.sort_by: if opts.sort_by:
@ -349,8 +339,7 @@ class CatalogPlugin(Plugin):
It should generate the catalog in the format specified It should generate the catalog in the format specified
in file_types, returning the absolute path to the in file_types, returning the absolute path to the
generated catalog file. If an error is encountered generated catalog file. If an error is encountered
it should raise an Exception and return None. The default it should raise an Exception.
implementation simply returns None.
The generated catalog file should be created with the The generated catalog file should be created with the
:meth:`temporary_file` method. :meth:`temporary_file` method.
@ -358,9 +347,6 @@ class CatalogPlugin(Plugin):
:param path_to_output: Absolute path to the generated catalog file. :param path_to_output: Absolute path to the generated catalog file.
:param opts: A dictionary of keyword arguments :param opts: A dictionary of keyword arguments
:param db: A LibraryDatabase2 object :param db: A LibraryDatabase2 object
:return: None
''' '''
# Default implementation does nothing # Default implementation does nothing
raise NotImplementedError('CatalogPlugin.generate_catalog() default ' raise NotImplementedError('CatalogPlugin.generate_catalog() default '

View File

@ -28,7 +28,7 @@ class ConversionOption(object):
def validate_parameters(self): def validate_parameters(self):
''' '''
Validate the parameters passed to :method:`__init__`. Validate the parameters passed to :meth:`__init__`.
''' '''
if re.match(r'[a-zA-Z_]([a-zA-Z0-9_])*', self.name) is None: if re.match(r'[a-zA-Z_]([a-zA-Z0-9_])*', self.name) is None:
raise ValueError(self.name + ' is not a valid Python identifier') raise ValueError(self.name + ' is not a valid Python identifier')
@ -96,7 +96,7 @@ class InputFormatPlugin(Plugin):
InputFormatPlugins are responsible for converting a document into InputFormatPlugins are responsible for converting a document into
HTML+OPF+CSS+etc. HTML+OPF+CSS+etc.
The results of the conversion *must* be encoded in UTF-8. The results of the conversion *must* be encoded in UTF-8.
The main action happens in :method:`convert`. The main action happens in :meth:`convert`.
''' '''
type = _('Conversion Input') type = _('Conversion Input')
@ -109,7 +109,7 @@ class InputFormatPlugin(Plugin):
#: If True, this input plugin generates a collection of images, #: If True, this input plugin generates a collection of images,
#: one per HTML file. You can obtain access to the images via #: one per HTML file. You can obtain access to the images via
#: convenience method, :method:`get_image_collection`. #: convenience method, :meth:`get_image_collection`.
is_image_collection = False is_image_collection = False
#: If set to True, the input plugin will perform special processing #: If set to True, the input plugin will perform special processing
@ -117,7 +117,7 @@ class InputFormatPlugin(Plugin):
for_viewer = False for_viewer = False
#: Options shared by all Input format plugins. Do not override #: Options shared by all Input format plugins. Do not override
#: in sub-classes. Use :member:`options` instead. Every option must be an #: in sub-classes. Use :attr:`options` instead. Every option must be an
#: instance of :class:`OptionRecommendation`. #: instance of :class:`OptionRecommendation`.
common_options = set([ common_options = set([
OptionRecommendation(name='input_encoding', OptionRecommendation(name='input_encoding',
@ -173,7 +173,6 @@ class InputFormatPlugin(Plugin):
returns. returns.
:param stream: A file like object that contains the input file. :param stream: A file like object that contains the input file.
:param options: Options to customize the conversion process. :param options: Options to customize the conversion process.
Guaranteed to have attributes corresponding Guaranteed to have attributes corresponding
to all the options declared by this plugin. In to all the options declared by this plugin. In
@ -182,14 +181,11 @@ class InputFormatPlugin(Plugin):
mean be more verbose. Another useful attribute is mean be more verbose. Another useful attribute is
``input_profile`` that is an instance of ``input_profile`` that is an instance of
:class:`calibre.customize.profiles.InputProfile`. :class:`calibre.customize.profiles.InputProfile`.
:param file_ext: The extension (without the .) of the input file. It :param file_ext: The extension (without the .) of the input file. It
is guaranteed to be one of the `file_types` supported is guaranteed to be one of the `file_types` supported
by this plugin. by this plugin.
:param log: A :class:`calibre.utils.logging.Log` object. All output :param log: A :class:`calibre.utils.logging.Log` object. All output
should use this object. should use this object.
:param accelarators: A dictionary of various information that the input :param accelarators: A dictionary of various information that the input
plugin can get easily that would speed up the plugin can get easily that would speed up the
subsequent stages of the conversion. subsequent stages of the conversion.
@ -235,7 +231,7 @@ class OutputFormatPlugin(Plugin):
(OPF+HTML) into an output ebook. (OPF+HTML) into an output ebook.
The OEB document can be assumed to be encoded in UTF-8. The OEB document can be assumed to be encoded in UTF-8.
The main action happens in :method:`convert`. The main action happens in :meth:`convert`.
''' '''
type = _('Conversion Output') type = _('Conversion Output')
@ -247,7 +243,7 @@ class OutputFormatPlugin(Plugin):
file_type = None file_type = None
#: Options shared by all Input format plugins. Do not override #: Options shared by all Input format plugins. Do not override
#: in sub-classes. Use :member:`options` instead. Every option must be an #: in sub-classes. Use :attr:`options` instead. Every option must be an
#: instance of :class:`OptionRecommendation`. #: instance of :class:`OptionRecommendation`.
common_options = set([ common_options = set([
OptionRecommendation(name='pretty_print', OptionRecommendation(name='pretty_print',
@ -277,17 +273,15 @@ class OutputFormatPlugin(Plugin):
:class:`calibre.ebooks.oeb.OEBBook` to the file specified by output. :class:`calibre.ebooks.oeb.OEBBook` to the file specified by output.
:param output: Either a file like object or a string. If it is a string :param output: Either a file like object or a string. If it is a string
it is the path to a directory that may or may not exist. The output it is the path to a directory that may or may not exist. The output
plugin should write its output into that directory. If it is a file like plugin should write its output into that directory. If it is a file like
object, the output plugin should write its output into the file. object, the output plugin should write its output into the file.
:param input_plugin: The input plugin that was used at the beginning of :param input_plugin: The input plugin that was used at the beginning of
the conversion pipeline. the conversion pipeline.
:param opts: Conversion options. Guaranteed to have attributes :param opts: Conversion options. Guaranteed to have attributes
corresponding to the OptionRecommendations of this plugin. corresponding to the OptionRecommendations of this plugin.
:param log: The logger. Print debug/info messages etc. using this. :param log: The logger. Print debug/info messages etc. using this.
''' '''
raise NotImplementedError raise NotImplementedError

View File

@ -1,10 +1,5 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
"""
Define the minimum interface that a device backend must satisfy to be used in
the GUI. A device backend must subclass the L{Device} class. See prs500.py for
a backend that implement the Device interface for the SONY PRS500 Reader.
"""
import os import os
from collections import namedtuple from collections import namedtuple
@ -15,32 +10,38 @@ class DevicePlugin(Plugin):
""" """
Defines the interface that should be implemented by backends that Defines the interface that should be implemented by backends that
communicate with an ebook reader. communicate with an ebook reader.
The C{end_session} variables are used for USB session management. Sometimes
the front-end needs to call several methods one after another, in which case
the USB session should not be closed after each method call.
""" """
type = _('Device Interface') type = _('Device Interface')
# Ordered list of supported formats #: Ordered list of supported formats
FORMATS = ["lrf", "rtf", "pdf", "txt"] FORMATS = ["lrf", "rtf", "pdf", "txt"]
#: VENDOR_ID can be either an integer, a list of integers or a dictionary #: VENDOR_ID can be either an integer, a list of integers or a dictionary
#: If it is a dictionary, it must be a dictionary of dictionaries, of the form #: If it is a dictionary, it must be a dictionary of dictionaries,
#: { #: of the form::
#: integer_vendor_id : { product_id : [list of BCDs], ... }, #:
#: ... #: {
#: } #: integer_vendor_id : { product_id : [list of BCDs], ... },
#: ...
#: }
#:
VENDOR_ID = 0x0000 VENDOR_ID = 0x0000
#: An integer or a list of integers #: An integer or a list of integers
PRODUCT_ID = 0x0000 PRODUCT_ID = 0x0000
# BCD can be either None to not distinguish between devices based on BCD, or #: BCD can be either None to not distinguish between devices based on BCD, or
# it can be a list of the BCD numbers of all devices supported by this driver. #: it can be a list of the BCD numbers of all devices supported by this driver.
BCD = None BCD = None
THUMBNAIL_HEIGHT = 68 # Height for thumbnails on device
# Whether the metadata on books can be set via the GUI. #: Height for thumbnails on the device
THUMBNAIL_HEIGHT = 68
#: Whether the metadata on books can be set via the GUI.
CAN_SET_METADATA = True CAN_SET_METADATA = True
#: Path separator for paths to books on device #: Path separator for paths to books on device
path_sep = os.sep path_sep = os.sep
#: Icon for this device #: Icon for this device
icon = I('reader.svg') icon = I('reader.svg')
@ -121,6 +122,7 @@ class DevicePlugin(Plugin):
Return True, device_info if a device handled by this plugin is currently connected. Return True, device_info if a device handled by this plugin is currently connected.
:param devices_on_system: List of devices currently connected :param devices_on_system: List of devices currently connected
''' '''
if iswindows: if iswindows:
return self.is_usb_connected_windows(devices_on_system, return self.is_usb_connected_windows(devices_on_system,
@ -157,13 +159,14 @@ class DevicePlugin(Plugin):
def reset(self, key='-1', log_packets=False, report_progress=None, def reset(self, key='-1', log_packets=False, report_progress=None,
detected_device=None) : detected_device=None) :
""" """
:key: The key to unlock the device :param key: The key to unlock the device
:log_packets: If true the packet stream to/from the device is logged :param log_packets: If true the packet stream to/from the device is logged
:report_progress: Function that is called with a % progress :param report_progress: Function that is called with a % progress
(number between 0 and 100) for various tasks (number between 0 and 100) for various tasks
If it is called with -1 that means that the If it is called with -1 that means that the
task does not have any progress information task does not have any progress information
:detected_device: Device information from the device scanner :param detected_device: Device information from the device scanner
""" """
raise NotImplementedError() raise NotImplementedError()
@ -174,19 +177,21 @@ class DevicePlugin(Plugin):
is only called after the vendor, product ids and the bcd have matched, so is only called after the vendor, product ids and the bcd have matched, so
it can do some relatively time intensive checks. The default implementation it can do some relatively time intensive checks. The default implementation
returns True. This method is called only on windows. See also returns True. This method is called only on windows. See also
:method:`can_handle`. :meth:`can_handle`.
:param device_info: On windows a device ID string. On Unix a tuple of :param device_info: On windows a device ID string. On Unix a tuple of
``(vendor_id, product_id, bcd)``. ``(vendor_id, product_id, bcd)``.
''' '''
return True return True
def can_handle(self, device_info, debug=False): def can_handle(self, device_info, debug=False):
''' '''
Unix version of :method:`can_handle_windows` Unix version of :meth:`can_handle_windows`
:param device_info: Is a tupe of (vid, pid, bcd, manufacturer, product, :param device_info: Is a tupe of (vid, pid, bcd, manufacturer, product,
serial number) serial number)
''' '''
return True return True
@ -198,7 +203,8 @@ class DevicePlugin(Plugin):
For example: For devices that present themselves as USB Mass storage For example: For devices that present themselves as USB Mass storage
devices, this method would be responsible for mounting the device or devices, this method would be responsible for mounting the device or
if the device has been automounted, for finding out where it has been if the device has been automounted, for finding out where it has been
mounted. The base class within USBMS device.py has a implementation of mounted. The method :meth:`calibre.devices.usbms.device.Device.open` has
an implementation of
this function that should serve as a good example for USB Mass storage this function that should serve as a good example for USB Mass storage
devices. devices.
''' '''
@ -219,17 +225,20 @@ class DevicePlugin(Plugin):
def set_progress_reporter(self, report_progress): def set_progress_reporter(self, report_progress):
''' '''
@param report_progress: Function that is called with a % progress :param report_progress: Function that is called with a % progress
(number between 0 and 100) for various tasks (number between 0 and 100) for various tasks
If it is called with -1 that means that the If it is called with -1 that means that the
task does not have any progress information task does not have any progress information
''' '''
raise NotImplementedError() raise NotImplementedError()
def get_device_information(self, end_session=True): def get_device_information(self, end_session=True):
""" """
Ask device for device information. See L{DeviceInfoQuery}. Ask device for device information. See L{DeviceInfoQuery}.
@return: (device name, device version, software version on device, mime type)
:return: (device name, device version, software version on device, mime type)
""" """
raise NotImplementedError() raise NotImplementedError()
@ -252,8 +261,9 @@ class DevicePlugin(Plugin):
2. Memory Card A 2. Memory Card A
3. Memory Card B 3. Memory Card B
@return: A 3 element list with total space in bytes of (1, 2, 3). If a :return: A 3 element list with total space in bytes of (1, 2, 3). If a
particular device doesn't have any of these locations it should return 0. particular device doesn't have any of these locations it should return 0.
""" """
raise NotImplementedError() raise NotImplementedError()
@ -264,19 +274,23 @@ class DevicePlugin(Plugin):
2. Card A 2. Card A
3. Card B 3. Card B
@return: A 3 element list with free space in bytes of (1, 2, 3). If a :return: A 3 element list with free space in bytes of (1, 2, 3). If a
particular device doesn't have any of these locations it should return -1. particular device doesn't have any of these locations it should return -1.
""" """
raise NotImplementedError() raise NotImplementedError()
def books(self, oncard=None, end_session=True): def books(self, oncard=None, end_session=True):
""" """
Return a list of ebooks on the device. Return a list of ebooks on the device.
@param oncard: If 'carda' or 'cardb' return a list of ebooks on the
:param oncard: If 'carda' or 'cardb' return a list of ebooks on the
specific storage card, otherwise return list of ebooks specific storage card, otherwise return list of ebooks
in main memory of device. If a card is specified and no in main memory of device. If a card is specified and no
books are on the card return empty list. books are on the card return empty list.
@return: A BookList.
:return: A BookList.
""" """
raise NotImplementedError() raise NotImplementedError()
@ -285,25 +299,27 @@ class DevicePlugin(Plugin):
''' '''
Upload a list of books to the device. If a file already Upload a list of books to the device. If a file already
exists on the device, it should be replaced. exists on the device, it should be replaced.
This method should raise a L{FreeSpaceError} if there is not enough This method should raise a :class:`FreeSpaceError` if there is not enough
free space on the device. The text of the FreeSpaceError must contain the free space on the device. The text of the FreeSpaceError must contain the
word "card" if C{on_card} is not None otherwise it must contain the word "memory". word "card" if ``on_card`` is not None otherwise it must contain the word "memory".
:files: A list of paths and/or file-like objects. If they are paths and
the paths point to temporary files, they may have an additional :param files: A list of paths and/or file-like objects. If they are paths and
attribute, original_file_path pointing to the originals. They may have the paths point to temporary files, they may have an additional
another optional attribute, deleted_after_upload which if True means attribute, original_file_path pointing to the originals. They may have
that the file pointed to by original_file_path will be deleted after another optional attribute, deleted_after_upload which if True means
being uploaded to the device. that the file pointed to by original_file_path will be deleted after
:names: A list of file names that the books should have being uploaded to the device.
once uploaded to the device. len(names) == len(files) :param names: A list of file names that the books should have
once uploaded to the device. len(names) == len(files)
:param metadata: If not None, it is a list of :class:`MetaInformation` objects.
The idea is to use the metadata to determine where on the device to
put the book. len(metadata) == len(files). Apart from the regular
cover (path to cover), there may also be a thumbnail attribute, which should
be used in preference. The thumbnail attribute is of the form
(width, height, cover_data as jpeg).
:return: A list of 3-element tuples. The list is meant to be passed :return: A list of 3-element tuples. The list is meant to be passed
to L{add_books_to_metadata}. to :meth:`add_books_to_metadata`.
:metadata: If not None, it is a list of :class:`MetaInformation` objects.
The idea is to use the metadata to determine where on the device to
put the book. len(metadata) == len(files). Apart from the regular
cover (path to cover), there may also be a thumbnail attribute, which should
be used in preference. The thumbnail attribute is of the form
(width, height, cover_data as jpeg).
''' '''
raise NotImplementedError() raise NotImplementedError()
@ -312,12 +328,15 @@ class DevicePlugin(Plugin):
''' '''
Add locations to the booklists. This function must not communicate with Add locations to the booklists. This function must not communicate with
the device. the device.
@param locations: Result of a call to L{upload_books}
@param metadata: List of MetaInformation objects, same as for :param locations: Result of a call to L{upload_books}
:method:`upload_books`. :param metadata: List of :class:`MetaInformation` objects, same as for
@param booklists: A tuple containing the result of calls to :meth:`upload_books`.
(L{books}(oncard=None), L{books}(oncard='carda'), :param booklists: A tuple containing the result of calls to
L{books}(oncard='cardb')). (:meth:`books(oncard=None)`,
:meth:`books(oncard='carda')`,
:meth`books(oncard='cardb')`).
''' '''
raise NotImplementedError raise NotImplementedError
@ -332,26 +351,35 @@ class DevicePlugin(Plugin):
''' '''
Remove books from the metadata list. This function must not communicate Remove books from the metadata list. This function must not communicate
with the device. with the device.
@param paths: paths to books on the device.
@param booklists: A tuple containing the result of calls to :param paths: paths to books on the device.
(L{books}(oncard=None), L{books}(oncard='carda'), :param booklists: A tuple containing the result of calls to
L{books}(oncard='cardb')). (:meth:`books(oncard=None)`,
:meth:`books(oncard='carda')`,
:meth`books(oncard='cardb')`).
''' '''
raise NotImplementedError() raise NotImplementedError()
def sync_booklists(self, booklists, end_session=True): def sync_booklists(self, booklists, end_session=True):
''' '''
Update metadata on device. Update metadata on device.
@param booklists: A tuple containing the result of calls to
(L{books}(oncard=None), L{books}(oncard='carda'), :param booklists: A tuple containing the result of calls to
L{books}(oncard='cardb')). (:meth:`books(oncard=None)`,
:meth:`books(oncard='carda')`,
:meth`books(oncard='cardb')`).
''' '''
raise NotImplementedError() raise NotImplementedError()
def get_file(self, path, outfile, end_session=True): def get_file(self, path, outfile, end_session=True):
''' '''
Read the file at C{path} on the device and write it to outfile. Read the file at ``path`` on the device and write it to outfile.
@param outfile: file object like C{sys.stdout} or the result of an C{open} call
:param outfile: file object like ``sys.stdout`` or the result of an
:func:`open` call.
''' '''
raise NotImplementedError() raise NotImplementedError()
@ -365,8 +393,8 @@ class DevicePlugin(Plugin):
@classmethod @classmethod
def save_settings(cls, settings_widget): def save_settings(cls, settings_widget):
''' '''
Should save settings to disk. Takes the widget created in config_widget Should save settings to disk. Takes the widget created in
and saves all settings to disk. :meth:`config_widget` and saves all settings to disk.
''' '''
raise NotImplementedError() raise NotImplementedError()
@ -381,16 +409,18 @@ class DevicePlugin(Plugin):
class BookList(list): class BookList(list):
''' '''
A list of books. Each Book object must have the fields: A list of books. Each Book object must have the fields
1. title
2. authors #. title
3. size (file size of the book) #. authors
4. datetime (a UTC time tuple) #. size (file size of the book)
5. path (path on the device to the book) #. datetime (a UTC time tuple)
6. thumbnail (can be None) thumbnail is either a str/bytes object with the #. path (path on the device to the book)
#. thumbnail (can be None) thumbnail is either a str/bytes object with the
image data or it should have an attribute image_path that stores an image data or it should have an attribute image_path that stores an
absolute (platform native) path to the image absolute (platform native) path to the image
7. tags (a list of strings, can be empty). #. tags (a list of strings, can be empty).
''' '''
__getslice__ = None __getslice__ = None
@ -427,6 +457,7 @@ class BookList(list):
created from series, in which case series_index is used. created from series, in which case series_index is used.
:param collection_attributes: A list of attributes of the Book object :param collection_attributes: A list of attributes of the Book object
''' '''
raise NotImplementedError() raise NotImplementedError()

View File

@ -46,7 +46,11 @@ def strptime(src):
return time.strptime(' '.join(src), '%w, %d %m %Y %H:%M:%S %Z') return time.strptime(' '.join(src), '%w, %d %m %Y %H:%M:%S %Z')
def strftime(epoch, zone=time.localtime): def strftime(epoch, zone=time.localtime):
src = time.strftime("%w, %d %m %Y %H:%M:%S GMT", zone(epoch)).split() try:
src = time.strftime("%w, %d %m %Y %H:%M:%S GMT", zone(epoch)).split()
except:
src = time.strftime("%w, %d %m %Y %H:%M:%S GMT", zone()).split()
src[0] = INVERSE_DAY_MAP[int(src[0][:-1])]+',' src[0] = INVERSE_DAY_MAP[int(src[0][:-1])]+','
src[2] = INVERSE_MONTH_MAP[int(src[2])] src[2] = INVERSE_MONTH_MAP[int(src[2])]
return ' '.join(src) return ' '.join(src)
@ -328,7 +332,10 @@ class XMLCache(object):
'descendant::*[local-name()="jpeg"]|' 'descendant::*[local-name()="jpeg"]|'
'descendant::*[local-name()="png"]'): 'descendant::*[local-name()="png"]'):
if img.text: if img.text:
raw = b64decode(img.text.strip()) try:
raw = b64decode(img.text.strip())
except:
continue
book.thumbnail = raw book.thumbnail = raw
break break
break break

View File

@ -47,8 +47,8 @@ class Device(DeviceConfig, DevicePlugin):
''' '''
This class provides logic common to all drivers for devices that export themselves This class provides logic common to all drivers for devices that export themselves
as USB Mass Storage devices. If you are writing such a driver, inherit from this as USB Mass Storage devices. Provides implementations for mounting/ejecting
class. of USBMS devices on all platforms.
''' '''
VENDOR_ID = 0x0 VENDOR_ID = 0x0
@ -57,9 +57,19 @@ class Device(DeviceConfig, DevicePlugin):
VENDOR_NAME = None VENDOR_NAME = None
# These can be None, string, list of strings or compiled regex #: String identifying the main memory of the device in the windows PnP id
#: strings
#: This can be None, string, list of strings or compiled regex
WINDOWS_MAIN_MEM = None WINDOWS_MAIN_MEM = None
#: String identifying the first card of the device in the windows PnP id
#: strings
#: This can be None, string, list of strings or compiled regex
WINDOWS_CARD_A_MEM = None WINDOWS_CARD_A_MEM = None
#: String identifying the second card of the device in the windows PnP id
#: strings
#: This can be None, string, list of strings or compiled regex
WINDOWS_CARD_B_MEM = None WINDOWS_CARD_B_MEM = None
# The following are used by the check_ioreg_line method and can be either: # The following are used by the check_ioreg_line method and can be either:
@ -68,9 +78,9 @@ class Device(DeviceConfig, DevicePlugin):
OSX_CARD_A_MEM = None OSX_CARD_A_MEM = None
OSX_CARD_B_MEM = None OSX_CARD_B_MEM = None
# Used by the new driver detection to disambiguate main memory from #: Used by the new driver detection to disambiguate main memory from
# storage cards. Should be a regular expression that matches the #: storage cards. Should be a regular expression that matches the
# main memory mount point assigned by OS X #: main memory mount point assigned by OS X
OSX_MAIN_MEM_VOL_PAT = None OSX_MAIN_MEM_VOL_PAT = None
OSX_EJECT_COMMAND = ['diskutil', 'eject'] OSX_EJECT_COMMAND = ['diskutil', 'eject']
@ -780,7 +790,7 @@ class Device(DeviceConfig, DevicePlugin):
def filename_callback(self, default, mi): def filename_callback(self, default, mi):
''' '''
Callback to allow drivers to change the default file name Callback to allow drivers to change the default file name
set by :method:`create_upload_path`. set by :meth:`create_upload_path`.
''' '''
return default return default

View File

@ -33,6 +33,10 @@ def debug_print(*args):
# CLI must come before Device as it implements the CLI functions that # CLI must come before Device as it implements the CLI functions that
# are inherited from the device interface in Device. # are inherited from the device interface in Device.
class USBMS(CLI, Device): class USBMS(CLI, Device):
'''
The base class for all USBMS devices. Implements the logic for
sending/getting/updating metadata/caching metadata/etc.
'''
description = _('Communicate with an eBook reader.') description = _('Communicate with an eBook reader.')
author = _('John Schember') author = _('John Schember')
@ -195,10 +199,13 @@ class USBMS(CLI, Device):
def upload_cover(self, path, filename, metadata): def upload_cover(self, path, filename, metadata):
''' '''
:path: the full path were the associated book is located. Upload book cover to the device. Default implementation does nothing.
:filename: the name of the book file without the extension.
:metadata: metadata belonging to the book. Use metadata.thumbnail :param path: the full path were the associated book is located.
for cover :param filename: the name of the book file without the extension.
:param metadata: metadata belonging to the book. Use metadata.thumbnail
for cover
''' '''
pass pass

View File

@ -15,6 +15,22 @@ from calibre.ebooks.metadata.library_thing import check_for_cover
metadata_config = None metadata_config = None
class MetadataSource(Plugin): # {{{ class MetadataSource(Plugin): # {{{
'''
Represents a source to query for metadata. Subclasses must implement
at least the fetch method.
When :meth:`fetch` is called, the `self` object will have the following
useful attributes (each of which may be None)::
title, book_author, publisher, isbn, log, verbose and extra
Use these attributes to construct the search query. extra is reserved for
future use.
The fetch method must store the results in `self.results` as a list of
:class:`MetaInformation` objects. If there is an error, it should be stored
in `self.exception` and `self.tb` (for the traceback).
'''
author = 'Kovid Goyal' author = 'Kovid Goyal'

View File

@ -119,10 +119,11 @@ class RTFMLizer(object):
output += '{\\page } ' output += '{\\page } '
for item in self.oeb_book.spine: for item in self.oeb_book.spine:
self.log.debug('Converting %s to RTF markup...' % item.href) self.log.debug('Converting %s to RTF markup...' % item.href)
stylizer = Stylizer(item.data, item.href, self.oeb_book, self.opts, self.opts.output_profile) content = unicode(etree.tostring(item.data, encoding=unicode))
content = unicode(etree.tostring(item.data.find(XHTML('body')), encoding=unicode))
content = self.remove_newlines(content) content = self.remove_newlines(content)
output += self.dump_text(etree.fromstring(content), stylizer) content = etree.fromstring(content)
stylizer = Stylizer(content, item.href, self.oeb_book, self.opts, self.opts.output_profile)
output += self.dump_text(content.find(XHTML('body')), stylizer)
output += self.footer() output += self.footer()
output = self.insert_images(output) output = self.insert_images(output)
output = self.clean_text(output) output = self.clean_text(output)

View File

@ -512,9 +512,18 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
idx = i idx = i
self.opt_toolbar_text.addItem(x[0], x[1]) self.opt_toolbar_text.addItem(x[0], x[1])
self.opt_toolbar_text.setCurrentIndex(idx) self.opt_toolbar_text.setCurrentIndex(idx)
self.reset_confirmation_button.clicked.connect(self.reset_confirmation)
self.category_view.setCurrentIndex(self.category_view.model().index_for_name(initial_category)) self.category_view.setCurrentIndex(self.category_view.model().index_for_name(initial_category))
def reset_confirmation(self):
from calibre.gui2 import dynamic
for key in dynamic.keys():
if key.endswith('_again') and dynamic[key] is False:
dynamic[key] = True
info_dialog(self, _('Done'),
_('Confirmation dialogs have all been reset'), show=True)
def check_port_value(self, *args): def check_port_value(self, *args):
port = self.port.value() port = self.port.value()
if port < 1025: if port < 1025:

View File

@ -89,8 +89,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>724</width> <width>720</width>
<height>683</height> <height>679</height>
</rect> </rect>
</property> </property>
<layout class="QGridLayout" name="gridLayout_7"> <layout class="QGridLayout" name="gridLayout_7">
@ -222,6 +222,13 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QPushButton" name="reset_confirmation_button">
<property name="text">
<string>Reset all disabled &amp;confirmation dialogs</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="groupBox_5"> <widget class="QGroupBox" name="groupBox_5">
<property name="title"> <property name="title">

View File

@ -5,7 +5,7 @@ __docformat__ = 'restructuredtext en'
from calibre.gui2 import dynamic from calibre.gui2 import dynamic
from calibre.gui2.dialogs.confirm_delete_ui import Ui_Dialog from calibre.gui2.dialogs.confirm_delete_ui import Ui_Dialog
from PyQt4.Qt import QDialog, SIGNAL, Qt from PyQt4.Qt import QDialog, Qt, QPixmap, QIcon
def _config_name(name): def _config_name(name):
return name + '_again' return name + '_again'
@ -18,15 +18,17 @@ class Dialog(QDialog, Ui_Dialog):
self.msg.setText(msg) self.msg.setText(msg)
self.name = name self.name = name
self.connect(self.again, SIGNAL('stateChanged(int)'), self.toggle) self.again.stateChanged.connect(self.toggle)
self.buttonBox.setFocus(Qt.OtherFocusReason) self.buttonBox.setFocus(Qt.OtherFocusReason)
def toggle(self, x): def toggle(self, *args):
dynamic[_config_name(self.name)] = self.again.isChecked() dynamic[_config_name(self.name)] = self.again.isChecked()
def confirm(msg, name, parent=None): def confirm(msg, name, parent=None, pixmap='dialog_warning.svg'):
if not dynamic.get(_config_name(name), True): if not dynamic.get(_config_name(name), True):
return True return True
d = Dialog(msg, name, parent) d = Dialog(msg, name, parent)
d.label.setPixmap(QPixmap(I(pixmap)))
d.setWindowIcon(QIcon(I(pixmap)))
return d.exec_() == d.Accepted return d.exec_() == d.Accepted

View File

@ -26,7 +26,10 @@ class ThrobbingButton(QToolButton):
def set_normal_icon_size(self, w, h): def set_normal_icon_size(self, w, h):
self.normal_icon_size = QSize(w, h) self.normal_icon_size = QSize(w, h)
self.setIconSize(self.normal_icon_size) self.setIconSize(self.normal_icon_size)
self.setMinimumSize(self.sizeHint()) try:
self.setMinimumSize(self.sizeHint())
except:
self.setMinimumSize(QSize(w+5, h+5))
def animation_finished(self): def animation_finished(self):
self.setIconSize(self.normal_icon_size) self.setIconSize(self.normal_icon_size)

View File

@ -3,12 +3,14 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
''' Post installation script for linux ''' ''' Post installation script for linux '''
import sys, os, shutil, cPickle, textwrap, stat import sys, os, cPickle, textwrap, stat
from subprocess import check_call from subprocess import check_call
from calibre import __appname__, prints, guess_type from calibre import __appname__, prints, guess_type
from calibre.constants import islinux, isfreebsd from calibre.constants import islinux, isfreebsd
from calibre.customize.ui import all_input_formats from calibre.customize.ui import all_input_formats
from calibre.ptempfile import TemporaryDirectory
from calibre import CurrentDir
entry_points = { entry_points = {
@ -39,6 +41,7 @@ entry_points = {
], ],
} }
# Uninstall script {{{
UNINSTALL = '''\ UNINSTALL = '''\
#!{python} #!{python}
euid = {euid} euid = {euid}
@ -79,6 +82,8 @@ for f in mr:
os.remove(os.path.abspath(__file__)) os.remove(os.path.abspath(__file__))
''' '''
# }}}
class PostInstall: class PostInstall:
def task_failed(self, msg): def task_failed(self, msg):
@ -171,7 +176,7 @@ class PostInstall:
self.task_failed('Creating uninstaller failed') self.task_failed('Creating uninstaller failed')
def setup_completion(self): def setup_completion(self): # {{{
try: try:
self.info('Setting up bash completion...') self.info('Setting up bash completion...')
from calibre.ebooks.metadata.cli import option_parser as metaop, filetypes as meta_filetypes from calibre.ebooks.metadata.cli import option_parser as metaop, filetypes as meta_filetypes
@ -287,8 +292,9 @@ class PostInstall:
if self.opts.fatal_errors: if self.opts.fatal_errors:
raise raise
self.task_failed('Setting up completion failed') self.task_failed('Setting up completion failed')
# }}}
def install_man_pages(self): def install_man_pages(self): # {{{
try: try:
from calibre.utils.help2man import create_man_page from calibre.utils.help2man import create_man_page
if isfreebsd: if isfreebsd:
@ -318,73 +324,69 @@ class PostInstall:
if self.opts.fatal_errors: if self.opts.fatal_errors:
raise raise
self.task_failed('Installing MAN pages failed') self.task_failed('Installing MAN pages failed')
# }}}
def setup_desktop_integration(self): def setup_desktop_integration(self): # {{{
try: try:
from PyQt4.QtCore import QFile
from tempfile import mkdtemp
self.info('Setting up desktop integration...') self.info('Setting up desktop integration...')
tdir = mkdtemp() with TemporaryDirectory() as tdir:
cwd = os.getcwdu() with CurrentDir(tdir):
try: render_img('mimetypes/lrf.svg', 'calibre-lrf.png')
os.chdir(tdir) check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png application-lrf', shell=True)
render_svg(QFile(I('mimetypes/lrf.svg')), os.path.join(tdir, 'calibre-lrf.png')) self.icon_resources.append(('mimetypes', 'application-lrf', '128'))
check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png application-lrf', shell=True) check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png text-lrs', shell=True)
self.icon_resources.append(('mimetypes', 'application-lrf', '128')) self.icon_resources.append(('mimetypes', 'application-lrs',
check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png text-lrs', shell=True) '128'))
self.icon_resources.append(('mimetypes', 'application-lrs', render_img('lt.png', 'calibre-gui.png')
'128')) check_call('xdg-icon-resource install --noupdate --size 128 calibre-gui.png calibre-gui', shell=True)
QFile(I('library.png')).copy(os.path.join(tdir, 'calibre-gui.png')) self.icon_resources.append(('apps', 'calibre-gui', '128'))
check_call('xdg-icon-resource install --noupdate --size 128 calibre-gui.png calibre-gui', shell=True) render_img('viewer.svg', 'calibre-viewer.png')
self.icon_resources.append(('apps', 'calibre-gui', '128')) check_call('xdg-icon-resource install --size 128 calibre-viewer.png calibre-viewer', shell=True)
render_svg(QFile(I('viewer.svg')), os.path.join(tdir, 'calibre-viewer.png')) self.icon_resources.append(('apps', 'calibre-viewer', '128'))
check_call('xdg-icon-resource install --size 128 calibre-viewer.png calibre-viewer', shell=True)
self.icon_resources.append(('apps', 'calibre-viewer', '128'))
mimetypes = set([]) mimetypes = set([])
for x in all_input_formats(): for x in all_input_formats():
mt = guess_type('dummy.'+x)[0] mt = guess_type('dummy.'+x)[0]
if mt and 'chemical' not in mt: if mt and 'chemical' not in mt:
mimetypes.add(mt) mimetypes.add(mt)
def write_mimetypes(f): def write_mimetypes(f):
f.write('MimeType=%s;\n'%';'.join(mimetypes)) f.write('MimeType=%s;\n'%';'.join(mimetypes))
f = open('calibre-lrfviewer.desktop', 'wb') f = open('calibre-lrfviewer.desktop', 'wb')
f.write(VIEWER) f.write(VIEWER)
f.close() f.close()
f = open('calibre-ebook-viewer.desktop', 'wb') f = open('calibre-ebook-viewer.desktop', 'wb')
f.write(EVIEWER) f.write(EVIEWER)
write_mimetypes(f) write_mimetypes(f)
f.close() f.close()
f = open('calibre-gui.desktop', 'wb') f = open('calibre-gui.desktop', 'wb')
f.write(GUI) f.write(GUI)
write_mimetypes(f) write_mimetypes(f)
f.close() f.close()
des = ('calibre-gui.desktop', 'calibre-lrfviewer.desktop', des = ('calibre-gui.desktop', 'calibre-lrfviewer.desktop',
'calibre-ebook-viewer.desktop') 'calibre-ebook-viewer.desktop')
for x in des: for x in des:
cmd = ['xdg-desktop-menu', 'install', './'+x] cmd = ['xdg-desktop-menu', 'install', './'+x]
if x != des[-1]: if x != des[-1]:
cmd.insert(2, '--noupdate') cmd.insert(2, '--noupdate')
check_call(' '.join(cmd), shell=True) check_call(' '.join(cmd), shell=True)
self.menu_resources.append(x) self.menu_resources.append(x)
f = open('calibre-mimetypes', 'wb') f = open('calibre-mimetypes', 'wb')
f.write(MIME) f.write(MIME)
f.close() f.close()
self.mime_resources.append('calibre-mimetypes') self.mime_resources.append('calibre-mimetypes')
check_call('xdg-mime install ./calibre-mimetypes', shell=True) check_call('xdg-mime install ./calibre-mimetypes', shell=True)
finally:
os.chdir(cwd)
shutil.rmtree(tdir)
except Exception: except Exception:
if self.opts.fatal_errors: if self.opts.fatal_errors:
raise raise
self.task_failed('Setting up desktop integration failed') self.task_failed('Setting up desktop integration failed')
# }}}
def option_parser(): def option_parser():
from calibre.utils.config import OptionParser from calibre.utils.config import OptionParser
parser = OptionParser() parser = OptionParser()
@ -542,21 +544,10 @@ MIME = '''\
</mime-info> </mime-info>
''' '''
def render_svg(image, dest, width=128, height=128): def render_img(image, dest, width=128, height=128):
from PyQt4.QtGui import QPainter, QImage from PyQt4.Qt import QImage, Qt
from PyQt4.QtSvg import QSvgRenderer img = QImage(I(image)).scaled(width, height, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
image = image.readAll() if hasattr(image, 'readAll') else image img.save(dest)
svg = QSvgRenderer(image)
painter = QPainter()
image = QImage(width, height, QImage.Format_ARGB32)
painter.begin(image)
painter.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform|QPainter.HighQualityAntialiasing)
painter.setCompositionMode(QPainter.CompositionMode_SourceOver)
svg.render(painter)
painter.end()
if dest is None:
return image
image.save(dest)
def main(): def main():
p = option_parser() p = option_parser()

View File

@ -25,7 +25,7 @@ clean:
html: html:
mkdir -p .build/html .build/doctrees mkdir -p .build/html .build/doctrees
$(SPHINXBUILD) -b custom $(ALLSPHINXOPTS) .build/html $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html
@echo @echo
@echo "Build finished. The HTML pages are in .build/html." @echo "Build finished. The HTML pages are in .build/html."
@ -37,7 +37,7 @@ qthelp:
epub: epub:
mkdir -p .build/qthelp .build/doctrees mkdir -p .build/qthelp .build/doctrees
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) .build/epub $(SPHINXBUILD) -b myepub $(ALLSPHINXOPTS) .build/epub
@echo @echo
@echo "Build finished." @echo "Build finished."

View File

@ -23,9 +23,11 @@ custom
# General configuration # General configuration
# --------------------- # ---------------------
needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions # Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.addons.*') or your custom ones. # coming with Sphinx (named 'sphinx.addons.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'custom'] extensions = ['sphinx.ext.autodoc', 'custom', 'sphinx.ext.viewcode']
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['templates'] templates_path = ['templates']
@ -36,6 +38,9 @@ source_suffix = '.rst'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = 'index'
# The language
language = 'en'
# General substitutions. # General substitutions.
project = __appname__ project = __appname__
copyright = '2008, Kovid Goyal' copyright = '2008, Kovid Goyal'
@ -81,7 +86,6 @@ pygments_style = 'sphinx'
# given in html_static_path. # given in html_static_path.
html_theme = 'default' html_theme = 'default'
html_theme_options = {'stickysidebar':'true', 'relbarbgcolor':'black'} html_theme_options = {'stickysidebar':'true', 'relbarbgcolor':'black'}
html_style = 'calibre.css'
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
@ -100,8 +104,16 @@ html_use_smartypants = True
html_title = 'calibre User Manual' html_title = 'calibre User Manual'
html_short_title = 'Start' html_short_title = 'Start'
html_logo = 'resources/logo.png' html_logo = 'resources/logo.png'
epub_author = 'Kovid Goyal' epub_author = 'Kovid Goyal'
epub_cover = 'resources/epub_cover.jpg' epub_cover = 'epub_cover.jpg'
epub_publisher = 'Kovid Goyal'
epub_identifier = 'http://calibre-ebook.com/user_manual'
epub_scheme = 'url'
epub_uid = 'S54a88f8e9d42455e9c6db000e989225f'
epub_tocdepth = 4
epub_tocdup = True
epub_pre_files = [('epub_titlepage.html', 'Cover')]
# Custom sidebar templates, maps document names to template names. # Custom sidebar templates, maps document names to template names.
#html_sidebars = {} #html_sidebars = {}

View File

@ -3,29 +3,17 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import sys, os, inspect, re, textwrap import sys, os, re, textwrap
sys.path.insert(0, os.path.abspath('../../')) sys.path.insert(0, os.path.abspath('../../'))
sys.extensions_location = '../plugins' sys.extensions_location = '../plugins'
sys.resources_location = '../../../resources' sys.resources_location = '../../../resources'
from sphinx.builders.html import StandaloneHTMLBuilder
from qthelp import QtHelpBuilder
from epub import EPUBHelpBuilder
from sphinx.util import rpartition
from sphinx.util.console import bold from sphinx.util.console import bold
from sphinx.ext.autodoc import prepare_docstring
from docutils.statemachine import ViewList
from docutils import nodes
sys.path.append(os.path.abspath('../../../')) sys.path.append(os.path.abspath('../../../'))
from calibre.linux import entry_points from calibre.linux import entry_points
from epub import EPUBHelpBuilder
class CustomBuilder(StandaloneHTMLBuilder):
name = 'custom'
class CustomQtBuild(QtHelpBuilder):
name = 'customqt'
def substitute(app, doctree): def substitute(app, doctree):
pass pass
@ -252,64 +240,9 @@ def cli_docs(app):
raw += '\n'+'\n'.join(lines) raw += '\n'+'\n'.join(lines)
update_cli_doc(os.path.join('cli', cmd+'.rst'), raw, info) update_cli_doc(os.path.join('cli', cmd+'.rst'), raw, info)
def auto_member(dirname, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
name = arguments[0]
env = state.document.settings.env
mod_cls, obj = rpartition(name, '.')
if not mod_cls and hasattr(env, 'autodoc_current_class'):
mod_cls = env.autodoc_current_class
if not mod_cls:
mod_cls = env.currclass
mod, cls = rpartition(mod_cls, '.')
if not mod and hasattr(env, 'autodoc_current_module'):
mod = env.autodoc_current_module
if not mod:
mod = env.currmodule
module = __import__(mod, None, None, ['foo'])
cls = getattr(module, cls)
lines = inspect.getsourcelines(cls)[0]
comment_lines = []
for i, line in enumerate(lines):
if re.search(r'%s\s*=\s*\S+'%obj, line) and not line.strip().startswith('#:'):
for j in range(i-1, 0, -1):
raw = lines[j].strip()
if not raw.startswith('#:'):
break
comment_lines.append(raw[2:])
break
comment_lines.reverse()
docstring = '\n'.join(comment_lines)
if module is not None and docstring is not None:
docstring = docstring.decode('utf-8')
result = ViewList()
result.append('.. attribute:: %s.%s'%(cls.__name__, obj), '<autodoc>')
result.append('', '<autodoc>')
docstring = prepare_docstring(docstring)
for i, line in enumerate(docstring):
result.append(' ' + line, '<docstring of %s>' % name, i)
result.append('', '')
result.append(' **Default**: ``%s``'%repr(getattr(cls, obj, None)), '<default memeber value>')
result.append('', '')
node = nodes.paragraph()
state.nested_parse(result, content_offset, node)
return list(node)
def setup(app): def setup(app):
app.add_config_value('epub_cover', None, False) app.add_config_value('epub_cover', None, False)
app.add_config_value('epub_author', '', False)
app.add_builder(CustomBuilder)
app.add_builder(CustomQtBuild)
app.add_builder(EPUBHelpBuilder) app.add_builder(EPUBHelpBuilder)
app.add_directive('automember', auto_member, 1, (1, 0, 1))
app.connect('doctree-read', substitute) app.connect('doctree-read', substitute)
app.connect('builder-inited', cli_docs) app.connect('builder-inited', cli_docs)
app.connect('build-finished', finished) app.connect('build-finished', finished)

View File

@ -45,7 +45,8 @@ All static resources are stored in the resources sub-folder of the calibre insta
from the calibre website it will be :file:`/opt/calibre/resources`. These paths can change depending on where you choose to install |app|. from the calibre website it will be :file:`/opt/calibre/resources`. These paths can change depending on where you choose to install |app|.
You should not change the files in this resources folder, as your changes will get overwritten the next time you update |app|. Instead, go to You should not change the files in this resources folder, as your changes will get overwritten the next time you update |app|. Instead, go to
:guilabel:`Preferences->Advanced` and click :guilabel:`Open calibre configuration directory`. In this configuration directory, create a sub-folder called resources and place the files you want to override in it. |app| will automatically use your custom file in preference to the builtin one the next time it is started. :guilabel:`Preferences->Advanced` and click :guilabel:`Open calibre configuration directory`. In this configuration directory, create a sub-folder called resources and place the files you want to override in it. Place the files in the appropriate sub folders, for example place images in :file:`resources/images`, etc.
|app| will automatically use your custom file in preference to the builtin one the next time it is started.
For example, if you wanted to change the icon for the :guilabel:`Remove books` action, you would first look in the builtin resources folder and see that the relevant file is For example, if you wanted to change the icon for the :guilabel:`Remove books` action, you would first look in the builtin resources folder and see that the relevant file is
:file:`resources/images/trash.svg`. Assuming you have an alternate icon in svg format called :file:`mytrash.svg` you would save it in the configuration directory as :file:`resources/images/trash.svg`. All the icons used by the calibre user interface are in :file:`resources/images` and its sub-folders. :file:`resources/images/trash.svg`. Assuming you have an alternate icon in svg format called :file:`mytrash.svg` you would save it in the configuration directory as :file:`resources/images/trash.svg`. All the icons used by the calibre user interface are in :file:`resources/images` and its sub-folders.

View File

@ -6,298 +6,53 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os, mimetypes, uuid, shutil import os, time
from datetime import datetime
from docutils import nodes
from xml.sax.saxutils import escape, quoteattr
from urlparse import urldefrag
from zipfile import ZipFile, ZIP_STORED, ZipInfo
from sphinx import addnodes from sphinx.builders.epub import EpubBuilder
from sphinx.builders.html import StandaloneHTMLBuilder
NCX = '''\ class EPUBHelpBuilder(EpubBuilder):
<?xml version="1.0" encoding="UTF-8"?> name = 'myepub'
<ncx version="2005-1"
xml:lang="en"
xmlns="http://www.daisy.org/z3986/2005/ncx/"
xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata"
>
<head>
<meta name="dtb:uid" content="{uid}"/>
<meta name="dtb:depth" content="{depth}"/>
<meta name="dtb:generator" content="sphinx"/>
<meta name="dtb:totalPageCount" content="0"/>
<meta name="dtb:maxPageNumber" content="0"/>
</head>
<docTitle><text>Table of Contents</text></docTitle>
<navMap>
{navpoints}
</navMap>
</ncx>
'''
OPF = '''\ def add_cover(self, outdir, cover_fname):
<?xml version="1.0" encoding="UTF-8"?> href = '_static/'+cover_fname
<package version="2.0" opf = os.path.join(self.outdir, 'content.opf')
xmlns="http://www.idpf.org/2007/opf"
unique-identifier="sphinx_id"
>
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata">
<dc:title>{title}</dc:title>
<dc:creator opf:role="aut">{author}</dc:creator>
<dc:contributor opf:role="bkp">Sphinx</dc:contributor>
<dc:identifier opf:scheme="sphinx" id="sphinx_id">{uid}</dc:identifier>
<dc:date>{date}</dc:date>
<meta name="calibre:publication_type" content="sphinx_manual" />
<meta name="cover" content="cover"/>
</metadata>
<manifest>
{manifest}
</manifest>
<spine toc="ncx">
{spine}
</spine>
<guide>
{guide}
</guide>
</package>
'''
CONTAINER='''\ cover = '''\
<?xml version="1.0"?> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container"> <head>
<rootfiles> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<rootfile full-path="{0}" media-type="application/oebps-package+xml"/> <meta name="calibre:cover" content="true" />
</rootfiles> <title>Cover</title>
</container> <style type="text/css" title="override_css">
''' @page {padding: 0pt; margin:0pt}
body { text-align: center; padding:0pt; margin: 0pt; }
SVG_TEMPLATE = '''\ </style>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> </head>
<head> <body>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <svg version="1.1" xmlns="http://www.w3.org/2000/svg"
<meta name="calibre:cover" content="true" /> xmlns:xlink="http://www.w3.org/1999/xlink"
<title>Cover</title> width="100%%" height="100%%" viewBox="0 0 600 800"
<style type="text/css" title="override_css"> preserveAspectRatio="none">
@page {padding: 0pt; margin:0pt} <image width="600" height="800" xlink:href="%s"/>
body { text-align: center; padding:0pt; margin: 0pt; } </svg>
</style> </body>
</head> </html>
<body> '''%href
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" self.files.append('epub_titlepage.html')
xmlns:xlink="http://www.w3.org/1999/xlink" open(os.path.join(outdir, self.files[-1]), 'wb').write(cover)
width="100%%" height="100%%" viewBox="0 0 600 800"
preserveAspectRatio="none">
<image width="600" height="800" xlink:href="%s"/>
</svg>
</body>
</html>
'''
class TOC(list):
def __init__(self, title=None, href=None):
list.__init__(self)
self.title, self.href = title, href
def create_child(self, title, href):
self.append(TOC(title, href))
return self[-1]
def depth(self):
try:
return max(node.depth() for node in self)+1
except ValueError:
return 1
class EPUBHelpBuilder(StandaloneHTMLBuilder): raw = open(opf, 'rb').read()
""" raw = raw.replace('</metadata>',
Builder that also outputs Qt help project, contents and index files. ('<meta name="cover" content="%s"/>\n'
""" '<dc:date>%s</dc:date>\n</metadata>') %
name = 'epub' (href.replace('/', '_'), time.strftime('%Y-%m-%d')))
raw = raw.replace('</manifest>',
# don't copy the reST source ('<item id="{0}" href="{0}" media-type="application/xhtml+xml"/>\n</manifest>').\
copysource = False format('epub_titlepage.html'))
open(opf, 'wb').write(raw)
supported_image_types = ['image/svg+xml', 'image/png', 'image/gif',
'image/jpeg']
# don't add links
add_permalinks = False
# don't add sidebar etc.
embedded = True
def init(self):
StandaloneHTMLBuilder.init(self)
self.out_suffix = '.html'
self.link_suffix = '.html'
self.html_outdir = self.outdir = os.path.join(self.outdir, 'src')
self.conf = self.config
def finish(self):
StandaloneHTMLBuilder.finish(self)
self.create_titlepage()
self.outdir = os.path.dirname(self.outdir)
cwd = os.getcwd()
os.chdir(self.html_outdir)
try:
self.generate_manifest()
self.generate_toc()
self.render_opf()
self.render_epub()
finally:
os.chdir(cwd)
def render_epub(self):
container = CONTAINER.format('content.opf')
path = os.path.abspath('..'+os.sep+self.conf.project+'.epub')
zf = ZipFile(path, 'w')
zi = ZipInfo('mimetype')
zi.compress_type = ZIP_STORED
zf.writestr(zi, 'application/epub+zip')
zf.writestr('META-INF/container.xml', container)
for url in self.manifest:
fp = os.path.join(self.html_outdir, *url.split('/'))
zf.write(fp, url)
zf.close()
self.info('EPUB created at: '+path)
def render_opf(self):
manifest = []
for href in self.manifest:
mt, id = self.manifest[href]
manifest.append(' '*8 + '<item id=%s href=%s media-type=%s />'%\
tuple(map(quoteattr, (id, href, mt))))
manifest = '\n'.join(manifest)
spine = [' '*8+'<itemref idref=%s />'%quoteattr(x) for x in self.spine]
spine = '\n'.join(spine)
guide = ''
opf = OPF.format(title=escape(self.conf.html_title),
author=escape(self.conf.epub_author), uid=str(uuid.uuid4()),
date=datetime.now().isoformat(), manifest=manifest, spine=spine,
guide=guide)
open('content.opf', 'wb').write(opf)
self.manifest['content.opf'] = ('application/oebps-package+xml', 'opf')
def create_titlepage(self):
self.cover_image_url = None
if self.conf.epub_cover:
img = '_static/'+os.path.basename(self.conf.epub_cover)
shutil.copyfile(self.conf.epub_cover, os.path.join(self.html_outdir,
*img.split('/')))
self.cover_image_url = img
tp = SVG_TEMPLATE%img.split('/')[-1]
open(os.path.join(self.html_outdir, '_static', 'titlepage.html'),
'wb').write(tp)
def generate_manifest(self):
self.manifest = {}
id = 1
for dirpath, dirnames, filenames in os.walk('.'):
for fname in filenames:
if fname == '.buildinfo':
continue
fpath = os.path.abspath(os.path.join(dirpath, fname))
url = os.path.relpath(fpath).replace(os.sep, '/')
self.manifest[url] = mimetypes.guess_type(url, False)[0]
if self.manifest[url] is None:
self.warn('Unknown mimetype for: ' + url)
self.manifest[url] = 'application/octet-stream'
if self.manifest[url] == 'text/html':
self.manifest[url] = 'application/xhtml+xml'
if self.cover_image_url and url.endswith(self.cover_image_url):
id_ = 'cover'
else:
id_ = 'id'+str(id)
id += 1
self.manifest[url] = (self.manifest[url], id_)
def isdocnode(self, node):
if not isinstance(node, nodes.list_item):
return False
if len(node.children) != 2:
return False
if not isinstance(node.children[0], addnodes.compact_paragraph):
return False
if not isinstance(node.children[0][0], nodes.reference):
return False
if not isinstance(node.children[1], nodes.bullet_list):
return False
return True
def generate_toc(self):
tocdoc = self.env.get_and_resolve_doctree(self.config.master_doc, self,
prune_toctrees=False)
istoctree = lambda node: (
isinstance(node, addnodes.compact_paragraph)
and node.has_key('toctree'))
toc = TOC()
for node in tocdoc.traverse(istoctree):
self.extend_toc(toc, node)
self._parts = []
self._po = 0
self._po_map = {}
self.spine_map = {}
self.spine = []
self.render_toc(toc)
navpoints = '\n'.join(self._parts).strip()
ncx = NCX.format(uid=str(uuid.uuid4()), depth=toc.depth(),
navpoints=navpoints)
open('toc.ncx', 'wb').write(ncx)
self.manifest['toc.ncx'] = ('application/x-dtbncx+xml', 'ncx')
self.spine.insert(0, self.manifest[self.conf.master_doc+'.html'][1])
if self.conf.epub_cover:
self.spine.insert(0, self.manifest['_static/titlepage.html'][1])
def add_to_spine(self, href):
href = urldefrag(href)[0]
if href not in self.spine_map:
for url in self.manifest:
if url == href:
self.spine_map[href]= self.manifest[url][1]
self.spine.append(self.spine_map[href])
def render_toc(self, toc, level=2):
for child in toc:
if child.title and child.href:
href = child.href
self.add_to_spine(href)
title = escape(child.title)
if isinstance(title, unicode):
title = title.encode('utf-8')
if child.href in self._po_map:
po = self._po_map[child.href]
else:
self._po += 1
po = self._po
self._parts.append(' '*(level*4)+
'<navPoint id="%s" playOrder="%d">'%(uuid.uuid4(),
po))
self._parts.append(' '*((level+1)*4)+
'<navLabel><text>%s</text></navLabel>'%title)
self._parts.append(' '*((level+1)*4)+
'<content src=%s />'%quoteattr(href))
self.render_toc(child, level+1)
self._parts.append(' '*(level*4)+'</navPoint>')
def extend_toc(self, toc, node):
if self.isdocnode(node):
refnode = node.children[0][0]
parent = toc.create_child(refnode.astext(), refnode['refuri'])
for subnode in node.children[1]:
self.extend_toc(parent, subnode)
elif isinstance(node, (nodes.list_item, nodes.bullet_list,
addnodes.compact_paragraph)):
for subnode in node:
self.extend_toc(toc, subnode)
elif isinstance(node, nodes.reference):
parent = toc.create_child(node.astext(), node['refuri'])
def build_epub(self, outdir, *args, **kwargs):
if self.config.epub_cover:
self.add_cover(outdir, self.config.epub_cover)
EpubBuilder.build_epub(self, outdir, *args, **kwargs)

View File

@ -6,145 +6,12 @@ API Documentation for recipes
=============================== ===============================
.. module:: calibre.web.feeds.news .. module:: calibre.web.feeds.news
:synopsis: Defines various abstract base classes that can be subclassed to create powerful news fetching recipes. :synopsis: The API for writing recipes is defined by the :class:`BasicNewsRecipe`
Defines various abstract base classes that can be subclassed to create powerful news fetching recipes. The useful The API for writing recipes is defined by the :class:`BasicNewsRecipe`
subclasses are:
.. contents:: .. autoclass:: BasicNewsRecipe
:depth: 1 :members:
:local: :member-order: groupwise
BasicNewsRecipe
-----------------
.. class:: BasicNewsRecipe
Abstract base class that contains a number of members and methods to customize the fetching of contents in your recipes. All
recipes must inherit from this class or a subclass of it.
The members and methods are organized as follows:
.. contents::
:depth: 1
:local:
Customizing e-book download
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automember:: BasicNewsRecipe.title
.. automember:: BasicNewsRecipe.description
.. automember:: BasicNewsRecipe.__author__
.. automember:: BasicNewsRecipe.max_articles_per_feed
.. automember:: BasicNewsRecipe.oldest_article
.. automember:: BasicNewsRecipe.recursions
.. automember:: BasicNewsRecipe.delay
.. automember:: BasicNewsRecipe.simultaneous_downloads
.. automember:: BasicNewsRecipe.timeout
.. automember:: BasicNewsRecipe.timefmt
.. automember:: BasicNewsRecipe.conversion_options
.. automember:: BasicNewsRecipe.feeds
.. automember:: BasicNewsRecipe.no_stylesheets
.. automember:: BasicNewsRecipe.encoding
.. automethod:: BasicNewsRecipe.get_browser
.. automethod:: BasicNewsRecipe.get_cover_url
.. automethod:: BasicNewsRecipe.get_feeds
.. automethod:: BasicNewsRecipe.parse_index
Customizing feed parsing
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automember:: BasicNewsRecipe.summary_length
.. automember:: BasicNewsRecipe.use_embedded_content
.. automethod:: BasicNewsRecipe.get_article_url
.. automethod:: BasicNewsRecipe.print_version
.. automethod:: BasicNewsRecipe.parse_feeds
Pre/post processing of downloaded HTML
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automember:: BasicNewsRecipe.extra_css
.. automember:: BasicNewsRecipe.match_regexps
.. automember:: BasicNewsRecipe.filter_regexps
.. automember:: BasicNewsRecipe.remove_tags
.. automember:: BasicNewsRecipe.remove_tags_after
.. automember:: BasicNewsRecipe.remove_tags_before
.. automember:: BasicNewsRecipe.remove_attributes
.. automember:: BasicNewsRecipe.keep_only_tags
.. automember:: BasicNewsRecipe.preprocess_regexps
.. automember:: BasicNewsRecipe.template_css
.. automember:: BasicNewsRecipe.remove_javascript
.. automethod:: BasicNewsRecipe.skip_ad_pages
.. automethod:: BasicNewsRecipe.preprocess_html
.. automethod:: BasicNewsRecipe.postprocess_html
.. automethod:: BasicNewsRecipe.populate_article_metadata
Convenience methods
~~~~~~~~~~~~~~~~~~~~~~~
.. automethod:: BasicNewsRecipe.cleanup
.. automethod:: BasicNewsRecipe.index_to_soup
.. automethod:: BasicNewsRecipe.sort_index_by
.. automethod:: BasicNewsRecipe.tag_to_string
Miscellaneous
~~~~~~~~~~~~~~~~~~
.. automember:: BasicNewsRecipe.requires_version
CustomIndexRecipe
---------------------
.. class:: CustomIndexRecipe
This class is useful for getting content from websites that don't follow the "multiple articles in several feeds" content model.
.. automethod:: CustomIndexRecipe.custom_index

View File

@ -5,7 +5,7 @@
API Documentation for plugins API Documentation for plugins
=============================== ===============================
.. module:: calibre.customize.__init__ .. module:: calibre.customize
:synopsis: Defines various abstract base classes that can be subclassed to create plugins. :synopsis: Defines various abstract base classes that can be subclassed to create plugins.
Defines various abstract base classes that can be subclassed to create powerful plugins. The useful Defines various abstract base classes that can be subclassed to create powerful plugins. The useful
@ -20,113 +20,136 @@ classes are:
Plugin Plugin
----------------- -----------------
.. class:: Plugin .. autoclass:: Plugin
:members:
Abstract base class that contains a number of members and methods to create your plugin. All :member-order: bysource
plugins must inherit from this class or a subclass of it.
The members and methods are:
.. automember:: Plugin.name
.. automember:: Plugin.author
.. automember:: Plugin.description
.. automember:: Plugin.version
.. automember:: Plugin.supported_platforms
.. automember:: Plugin.priority
.. automember:: Plugin.minimum_calibre_version
.. automember:: Plugin.can_be_disabled
.. automethod:: Plugin.initialize
.. automethod:: Plugin.customization_help
.. automethod:: Plugin.temporary_file
.. _pluginsFTPlugin: .. _pluginsFTPlugin:
FileTypePlugin FileTypePlugin
----------------- -----------------
.. class:: Plugin .. autoclass:: FileTypePlugin
:show-inheritance:
Abstract base class that contains a number of members and methods to create your file type plugin. All file type :members:
plugins must inherit from this class or a subclass of it. :member-order: bysource
The members and methods are:
.. automember:: FileTypePlugin.file_types
.. automember:: FileTypePlugin.on_import
.. automember:: FileTypePlugin.on_preprocess
.. automember:: FileTypePlugin.on_postprocess
.. automethod:: FileTypePlugin.run
.. _pluginsMetadataPlugin: .. _pluginsMetadataPlugin:
Metadata plugins Metadata plugins
------------------- -------------------
.. class:: MetadataReaderPlugin .. autoclass:: MetadataReaderPlugin
:show-inheritance:
Abstract base class that contains a number of members and methods to create your metadata reader plugin. All metadata :members:
reader plugins must inherit from this class or a subclass of it. :member-order: bysource
The members and methods are:
.. automember:: MetadataReaderPlugin.file_types
.. automethod:: MetadataReaderPlugin.get_metadata
.. class:: MetadataWriterPlugin .. autoclass:: MetadataWriterPlugin
:show-inheritance:
Abstract base class that contains a number of members and methods to create your metadata writer plugin. All metadata :members:
writer plugins must inherit from this class or a subclass of it. :member-order: bysource
The members and methods are:
.. automember:: MetadataWriterPlugin.file_types
.. automethod:: MetadataWriterPlugin.set_metadata
.. _pluginsMetadataSource: .. _pluginsMetadataSource:
Catalog plugins
----------------
.. autoclass:: CatalogPlugin
:show-inheritance:
:members:
:member-order: bysource
Metadata download plugins Metadata download plugins
-------------------------- --------------------------
.. class:: calibre.ebooks.metadata.fetch.MetadataSource .. module:: calibre.ebooks.metadata.fetch
Represents a source to query for metadata. Subclasses must implement .. autoclass:: MetadataSource
at least the fetch method. :show-inheritance:
:members:
:member-order: bysource
When :meth:`fetch` is called, the `self` object will have the following Conversion plugins
useful attributes (each of which may be None):: --------------------
title, book_author, publisher, isbn, log, verbose and extra .. module:: calibre.customize.conversion
Use these attributes to construct the search query. extra is reserved for .. autoclass:: InputFormatPlugin
future use. :show-inheritance:
:members:
:member-order: bysource
The fetch method must store the results in `self.results` as a list of .. autoclass:: OutputFormatPlugin
:class:`MetaInformation` objects. If there is an error, it should be stored :show-inheritance:
in `self.exception` and `self.tb` (for the traceback). :members:
:member-order: bysource
.. automember:: calibre.ebooks.metadata.fetch.MetadataSource.metadata_type Device Drivers
-----------------
.. automember:: calibre.ebooks.metadata.fetch.MetadataSource.string_customization_help .. module:: calibre.devices.interface
.. automethod:: calibre.ebooks.metadata.fetch.MetadataSource.fetch The base class for all device drivers is :class:`DevicePlugin`. However, if your device exposes itself as a USBMS drive to the operating system, you should use the USBMS class instead as it implements all the logic needed to support these kinds of devices.
.. autoclass:: DevicePlugin
:show-inheritance:
:members:
:member-order: bysource
.. autoclass:: BookList
:show-inheritance:
:members:
:member-order: bysource
USB Mass Storage based devices
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The base class for such devices is :class:`calibre.devices.usbms.driver.USBMS`. This class in turn inherits some of its functionality from its bases, documented below. A typical basic USBMS based driver looks like this:
.. code-block:: python
from calibre.devices.usbms.driver import USBMS
class PDNOVEL(USBMS):
name = 'Pandigital Novel device interface'
gui_name = 'PD Novel'
description = _('Communicate with the Pandigital Novel')
author = 'Kovid Goyal'
supported_platforms = ['windows', 'linux', 'osx']
FORMATS = ['epub', 'pdf']
VENDOR_ID = [0x18d1]
PRODUCT_ID = [0xb004]
BCD = [0x224]
VENDOR_NAME = 'ANDROID'
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '__UMS_COMPOSITE'
THUMBNAIL_HEIGHT = 144
EBOOK_DIR_MAIN = 'eBooks'
SUPPORTS_SUB_DIRS = False
def upload_cover(self, path, filename, metadata):
coverdata = getattr(metadata, 'thumbnail', None)
if coverdata and coverdata[2]:
with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile:
coverfile.write(coverdata[2])
.. autoclass:: calibre.devices.usbms.device.Device
:show-inheritance:
:members:
:member-order: bysource
.. autoclass:: calibre.devices.usbms.cli.CLI
:members:
:member-order: bysource
.. autoclass:: calibre.devices.usbms.driver.USBMS
:show-inheritance:
:members:
:member-order: bysource

View File

@ -1,5 +0,0 @@
@import url("default.css");
table.docutils td, table.docutils th { padding: 1em; border-bottom: 0; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -312,10 +312,10 @@ class OptionSet(object):
def parse_string(self, src): def parse_string(self, src):
options = {'cPickle':cPickle} options = {'cPickle':cPickle}
if not isinstance(src, unicode):
src = src.decode('utf-8')
if src is not None: if src is not None:
try: try:
if not isinstance(src, unicode):
src = src.decode('utf-8')
exec src in options exec src in options
except: except:
print 'Failed to parse options string:' print 'Failed to parse options string:'

View File

@ -115,7 +115,7 @@ class Feed(object):
max_articles_per_feed=100): max_articles_per_feed=100):
entries = feed.entries entries = feed.entries
feed = feed.feed feed = feed.feed
self.title = feed.get('title', _('Unknown feed')) if not title else title self.title = feed.get('title', _('Unknown section')) if not title else title
self.description = feed.get('description', '') self.description = feed.get('description', '')
image = feed.get('image', {}) image = feed.get('image', {})
self.image_url = image.get('href', None) self.image_url = image.get('href', None)

View File

@ -37,7 +37,10 @@ class DownloadDenied(ValueError):
class BasicNewsRecipe(Recipe): class BasicNewsRecipe(Recipe):
''' '''
Abstract base class that contains logic needed in all feed fetchers. Base class that contains logic needed in all recipes. By overriding
progressively more of the functionality in this class, you can make
progressively more customized/powerful recipes. For a tutorial introduction
to creating recipes, see :doc:`news`.
''' '''
#: The title to use for the ebook #: The title to use for the ebook
@ -127,7 +130,7 @@ class BasicNewsRecipe(Recipe):
#: embedded content. #: embedded content.
use_embedded_content = None use_embedded_content = None
#: Set to True and implement :method:`get_obfuscated_article` to handle #: Set to True and implement :meth:`get_obfuscated_article` to handle
#: websites that try to make it difficult to scrape content. #: websites that try to make it difficult to scrape content.
articles_are_obfuscated = False articles_are_obfuscated = False
@ -147,7 +150,7 @@ class BasicNewsRecipe(Recipe):
#: If True empty feeds are removed from the output. #: If True empty feeds are removed from the output.
#: This option has no effect if parse_index is overriden in #: This option has no effect if parse_index is overriden in
#: the sub class. It is meant only for recipes that return a list #: the sub class. It is meant only for recipes that return a list
#: of feeds using `feeds` or :method:`get_feeds`. #: of feeds using `feeds` or :meth:`get_feeds`.
remove_empty_feeds = False remove_empty_feeds = False
#: List of regular expressions that determines which links to follow #: List of regular expressions that determines which links to follow
@ -538,8 +541,7 @@ class BasicNewsRecipe(Recipe):
HTML fetching engine, so it can contain links to pages/images on the web. HTML fetching engine, so it can contain links to pages/images on the web.
This method is typically useful for sites that try to make it difficult to This method is typically useful for sites that try to make it difficult to
access article content automatically. See for example the access article content automatically.
:module:`calibre.web.recipes.iht` recipe.
''' '''
raise NotImplementedError raise NotImplementedError
@ -700,8 +702,7 @@ class BasicNewsRecipe(Recipe):
Download and pre-process all articles from the feeds in this recipe. Download and pre-process all articles from the feeds in this recipe.
This method should be called only once on a particular Recipe instance. This method should be called only once on a particular Recipe instance.
Calling it more than once will lead to undefined behavior. Calling it more than once will lead to undefined behavior.
@return: Path to index.html :return: Path to index.html
@rtype: string
''' '''
try: try:
res = self.build_index() res = self.build_index()
@ -1359,7 +1360,7 @@ class BasicNewsRecipe(Recipe):
''' '''
If your recipe when converted to EPUB has problems with images when If your recipe when converted to EPUB has problems with images when
viewed in Adobe Digital Editions, call this method from within viewed in Adobe Digital Editions, call this method from within
:method:`postprocess_html`. :meth:`postprocess_html`.
''' '''
for item in soup.findAll('img'): for item in soup.findAll('img'):
for attrib in ['height','width','border','align','style']: for attrib in ['height','width','border','align','style']:

View File

@ -103,6 +103,32 @@ class IndexTemplate(Template):
class FeedTemplate(Template): class FeedTemplate(Template):
def get_navbar(self, f, feeds, top=True):
if len(feeds) < 2:
return DIV()
navbar = DIV('| ', CLASS('calibre_navbar', 'calibre_rescale_70',
style='text-align:center'))
if not top:
hr = HR()
navbar.append(hr)
navbar.text = None
hr.tail = '| '
if f+1 < len(feeds):
link = A('Next section', href='../feed_%d/index.html'%(f+1))
link.tail = ' | '
navbar.append(link)
link = A('Main menu', href="../index.html")
link.tail = ' | '
navbar.append(link)
if f > 0:
link = A('Previous section', href='../feed_%d/index.html'%(f-1))
link.tail = ' |'
navbar.append(link)
if top:
navbar.append(HR())
return navbar
def _generate(self, f, feeds, cutoff, extra_css=None, style=None): def _generate(self, f, feeds, cutoff, extra_css=None, style=None):
feed = feeds[f] feed = feeds[f]
head = HEAD(TITLE(feed.title)) head = HEAD(TITLE(feed.title))
@ -111,6 +137,8 @@ class FeedTemplate(Template):
if extra_css: if extra_css:
head.append(STYLE(extra_css, type='text/css')) head.append(STYLE(extra_css, type='text/css'))
body = BODY(style='page-break-before:always') body = BODY(style='page-break-before:always')
body.append(self.get_navbar(f, feeds))
div = DIV( div = DIV(
H2(feed.title, H2(feed.title,
CLASS('calibre_feed_title', 'calibre_rescale_160')), CLASS('calibre_feed_title', 'calibre_rescale_160')),
@ -144,12 +172,7 @@ class FeedTemplate(Template):
CLASS('article_description', 'calibre_rescale_70'))) CLASS('article_description', 'calibre_rescale_70')))
ul.append(li) ul.append(li)
div.append(ul) div.append(ul)
navbar = DIV('| ', CLASS('calibre_navbar', 'calibre_rescale_70')) div.append(self.get_navbar(f, feeds, top=False))
link = A('Up one level', href="../index.html")
link.tail = ' |'
navbar.append(link)
div.append(navbar)
self.root = HTML(head, body) self.root = HTML(head, body)
class NavBarTemplate(Template): class NavBarTemplate(Template):