Merge remote-tracking branch 'origin/pr/6233'

This commit is contained in:
Florian Bruhin 2021-03-23 20:28:05 +01:00
commit 0267654cd7
6 changed files with 164 additions and 29 deletions

View File

@ -22,9 +22,10 @@
import os
import sys
import html
import enum
import netrc
from typing import Callable, Mapping, List, Optional, Iterable
import tempfile
from typing import Callable, Mapping, List, Optional, Iterable, Iterator
from PyQt5.QtCore import QUrl, pyqtBoundSignal
@ -392,20 +393,29 @@ def netrc_authentication(url, authenticator):
return True
def choose_file(multiple: bool) -> List[str]:
"""Select file(s) for uploading, using external command defined in config.
class FileSelectionMode(enum.Enum):
"""Mode to use for file selectors in choose_file."""
single_file = enum.auto()
multiple_files = enum.auto()
folder = enum.auto()
def choose_file(qb_mode: FileSelectionMode) -> List[str]:
"""Select file(s)/folder for uploading, using external command defined in config.
Args:
multiple: Should selecting multiple files be allowed.
qb_mode: File selection mode
Return:
A list of selected file paths, or empty list if no file is selected.
If multiple is False, the return value will have at most 1 item.
"""
if multiple:
command = config.val.fileselect.multiple_files.command
else:
command = config.val.fileselect.single_file.command
command = {
FileSelectionMode.single_file: config.val.fileselect.single_file.command,
FileSelectionMode.multiple_files: config.val.fileselect.multiple_files.command,
FileSelectionMode.folder: config.val.fileselect.folder.command,
}[qb_mode]
use_tmp_file = any('{}' in arg for arg in command[1:])
if use_tmp_file:
handle = tempfile.NamedTemporaryFile(
@ -421,19 +431,19 @@ def choose_file(multiple: bool) -> List[str]:
)
return _execute_fileselect_command(
command=command,
multiple=multiple,
qb_mode=qb_mode,
tmpfilename=tmpfilename,
)
else:
return _execute_fileselect_command(
command=command,
multiple=multiple,
qb_mode=qb_mode,
)
def _execute_fileselect_command(
command: List[str],
multiple: bool,
qb_mode: FileSelectionMode,
tmpfilename: Optional[str] = None
) -> List[str]:
"""Execute external command to choose file.
@ -463,8 +473,44 @@ def _execute_fileselect_command(
message.error(f"Failed to open tempfile {tmpfilename} ({e})!")
selected_files = []
if not multiple:
return list(_validated_selected_files(qb_mode=qb_mode, selected_files=selected_files))
def _validated_selected_files(
qb_mode: FileSelectionMode,
selected_files: List[str],
) -> Iterator[str]:
"""Validates selected files if they are.
* Of correct type
* Of correct number
* Existent
Args:
qb_mode: File selection mode used
selected_files: files selected
Return:
List of selected files that pass the checks.
"""
if qb_mode != FileSelectionMode.multiple_files:
if len(selected_files) > 1:
message.warning("More than one file chosen, using only the first")
return selected_files[:1]
return selected_files
message.warning("More than one file/folder chosen, using only the first")
selected_files = selected_files[:1]
for selected_file in selected_files:
if not os.path.exists(selected_file):
message.warning(f"Ignoring non-existent file '{selected_file}'")
continue
if qb_mode == FileSelectionMode.folder:
if not os.path.isdir(selected_file):
message.warning(
f"Expected folder but got file, ignoring '{selected_file}'"
)
continue
else:
if not os.path.isfile(selected_file):
message.warning(
f"Expected file but got folder, ignoring '{selected_file}'"
)
continue
yield selected_file

View File

@ -31,6 +31,20 @@ from qutebrowser.config import config
from qutebrowser.utils import log, debug, usertypes
_QB_FILESELECTION_MODES = {
QWebEnginePage.FileSelectOpen: shared.FileSelectionMode.single_file,
QWebEnginePage.FileSelectOpenMultiple: shared.FileSelectionMode.multiple_files,
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-91489
#
# QtWebEngine doesn't expose this value from its internal
# FilePickerControllerPrivate::FileChooserMode enum (i.e. it's not included in
# the public QWebEnginePage::FileSelectionMode enum).
# However, QWebEnginePage::chooseFiles is still called with the matching value
# (2) when a file input with "webkitdirectory" is used.
QWebEnginePage.FileSelectionMode(2): shared.FileSelectionMode.folder,
}
class WebEngineView(QWebEngineView):
"""Custom QWebEngineView subclass with qutebrowser-specific features."""
@ -250,7 +264,13 @@ class WebEnginePage(QWebEnginePage):
handler = config.val.fileselect.handler
if handler == "default":
return super().chooseFiles(mode, old_files, accepted_mimetypes)
assert handler == "external", handler
return shared.choose_file(
multiple=(mode == QWebEnginePage.FileSelectOpenMultiple))
try:
qb_mode = _QB_FILESELECTION_MODES[mode]
except KeyError:
log.webview.warning(
f"Got file selection mode {mode}, but we don't support that!"
)
return super().chooseFiles(mode, old_files, accepted_mimetypes)
return shared.choose_file(qb_mode=qb_mode)

View File

@ -199,7 +199,7 @@ class BrowserPage(QWebPage):
return super().chooseFile(parent_frame, suggested_file)
assert handler == "external", handler
selected = shared.choose_file(multiple=False)
selected = shared.choose_file(qb_mode=shared.FileSelectionMode.single_file)
if not selected:
return ''
else:
@ -229,7 +229,7 @@ class BrowserPage(QWebPage):
return True
assert handler == "external", handler
files.fileNames = shared.choose_file(multiple=True)
files.fileNames = shared.choose_file(shared.FileSelectionMode.multiple_files)
return True
def shutdown(self):

View File

@ -1338,6 +1338,24 @@ fileselect.multiple_files.command:
* `{}`: Filename of the file to be written to. If not contained in any argument, the
standard output of the command is read instead.
fileselect.folder.command:
type:
name: ShellCommand
placeholder: false
completions:
- ['["xterm", "-e", "ranger", "--choosedir={}"]', "Ranger in xterm"]
- ['["xterm", "-e", "vifm", "--choose-dir", "{}"]', "vifm in xterm"]
- ['["xterm", "-e", "nnn", "-p", "{}"]', "nnn in xterm"]
default: ['xterm', '-e', 'ranger', '--choosedir={}']
desc: >-
Command (and arguments) to use for selecting a single folder in forms.
The command should write the selected folder path to the specified file or stdout.
The following placeholders are defined:
* `{}`: Filename of the file to be written to. If not contained in any argument, the
standard output of the command is read instead.
## hints
hints.auto_follow:

View File

@ -5,7 +5,7 @@
<title>Testing fileselection</title>
<script type="text/javascript">
function onLoad() {
for (let id of ['#single_file', '#multiple_files']) {
for (let id of ['#single_file', '#multiple_files', '#folder']) {
const input = document.querySelector(id);
input.addEventListener('change', function (e) {
console.log(`Files: ${Array.from(input.files).map(f => f.name).join(", ")}`);
@ -15,7 +15,17 @@
</script>
</head>
<body onload="onLoad()">
<input type="file" id="single_file" name="single_file" accept=".txt">
<input type="file" id="multiple_files" name="multiple_files" accept=".txt" multiple>
<div>
<label for="single_file">Select single file</label>
<input type="file" id="single_file" name="single_file" accept=".txt">
</div>
<div>
<label for="multiple_files">Select multiple files</label>
<input type="file" id="multiple_files" value="Select multiple files" name="multiple_files" accept=".txt" multiple>
</div>
<div>
<label for="folder">Select single folder</label>
<input type="file" id="folder" value="Select single folder" name="folder" webkitdirectory>
</div>
</body>
</html>

View File

@ -193,36 +193,36 @@ Feature: Opening external editors
## select single file
Scenario: Select one file with single command
Scenario: Select one file with single file command
When I setup a fake single_file fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to a temporary file
And I open data/fileselect.html
And I run :click-element id single_file
Then the javascript message "Files: 1.txt" should be logged
Scenario: Select one file with single command that writes to stdout
Scenario: Select one file with single file command that writes to stdout
When I setup a fake single_file fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to stdout
And I open data/fileselect.html
And I run :click-element id single_file
Then the javascript message "Files: 1.txt" should be logged
Scenario: Select two files with single command
Scenario: Select two files with single file command
When I setup a fake single_file fileselector selecting "tests/end2end/data/numbers/1.txt tests/end2end/data/numbers/2.txt" and writes to a temporary file
And I open data/fileselect.html
And I run :click-element id single_file
Then the javascript message "Files: 1.txt" should be logged
And the warning "More than one file chosen, using only the first" should be shown
And the warning "More than one file/folder chosen, using only the first" should be shown
## select multiple files
Scenario: Select one file with multiple command
Scenario: Select one file with multiple files command
When I setup a fake multiple_files fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to a temporary file
And I open data/fileselect.html
And I run :click-element id multiple_files
Then the javascript message "Files: 1.txt" should be logged
Scenario: Select two files with multiple command
Scenario: Select two files with multiple files command
When I setup a fake multiple_files fileselector selecting "tests/end2end/data/numbers/1.txt tests/end2end/data/numbers/2.txt" and writes to a temporary file
And I open data/fileselect.html
@ -239,3 +239,44 @@ Feature: Opening external editors
Then the javascript message "Files: 1.txt" should not be logged
And the error "Failed to open tempfile *" should be shown
And "Failed to delete tempfile *" should be logged with level error
## Select non-existent file
Scenario: Select non-existent file
When I set fileselect.handler to external
When I setup a fake single_file fileselector selecting "tests/end2end/data/numbers/non-existent.txt" and writes to a temporary file
And I open data/fileselect.html
And I run :click-element id single_file
Then the javascript message "Files: non-existent.txt" should not be logged
And the warning "Ignoring non-existent file *non-existent.txt'" should be shown
## Select folder when expecting file
Scenario: Select folder for file
When I set fileselect.handler to external
When I setup a fake single_file fileselector selecting "tests/end2end/data/numbers" and writes to a temporary file
And I open data/fileselect.html
And I run :click-element id single_file
Then the javascript message "Files: *" should not be logged
And the warning "Expected file but got folder, ignoring *numbers'" should be shown
## Select file when expecting folder
@qtwebkit_skip
Scenario: Select file for folder
When I set fileselect.handler to external
When I setup a fake folder fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to a temporary file
And I open data/fileselect.html
And I run :click-element id folder
Then the javascript message "Files: 1.txt" should not be logged
And the warning "Expected folder but got file, ignoring *1.txt'" should be shown
## Select folder
@qtwebkit_skip
Scenario: Select one folder with folder command
When I set fileselect.handler to external
And I setup a fake folder fileselector selecting "tests/end2end/data/backforward/" and writes to a temporary file
And I open data/fileselect.html
And I run :click-element id folder
Then the javascript message "Files: 1.txt, 2.txt, 3.txt" should be logged