mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54:09 -04:00
Start work on migrating get books internal browser to web engine
This commit is contained in:
parent
581c5d8988
commit
c8e78749eb
@ -43,6 +43,7 @@ FAKE_PROTOCOL, FAKE_HOST = 'clbr', 'internal.invalid'
|
|||||||
VIEWER_APP_UID = 'com.calibre-ebook.viewer'
|
VIEWER_APP_UID = 'com.calibre-ebook.viewer'
|
||||||
EDITOR_APP_UID = 'com.calibre-ebook.edit-book'
|
EDITOR_APP_UID = 'com.calibre-ebook.edit-book'
|
||||||
MAIN_APP_UID = 'com.calibre-ebook.main-gui'
|
MAIN_APP_UID = 'com.calibre-ebook.main-gui'
|
||||||
|
STORE_DIALOG_APP_UID = 'com.calibre-ebook.store-dialog'
|
||||||
try:
|
try:
|
||||||
preferred_encoding = locale.getpreferredencoding()
|
preferred_encoding = locale.getpreferredencoding()
|
||||||
codecs.lookup(preferred_encoding)
|
codecs.lookup(preferred_encoding)
|
||||||
|
@ -1,136 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
|
||||||
|
|
||||||
|
|
||||||
__license__ = 'GPL 3'
|
|
||||||
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
|
||||||
__docformat__ = 'restructuredtext en'
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from PyQt5.Qt import QNetworkCookieJar, QNetworkProxy
|
|
||||||
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
|
||||||
|
|
||||||
from calibre import USER_AGENT, get_proxies
|
|
||||||
from calibre.ebooks import BOOK_EXTENSIONS
|
|
||||||
from calibre.gui2 import choose_save_file, NO_URL_FORMATTING
|
|
||||||
from calibre.gui2.ebook_download import show_download_info
|
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
|
||||||
from calibre.utils.filenames import ascii_filename
|
|
||||||
from calibre.web import get_download_filename
|
|
||||||
from polyglot.builtins import unicode_type
|
|
||||||
from polyglot.urllib import urlparse
|
|
||||||
|
|
||||||
|
|
||||||
class NPWebView(QWebView):
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
QWebView.__init__(self, *args)
|
|
||||||
self.gui = None
|
|
||||||
self.tags = ''
|
|
||||||
self.create_browser = None
|
|
||||||
|
|
||||||
self._page = NPWebPage()
|
|
||||||
self.setPage(self._page)
|
|
||||||
self.cookie_jar = QNetworkCookieJar()
|
|
||||||
self.page().networkAccessManager().setCookieJar(self.cookie_jar)
|
|
||||||
|
|
||||||
http_proxy = get_proxies().get('http', None)
|
|
||||||
if http_proxy:
|
|
||||||
proxy_parts = urlparse(http_proxy)
|
|
||||||
proxy = QNetworkProxy()
|
|
||||||
proxy.setType(QNetworkProxy.HttpProxy)
|
|
||||||
if proxy_parts.username:
|
|
||||||
proxy.setUser(proxy_parts.username)
|
|
||||||
if proxy_parts.password:
|
|
||||||
proxy.setPassword(proxy_parts.password)
|
|
||||||
if proxy_parts.hostname:
|
|
||||||
proxy.setHostName(proxy_parts.hostname)
|
|
||||||
if proxy_parts.port:
|
|
||||||
proxy.setPort(proxy_parts.port)
|
|
||||||
self.page().networkAccessManager().setProxy(proxy)
|
|
||||||
|
|
||||||
self.page().setForwardUnsupportedContent(True)
|
|
||||||
self.page().unsupportedContent.connect(self.start_download)
|
|
||||||
self.page().downloadRequested.connect(self.start_download)
|
|
||||||
self.page().networkAccessManager().sslErrors.connect(self.ignore_ssl_errors)
|
|
||||||
|
|
||||||
def createWindow(self, type):
|
|
||||||
if type == QWebPage.WebBrowserWindow:
|
|
||||||
return self
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def set_gui(self, gui):
|
|
||||||
self.gui = gui
|
|
||||||
|
|
||||||
def set_tags(self, tags):
|
|
||||||
self.tags = tags
|
|
||||||
|
|
||||||
def start_download(self, request):
|
|
||||||
if not self.gui:
|
|
||||||
return
|
|
||||||
|
|
||||||
url = unicode_type(request.url().toString(NO_URL_FORMATTING))
|
|
||||||
cf = self.get_cookies()
|
|
||||||
|
|
||||||
filename = get_download_filename(url, cf)
|
|
||||||
ext = os.path.splitext(filename)[1][1:].lower()
|
|
||||||
filename = ascii_filename(filename[:60] + '.' + ext)
|
|
||||||
if ext not in BOOK_EXTENSIONS:
|
|
||||||
if ext == 'acsm':
|
|
||||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
|
||||||
if not confirm('<p>' + _('This e-book is a DRMed EPUB file. '
|
|
||||||
'You will be prompted to save this file to your '
|
|
||||||
'computer. Once it is saved, open it with '
|
|
||||||
'<a href="https://www.adobe.com/solutions/ebook/digital-editions.html">'
|
|
||||||
'Adobe Digital Editions</a> (ADE).<p>ADE, in turn '
|
|
||||||
'will download the actual e-book, which will be a '
|
|
||||||
'.epub file. You can add this book to calibre '
|
|
||||||
'using "Add Books" and selecting the file from '
|
|
||||||
'the ADE library folder.'),
|
|
||||||
'acsm_download', self):
|
|
||||||
return
|
|
||||||
name = choose_save_file(self, 'web-store-download-unknown', _('File is not a supported e-book type. Save to disk?'), initial_filename=filename)
|
|
||||||
if name:
|
|
||||||
self.gui.download_ebook(url, cf, name, name, False, create_browser=self.create_browser)
|
|
||||||
else:
|
|
||||||
show_download_info(filename, self)
|
|
||||||
self.gui.download_ebook(url, cf, filename, tags=self.tags, create_browser=self.create_browser)
|
|
||||||
|
|
||||||
def ignore_ssl_errors(self, reply, errors):
|
|
||||||
reply.ignoreSslErrors(errors)
|
|
||||||
|
|
||||||
def get_cookies(self):
|
|
||||||
'''
|
|
||||||
Writes QNetworkCookies to Mozilla cookie .txt file.
|
|
||||||
|
|
||||||
:return: The file path to the cookie file.
|
|
||||||
'''
|
|
||||||
cf = PersistentTemporaryFile(suffix='.txt')
|
|
||||||
|
|
||||||
cf.write('# Netscape HTTP Cookie File\n\n')
|
|
||||||
|
|
||||||
for c in self.page().networkAccessManager().cookieJar().allCookies():
|
|
||||||
cookie = []
|
|
||||||
domain = unicode_type(c.domain())
|
|
||||||
|
|
||||||
cookie.append(domain)
|
|
||||||
cookie.append('TRUE' if domain.startswith('.') else 'FALSE')
|
|
||||||
cookie.append(unicode_type(c.path()))
|
|
||||||
cookie.append('TRUE' if c.isSecure() else 'FALSE')
|
|
||||||
cookie.append(unicode_type(c.expirationDate().toTime_t()))
|
|
||||||
cookie.append(unicode_type(c.name()))
|
|
||||||
cookie.append(unicode_type(c.value()))
|
|
||||||
|
|
||||||
cf.write('\t'.join(cookie))
|
|
||||||
cf.write('\n')
|
|
||||||
|
|
||||||
cf.close()
|
|
||||||
return cf.name
|
|
||||||
|
|
||||||
|
|
||||||
class NPWebPage(QWebPage):
|
|
||||||
|
|
||||||
def userAgentForUrl(self, url):
|
|
||||||
return USER_AGENT
|
|
112
src/calibre/gui2/store/web_store.py
Normal file
112
src/calibre/gui2/store/web_store.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPL v3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from base64 import standard_b64decode, standard_b64encode
|
||||||
|
|
||||||
|
from PyQt5.Qt import (
|
||||||
|
QHBoxLayout, QProgressBar, QPushButton, QVBoxLayout, QWidget, pyqtSignal
|
||||||
|
)
|
||||||
|
from PyQt5.QtWebEngineWidgets import QWebEngineView
|
||||||
|
|
||||||
|
from calibre import url_slash_cleaner
|
||||||
|
from calibre.constants import STORE_DIALOG_APP_UID, islinux, iswindows
|
||||||
|
from calibre.gui2 import Application, set_app_uid
|
||||||
|
from calibre.gui2.main_window import MainWindow
|
||||||
|
from calibre.ptempfile import reset_base_dir
|
||||||
|
|
||||||
|
|
||||||
|
class View(QWebEngineView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Central(QWidget):
|
||||||
|
|
||||||
|
home = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
QWidget.__init__(self, parent)
|
||||||
|
self.l = l = QVBoxLayout(self)
|
||||||
|
self.view = v = View(self)
|
||||||
|
v.loadStarted.connect(self.load_started)
|
||||||
|
v.loadProgress.connect(self.load_progress)
|
||||||
|
v.loadFinished.connect(self.load_finished)
|
||||||
|
l.addWidget(v)
|
||||||
|
self.h = h = QHBoxLayout()
|
||||||
|
l.addLayout(h)
|
||||||
|
self.home_button = b = QPushButton(_('Home'))
|
||||||
|
b.clicked.connect(self.home)
|
||||||
|
h.addWidget(b)
|
||||||
|
self.back_button = b = QPushButton(_('Back'))
|
||||||
|
b.clicked.connect(v.back)
|
||||||
|
h.addWidget(b)
|
||||||
|
self.forward_button = b = QPushButton(_('Forward'))
|
||||||
|
b.clicked.connect(v.forward)
|
||||||
|
h.addWidget(b)
|
||||||
|
|
||||||
|
self.progress_bar = b = QProgressBar(self)
|
||||||
|
h.addWidget(b)
|
||||||
|
|
||||||
|
def load_started(self):
|
||||||
|
self.progress_bar.setValue(0)
|
||||||
|
|
||||||
|
def load_progress(self, amt):
|
||||||
|
self.progress_bar.setValue(amt)
|
||||||
|
|
||||||
|
def load_finished(self, ok):
|
||||||
|
self.progress_bar.setValue(100)
|
||||||
|
|
||||||
|
|
||||||
|
class Main(MainWindow):
|
||||||
|
|
||||||
|
def __init__(self, data):
|
||||||
|
MainWindow.__init__(self, None)
|
||||||
|
self.data = data
|
||||||
|
self.central = c = Central(self)
|
||||||
|
c.home.connect(self.go_home)
|
||||||
|
self.setCentralWidget(c)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def view(self):
|
||||||
|
return self.central.view
|
||||||
|
|
||||||
|
def go_home(self):
|
||||||
|
self.go_to()
|
||||||
|
|
||||||
|
def go_to(self, url=None):
|
||||||
|
url = url or self.data['base_url']
|
||||||
|
url = url_slash_cleaner(url)
|
||||||
|
self.view.load(url)
|
||||||
|
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
# Ensure we can continue to function if GUI is closed
|
||||||
|
os.environ.pop('CALIBRE_WORKER_TEMP_DIR', None)
|
||||||
|
reset_base_dir()
|
||||||
|
if iswindows:
|
||||||
|
# Ensure that all instances are grouped together in the task bar. This
|
||||||
|
# prevents them from being grouped with viewer/editor process when
|
||||||
|
# launched from within calibre, as both use calibre-parallel.exe
|
||||||
|
set_app_uid(STORE_DIALOG_APP_UID)
|
||||||
|
|
||||||
|
data = args[-1]
|
||||||
|
data = json.loads(standard_b64decode(data))
|
||||||
|
override = 'calibre-ebook-viewer' if islinux else None
|
||||||
|
app = Application(args, override_program_name=override)
|
||||||
|
app.exec_()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sample_data = standard_b64encode(
|
||||||
|
json.dumps({
|
||||||
|
u'window_title': u'MobileRead',
|
||||||
|
u'base_url': u'https://www.mobileread.com/',
|
||||||
|
u'detail_url': u'http://www.mobileread.com/forums/showthread.php?t=54477',
|
||||||
|
u'tags': u''
|
||||||
|
})
|
||||||
|
)
|
||||||
|
main([sample_data])
|
@ -1,57 +1,36 @@
|
|||||||
# -*- coding: utf-8 -*-
|
#!/usr/bin/env python2
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPLv3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
__license__ = 'GPL 3'
|
from base64 import standard_b64encode
|
||||||
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
|
|
||||||
__docformat__ = 'restructuredtext en'
|
|
||||||
|
|
||||||
from PyQt5.Qt import QDialog, QUrl
|
|
||||||
|
|
||||||
from calibre import url_slash_cleaner
|
|
||||||
from calibre.gui2.store.web_store_dialog_ui import Ui_Dialog
|
|
||||||
|
|
||||||
|
|
||||||
class WebStoreDialog(QDialog, Ui_Dialog):
|
class WebStoreDialog(object):
|
||||||
|
|
||||||
def __init__(self, gui, base_url, parent=None, detail_url=None, create_browser=None):
|
def __init__(self, gui, base_url, parent=None, detail_url=None, create_browser=None):
|
||||||
QDialog.__init__(self, parent=parent)
|
|
||||||
self.setupUi(self)
|
|
||||||
|
|
||||||
self.gui = gui
|
self.gui = gui
|
||||||
self.base_url = base_url
|
self.base_url = base_url
|
||||||
|
self.detail_url = detail_url
|
||||||
|
self.create_browser = create_browser
|
||||||
|
self.window_title = None
|
||||||
|
self.tags = None
|
||||||
|
|
||||||
self.view.set_gui(self.gui)
|
def setWindowTitle(self, title):
|
||||||
self.view.create_browser = create_browser
|
self.window_title = title
|
||||||
self.view.loadStarted.connect(self.load_started)
|
|
||||||
self.view.loadProgress.connect(self.load_progress)
|
|
||||||
self.view.loadFinished.connect(self.load_finished)
|
|
||||||
self.home.clicked.connect(self.go_home)
|
|
||||||
self.reload.clicked.connect(self.view.reload)
|
|
||||||
self.back.clicked.connect(self.view.back)
|
|
||||||
|
|
||||||
self.go_home(detail_url=detail_url)
|
|
||||||
|
|
||||||
def set_tags(self, tags):
|
def set_tags(self, tags):
|
||||||
self.view.set_tags(tags)
|
self.tags = tags
|
||||||
|
|
||||||
def load_started(self):
|
def exec_(self):
|
||||||
self.progress.setValue(0)
|
data = {'base_url': self.base_url, 'detail_url': self.detail_url, 'window_title': self.window_title, 'tags': self.tags}
|
||||||
|
data = json.dumps(data)
|
||||||
def load_progress(self, val):
|
if not isinstance(data, bytes):
|
||||||
self.progress.setValue(val)
|
data = data.encode('utf-8')
|
||||||
|
data = standard_b64encode(data)
|
||||||
def load_finished(self, ok=True):
|
if isinstance(data, bytes):
|
||||||
self.progress.setValue(100)
|
data = data.decode('ascii')
|
||||||
|
args = ['store-dialog', data]
|
||||||
def go_home(self, checked=False, detail_url=None):
|
self.gui.job_manager.launch_gui_app(args[0], kwargs={'args':args})
|
||||||
if detail_url:
|
|
||||||
url = detail_url
|
|
||||||
else:
|
|
||||||
url = self.base_url
|
|
||||||
|
|
||||||
# Reduce redundant /'s because some stores
|
|
||||||
# (Feedbooks) and server frameworks (cherrypy)
|
|
||||||
# choke on them.
|
|
||||||
url = url_slash_cleaner(url)
|
|
||||||
self.view.load(QUrl(url))
|
|
||||||
|
@ -1,110 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>Dialog</class>
|
|
||||||
<widget class="QDialog" name="Dialog">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>962</width>
|
|
||||||
<height>656</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="sizeGripEnabled">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
|
||||||
<item row="0" column="0" colspan="5">
|
|
||||||
<widget class="QFrame" name="frame">
|
|
||||||
<property name="frameShape">
|
|
||||||
<enum>QFrame::StyledPanel</enum>
|
|
||||||
</property>
|
|
||||||
<property name="frameShadow">
|
|
||||||
<enum>QFrame::Raised</enum>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<property name="margin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<widget class="NPWebView" name="view">
|
|
||||||
<property name="url">
|
|
||||||
<url>
|
|
||||||
<string>about:blank</string>
|
|
||||||
</url>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QPushButton" name="home">
|
|
||||||
<property name="text">
|
|
||||||
<string>Home</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QPushButton" name="reload">
|
|
||||||
<property name="text">
|
|
||||||
<string>Reload</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="3">
|
|
||||||
<widget class="QProgressBar" name="progress">
|
|
||||||
<property name="value">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="format">
|
|
||||||
<string>%p%</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="2">
|
|
||||||
<widget class="QPushButton" name="back">
|
|
||||||
<property name="text">
|
|
||||||
<string>Back</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="4">
|
|
||||||
<widget class="QPushButton" name="close">
|
|
||||||
<property name="text">
|
|
||||||
<string>&Close</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<customwidgets>
|
|
||||||
<customwidget>
|
|
||||||
<class>NPWebView</class>
|
|
||||||
<extends>QWidget</extends>
|
|
||||||
<header>calibre/gui2/store/web_control.h</header>
|
|
||||||
</customwidget>
|
|
||||||
</customwidgets>
|
|
||||||
<resources/>
|
|
||||||
<connections>
|
|
||||||
<connection>
|
|
||||||
<sender>close</sender>
|
|
||||||
<signal>clicked()</signal>
|
|
||||||
<receiver>Dialog</receiver>
|
|
||||||
<slot>accept()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>917</x>
|
|
||||||
<y>635</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>480</x>
|
|
||||||
<y>327</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
|
@ -81,6 +81,13 @@ def ebook_viewer(args=sys.argv):
|
|||||||
main(args)
|
main(args)
|
||||||
|
|
||||||
|
|
||||||
|
def store_dialog(args=sys.argv):
|
||||||
|
detach_gui()
|
||||||
|
init_dbus()
|
||||||
|
from calibre.gui2.store.web_store import main
|
||||||
|
main(args)
|
||||||
|
|
||||||
|
|
||||||
def gui_ebook_edit(path=None, notify=None):
|
def gui_ebook_edit(path=None, notify=None):
|
||||||
' For launching the editor from inside calibre '
|
' For launching the editor from inside calibre '
|
||||||
init_dbus()
|
init_dbus()
|
||||||
|
@ -29,6 +29,9 @@ PARALLEL_FUNCS = {
|
|||||||
'ebook-edit' :
|
'ebook-edit' :
|
||||||
('calibre.gui_launch', 'gui_ebook_edit', None),
|
('calibre.gui_launch', 'gui_ebook_edit', None),
|
||||||
|
|
||||||
|
'store-dialog' :
|
||||||
|
('calibre.gui_launch', 'store_dialog', None),
|
||||||
|
|
||||||
'render_pages' :
|
'render_pages' :
|
||||||
('calibre.ebooks.comic.input', 'render_pages', 'notification'),
|
('calibre.ebooks.comic.input', 'render_pages', 'notification'),
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user