Merge remote-tracking branch 'origin/pr/6233'
This commit is contained in:
commit
0267654cd7
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue