mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44: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'
|
||||
EDITOR_APP_UID = 'com.calibre-ebook.edit-book'
|
||||
MAIN_APP_UID = 'com.calibre-ebook.main-gui'
|
||||
STORE_DIALOG_APP_UID = 'com.calibre-ebook.store-dialog'
|
||||
try:
|
||||
preferred_encoding = locale.getpreferredencoding()
|
||||
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
|
||||
|
||||
|
||||
__license__ = 'GPL 3'
|
||||
__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
|
||||
import json
|
||||
from base64 import standard_b64encode
|
||||
|
||||
|
||||
class WebStoreDialog(QDialog, Ui_Dialog):
|
||||
class WebStoreDialog(object):
|
||||
|
||||
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.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)
|
||||
self.view.create_browser = create_browser
|
||||
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 setWindowTitle(self, title):
|
||||
self.window_title = title
|
||||
|
||||
def set_tags(self, tags):
|
||||
self.view.set_tags(tags)
|
||||
self.tags = tags
|
||||
|
||||
def load_started(self):
|
||||
self.progress.setValue(0)
|
||||
|
||||
def load_progress(self, val):
|
||||
self.progress.setValue(val)
|
||||
|
||||
def load_finished(self, ok=True):
|
||||
self.progress.setValue(100)
|
||||
|
||||
def go_home(self, checked=False, detail_url=None):
|
||||
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))
|
||||
def exec_(self):
|
||||
data = {'base_url': self.base_url, 'detail_url': self.detail_url, 'window_title': self.window_title, 'tags': self.tags}
|
||||
data = json.dumps(data)
|
||||
if not isinstance(data, bytes):
|
||||
data = data.encode('utf-8')
|
||||
data = standard_b64encode(data)
|
||||
if isinstance(data, bytes):
|
||||
data = data.decode('ascii')
|
||||
args = ['store-dialog', data]
|
||||
self.gui.job_manager.launch_gui_app(args[0], kwargs={'args':args})
|
||||
|
@ -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)
|
||||
|
||||
|
||||
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):
|
||||
' For launching the editor from inside calibre '
|
||||
init_dbus()
|
||||
|
@ -29,6 +29,9 @@ PARALLEL_FUNCS = {
|
||||
'ebook-edit' :
|
||||
('calibre.gui_launch', 'gui_ebook_edit', None),
|
||||
|
||||
'store-dialog' :
|
||||
('calibre.gui_launch', 'store_dialog', None),
|
||||
|
||||
'render_pages' :
|
||||
('calibre.ebooks.comic.input', 'render_pages', 'notification'),
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user