Merge 2274dd2d39 into 7e3df43463
This commit is contained in:
commit
2cc1b14602
|
|
@ -7,6 +7,7 @@
|
|||
import re
|
||||
import sys
|
||||
import html
|
||||
import io as _io
|
||||
import os.path
|
||||
import collections
|
||||
import functools
|
||||
|
|
@ -813,7 +814,8 @@ class AbstractDownloadItem(QObject):
|
|||
if filename is None: # pragma: no cover
|
||||
log.downloads.error("No filename to open the download!")
|
||||
return
|
||||
self.pdfjs_requested.emit(os.path.basename(filename),
|
||||
dirname = os.path.basename(os.path.dirname(filename))
|
||||
self.pdfjs_requested.emit(os.path.join(dirname, os.path.basename(filename)),
|
||||
self.url())
|
||||
|
||||
def cancel_for_origin(self) -> bool:
|
||||
|
|
@ -1373,8 +1375,8 @@ class TempDownloadManager:
|
|||
# Make sure that the filename is not too long
|
||||
suggested_name = utils.elide_filename(suggested_name, 50)
|
||||
# pylint: disable=consider-using-with
|
||||
fobj = tempfile.NamedTemporaryFile(dir=tmpdir.name, delete=False,
|
||||
suffix='_' + suggested_name)
|
||||
tmpfiledir = tempfile.mkdtemp(dir=tmpdir.name)
|
||||
fobj = _io.open(os.path.join(tmpfiledir, suggested_name), 'w+b')
|
||||
self.files.append(fobj)
|
||||
return fobj
|
||||
|
||||
|
|
|
|||
|
|
@ -517,8 +517,6 @@ def qute_pdfjs(url: QUrl) -> _HandlerRet:
|
|||
filename = QUrlQuery(url).queryItemValue('filename')
|
||||
if not filename:
|
||||
raise UrlInvalidError("Missing filename")
|
||||
if '/' in filename or os.sep in filename:
|
||||
raise RequestDeniedError("Path separator in filename.")
|
||||
|
||||
path = _pdf_path(filename)
|
||||
with open(path, 'rb') as f:
|
||||
|
|
|
|||
|
|
@ -130,6 +130,23 @@ session.lazy_restore:
|
|||
default: false
|
||||
desc: Load a restored tab as soon as it takes focus.
|
||||
|
||||
session.save_when_close:
|
||||
type: Bool
|
||||
default: True
|
||||
desc: >-
|
||||
Whether to automatically save a session when it is closed.
|
||||
|
||||
session.startup_sessions:
|
||||
type:
|
||||
name: List
|
||||
valtype: String
|
||||
none_ok: true
|
||||
default: null
|
||||
#TODO The default value doesn't seem to actually be set to None, so this doesn't work
|
||||
desc: >-
|
||||
Which sessions to load on startup. None will load the last saved sessions
|
||||
from the sate file, if you want to not load any sessions, pass an empty list.
|
||||
|
||||
backend:
|
||||
type:
|
||||
name: String
|
||||
|
|
@ -2665,6 +2682,7 @@ window.title_format:
|
|||
- current_title
|
||||
- title_sep
|
||||
- id
|
||||
- session
|
||||
- scroll_pos
|
||||
- host
|
||||
- backend
|
||||
|
|
@ -2672,7 +2690,8 @@ window.title_format:
|
|||
- current_url
|
||||
- protocol
|
||||
- audio
|
||||
default: '{perc}{current_title}{title_sep}qutebrowser'
|
||||
#TODO default should not display session, especially if no session is set
|
||||
default: '[{session}] {perc}{current_title}{title_sep}qutebrowser'
|
||||
desc: |
|
||||
Format to use for the window title. The same placeholders like for
|
||||
`tabs.title.format` are defined.
|
||||
|
|
|
|||
|
|
@ -241,6 +241,7 @@ class TabbedBrowser(QWidget):
|
|||
self._global_marks: MutableMapping[str, tuple[QPoint, QUrl]] = {}
|
||||
self.default_window_icon = self._window().windowIcon()
|
||||
self.is_private = private
|
||||
self.session = "_nosession"
|
||||
self.tab_deque = TabDeque()
|
||||
config.instance.changed.connect(self._on_config_changed)
|
||||
quitter.instance.shutting_down.connect(self.shutdown)
|
||||
|
|
@ -317,6 +318,9 @@ class TabbedBrowser(QWidget):
|
|||
return
|
||||
fields = self.widget.get_tab_fields(idx)
|
||||
fields['id'] = self._win_id
|
||||
#TODO update on window session changed
|
||||
#TODO don't leak _nosession into window title
|
||||
fields['session'] = self.session
|
||||
|
||||
title = title_format.format(**fields)
|
||||
# prevent hanging WMs and similar issues with giant URLs
|
||||
|
|
|
|||
|
|
@ -256,9 +256,35 @@ class SessionManager(QObject):
|
|||
data['history'].append(item_data)
|
||||
return data
|
||||
|
||||
def _save_window(self, *, win_id=None, with_history=True):
|
||||
"""Get a dict with data for single window and its tabs."""
|
||||
main_window = objreg.get('main-window', scope='window', window=win_id)
|
||||
|
||||
# We could be in the middle of destroying a window here
|
||||
if sip.isdeleted(main_window):
|
||||
return None
|
||||
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id)
|
||||
|
||||
win_data: _JsonType = {}
|
||||
win_data['session'] = tabbed_browser.session
|
||||
active_window = objects.qapp.activeWindow()
|
||||
if getattr(active_window, 'win_id', None) == win_id:
|
||||
win_data['active'] = True
|
||||
win_data['geometry'] = bytes(main_window.saveGeometry())
|
||||
win_data['tabs'] = []
|
||||
if tabbed_browser.is_private:
|
||||
win_data['private'] = True
|
||||
for i, tab in enumerate(tabbed_browser.widgets()):
|
||||
active = i == tabbed_browser.widget.currentIndex()
|
||||
win_data['tabs'].append(self._save_tab(tab, active,
|
||||
with_history=with_history))
|
||||
|
||||
return win_data
|
||||
|
||||
def _save_all(self, *, only_window=None, with_private=False, with_history=True):
|
||||
"""Get a dict with data for all windows/tabs."""
|
||||
data: _JsonType = {'windows': []}
|
||||
session_data: _JsonType = {}
|
||||
if only_window is not None:
|
||||
winlist: Iterable[int] = [only_window]
|
||||
else:
|
||||
|
|
@ -267,30 +293,18 @@ class SessionManager(QObject):
|
|||
for win_id in sorted(winlist):
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
main_window = objreg.get('main-window', scope='window',
|
||||
window=win_id)
|
||||
|
||||
# We could be in the middle of destroying a window here
|
||||
if sip.isdeleted(main_window):
|
||||
continue
|
||||
|
||||
if tabbed_browser.is_private and not with_private:
|
||||
continue
|
||||
|
||||
win_data: _JsonType = {}
|
||||
active_window = objects.qapp.activeWindow()
|
||||
if getattr(active_window, 'win_id', None) == win_id:
|
||||
win_data['active'] = True
|
||||
win_data['geometry'] = bytes(main_window.saveGeometry())
|
||||
win_data['tabs'] = []
|
||||
if tabbed_browser.is_private:
|
||||
win_data['private'] = True
|
||||
for i, tab in enumerate(tabbed_browser.widgets()):
|
||||
active = i == tabbed_browser.widget.currentIndex()
|
||||
win_data['tabs'].append(self._save_tab(tab, active,
|
||||
with_history=with_history))
|
||||
data['windows'].append(win_data)
|
||||
return data
|
||||
if tabbed_browser.session not in session_data:
|
||||
session_data[tabbed_browser.session] = {'windows': []} # type: _JsonType
|
||||
|
||||
win_data = self._save_window(win_id=win_id, with_history=with_history)
|
||||
if win_data is not None:
|
||||
session_data[tabbed_browser.session]['windows'].append(win_data)
|
||||
|
||||
return session_data
|
||||
|
||||
def _get_session_name(self, name):
|
||||
"""Helper for save to get the name to save the session to.
|
||||
|
|
@ -308,6 +322,13 @@ class SessionManager(QObject):
|
|||
name = 'default'
|
||||
return name
|
||||
|
||||
def _dump_session(self, path, data):
|
||||
try:
|
||||
with qtutils.savefile_open(path) as f:
|
||||
utils.yaml_dump(data, f)
|
||||
except (OSError, UnicodeEncodeError, yaml.YAMLError) as e:
|
||||
raise SessionError(e)
|
||||
|
||||
def save(self, name, last_window=False, load_next_time=False,
|
||||
only_window=None, with_private=False, with_history=True):
|
||||
"""Save a named session.
|
||||
|
|
@ -325,35 +346,54 @@ class SessionManager(QObject):
|
|||
Return:
|
||||
The name of the saved session.
|
||||
"""
|
||||
name = self._get_session_name(name)
|
||||
path = self._get_session_path(name)
|
||||
|
||||
log.sessions.debug("Saving session {} to {}...".format(name, path))
|
||||
if last_window:
|
||||
data = self._last_window_session
|
||||
if data is None:
|
||||
session_data = self._last_window_session
|
||||
if session_data is None:
|
||||
log.sessions.error("last_window_session is None while saving!")
|
||||
return None
|
||||
else:
|
||||
data = self._save_all(only_window=only_window,
|
||||
with_private=with_private,
|
||||
with_history=with_history)
|
||||
session_data = self._save_all(only_window=only_window,
|
||||
with_private=with_private,
|
||||
with_history=with_history)
|
||||
|
||||
log.sessions.vdebug( # type: ignore[attr-defined]
|
||||
"Saving data: {}".format(data))
|
||||
try:
|
||||
with qtutils.savefile_open(path) as f:
|
||||
utils.yaml_dump(data, f)
|
||||
except (OSError, UnicodeEncodeError, yaml.YAMLError) as e:
|
||||
raise SessionError(e)
|
||||
"Saving data: {}".format(session_data))
|
||||
if name == default:
|
||||
# save all active sessions by default
|
||||
names = list(session_data.keys())
|
||||
# don't save sessionless windows (TODO should be configurable)
|
||||
if '_nosession' in names:
|
||||
names.remove('_nosession')
|
||||
elif os.path.isabs(name):
|
||||
_save_all_to(name)
|
||||
if load_next_time:
|
||||
configfiles.state['general']['session'] = name
|
||||
return [name]
|
||||
else:
|
||||
# could possibly support `:session_save session1 session2`?
|
||||
names = [name]
|
||||
|
||||
# current session is not used atm (could be session of current active window)
|
||||
for s in names:
|
||||
path = self._get_session_path(s)
|
||||
self._dump_session(path, session_data.get(s, { 'windows' : [] }))
|
||||
|
||||
if load_next_time:
|
||||
configfiles.state['general']['session'] = name
|
||||
return name
|
||||
configfiles.state['general']['session'] = ','.join(names)
|
||||
return names
|
||||
|
||||
def _save_all_to(self, name):
|
||||
session_data = self._save_all()
|
||||
data = {'windows': []} # type: _JsonType
|
||||
for d in session_data.values():
|
||||
data['windows'].extend(d['windows'])
|
||||
path = self._get_session_path(name)
|
||||
self._dump_session(path, data)
|
||||
|
||||
def _save_autosave(self):
|
||||
"""Save the autosave session."""
|
||||
try:
|
||||
self.save('_autosave')
|
||||
self._save_all_to('_autosave')
|
||||
except SessionError as e:
|
||||
log.sessions.error("Failed to save autosave session: {}".format(e))
|
||||
|
||||
|
|
@ -372,6 +412,7 @@ class SessionManager(QObject):
|
|||
|
||||
def save_last_window_session(self):
|
||||
"""Temporarily save the session for the last closed window."""
|
||||
#TODO needs to be saved to correct session file and not break if current is None
|
||||
self._last_window_session = self._save_all()
|
||||
|
||||
def _load_tab(self, new_tab, data): # noqa: C901
|
||||
|
|
@ -462,6 +503,7 @@ class SessionManager(QObject):
|
|||
private=win.get('private', None))
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=window.win_id)
|
||||
tabbed_browser.session = win.get('session')
|
||||
tab_to_focus = None
|
||||
for i, tab in enumerate(win['tabs']):
|
||||
new_tab = tabbed_browser.tabopen(background=False)
|
||||
|
|
@ -566,6 +608,77 @@ def session_load(name: str, *,
|
|||
log.sessions.debug("Loaded & deleted session {}.".format(name))
|
||||
|
||||
|
||||
@cmdutils.register()
|
||||
@cmdutils.argument('name', completion=miscmodels.session)
|
||||
@cmdutils.argument('win_id', value=cmdutils.Value.win_id)
|
||||
def session_window_set(name: str, *,
|
||||
win_id: int = None) -> None:
|
||||
"""Set session for current window
|
||||
|
||||
Args:
|
||||
name: The name of the session.
|
||||
"""
|
||||
if not isinstance(name, Sentinel) and name.startswith('_') and not force:
|
||||
raise cmdutils.CommandError("{} is an internal session, a window "
|
||||
"cannot be assigned to it".format(name))
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
tabbed_browser.session = name
|
||||
#FIXME Probably not meant to call this from here
|
||||
tabbed_browser._update_window_title("session")
|
||||
|
||||
@cmdutils.register()
|
||||
@cmdutils.argument('win_id', value=cmdutils.Value.win_id)
|
||||
def session_window_unset(*, win_id: int = None) -> None:
|
||||
"""Unset session for current window """
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
tabbed_browser.session = "_nosession"
|
||||
#FIXME Probably not meant to call this from here
|
||||
tabbed_browser._update_window_title("session")
|
||||
|
||||
|
||||
@cmdutils.register()
|
||||
@cmdutils.argument('name', completion=miscmodels.session)
|
||||
@cmdutils.argument('win_id', value=cmdutils.Value.win_id)
|
||||
def session_close(name: str = None, *,
|
||||
save: bool = False,
|
||||
nosave: bool = False,
|
||||
force: bool = False,
|
||||
win_id: int = None) -> None:
|
||||
"""Close a session.
|
||||
|
||||
Args:
|
||||
name: The name of the session. If not given the session of the
|
||||
current window is closed.
|
||||
save: Save the session before closing it
|
||||
nosave: Don't save the session, overrides config.session.save_when_close
|
||||
"""
|
||||
if save and nosave:
|
||||
raise cmdutils.CommandError("Both --save and --nosave have been given.")
|
||||
|
||||
if name is not None and name.startswith('_') and not force:
|
||||
raise cmdutils.CommandError("{} is an internal session, use --force "
|
||||
"to close anyways.".format(name))
|
||||
elif name is None:
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id)
|
||||
name = tabbed_browser.session
|
||||
|
||||
log.sessions.vdebug("Closing session: {}".format(name))
|
||||
|
||||
if not nosave and (save or config.val.session.save_when_close):
|
||||
#TODO also needs to handle private windows if required
|
||||
session_manager.save(name)
|
||||
|
||||
windows = list(objreg.window_registry.values())
|
||||
|
||||
for w in windows:
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=w.win_id)
|
||||
if tabbed_browser.session == name:
|
||||
w.close()
|
||||
|
||||
|
||||
@cmdutils.register()
|
||||
@cmdutils.argument('name', completion=miscmodels.session)
|
||||
@cmdutils.argument('win_id', value=cmdutils.Value.win_id)
|
||||
|
|
@ -601,18 +714,19 @@ def session_save(name: ArgType = default, *,
|
|||
assert not name.startswith('_')
|
||||
try:
|
||||
if only_active_window:
|
||||
name = session_manager.save(name, only_window=win_id,
|
||||
with_private=True,
|
||||
with_history=not no_history)
|
||||
names = session_manager.save(name, only_window=win_id,
|
||||
with_private=True,
|
||||
with_history=not no_history)
|
||||
else:
|
||||
name = session_manager.save(name, with_private=with_private,
|
||||
names = session_manager.save(name, with_private=with_private,
|
||||
with_history=not no_history)
|
||||
except SessionError as e:
|
||||
raise cmdutils.CommandError("Error while saving session: {}".format(e))
|
||||
if quiet:
|
||||
log.sessions.debug("Saved session {}.".format(name))
|
||||
log.sessions.debug("Saved sessions {}.".format(','.join(names)))
|
||||
else:
|
||||
message.info("Saved session {}.".format(name))
|
||||
#TODO handle Sentinel
|
||||
message.info("Saved session {}.".format(','.join(names)))
|
||||
|
||||
|
||||
@cmdutils.register()
|
||||
|
|
@ -644,26 +758,34 @@ def load_default(name):
|
|||
Args:
|
||||
name: The name of the session to load, or None to read state file.
|
||||
"""
|
||||
if name is None and session_manager.exists('_autosave'):
|
||||
name = '_autosave'
|
||||
elif name is None:
|
||||
if name is not None:
|
||||
names = name.split(',')
|
||||
elif session_manager.exists('_autosave'):
|
||||
names = ['_autosave']
|
||||
elif config.val.session.startup_sessions is not None:
|
||||
names = config.val.session.startup_sessions
|
||||
if not isinstance(names, list):
|
||||
names = [names]
|
||||
else:
|
||||
try:
|
||||
name = configfiles.state['general']['session']
|
||||
names = configfiles.state['general']['session'].split(',')
|
||||
except KeyError:
|
||||
# No session given as argument and none in the session file ->
|
||||
# start without loading a session
|
||||
return
|
||||
|
||||
try:
|
||||
session_manager.load(name)
|
||||
except SessionNotFoundError:
|
||||
message.error("Session {} not found!".format(name))
|
||||
except SessionError as e:
|
||||
message.error("Failed to load session {}: {}".format(name, e))
|
||||
for name in names:
|
||||
try:
|
||||
session_manager.load(name)
|
||||
except SessionNotFoundError:
|
||||
message.error("Session {} not found!".format(name))
|
||||
except SessionError as e:
|
||||
message.error("Failed to load session {}: {}".format(name, e))
|
||||
try:
|
||||
del configfiles.state['general']['session']
|
||||
except KeyError:
|
||||
pass
|
||||
# If this was a _restart session, delete it.
|
||||
#TODO don't forget to handle this special session too
|
||||
if name == '_restart':
|
||||
session_manager.delete('_restart')
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ class TestSaveAll:
|
|||
# FIXME can this ever actually happen?
|
||||
assert not objreg.window_registry
|
||||
data = sess_man._save_all()
|
||||
assert not data['windows']
|
||||
assert not data
|
||||
|
||||
@webengine_refactoring_xfail
|
||||
def test_no_active_window(self, sess_man, fake_window, stubs,
|
||||
|
|
|
|||
Loading…
Reference in New Issue