Commit 3f35200a authored by OzzieIsaacs's avatar OzzieIsaacs

Merge remote-tracking branch 'Comic/comic_server'

# Conflicts:
#	cps/helper.py
#	cps/static/js/archive.js
#	cps/static/js/io.js
#	cps/static/js/kthoom.js
#	cps/static/js/unrar.js
#	cps/static/js/untar.js
#	cps/static/js/unzip.js
#	cps/templates/config_edit.html
#	cps/templates/detail.html
#	cps/templates/readcbr.html
#	cps/templates/stats.html
#	cps/ub.py
#	cps/web.py
#	optional-requirements.txt
parents 9c889d16 b2ba8bce
...@@ -27,6 +27,7 @@ except ImportError: ...@@ -27,6 +27,7 @@ except ImportError:
import web import web
import server import server
import random import random
import subprocess
try: try:
import unidecode import unidecode
...@@ -512,3 +513,28 @@ class Updater(threading.Thread): ...@@ -512,3 +513,28 @@ class Updater(threading.Thread):
except Exception: except Exception:
logging.getLogger('cps.web').debug("Could not remove:" + item_path) logging.getLogger('cps.web').debug("Could not remove:" + item_path)
shutil.rmtree(source, ignore_errors=True) shutil.rmtree(source, ignore_errors=True)
def check_unrar(unrarLocation):
error = False
if os.path.exists(unrarLocation):
try:
if sys.version_info < (3, 0):
unrarLocation = unrarLocation.encode(sys.getfilesystemencoding())
p = subprocess.Popen(unrarLocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.wait()
for lines in p.stdout.readlines():
if isinstance(lines, bytes):
lines = lines.decode('utf-8')
value=re.search('UNRAR (.*) freeware', lines)
if value:
version = value.group(1)
except OSError as e:
error = True
web.app.logger.exception(e)
version =_(u'Error excecuting UnRar')
else:
version = _(u'Unrar binary file not found')
error=True
return (error, version)
body { body {
background: #444; background: #444;
overflow: hidden; overflow-x: hidden;
color: white; overflow-y: auto;
font-family: sans-serif; color: white;
margin: 0px; font-family: sans-serif;
margin: 0px;
} }
.main { #main {
position: re; text-align: center;
left: 5px; z-index: 2;
overflow: hidden; }
right: 5px;
text-align: center; .view {
top: 5px; padding-top:0px;
}
#sidebar a,
#sidebar ul,
#sidebar li,
#sidebar li img {
max-width: 100%;
text-align: center;
}
#sidebar ul {
position: relative;
}
#sidebar a {
display: inline-block;
position: relative;
cursor: pointer;
padding: 4px;
transition: all .2s ease;
}
#sidebar a:hover,
#sidebar a:focus {
outline: none;
box-shadow: 0px 2px 8px 1px black;
}
#sidebar a.active,
#sidebar a.active img + span {
background-color: #45B29D;
}
#sidebar li img {
display: block;
max-height: 200px;
}
#sidebar li img + span {
position: absolute;
bottom: 0;
right: 0;
padding: 2px;
min-width: 25px;
line-height: 25px;
background: #6b6b6b;
border-top-left-radius: 5px;
}
#sidebar #panels {
z-index: 1;
} }
#progress { #progress {
position: absolute; position: absolute;
display: inline; display: inline;
left: 90px; top: 0;
right: 160px; left: 0;
height: 20px; right: 0;
margin-top: 1px; min-height: 4px;
text-align: right; font-family: sans-serif;
font-size: 10px;
line-height: 10px;
text-align: right;
transition: min-height 150ms ease-in-out;
}
#progress .bar-load,
#progress .bar-read {
display: flex;
align-items: flex-end;
justify-content: flex-end;
position: absolute;
top: 0;
left: 0;
bottom: 0;
transition: width 150ms ease-in-out;
}
#progress .bar-load {
color: #000;
background-color: #CCC;
}
#progress .bar-read {
color: #FFF;
background-color: #45B29D;
}
#progress .text {
display: none;
padding: 0 5px;
}
#progress.loading,
#titlebar:hover #progress {
min-height: 10px;
}
#progress.loading .text,
#titlebar:hover #progress .text {
display: inline-block;
} }
.hide { .hide {
display: none !important; display: none !important;
}
#mainContent {
overflow: auto;
outline: none;
} }
#mainText { #mainText {
...@@ -42,29 +143,13 @@ body { ...@@ -42,29 +143,13 @@ body {
word-wrap: break-word; word-wrap: break-word;
} }
#mainImage{ #titlebar {
margin-top: 32px; min-height: 25px;
height: auto;
} }
#titlebar.main { #metainfo {
opacity: 0; max-width: 70%;
position: absolute;
top: 0;
height: 30px;
left: 0;
right: 0;
background-color: black;
padding-bottom: 70px;
-webkit-transition: opacity 0.2s ease;
-moz-transition: opacity 0.2s ease;
transition: opacity 0.2s ease;
background: -moz-linear-gradient(top, rgba(0,2,34,1) 0%, rgba(0,1,24,1) 30%, rgba(0,0,0,0) 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,2,34,1)), color-stop(30%,rgba(0,1,24,1)), color-stop(100%,rgba(0,0,0,0))); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, rgba(0,2,34,1) 0%,rgba(0,1,24,1) 30%,rgba(0,0,0,0) 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, rgba(0,2,34,1) 0%,rgba(0,1,24,1) 30%,rgba(0,0,0,0) 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, rgba(0,2,34,1) 0%,rgba(0,1,24,1) 30%,rgba(0,0,0,0) 100%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#000222', endColorstr='#00000000',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, rgba(0,2,34,1) 0%,rgba(0,1,24,1) 30%,rgba(0,0,0,0) 100%); /* W3C */
} }
#prev { #prev {
...@@ -100,6 +185,72 @@ body { ...@@ -100,6 +185,72 @@ body {
color: #000; color: #000;
} }
th, td {
padding: 5px;
}
th {
text-align: right;
vertical-align: top;
}
.modal {
/* Makes the modal responsive. Note sure if this should be moved to main.css */
margin: 0;
max-width: 96%;
transform: translate(-50%, -50%);
}
.md-content {
min-height: 320px;
height: auto;
}
.md-content > div {
overflow: hidden;
}
.md-content > div p {
padding: 5px 0;
}
.settings-column {
float: left;
min-width: 35%;
padding-bottom: 10px;
}
.inputs {
margin: -5px;
}
.inputs input {
vertical-align: middle;
}
.inputs label {
display: inline-block;
margin: 5px;
white-space: nowrap;
}
.dark-theme #main {
background-color: #000;
}
.dark-theme #titlebar {
color: #DDD;
}
.dark-theme #titlebar a:active {
color: #FFF;
}
.dark-theme #progress .bar-read {
background-color: red;
}
.dark-theme .overlay {
background-color: rgba(0,0,0,0.8);
}
/*
* bytestream.js
*
* Provides readers for byte streams.
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*/
var bitjs = bitjs || {};
bitjs.io = bitjs.io || {};
/**
* This object allows you to peek and consume bytes as numbers and strings out
* of a stream. More bytes can be pushed into the back of the stream via the
* push() method.
*/
bitjs.io.ByteStream = class {
/**
* @param {ArrayBuffer} ab The ArrayBuffer object.
* @param {number=} opt_offset The offset into the ArrayBuffer
* @param {number=} opt_length The length of this BitStream
*/
constructor(ab, opt_offset, opt_length) {
if (!(ab instanceof ArrayBuffer)) {
throw 'Error! BitArray constructed with an invalid ArrayBuffer object';
}
const offset = opt_offset || 0;
const length = opt_length || ab.byteLength;
/**
* The current page of bytes in the stream.
* @type {Uint8Array}
* @private
*/
this.bytes = new Uint8Array(ab, offset, length);
/**
* The next pages of bytes in the stream.
* @type {Array<Uint8Array>}
* @private
*/
this.pages_ = [];
/**
* The byte in the current page that we will read next.
* @type {Number}
* @private
*/
this.ptr = 0;
/**
* An ever-increasing number.
* @type {Number}
* @private
*/
this.bytesRead_ = 0;
}
/**
* Returns how many bytes have been read in the stream since the beginning of time.
*/
getNumBytesRead() {
return this.bytesRead_;
}
/**
* Returns how many bytes are currently in the stream left to be read.
*/
getNumBytesLeft() {
const bytesInCurrentPage = (this.bytes.byteLength - this.ptr);
return this.pages_.reduce((acc, arr) => acc + arr.length, bytesInCurrentPage);
}
/**
* Move the pointer ahead n bytes. If the pointer is at the end of the current array
* of bytes and we have another page of bytes, point at the new page. This is a private
* method, no validation is done.
* @param {number} n Number of bytes to increment.
* @private
*/
movePointer_(n) {
this.ptr += n;
this.bytesRead_ += n;
while (this.ptr >= this.bytes.length && this.pages_.length > 0) {
this.ptr -= this.bytes.length;
this.bytes = this.pages_.shift();
}
}
/**
* Peeks at the next n bytes as an unsigned number but does not advance the
* pointer.
* @param {number} n The number of bytes to peek at. Must be a positive integer.
* @return {number} The n bytes interpreted as an unsigned number.
*/
peekNumber(n) {
const num = parseInt(n, 10);
if (n !== num || num < 0) {
throw 'Error! Called peekNumber() with a non-positive integer';
} else if (num === 0) {
return 0;
}
if (n > 4) {
throw 'Error! Called peekNumber(' + n +
') but this method can only reliably read numbers up to 4 bytes long';
}
if (this.getNumBytesLeft() < num) {
throw 'Error! Overflowed the byte stream while peekNumber()! n=' + num +
', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft();
}
let result = 0;
let curPage = this.bytes;
let pageIndex = 0;
let ptr = this.ptr;
for (let i = 0; i < num; ++i) {
result |= (curPage[ptr++] << (i * 8));
if (ptr >= curPage.length) {
curPage = this.pages_[pageIndex++];
ptr = 0;
}
}
return result;
}
/**
* Returns the next n bytes as an unsigned number (or -1 on error)
* and advances the stream pointer n bytes.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @return {number} The n bytes interpreted as an unsigned number.
*/
readNumber(n) {
const num = this.peekNumber(n);
this.movePointer_(n);
return num;
}
/**
* Returns the next n bytes as a signed number but does not advance the
* pointer.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @return {number} The bytes interpreted as a signed number.
*/
peekSignedNumber(n) {
let num = this.peekNumber(n);
const HALF = Math.pow(2, (n * 8) - 1);
const FULL = HALF * 2;
if (num >= HALF) num -= FULL;
return num;
}
/**
* Returns the next n bytes as a signed number and advances the stream pointer.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @return {number} The bytes interpreted as a signed number.
*/
readSignedNumber(n) {
const num = this.peekSignedNumber(n);
this.movePointer_(n);
return num;
}
/**
* This returns n bytes as a sub-array, advancing the pointer if movePointers
* is true.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @param {boolean} movePointers Whether to move the pointers.
* @return {Uint8Array} The subarray.
*/
peekBytes(n, movePointers) {
const num = parseInt(n, 10);
if (n !== num || num < 0) {
throw 'Error! Called peekBytes() with a non-positive integer';
} else if (num === 0) {
return new Uint8Array();
}
const totalBytesLeft = this.getNumBytesLeft();
if (num > totalBytesLeft) {
throw 'Error! Overflowed the byte stream during peekBytes! n=' + num +
', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft();
}
const result = new Uint8Array(num);
let curPage = this.bytes;
let ptr = this.ptr;
let bytesLeftToCopy = num;
let pageIndex = 0;
while (bytesLeftToCopy > 0) {
const bytesLeftInPage = curPage.length - ptr;
const sourceLength = Math.min(bytesLeftToCopy, bytesLeftInPage);
result.set(curPage.subarray(ptr, ptr + sourceLength), num - bytesLeftToCopy);
ptr += sourceLength;
if (ptr >= curPage.length) {
curPage = this.pages_[pageIndex++];
ptr = 0;
}
bytesLeftToCopy -= sourceLength;
}
if (movePointers) {
this.movePointer_(num);
}
return result;
}
/**
* Reads the next n bytes as a sub-array.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @return {Uint8Array} The subarray.
*/
readBytes(n) {
return this.peekBytes(n, true);
}
/**
* Peeks at the next n bytes as an ASCII string but does not advance the pointer.
* @param {number} n The number of bytes to peek at. Must be a positive integer.
* @return {string} The next n bytes as a string.
*/
peekString(n) {
const num = parseInt(n, 10);
if (n !== num || num < 0) {
throw 'Error! Called peekString() with a non-positive integer';
} else if (num === 0) {
return '';
}
const totalBytesLeft = this.getNumBytesLeft();
if (num > totalBytesLeft) {
throw 'Error! Overflowed the byte stream while peekString()! n=' + num +
', ptr=' + this.ptr + ', bytes.length=' + this.getNumBytesLeft();
}
let result = new Array(num);
let curPage = this.bytes;
let pageIndex = 0;
let ptr = this.ptr;
for (let i = 0; i < num; ++i) {
result[i] = String.fromCharCode(curPage[ptr++]);
if (ptr >= curPage.length) {
curPage = this.pages_[pageIndex++];
ptr = 0;
}
}
return result.join('');
}
/**
* Returns the next n bytes as an ASCII string and advances the stream pointer
* n bytes.
* @param {number} n The number of bytes to read. Must be a positive integer.
* @return {string} The next n bytes as a string.
*/
readString(n) {
const strToReturn = this.peekString(n);
this.movePointer_(n);
return strToReturn;
}
/**
* Feeds more bytes into the back of the stream.
* @param {ArrayBuffer} ab
*/
push(ab) {
if (!(ab instanceof ArrayBuffer)) {
throw 'Error! ByteStream.push() called with an invalid ArrayBuffer object';
}
this.pages_.push(new Uint8Array(ab));
// If the pointer is at the end of the current page of bytes, this will advance
// to the next page.
this.movePointer_(0);
}
/**
* Creates a new ByteStream from this ByteStream that can be read / peeked.
* @return {bitjs.io.ByteStream} A clone of this ByteStream.
*/
tee() {
const clone = new bitjs.io.ByteStream(this.bytes.buffer);
clone.bytes = this.bytes;
clone.ptr = this.ptr;
clone.pages_ = this.pages_.slice();
clone.bytesRead_ = this.bytesRead_;
return clone;
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -170,7 +170,7 @@ ...@@ -170,7 +170,7 @@
<h4 class="panel-title"> <h4 class="panel-title">
<a class="accordion-toggle" data-toggle="collapse" href="#collapseeight"> <a class="accordion-toggle" data-toggle="collapse" href="#collapseeight">
<span class="glyphicon glyphicon-plus"></span> <span class="glyphicon glyphicon-plus"></span>
{{_('E-Book converter')}} {{_('External binaries')}}
</a> </a>
</h4> </h4>
</div> </div>
...@@ -179,9 +179,9 @@ ...@@ -179,9 +179,9 @@
<div class="form-group"> <div class="form-group">
<div><input type="radio" name="config_ebookconverter" id="converter0" value="0" {% if content.config_ebookconverter == 0 %}checked{% endif %}> <div><input type="radio" name="config_ebookconverter" id="converter0" value="0" {% if content.config_ebookconverter == 0 %}checked{% endif %}>
<label for="converter0">{{_('No converter')}}</label></div> <label for="converter0">{{_('No converter')}}</label></div>
<div><label><input type="radio" name="config_ebookconverter" id="converter1" value="1" {% if content.config_ebookconverter == 1 %}checked{% endif %}> <div><input type="radio" name="config_ebookconverter" id="converter1" value="1" {% if content.config_ebookconverter == 1 %}checked{% endif %}>
<label for="converter1">{{_('Use Kindlegen')}}</label></div> <label for="converter1">{{_('Use Kindlegen')}}</label></div>
<div><label><input type="radio" name="config_ebookconverter" id="converter2" value="2" {% if content.config_ebookconverter == 2 %}checked{% endif %}> <div><input type="radio" name="config_ebookconverter" id="converter2" value="2" {% if content.config_ebookconverter == 2 %}checked{% endif %}>
<label for="converter2">{{_('Use calibre\'s ebook converter')}}</label></div> <label for="converter2">{{_('Use calibre\'s ebook converter')}}</label></div>
</div> </div>
<div data-related="calibre"> <div data-related="calibre">
...@@ -194,6 +194,12 @@ ...@@ -194,6 +194,12 @@
<input type="text" class="form-control" id="config_converterpath" name="config_converterpath" value="{% if content.config_converterpath != None %}{{ content.config_converterpath }}{% endif %}" autocomplete="off"> <input type="text" class="form-control" id="config_converterpath" name="config_converterpath" value="{% if content.config_converterpath != None %}{{ content.config_converterpath }}{% endif %}" autocomplete="off">
</div> </div>
</div> </div>
{% if rarfile_support %}
<div class="form-group">
<label for="config_rarfile_location">{{_('Location of Unrar binary')}}</label>
<input type="text" class="form-control" name="config_rarfile_location" id="config_rarfile_location" value="{% if content.config_rarfile_location != None %}{{ content.config_rarfile_location }}{% endif %}" autocomplete="off">
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
......
...@@ -2,15 +2,15 @@ ...@@ -2,15 +2,15 @@
{% block body %} {% block body %}
<h1>{{title}}</h1> <h1>{{title}}</h1>
<div class="container"> <div class="container">
<div class="col-sm-6"> <div class="col-xs-12 col-sm-6">
{% for lang in languages %} {% for lang in languages %}
{% if loop.index0 == (loop.length/2)|int and loop.length > 20 %} {% if loop.index0 == (loop.length/2)|int and loop.length > 20 %}
</div> </div>
<div class="col-sm-6"> <div class="col-xs-12 col-sm-6">
{% endif %} {% endif %}
<div class="row"> <div class="row">
<div class="col-xs-1" align="left"><span class="badge">{{lang_counter[loop.index0].bookcount}}</span></div> <div class="col-xs-2 col-sm-2 col-md-1" align="left"><span class="badge">{{lang_counter[loop.index0].bookcount}}</span></div>
<div class="col-xs-6"><a id="list_{{loop.index0}}" href="{{url_for('language', name=lang.lang_code)}}">{{lang.name}}</a></div> <div class="col-xs-10 col-sm-10 col-md-11"><a id="list_{{loop.index0}}" href="{{url_for('language', name=lang.lang_code)}}">{{lang.name}}</a></div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
......
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Comic Reader</title> <title>Comic Reader</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<link rel="stylesheet" href="{{ url_for('static', filename='css/kthoom.css') }}" type="text/css"/>
<link href="{{ url_for('static', filename='css/libs/bootstrap.min.css') }}" rel="stylesheet" media="screen">
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/kthoom.js') }}"></script> <link rel="stylesheet" href="{{ url_for('static', filename='css/libs/normalize.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}" type="text/css"/>
<link rel="stylesheet" href="{{ url_for('static', filename='css/kthoom.css') }}" type="text/css"/>
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/kthoom.js') }}"></script>
<script src="{{ url_for('static', filename='js/archive.js') }}"></script> <script src="{{ url_for('static', filename='js/archive.js') }}"></script>
<script> <script>
document.onreadystatechange = function () { document.onreadystatechange = function () {
if (document.readyState == "complete") { if (document.readyState == "complete") {
init("{{ url_for('serve_book', book_id=comicfile, book_format=extension) }}"); init("{{ url_for('serve_book', book_id=comicfile, book_format=extension) }}");
} }
}; };
</script> </script>
</head> </head>
<body> <body>
<div class="main" id="main"> <div id="sidebar">
<div id="titlebar"> <!--
<div id="progress" class="hide"></div> <div id="panels">
<div class="progress2" style="display:none"> <a id="show-Toc" class="show_view icon-list-1 active" data-view="Toc">TOC</a>
<div class="progress-bar" role="progressbar" aria-valuenow="70" <a id="show-Bookmarks" class="show_view icon-bookmark" data-view="Bookmarks">Bookmarks</a>
aria-valuemin="0" aria-valuemax="100" style="width:70%"> </div>
70% -->
</div> <div id="tocView" class="view" tabindex="-1">
<ul id="thumbnails"></ul>
</div>
<div id="bookmarksView" class="view">
<ul id="bookmarks"></ul>
</div>
</div>
<div id="main" class="main">
<div id="titlebar" class="main">
<div id="opener">
<a id="slider" class="icon-menu">Menu</a>
</div>
<div id="metainfo">
<span id="book-title">{{ title | shortentitle }}</span>
<span id="title-seperator">&nbsp;&nbsp;&nbsp;&nbsp;</span>
<span id="chapter-title"></span>
</div>
<div id="title-controls">
<a id="setting" class="icon-cog">Settings</a>
<a id="fullscreen" class="icon-resize-full">Fullscreen</a>
</div>
<div id="progress" role="progressbar" class="loading">
<div class="bar-load">
<div class="text load">
Loading...
</div> </div>
</div> </div>
<div id="mainContent"> <div class="bar-read">
<div id="mainText" style="display:none"></div> <div class="text page"></div>
<canvas id="mainImage"></canvas> </div>
</div> </div>
<div id="prev" class="arrow" onclick="showPrevPage()"></div> </div>
<div id="next" class="arrow" onclick="showNextPage()"></div>
<div id="mainContent" tabindex="-1">
<div id="mainText" style="display:none"></div>
<canvas id="mainImage"></canvas>
</div>
<div id="prev" class="arrow" onclick="showPrevPage()"></div>
<div id="next" class="arrow" onclick="showNextPage()"></div>
</div>
<div class="modal md-effect-1" id="settings-modal">
<div class="md-content">
<h3>{{_('Settings')}}</h3>
<div>
<div class="settings-column">
<table>
<thead>
<tr><th colspan="2">{{_('Keyboard Shortcuts')}}</th></tr>
</thead>
<tbody>
<tr><td>&larr;</td> <td>{{_('Previous Page')}}</td></tr>
<tr><td>&rarr;</td> <td>{{_('Next Page')}}</td></tr>
<tr><td>B</td> <td>{{_('Scale to Best')}}</td></tr>
<tr><td>W</td> <td>{{_('Scale to Width')}}</td></tr>
<tr><td>H</td> <td>{{_('Scale to Height')}}</td></tr>
<tr><td>N</td> <td>{{_('Scale to Native')}}</td></tr>
<tr><td>R</td> <td>{{_('Rotate Right')}}</td></tr>
<tr><td>L</td> <td>{{_('Rotate Left')}}</td></tr>
<tr><td>F</td> <td>{{_('Flip Image')}}</td></tr>
</tbody>
</table>
</div>
<div class="settings-column">
<table id="settings">
<thead>
<tr>
<th>{{_('Settings')}}</th>
</tr>
</thead>
<tbody>
<tr>
<th>{{_('Theme')}}:</th>
<td>
<div class="inputs">
<label for="lightTheme"><input type="radio" id="lightTheme" name="theme" value="light" /> {{_('Light')}}</label>
<label for="darkTheme"><input type="radio" id="darkTheme" name="theme" value="dark" /> {{_('Dark')}}</label>
</div>
</td>
</tr>
<tr>
<th>{{_('Scale')}}:</th>
<td>
<div class="inputs">
<label for="fitBest"><input type="radio" id="fitBest" name="fitMode" value="66" /> {{_('Best')}}</label>
<label for="fitWidth"><input type="radio" id="fitWidth" name="fitMode" value="87" /> {{_('Width')}}</label>
<label for="fitHeight"><input type="radio" id="fitHeight" name="fitMode" value="72" /> {{_('Height')}}</label>
<label for="fitNative"><input type="radio" id="fitNative" name="fitMode" value="78" /> {{_('Native')}}</label>
</div>
</td>
</tr>
<tr>
<th>{{_('Rotate')}}:</th>
<td>
<div class="inputs">
<label for="r0"><input type="radio" id="r0" name="rotateTimes" value="0" /> 0&deg;</label>
<label for="r90"><input type="radio" id="r90" name="rotateTimes" value="1" /> 90&deg;</label>
<label for="r180"><input type="radio" id="r180" name="rotateTimes" value="2" /> 180&deg;</label>
<label for="r270"><input type="radio" id="r270" name="rotateTimes" value="3" /> 270&deg;</label>
</div>
</td>
</tr>
<tr>
<th>{{_('Flip')}}:</th>
<td>
<div class="inputs">
<label for="vflip"><input type="checkbox" id="vflip" name="vflip" /> {{_('Horizontal')}}</label>
<label for="hflip"><input type="checkbox" id="hflip" name="hflip" /> {{_('Vertical')}}</label>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div> </div>
</body> <div class="closer icon-cancel-circled"></div>
</div>
</div>
<div class="overlay"></div>
</body>
</html> </html>
...@@ -311,6 +311,7 @@ class Settings(Base): ...@@ -311,6 +311,7 @@ class Settings(Base):
config_ebookconverter = Column(Integer, default=0) config_ebookconverter = Column(Integer, default=0)
config_converterpath = Column(String) config_converterpath = Column(String)
config_calibre = Column(String) config_calibre = Column(String)
config_rarfile_location = Column(String)
def __repr__(self): def __repr__(self):
pass pass
...@@ -383,6 +384,7 @@ class Config: ...@@ -383,6 +384,7 @@ class Config:
self.config_mature_content_tags = u'' self.config_mature_content_tags = u''
if data.config_logfile: if data.config_logfile:
self.config_logfile = data.config_logfile self.config_logfile = data.config_logfile
self.config_rarfile_location = data.config_rarfile_location
@property @property
def get_main_dir(self): def get_main_dir(self):
...@@ -566,6 +568,13 @@ def migrate_Database(): ...@@ -566,6 +568,13 @@ def migrate_Database():
conn = engine.connect() conn = engine.connect()
conn.execute("ALTER TABLE book_shelf_link ADD column 'order' INTEGER DEFAULT 1") conn.execute("ALTER TABLE book_shelf_link ADD column 'order' INTEGER DEFAULT 1")
session.commit() session.commit()
try:
session.query(exists().where(Settings.config_rarfile_location)).scalar()
session.commit()
except exc.OperationalError: # Database is not compatible, some rows are missing
conn = engine.connect()
conn.execute("ALTER TABLE Settings ADD column `config_rarfile_location` String DEFAULT ''")
session.commit()
try: try:
create = False create = False
session.query(exists().where(User.sidebar_view)).scalar() session.query(exists().where(User.sidebar_view)).scalar()
......
This diff is collapsed.
...@@ -16,3 +16,5 @@ goodreads>=0.3.2 ...@@ -16,3 +16,5 @@ goodreads>=0.3.2
python-Levenshtein>=0.12.0 python-Levenshtein>=0.12.0
# other # other
lxml==3.7.2 lxml==3.7.2
rarfile>=2.7
natsort>=2.2.0
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment