Merge branch 'master' of git://github.com/janeczku/calibre-web
commit
c07cb23ef2
@ -1,4 +1,4 @@
|
|||||||
helper.py ident export-subst
|
updater.py ident export-subst
|
||||||
/test export-ignore
|
/test export-ignore
|
||||||
cps/static/css/libs/* linguist-vendored
|
cps/static/css/libs/* linguist-vendored
|
||||||
cps/static/js/libs/* linguist-vendored
|
cps/static/js/libs/* linguist-vendored
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,505 +1,349 @@
|
|||||||
/*! normalize.css v1.0.1 | MIT License | git.io/normalize */
|
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||||
|
|
||||||
/* ==========================================================================
|
/* Document
|
||||||
HTML5 display definitions
|
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Corrects `block` display not defined in IE 6/7/8/9 and Firefox 3.
|
* 1. Correct the line height in all browsers.
|
||||||
*/
|
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||||
|
|
||||||
article,
|
|
||||||
aside,
|
|
||||||
details,
|
|
||||||
figcaption,
|
|
||||||
figure,
|
|
||||||
footer,
|
|
||||||
header,
|
|
||||||
hgroup,
|
|
||||||
nav,
|
|
||||||
section,
|
|
||||||
summary {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Corrects `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
|
|
||||||
*/
|
|
||||||
|
|
||||||
audio,
|
|
||||||
canvas,
|
|
||||||
video {
|
|
||||||
display: inline-block;
|
|
||||||
*display: inline;
|
|
||||||
*zoom: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Prevents modern browsers from displaying `audio` without controls.
|
|
||||||
* Remove excess height in iOS 5 devices.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
audio:not([controls]) {
|
html {
|
||||||
display: none;
|
line-height: 1.15; /* 1 */
|
||||||
height: 0;
|
-webkit-text-size-adjust: 100%; /* 2 */
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Addresses styling for `hidden` attribute not present in IE 7/8/9, Firefox 3,
|
|
||||||
* and Safari 4.
|
|
||||||
* Known issue: no IE 6 support.
|
|
||||||
*/
|
|
||||||
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* Sections
|
||||||
Base
|
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* 1. Corrects text resizing oddly in IE 6/7 when body `font-size` is set using
|
* Remove the margin in all browsers.
|
||||||
* `em` units.
|
|
||||||
* 2. Prevents iOS text size adjust after orientation change, without disabling
|
|
||||||
* user zoom.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
html {
|
body {
|
||||||
font-size: 100%; /* 1 */
|
margin: 0;
|
||||||
-webkit-text-size-adjust: 100%; /* 2 */
|
|
||||||
-ms-text-size-adjust: 100%; /* 2 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Addresses `font-family` inconsistency between `textarea` and other form
|
* Render the `main` element consistently in IE.
|
||||||
* elements.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
html,
|
main {
|
||||||
button,
|
display: block;
|
||||||
input,
|
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Addresses margins handled incorrectly in IE 6/7.
|
* Correct the font size and margin on `h1` elements within `section` and
|
||||||
|
* `article` contexts in Chrome, Firefox, and Safari.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
body {
|
h1 {
|
||||||
margin: 0;
|
font-size: 2em;
|
||||||
|
margin: 0.67em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* Grouping content
|
||||||
Links
|
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Addresses `outline` inconsistency between Chrome and other browsers.
|
* 1. Add the correct box sizing in Firefox.
|
||||||
|
* 2. Show the overflow in Edge and IE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
a:focus {
|
hr {
|
||||||
outline: thin dotted;
|
box-sizing: content-box; /* 1 */
|
||||||
|
height: 0; /* 1 */
|
||||||
|
overflow: visible; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Improves readability when focused and also mouse hovered in all browsers.
|
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||||
|
* 2. Correct the odd `em` font sizing in all browsers.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
a:active,
|
pre {
|
||||||
a:hover {
|
font-family: monospace, monospace; /* 1 */
|
||||||
outline: 0;
|
font-size: 1em; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* Text-level semantics
|
||||||
Typography
|
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Addresses font sizes and margins set differently in IE 6/7.
|
* Remove the gray background on active links in IE 10.
|
||||||
* Addresses font sizes within `section` and `article` in Firefox 4+, Safari 5,
|
|
||||||
* and Chrome.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
h1 {
|
a {
|
||||||
font-size: 2em;
|
background-color: transparent;
|
||||||
margin: 0.67em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 1.5em;
|
|
||||||
margin: 0.83em 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
/**
|
||||||
font-size: 1.17em;
|
* 1. Remove the bottom border in Chrome 57-
|
||||||
margin: 1em 0;
|
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-size: 1em;
|
|
||||||
margin: 1.33em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 {
|
|
||||||
font-size: 0.83em;
|
|
||||||
margin: 1.67em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h6 {
|
|
||||||
font-size: 0.75em;
|
|
||||||
margin: 2.33em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Addresses styling not present in IE 7/8/9, Safari 5, and Chrome.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
abbr[title] {
|
abbr[title] {
|
||||||
border-bottom: 1px dotted;
|
border-bottom: none; /* 1 */
|
||||||
|
text-decoration: underline; /* 2 */
|
||||||
|
text-decoration: underline dotted; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Addresses style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome.
|
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
b,
|
b,
|
||||||
strong {
|
strong {
|
||||||
font-weight: bold;
|
font-weight: bolder;
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
margin: 1em 40px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Addresses styling not present in Safari 5 and Chrome.
|
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||||
*/
|
* 2. Correct the odd `em` font sizing in all browsers.
|
||||||
|
|
||||||
dfn {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Addresses styling not present in IE 6/7/8/9.
|
|
||||||
*/
|
|
||||||
|
|
||||||
mark {
|
|
||||||
background: #ff0;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Addresses margins set differently in IE 6/7.
|
|
||||||
*/
|
|
||||||
|
|
||||||
p,
|
|
||||||
pre {
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Corrects font family set oddly in IE 6, Safari 4/5, and Chrome.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
code,
|
code,
|
||||||
kbd,
|
kbd,
|
||||||
pre,
|
|
||||||
samp {
|
samp {
|
||||||
font-family: monospace, serif;
|
font-family: monospace, monospace; /* 1 */
|
||||||
_font-family: 'courier new', monospace;
|
font-size: 1em; /* 2 */
|
||||||
font-size: 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Improves readability of pre-formatted text in all browsers.
|
* Add the correct font size in all browsers.
|
||||||
*/
|
|
||||||
|
|
||||||
pre {
|
|
||||||
white-space: pre;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Addresses CSS quotes not supported in IE 6/7.
|
|
||||||
*/
|
|
||||||
|
|
||||||
q {
|
|
||||||
quotes: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Addresses `quotes` property not supported in Safari 4.
|
|
||||||
*/
|
|
||||||
|
|
||||||
q:before,
|
|
||||||
q:after {
|
|
||||||
content: '';
|
|
||||||
content: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Addresses inconsistent and variable font size in all browsers.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
small {
|
small {
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Prevents `sub` and `sup` affecting `line-height` in all browsers.
|
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||||
|
* all browsers.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
sub,
|
sub,
|
||||||
sup {
|
sup {
|
||||||
font-size: 75%;
|
font-size: 75%;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
sup {
|
sub {
|
||||||
top: -0.5em;
|
bottom: -0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
sub {
|
sup {
|
||||||
bottom: -0.25em;
|
top: -0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* Embedded content
|
||||||
Lists
|
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Addresses margins set differently in IE 6/7.
|
* Remove the border on images inside links in IE 10.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
dl,
|
img {
|
||||||
menu,
|
border-style: none;
|
||||||
ol,
|
|
||||||
ul {
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dd {
|
/* Forms
|
||||||
margin: 0 0 0 40px;
|
========================================================================== */
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Addresses paddings set differently in IE 6/7.
|
* 1. Change the font styles in all browsers.
|
||||||
|
* 2. Remove the margin in Firefox and Safari.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
menu,
|
button,
|
||||||
ol,
|
input,
|
||||||
ul {
|
optgroup,
|
||||||
padding: 0 0 0 40px;
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit; /* 1 */
|
||||||
|
font-size: 100%; /* 1 */
|
||||||
|
line-height: 1.15; /* 1 */
|
||||||
|
margin: 0; /* 2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Corrects list images handled incorrectly in IE 7.
|
* Show the overflow in IE.
|
||||||
|
* 1. Show the overflow in Edge.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
nav ul,
|
button,
|
||||||
nav ol {
|
input { /* 1 */
|
||||||
list-style: none;
|
overflow: visible;
|
||||||
list-style-image: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/**
|
||||||
Embedded content
|
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||||
========================================================================== */
|
* 1. Remove the inheritance of text transform in Firefox.
|
||||||
|
|
||||||
/*
|
|
||||||
* 1. Removes border when inside `a` element in IE 6/7/8/9 and Firefox 3.
|
|
||||||
* 2. Improves image quality when scaled in IE 7.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
img {
|
button,
|
||||||
border: 0; /* 1 */
|
select { /* 1 */
|
||||||
-ms-interpolation-mode: bicubic; /* 2 */
|
text-transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Corrects overflow displayed oddly in IE 9.
|
* Correct the inability to style clickable types in iOS and Safari.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
svg:not(:root) {
|
button,
|
||||||
overflow: hidden;
|
[type="button"],
|
||||||
|
[type="reset"],
|
||||||
|
[type="submit"] {
|
||||||
|
-webkit-appearance: button;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/**
|
||||||
Figures
|
* Remove the inner border and padding in Firefox.
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Addresses margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
figure {
|
button::-moz-focus-inner,
|
||||||
margin: 0;
|
[type="button"]::-moz-focus-inner,
|
||||||
|
[type="reset"]::-moz-focus-inner,
|
||||||
|
[type="submit"]::-moz-focus-inner {
|
||||||
|
border-style: none;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/**
|
||||||
Forms
|
* Restore the focus styles unset by the previous rule.
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Corrects margin displayed oddly in IE 6/7.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
form {
|
button:-moz-focusring,
|
||||||
margin: 0;
|
[type="button"]:-moz-focusring,
|
||||||
|
[type="reset"]:-moz-focusring,
|
||||||
|
[type="submit"]:-moz-focusring {
|
||||||
|
outline: 1px dotted ButtonText;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Define consistent border, margin, and padding.
|
* Correct the padding in Firefox.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
border: 1px solid #c0c0c0;
|
padding: 0.35em 0.75em 0.625em;
|
||||||
margin: 0 2px;
|
|
||||||
padding: 0.35em 0.625em 0.75em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* 1. Corrects color not being inherited in IE 6/7/8/9.
|
* 1. Correct the text wrapping in Edge and IE.
|
||||||
* 2. Corrects text not wrapping in Firefox 3.
|
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||||
* 3. Corrects alignment displayed oddly in IE 6/7.
|
* 3. Remove the padding so developers are not caught out when they zero out
|
||||||
|
* `fieldset` elements in all browsers.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
legend {
|
legend {
|
||||||
border: 0; /* 1 */
|
box-sizing: border-box; /* 1 */
|
||||||
padding: 0;
|
color: inherit; /* 2 */
|
||||||
white-space: normal; /* 2 */
|
display: table; /* 1 */
|
||||||
*margin-left: -7px; /* 3 */
|
max-width: 100%; /* 1 */
|
||||||
|
padding: 0; /* 3 */
|
||||||
|
white-space: normal; /* 1 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* 1. Corrects font size not being inherited in all browsers.
|
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||||
* 2. Addresses margins set differently in IE 6/7, Firefox 3+, Safari 5,
|
|
||||||
* and Chrome.
|
|
||||||
* 3. Improves appearance and consistency in all browsers.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
button,
|
progress {
|
||||||
input,
|
vertical-align: baseline;
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
font-size: 100%; /* 1 */
|
|
||||||
margin: 0; /* 2 */
|
|
||||||
vertical-align: baseline; /* 3 */
|
|
||||||
*vertical-align: middle; /* 3 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Addresses Firefox 3+ setting `line-height` on `input` using `!important` in
|
* Remove the default vertical scrollbar in IE 10+.
|
||||||
* the UA stylesheet.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
button,
|
textarea {
|
||||||
input {
|
overflow: auto;
|
||||||
line-height: normal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
* 1. Add the correct box sizing in IE 10.
|
||||||
* and `video` controls.
|
* 2. Remove the padding in IE 10.
|
||||||
* 2. Corrects inability to style clickable `input` types in iOS.
|
|
||||||
* 3. Improves usability and consistency of cursor style between image-type
|
|
||||||
* `input` and others.
|
|
||||||
* 4. Removes inner spacing in IE 7 without affecting normal text inputs.
|
|
||||||
* Known issue: inner spacing remains in IE 6.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
button,
|
[type="checkbox"],
|
||||||
html input[type="button"], /* 1 */
|
[type="radio"] {
|
||||||
input[type="reset"],
|
box-sizing: border-box; /* 1 */
|
||||||
input[type="submit"] {
|
padding: 0; /* 2 */
|
||||||
-webkit-appearance: button; /* 2 */
|
|
||||||
cursor: pointer; /* 3 */
|
|
||||||
*overflow: visible; /* 4 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Re-set default cursor for disabled elements.
|
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
button[disabled],
|
[type="number"]::-webkit-inner-spin-button,
|
||||||
input[disabled] {
|
[type="number"]::-webkit-outer-spin-button {
|
||||||
cursor: default;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* 1. Addresses box sizing set to content-box in IE 8/9.
|
* 1. Correct the odd appearance in Chrome and Safari.
|
||||||
* 2. Removes excess padding in IE 8/9.
|
* 2. Correct the outline style in Safari.
|
||||||
* 3. Removes excess padding in IE 7.
|
|
||||||
* Known issue: excess padding remains in IE 6.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
input[type="checkbox"],
|
[type="search"] {
|
||||||
input[type="radio"] {
|
-webkit-appearance: textfield; /* 1 */
|
||||||
box-sizing: border-box; /* 1 */
|
outline-offset: -2px; /* 2 */
|
||||||
padding: 0; /* 2 */
|
|
||||||
*height: 13px; /* 3 */
|
|
||||||
*width: 13px; /* 3 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome.
|
* Remove the inner padding in Chrome and Safari on macOS.
|
||||||
* 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome
|
|
||||||
* (include `-moz` to future-proof).
|
|
||||||
*/
|
*/
|
||||||
/*
|
|
||||||
input[type="search"] {
|
[type="search"]::-webkit-search-decoration {
|
||||||
-webkit-appearance: textfield;
|
-webkit-appearance: none;
|
||||||
-moz-box-sizing: content-box;
|
|
||||||
-webkit-box-sizing: content-box;
|
|
||||||
box-sizing: content-box;
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Removes inner padding and search cancel button in Safari 5 and Chrome
|
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
* on OS X.
|
* 2. Change font properties to `inherit` in Safari.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* input[type="search"]::-webkit-search-cancel-button,
|
::-webkit-file-upload-button {
|
||||||
input[type="search"]::-webkit-search-decoration {
|
-webkit-appearance: button; /* 1 */
|
||||||
-webkit-appearance: none;
|
font: inherit; /* 2 */
|
||||||
} */
|
}
|
||||||
|
|
||||||
|
/* Interactive
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Removes inner padding and border in Firefox 3+.
|
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
button::-moz-focus-inner,
|
details {
|
||||||
input::-moz-focus-inner {
|
display: block;
|
||||||
border: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 1. Removes default vertical scrollbar in IE 6/7/8/9.
|
* Add the correct display in all browsers.
|
||||||
* 2. Improves readability and alignment in all browsers.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
textarea {
|
summary {
|
||||||
overflow: auto; /* 1 */
|
display: list-item;
|
||||||
vertical-align: top; /* 2 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
/* Misc
|
||||||
Tables
|
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Remove most spacing between table cells.
|
* Add the correct display in IE 10+.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
table {
|
template {
|
||||||
border-collapse: collapse;
|
display: none;
|
||||||
border-spacing: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct display in IE 10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
@media (min-device-width: 768px) {
|
||||||
|
.modal-dialog {
|
||||||
|
position: absolute;
|
||||||
|
top: 45%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%) !important;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,198 @@
|
|||||||
|
/*
|
||||||
|
* bootstrap-uploadprogress
|
||||||
|
* github: https://github.com/jakobadam/bootstrap-uploadprogress
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015 Jakob Aarøe Dam
|
||||||
|
* Version 1.0.0
|
||||||
|
* Licensed under the MIT license.
|
||||||
|
*/
|
||||||
|
(function($){
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
$.support.xhrFileUpload = !!(window.FileReader && window.ProgressEvent);
|
||||||
|
$.support.xhrFormData = !!window.FormData;
|
||||||
|
|
||||||
|
if(!$.support.xhrFileUpload || !$.support.xhrFormData){
|
||||||
|
// skip decorating form
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var template = '<div class="modal fade" id="file-progress-modal">\
|
||||||
|
<div class="modal-dialog">\
|
||||||
|
<div class="modal-content">\
|
||||||
|
<div class="modal-header">\
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>\
|
||||||
|
<h4 class="modal-title">Uploading</h4>\
|
||||||
|
</div>\
|
||||||
|
<div class="modal-body">\
|
||||||
|
<div class="modal-message"></div>\
|
||||||
|
<div class="progress">\
|
||||||
|
<div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="0" aria-valuemin="0"\
|
||||||
|
aria-valuemax="100" style="width: 0%;min-width: 2em;">\
|
||||||
|
0%\
|
||||||
|
</div>\
|
||||||
|
</div>\
|
||||||
|
</div>\
|
||||||
|
<div class="modal-footer" style="display:none">\
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>\
|
||||||
|
</div>\
|
||||||
|
</div>\
|
||||||
|
</div>\
|
||||||
|
</div>';
|
||||||
|
|
||||||
|
var UploadProgress = function(element, options){
|
||||||
|
this.options = options;
|
||||||
|
this.$element = $(element);
|
||||||
|
};
|
||||||
|
|
||||||
|
UploadProgress.prototype = {
|
||||||
|
|
||||||
|
constructor: function() {
|
||||||
|
this.$form = this.$element;
|
||||||
|
this.$form.on('submit', $.proxy(this.submit, this));
|
||||||
|
this.$modal = $(this.options.template);
|
||||||
|
this.$modal_message = this.$modal.find('.modal-message');
|
||||||
|
this.$modal_title = this.$modal.find('.modal-title');
|
||||||
|
this.$modal_footer = this.$modal.find('.modal-footer');
|
||||||
|
this.$modal_bar = this.$modal.find('.progress-bar');
|
||||||
|
|
||||||
|
this.$modal.on('hidden.bs.modal', $.proxy(this.reset, this));
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: function(){
|
||||||
|
this.$modal_title = this.$modal_title.text('Uploading');
|
||||||
|
this.$modal_footer.hide();
|
||||||
|
this.$modal_bar.addClass('progress-bar-success');
|
||||||
|
this.$modal_bar.removeClass('progress-bar-danger');
|
||||||
|
if(this.xhr){
|
||||||
|
this.xhr.abort();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
submit: function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.$modal.modal({
|
||||||
|
backdrop: 'static',
|
||||||
|
keyboard: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// We need the native XMLHttpRequest for the progress event
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
this.xhr = xhr;
|
||||||
|
|
||||||
|
xhr.addEventListener('load', $.proxy(this.success, this, xhr));
|
||||||
|
xhr.addEventListener('error', $.proxy(this.error, this, xhr));
|
||||||
|
//xhr.addEventListener('abort', function(){});
|
||||||
|
|
||||||
|
xhr.upload.addEventListener('progress', $.proxy(this.progress, this));
|
||||||
|
|
||||||
|
var form = this.$form;
|
||||||
|
|
||||||
|
xhr.open(form.attr('method'), form.attr("action"));
|
||||||
|
xhr.setRequestHeader('X-REQUESTED-WITH', 'XMLHttpRequest');
|
||||||
|
|
||||||
|
var data = new FormData(form.get(0));
|
||||||
|
xhr.send(data);
|
||||||
|
},
|
||||||
|
|
||||||
|
success: function(xhr) {
|
||||||
|
if(xhr.status == 0 || xhr.status >= 400){
|
||||||
|
// HTTP 500 ends up here!?!
|
||||||
|
return this.error(xhr);
|
||||||
|
}
|
||||||
|
this.set_progress(100);
|
||||||
|
var url;
|
||||||
|
var content_type = xhr.getResponseHeader('Content-Type');
|
||||||
|
|
||||||
|
// make it possible to return the redirect URL in
|
||||||
|
// a JSON response
|
||||||
|
if(content_type.indexOf('application/json') !== -1){
|
||||||
|
var response = $.parseJSON(xhr.responseText);
|
||||||
|
console.log(response);
|
||||||
|
url = response.location;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
url = this.options.redirect_url;
|
||||||
|
}
|
||||||
|
window.location.href = url;
|
||||||
|
},
|
||||||
|
|
||||||
|
// handle form error
|
||||||
|
// we replace the form with the returned one
|
||||||
|
error: function(xhr){
|
||||||
|
this.$modal_title.text('Upload failed');
|
||||||
|
|
||||||
|
this.$modal_bar.removeClass('progress-bar-success');
|
||||||
|
this.$modal_bar.addClass('progress-bar-danger');
|
||||||
|
this.$modal_footer.show();
|
||||||
|
|
||||||
|
var content_type = xhr.getResponseHeader('Content-Type');
|
||||||
|
|
||||||
|
// Replace the contents of the form, with the returned html
|
||||||
|
if(xhr.status === 422){
|
||||||
|
var new_html = $.parseHTML(xhr.responseText);
|
||||||
|
this.replace_form(new_html);
|
||||||
|
this.$modal.modal('hide');
|
||||||
|
}
|
||||||
|
// Write the error response to the document.
|
||||||
|
else{
|
||||||
|
var response_text = xhr.responseText;
|
||||||
|
if(content_type.indexOf('text/plain') !== -1){
|
||||||
|
response_text = '<pre>' + response_text + '</pre>';
|
||||||
|
}
|
||||||
|
document.write(xhr.responseText);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
set_progress: function(percent){
|
||||||
|
var txt = percent + '%';
|
||||||
|
if (percent == 100) {
|
||||||
|
txt = this.options.uploaded_msg;
|
||||||
|
}
|
||||||
|
this.$modal_bar.attr('aria-valuenow', percent);
|
||||||
|
this.$modal_bar.text(txt);
|
||||||
|
this.$modal_bar.css('width', percent + '%');
|
||||||
|
},
|
||||||
|
|
||||||
|
progress: function(/*ProgressEvent*/e){
|
||||||
|
var percent = Math.round((e.loaded / e.total) * 100);
|
||||||
|
this.set_progress(percent);
|
||||||
|
},
|
||||||
|
|
||||||
|
// replace_form replaces the contents of the current form
|
||||||
|
// with the form in the html argument.
|
||||||
|
// We use the id of the current form to find the new form in the html
|
||||||
|
replace_form: function(html){
|
||||||
|
var new_form;
|
||||||
|
var form_id = this.$form.attr('id');
|
||||||
|
if(form_id !== undefined){
|
||||||
|
new_form = $(html).find('#' + form_id);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
new_form = $(html).find('form');
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the filestyle again
|
||||||
|
new_form.find(':file').filestyle({buttonBefore: true});
|
||||||
|
this.$form.html(new_form.children());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.uploadprogress = function(options, value){
|
||||||
|
return this.each(function(){
|
||||||
|
var _options = $.extend({}, $.fn.uploadprogress.defaults, options);
|
||||||
|
var file_progress = new UploadProgress(this, _options);
|
||||||
|
file_progress.constructor();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$.fn.uploadprogress.defaults = {
|
||||||
|
template: template,
|
||||||
|
uploaded_msg: "Upload done, processing, please wait..."
|
||||||
|
//redirect_url: ...
|
||||||
|
|
||||||
|
// need to customize stuff? Add here, and change code accordingly.
|
||||||
|
};
|
||||||
|
|
||||||
|
})(window.jQuery);
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,514 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
||||||
|
# Copyright (C) 2018-2019 OzzieIsaacs
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import zipfile
|
||||||
|
import requests
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
import server
|
||||||
|
import time
|
||||||
|
from io import BytesIO
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
from ub import config, UPDATE_STABLE
|
||||||
|
from tempfile import gettempdir
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
from flask_babel import gettext as _
|
||||||
|
from babel.dates import format_datetime
|
||||||
|
import web
|
||||||
|
|
||||||
|
|
||||||
|
def is_sha1(sha1):
|
||||||
|
if len(sha1) != 40:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
int(sha1, 16)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Updater(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.status = 0
|
||||||
|
self.updateIndex = None
|
||||||
|
|
||||||
|
def get_current_version_info(self):
|
||||||
|
if config.get_update_channel == UPDATE_STABLE:
|
||||||
|
return self._stable_version_info()
|
||||||
|
else:
|
||||||
|
return self._nightly_version_info()
|
||||||
|
|
||||||
|
def get_available_updates(self, request_method):
|
||||||
|
if config.get_update_channel == UPDATE_STABLE:
|
||||||
|
return self._stable_available_updates(request_method)
|
||||||
|
else:
|
||||||
|
return self._nightly_available_updates(request_method)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
self.status = 1
|
||||||
|
r = requests.get(self._get_request_path(), stream=True)
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
self.status = 2
|
||||||
|
z = zipfile.ZipFile(BytesIO(r.content))
|
||||||
|
self.status = 3
|
||||||
|
tmp_dir = gettempdir()
|
||||||
|
z.extractall(tmp_dir)
|
||||||
|
foldername = os.path.join(tmp_dir, z.namelist()[0])[:-1]
|
||||||
|
if not os.path.isdir(foldername):
|
||||||
|
self.status = 11
|
||||||
|
logging.getLogger('cps.web').info(u'Extracted contents of zipfile not found in temp folder')
|
||||||
|
return
|
||||||
|
self.status = 4
|
||||||
|
self.update_source(foldername, config.get_main_dir)
|
||||||
|
self.status = 6
|
||||||
|
time.sleep(2)
|
||||||
|
server.Server.setRestartTyp(True)
|
||||||
|
server.Server.stopServer()
|
||||||
|
self.status = 7
|
||||||
|
time.sleep(2)
|
||||||
|
except requests.exceptions.HTTPError as ex:
|
||||||
|
logging.getLogger('cps.web').info( u'HTTP Error' + ' ' + str(ex))
|
||||||
|
self.status = 8
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
logging.getLogger('cps.web').info(u'Connection error')
|
||||||
|
self.status = 9
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
logging.getLogger('cps.web').info(u'Timeout while establishing connection')
|
||||||
|
self.status = 10
|
||||||
|
except requests.exceptions.RequestException:
|
||||||
|
self.status = 11
|
||||||
|
logging.getLogger('cps.web').info(u'General error')
|
||||||
|
|
||||||
|
def get_update_status(self):
|
||||||
|
return self.status
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def file_to_list(self, filelist):
|
||||||
|
return [x.strip() for x in open(filelist, 'r') if not x.startswith('#EXT')]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def one_minus_two(self, one, two):
|
||||||
|
return [x for x in one if x not in set(two)]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def reduce_dirs(self, delete_files, new_list):
|
||||||
|
new_delete = []
|
||||||
|
for filename in delete_files:
|
||||||
|
parts = filename.split(os.sep)
|
||||||
|
sub = ''
|
||||||
|
for part in parts:
|
||||||
|
sub = os.path.join(sub, part)
|
||||||
|
if sub == '':
|
||||||
|
sub = os.sep
|
||||||
|
count = 0
|
||||||
|
for song in new_list:
|
||||||
|
if song.startswith(sub):
|
||||||
|
count += 1
|
||||||
|
break
|
||||||
|
if count == 0:
|
||||||
|
if sub != '\\':
|
||||||
|
new_delete.append(sub)
|
||||||
|
break
|
||||||
|
return list(set(new_delete))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def reduce_files(self, remove_items, exclude_items):
|
||||||
|
rf = []
|
||||||
|
for item in remove_items:
|
||||||
|
if not item.startswith(exclude_items):
|
||||||
|
rf.append(item)
|
||||||
|
return rf
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def moveallfiles(self, root_src_dir, root_dst_dir):
|
||||||
|
change_permissions = True
|
||||||
|
if sys.platform == "win32" or sys.platform == "darwin":
|
||||||
|
change_permissions = False
|
||||||
|
else:
|
||||||
|
logging.getLogger('cps.web').debug('Update on OS-System : ' + sys.platform)
|
||||||
|
new_permissions = os.stat(root_dst_dir)
|
||||||
|
# print new_permissions
|
||||||
|
for src_dir, __, files in os.walk(root_src_dir):
|
||||||
|
dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1)
|
||||||
|
if not os.path.exists(dst_dir):
|
||||||
|
os.makedirs(dst_dir)
|
||||||
|
logging.getLogger('cps.web').debug('Create-Dir: '+dst_dir)
|
||||||
|
if change_permissions:
|
||||||
|
# print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid))
|
||||||
|
os.chown(dst_dir, new_permissions.st_uid, new_permissions.st_gid)
|
||||||
|
for file_ in files:
|
||||||
|
src_file = os.path.join(src_dir, file_)
|
||||||
|
dst_file = os.path.join(dst_dir, file_)
|
||||||
|
if os.path.exists(dst_file):
|
||||||
|
if change_permissions:
|
||||||
|
permission = os.stat(dst_file)
|
||||||
|
logging.getLogger('cps.web').debug('Remove file before copy: '+dst_file)
|
||||||
|
os.remove(dst_file)
|
||||||
|
else:
|
||||||
|
if change_permissions:
|
||||||
|
permission = new_permissions
|
||||||
|
shutil.move(src_file, dst_dir)
|
||||||
|
logging.getLogger('cps.web').debug('Move File '+src_file+' to '+dst_dir)
|
||||||
|
if change_permissions:
|
||||||
|
try:
|
||||||
|
os.chown(dst_file, permission.st_uid, permission.st_gid)
|
||||||
|
except (Exception) as e:
|
||||||
|
# ex = sys.exc_info()
|
||||||
|
old_permissions = os.stat(dst_file)
|
||||||
|
logging.getLogger('cps.web').debug('Fail change permissions of ' + str(dst_file) + '. Before: '
|
||||||
|
+ str(old_permissions.st_uid) + ':' + str(old_permissions.st_gid) + ' After: '
|
||||||
|
+ str(permission.st_uid) + ':' + str(permission.st_gid) + ' error: '+str(e))
|
||||||
|
return
|
||||||
|
|
||||||
|
def update_source(self, source, destination):
|
||||||
|
# destination files
|
||||||
|
old_list = list()
|
||||||
|
exclude = (
|
||||||
|
os.sep + 'app.db', os.sep + 'calibre-web.log1', os.sep + 'calibre-web.log2', os.sep + 'gdrive.db',
|
||||||
|
os.sep + 'vendor', os.sep + 'calibre-web.log', os.sep + '.git', os.sep +'client_secrets.json',
|
||||||
|
os.sep + 'gdrive_credentials', os.sep + 'settings.yaml')
|
||||||
|
for root, dirs, files in os.walk(destination, topdown=True):
|
||||||
|
for name in files:
|
||||||
|
old_list.append(os.path.join(root, name).replace(destination, ''))
|
||||||
|
for name in dirs:
|
||||||
|
old_list.append(os.path.join(root, name).replace(destination, ''))
|
||||||
|
# source files
|
||||||
|
new_list = list()
|
||||||
|
for root, dirs, files in os.walk(source, topdown=True):
|
||||||
|
for name in files:
|
||||||
|
new_list.append(os.path.join(root, name).replace(source, ''))
|
||||||
|
for name in dirs:
|
||||||
|
new_list.append(os.path.join(root, name).replace(source, ''))
|
||||||
|
|
||||||
|
delete_files = self.one_minus_two(old_list, new_list)
|
||||||
|
|
||||||
|
rf = self.reduce_files(delete_files, exclude)
|
||||||
|
|
||||||
|
remove_items = self.reduce_dirs(rf, new_list)
|
||||||
|
|
||||||
|
self.moveallfiles(source, destination)
|
||||||
|
|
||||||
|
for item in remove_items:
|
||||||
|
item_path = os.path.join(destination, item[1:])
|
||||||
|
if os.path.isdir(item_path):
|
||||||
|
logging.getLogger('cps.web').debug("Delete dir " + item_path)
|
||||||
|
shutil.rmtree(item_path, ignore_errors=True)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
logging.getLogger('cps.web').debug("Delete file " + item_path)
|
||||||
|
# log_from_thread("Delete file " + item_path)
|
||||||
|
os.remove(item_path)
|
||||||
|
except Exception:
|
||||||
|
logging.getLogger('cps.web').debug("Could not remove:" + item_path)
|
||||||
|
shutil.rmtree(source, ignore_errors=True)
|
||||||
|
|
||||||
|
def _nightly_version_info(self):
|
||||||
|
content = {}
|
||||||
|
content[0] = '$Format:%H$'
|
||||||
|
content[1] = '$Format:%cI$'
|
||||||
|
# content[0] = 'bb7d2c6273ae4560e83950d36d64533343623a57'
|
||||||
|
# content[1] = '2018-09-09T10:13:08+02:00'
|
||||||
|
if is_sha1(content[0]) and len(content[1]) > 0:
|
||||||
|
return {'version': content[0], 'datetime': content[1]}
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _stable_version_info(self):
|
||||||
|
return {'version': '0.6.1'} # Current version
|
||||||
|
|
||||||
|
def _nightly_available_updates(self, request_method):
|
||||||
|
tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone)
|
||||||
|
if request_method == "GET":
|
||||||
|
repository_url = 'https://api.github.com/repos/janeczku/calibre-web'
|
||||||
|
status, commit = self._load_remote_data(repository_url +'/git/refs/heads/master')
|
||||||
|
parents = []
|
||||||
|
if status['message'] != '':
|
||||||
|
return json.dumps(status)
|
||||||
|
if 'object' not in commit:
|
||||||
|
status['message'] = _(u'Unexpected data while reading update information')
|
||||||
|
return json.dumps(status)
|
||||||
|
|
||||||
|
if commit['object']['sha'] == status['current_commit_hash']:
|
||||||
|
status.update({
|
||||||
|
'update': False,
|
||||||
|
'success': True,
|
||||||
|
'message': _(u'No update available. You already have the latest version installed')
|
||||||
|
})
|
||||||
|
return json.dumps(status)
|
||||||
|
|
||||||
|
# a new update is available
|
||||||
|
status['update'] = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.get(repository_url + '/git/commits/' + commit['object']['sha'])
|
||||||
|
r.raise_for_status()
|
||||||
|
update_data = r.json()
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
status['error'] = _(u'HTTP Error') + ' ' + str(e)
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
status['error'] = _(u'Connection error')
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
status['error'] = _(u'Timeout while establishing connection')
|
||||||
|
except requests.exceptions.RequestException:
|
||||||
|
status['error'] = _(u'General error')
|
||||||
|
|
||||||
|
if status['message'] != '':
|
||||||
|
return json.dumps(status)
|
||||||
|
|
||||||
|
if 'committer' in update_data and 'message' in update_data:
|
||||||
|
status['success'] = True
|
||||||
|
status['message'] = _(
|
||||||
|
u'A new update is available. Click on the button below to update to the latest version.')
|
||||||
|
|
||||||
|
new_commit_date = datetime.datetime.strptime(
|
||||||
|
update_data['committer']['date'], '%Y-%m-%dT%H:%M:%SZ') - tz
|
||||||
|
parents.append(
|
||||||
|
[
|
||||||
|
format_datetime(new_commit_date, format='short', locale=web.get_locale()),
|
||||||
|
update_data['message'],
|
||||||
|
update_data['sha']
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# it only makes sense to analyze the parents if we know the current commit hash
|
||||||
|
if status['current_commit_hash'] != '':
|
||||||
|
try:
|
||||||
|
parent_commit = update_data['parents'][0]
|
||||||
|
# limit the maximum search depth
|
||||||
|
remaining_parents_cnt = 10
|
||||||
|
except IndexError:
|
||||||
|
remaining_parents_cnt = None
|
||||||
|
|
||||||
|
if remaining_parents_cnt is not None:
|
||||||
|
while True:
|
||||||
|
if remaining_parents_cnt == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
# check if we are more than one update behind if so, go up the tree
|
||||||
|
if parent_commit['sha'] != status['current_commit_hash']:
|
||||||
|
try:
|
||||||
|
r = requests.get(parent_commit['url'])
|
||||||
|
r.raise_for_status()
|
||||||
|
parent_data = r.json()
|
||||||
|
|
||||||
|
parent_commit_date = datetime.datetime.strptime(
|
||||||
|
parent_data['committer']['date'], '%Y-%m-%dT%H:%M:%SZ') - tz
|
||||||
|
parent_commit_date = format_datetime(
|
||||||
|
parent_commit_date, format='short', locale=web.get_locale())
|
||||||
|
|
||||||
|
parents.append([parent_commit_date,
|
||||||
|
parent_data['message'].replace('\r\n','<p>').replace('\n','<p>')])
|
||||||
|
parent_commit = parent_data['parents'][0]
|
||||||
|
remaining_parents_cnt -= 1
|
||||||
|
except Exception:
|
||||||
|
# it isn't crucial if we can't get information about the parent
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# parent is our current version
|
||||||
|
break
|
||||||
|
|
||||||
|
else:
|
||||||
|
status['success'] = False
|
||||||
|
status['message'] = _(u'Could not fetch update information')
|
||||||
|
|
||||||
|
# a new update is available
|
||||||
|
status['update'] = True
|
||||||
|
if 'body' in commit:
|
||||||
|
status['success'] = True
|
||||||
|
status['message'] = _(
|
||||||
|
u'A new update is available. Click on the button below to update to the latest version.')
|
||||||
|
|
||||||
|
new_commit_date = datetime.datetime.strptime(
|
||||||
|
commit['committer']['date'], '%Y-%m-%dT%H:%M:%SZ') - tz
|
||||||
|
parents.append(
|
||||||
|
[
|
||||||
|
format_datetime(new_commit_date, format='short', locale=web.get_locale()),
|
||||||
|
commit['message'],
|
||||||
|
commit['sha']
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# it only makes sense to analyze the parents if we know the current commit hash
|
||||||
|
if status['current_commit_hash'] != '':
|
||||||
|
try:
|
||||||
|
parent_commit = commit['parents'][0]
|
||||||
|
# limit the maximum search depth
|
||||||
|
remaining_parents_cnt = 10
|
||||||
|
except IndexError:
|
||||||
|
remaining_parents_cnt = None
|
||||||
|
|
||||||
|
if remaining_parents_cnt is not None:
|
||||||
|
while True:
|
||||||
|
if remaining_parents_cnt == 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
# check if we are more than one update behind if so, go up the tree
|
||||||
|
if commit['sha'] != status['current_commit_hash']:
|
||||||
|
try:
|
||||||
|
r = requests.get(parent_commit['url'])
|
||||||
|
r.raise_for_status()
|
||||||
|
parent_data = r.json()
|
||||||
|
|
||||||
|
parent_commit_date = datetime.datetime.strptime(
|
||||||
|
parent_data['committer']['date'], '%Y-%m-%dT%H:%M:%SZ') - tz
|
||||||
|
parent_commit_date = format_datetime(
|
||||||
|
parent_commit_date, format='short', locale=web.get_locale())
|
||||||
|
|
||||||
|
parents.append([parent_commit_date, parent_data['message'], parent_data['sha']])
|
||||||
|
parent_commit = parent_data['parents'][0]
|
||||||
|
remaining_parents_cnt -= 1
|
||||||
|
except Exception:
|
||||||
|
# it isn't crucial if we can't get information about the parent
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# parent is our current version
|
||||||
|
break
|
||||||
|
status['history'] = parents[::-1]
|
||||||
|
return json.dumps(status)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def _stable_available_updates(self, request_method):
|
||||||
|
if request_method == "GET":
|
||||||
|
parents = []
|
||||||
|
# repository_url = 'https://api.github.com/repos/flatpak/flatpak/releases' # test URL
|
||||||
|
repository_url = 'https://api.github.com/repos/janeczku/calibre-web/releases'
|
||||||
|
status, commit = self._load_remote_data(repository_url)
|
||||||
|
if status['message'] != '':
|
||||||
|
return json.dumps(status)
|
||||||
|
if not commit:
|
||||||
|
status['success'] = True
|
||||||
|
status['message'] = _(u'No release information available')
|
||||||
|
return json.dumps(status)
|
||||||
|
version = status['current_commit_hash']
|
||||||
|
current_version = status['current_commit_hash'].split('.')
|
||||||
|
|
||||||
|
# we are already on newest version, no update available
|
||||||
|
if 'tag_name' not in commit[0]:
|
||||||
|
status['message'] = _(u'Unexpected data while reading update information')
|
||||||
|
return json.dumps(status)
|
||||||
|
if commit[0]['tag_name'] == version:
|
||||||
|
status.update({
|
||||||
|
'update': False,
|
||||||
|
'success': True,
|
||||||
|
'message': _(u'No update available. You already have the latest version installed')
|
||||||
|
})
|
||||||
|
return json.dumps(status)
|
||||||
|
|
||||||
|
i = len(commit) - 1
|
||||||
|
while i >= 0:
|
||||||
|
if 'tag_name' not in commit[i] or 'body' not in commit[i]:
|
||||||
|
status['message'] = _(u'Unexpected data while reading update information')
|
||||||
|
return json.dumps(status)
|
||||||
|
major_version_update = int(commit[i]['tag_name'].split('.')[0])
|
||||||
|
minor_version_update = int(commit[i]['tag_name'].split('.')[1])
|
||||||
|
patch_version_update = int(commit[i]['tag_name'].split('.')[2])
|
||||||
|
|
||||||
|
# Check if major versions are identical search for newest nonenqual commit and update to this one
|
||||||
|
if major_version_update == int(current_version[0]):
|
||||||
|
if (minor_version_update == int(current_version[1]) and
|
||||||
|
patch_version_update > int(current_version[2])) or \
|
||||||
|
minor_version_update > int(current_version[1]):
|
||||||
|
parents.append([commit[i]['tag_name'],commit[i]['body'].replace('\r\n', '<p>')])
|
||||||
|
i -= 1
|
||||||
|
continue
|
||||||
|
if major_version_update < int(current_version[0]):
|
||||||
|
i -= 1
|
||||||
|
continue
|
||||||
|
if major_version_update > int(current_version[0]):
|
||||||
|
# found update update to last version before major update, unless current version is on last version
|
||||||
|
# before major update
|
||||||
|
if commit[i+1]['tag_name'].split('.')[1] == current_version[1]:
|
||||||
|
parents.append([commit[i]['tag_name'],
|
||||||
|
commit[i]['body'].replace('\r\n', '<p>').replace('\n', '<p>')])
|
||||||
|
status.update({
|
||||||
|
'update': True,
|
||||||
|
'success': True,
|
||||||
|
'message': _(u'A new update is available. Click on the button below to '
|
||||||
|
u'update to version: %(version)s', version=commit[i]['tag_name']),
|
||||||
|
'history': parents
|
||||||
|
})
|
||||||
|
self.updateFile = commit[i]['zipball_url']
|
||||||
|
else:
|
||||||
|
status.update({
|
||||||
|
'update': True,
|
||||||
|
'success': True,
|
||||||
|
'message': _(u'A new update is available. Click on the button below to '
|
||||||
|
u'update to version: %(version)s', version=commit[i]['tag_name']),
|
||||||
|
'history': parents
|
||||||
|
})
|
||||||
|
self.updateFile = commit[i +1]['zipball_url']
|
||||||
|
break
|
||||||
|
if i == -1:
|
||||||
|
status.update({
|
||||||
|
'update': True,
|
||||||
|
'success': True,
|
||||||
|
'message': _(
|
||||||
|
u'A new update is available. Click on the button below to update to the latest version.'),
|
||||||
|
'history': parents
|
||||||
|
})
|
||||||
|
self.updateFile = commit[0]['zipball_url']
|
||||||
|
return json.dumps(status)
|
||||||
|
|
||||||
|
def _get_request_path(self):
|
||||||
|
if config.get_update_channel == UPDATE_STABLE:
|
||||||
|
return self.updateFile
|
||||||
|
else:
|
||||||
|
return 'https://api.github.com/repos/janeczku/calibre-web/zipball/master'
|
||||||
|
|
||||||
|
def _load_remote_data(self, repository_url):
|
||||||
|
status = {
|
||||||
|
'update': False,
|
||||||
|
'success': False,
|
||||||
|
'message': '',
|
||||||
|
'current_commit_hash': ''
|
||||||
|
}
|
||||||
|
commit = None
|
||||||
|
version = self.get_current_version_info()
|
||||||
|
if version is False:
|
||||||
|
status['current_commit_hash'] = _(u'Unknown')
|
||||||
|
else:
|
||||||
|
status['current_commit_hash'] = version['version']
|
||||||
|
try:
|
||||||
|
r = requests.get(repository_url)
|
||||||
|
commit = r.json()
|
||||||
|
r.raise_for_status()
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
if commit:
|
||||||
|
if 'message' in commit:
|
||||||
|
status['message'] = _(u'HTTP Error') + ': ' + commit['message']
|
||||||
|
else:
|
||||||
|
status['message'] = _(u'HTTP Error') + ': ' + str(e)
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
status['message'] = _(u'Connection error')
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
status['message'] = _(u'Timeout while establishing connection')
|
||||||
|
except requests.exceptions.RequestException:
|
||||||
|
status['message'] = _(u'General error')
|
||||||
|
|
||||||
|
return status, commit
|
||||||
|
|
||||||
|
|
||||||
|
updater_thread = Updater()
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue