Skip to content

Commit

Permalink
Merge pull request #5528 from opengisch/huh
Browse files Browse the repository at this point in the history
Improve robustness of our handling of file path to source URLs
  • Loading branch information
nirvn authored Aug 7, 2024
2 parents be5843b + c78d14e commit 6e7c2a4
Show file tree
Hide file tree
Showing 22 changed files with 114 additions and 33 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
- name: 🔨 Prepare build env
run: |
sudo apt-get update
sudo apt-get install -y gperf autopoint '^libxcb.*-dev' libx11-xcb-dev libegl1-mesa libegl1-mesa-dev libgl1-mesa-dev libglu1-mesa-dev mesa-common-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev libxrandr-dev libxxf86vm-dev autoconf-archive libgstreamer-gl1.0-0 libgstreamer-plugins-base1.0-0 libfuse2 libpulse-dev libcups2-dev nasm
sudo apt-get install -y gperf autopoint '^libxcb.*-dev' libx11-xcb-dev libegl1-mesa libegl1-mesa-dev libgl1-mesa-dev libglu1-mesa-dev mesa-common-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev libxrandr-dev libxxf86vm-dev autoconf-archive libgstreamer-gl1.0-0 libgstreamer-plugins-base1.0-0 libfuse2 libpulse-dev libcups2-dev nasm python3-tk
# Required to run unit tests on linux
- name: Install linuxdeploy
Expand Down
3 changes: 2 additions & 1 deletion src/core/platforms/platformutilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "qgsmessagelog.h"
#include "resourcesource.h"
#include "stringutils.h"
#include "urlutils.h"

#include <QApplication>
#include <QClipboard>
Expand Down Expand Up @@ -324,7 +325,7 @@ ResourceSource *PlatformUtilities::getFile( const QString &prefix, const QString

ViewStatus *PlatformUtilities::open( const QString &uri, bool, QObject * )
{
QDesktopServices::openUrl( QStringLiteral( "file://%1" ).arg( uri ) );
QDesktopServices::openUrl( UrlUtils::fromString( uri ) );
return nullptr;
}

Expand Down
14 changes: 12 additions & 2 deletions src/core/utils/urlutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@

#include "urlutils.h"

#include <QFileInfo>
#include <QUrl>


UrlUtils::UrlUtils( QObject *parent )
: QObject( parent )
{
Expand All @@ -28,8 +28,18 @@ UrlUtils::UrlUtils( QObject *parent )

bool UrlUtils::isRelativeOrFileUrl( const QString &url )
{
if ( url.startsWith( QStringLiteral( "file:///" ) ) )
if ( url.startsWith( QStringLiteral( "file://" ) ) )
return true;

return QUrl( url ).isRelative();
}

QUrl UrlUtils::fromString( const QString &string )
{
if ( QFileInfo::exists( string ) )
{
return QUrl::fromLocalFile( string );
}

return QUrl( string );
}
6 changes: 4 additions & 2 deletions src/core/utils/urlutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ class QFIELD_CORE_EXPORT UrlUtils : public QObject
public:
explicit UrlUtils( QObject *parent = nullptr );


/**
* Checks whether the provided string is a relative url (has no protocol or starts with `file://`).
* Checks whether the provided string is a relative \a url (has no protocol or starts with `file://`).
*/
static Q_INVOKABLE bool isRelativeOrFileUrl( const QString &url );

//! Returns a URL from a \a string with logic to handle local paths
static Q_INVOKABLE QUrl fromString( const QString &string );
};

#endif // URLUTILS_H
2 changes: 1 addition & 1 deletion src/qml/About.qml
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ Item {
label = dataDirs.length > 1 ? qsTr('QField app directories') : qsTr('QField app directory');
for (let dataDir of dataDirs) {
if (isDesktopPlatform) {
label += '<br><a href="file://' + dataDir + '">' + dataDir + '</a>';
label += '<br><a href="' + UrlUtils.fromString(dataDir) + '">' + dataDir + '</a>';
} else {
label += '<br>' + dataDir;
}
Expand Down
2 changes: 1 addition & 1 deletion src/qml/FeatureListForm.qml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Rectangle {
width: {
if ( props.isVisible || featureForm.canvasOperationRequested )
{
if (fullScreenView || parent.width < parent.height || parent.width < 300)
if (fullScreenView || parent.width <= parent.height || parent.width < 300)
{
parent.width
}
Expand Down
2 changes: 1 addition & 1 deletion src/qml/PluginManagerSettings.qml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Popup {
Layout.preferredWidth: 24
Layout.preferredHeight: 24

source: Icon !== '' ? 'file://' + Icon : ''
source: Icon !== '' ? UrlUtils.fromString(Icon) : ''
fillMode: Image.PreserveAspectFit
mipmap: true
}
Expand Down
6 changes: 1 addition & 5 deletions src/qml/QFieldAudioRecorder.qml
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,7 @@ Popup {
running: false

onTriggered: {
var path = recorder.actualLocation.toString()
// On Android, the file protocol prefix is present while on Linux it isn't
var filePos = path.indexOf('file://')
path = filePos == -1 ? 'file://' + path : path
player.source = path
player.source = UrlUtils.fromString(recorder.actualLocation.toString())
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/qml/QFieldCamera.qml
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ Popup {

onImageSaved: (requestId, path) => {
currentPath = path
photoPreview.source = 'file://'+path
photoPreview.source = UrlUtils.fromString(path)
cameraItem.state = "PhotoPreview"
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/qml/QFieldSketcher.qml
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ Popup {
anchors.margins: 5
visible: templatePath !== ''
fillMode: Image.PreserveAspectFit
source: templatePath !== '' ? 'file://' + templatePath : ''
source: templatePath !== '' ? UrlUtils.fromString(templatePath) : ''
}

Rectangle {
Expand Down
13 changes: 9 additions & 4 deletions src/qml/editorwidgets/ExternalResource.qml
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,16 @@ EditorWidgetBase {
image.hasImage = true
image.opacity = 1
image.anchors.topMargin = 0
image.source = (!isHttp ? 'file://' : '') + fullValue
image.source = UrlUtils.fromString(fullValue)
geoTagBadge.hasGeoTag = ExifTools.hasGeoTag(fullValue)
} else if (isAudio || isVideo) {
mediaFrame.height = 48

image.visible = false
image.opacity = 0.5
image.source = ''
player.firstFrameDrawn = false
player.sourceUrl = (!isHttp ? 'file://' : '') + fullValue
player.sourceUrl = UrlUtils.fromString(fullValue)
}
} else {
image.source = ''
Expand Down Expand Up @@ -262,7 +267,7 @@ EditorWidgetBase {
id: player
active: isAudio || isVideo

property string sourceUrl: ''
property url sourceUrl: ''
property bool firstFrameDrawn: false

anchors.left: parent.left
Expand Down Expand Up @@ -324,7 +329,7 @@ EditorWidgetBase {
id: sketchButton
anchors.top: image.top
anchors.right: image.right
visible: image.status === Image.Ready && isEnabled
visible: image.source != '' && image.status === Image.Ready && isEnabled

round: true
iconSource: Theme.getThemeVectorIcon( "ic_freehand_white_24dp" )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ EditorWidgetBase {
Image {
id: featureImage
source: ImagePath
? ('file://' + ImagePath)
? UrlUtils.fromString(ImagePath)
: Theme.getThemeIcon("ic_photo_notavailable_black_24dp")
width: parent.height
height: parent.height
Expand Down
2 changes: 2 additions & 0 deletions test/spix/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ py
pytest
pytest-html
pytest-image-diff
pyautogui
tk
53 changes: 50 additions & 3 deletions test/spix/smoke_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import platform
from pathlib import Path
from PIL import Image
import pyautogui


@pytest.fixture
Expand Down Expand Up @@ -124,7 +125,7 @@ def test_wms_layer(app, screenshot_path, screenshot_check, extra, process_alive)
messagesCount = 0
for i in range(0, 10):
message = app.getStringProperty(
"mainWindow/messageLog/messageItem_{}/messageText".format(i), "text"
f"mainWindow/messageLog/messageItem_{i}/messageText", "text"
)
if message == "":
break
Expand Down Expand Up @@ -156,7 +157,7 @@ def test_projection(app, screenshot_path, screenshot_check, extra, process_alive
messagesCount = 0
for i in range(0, 10):
message = app.getStringProperty(
"mainWindow/messageLog/messageItem_{}/messageText".format(i), "text"
f"mainWindow/messageLog/messageItem_{i}/messageText", "text"
)
if message == "":
break
Expand All @@ -166,6 +167,52 @@ def test_projection(app, screenshot_path, screenshot_check, extra, process_alive
assert messagesCount == 0


@pytest.mark.project_file("test_image_attachment.qgz")
def test_projection(app, screenshot_path, screenshot_check, extra, process_alive):
"""
Starts a test app and check for proper reprojection support (including rendering check and message logs).
This also tests that QField is able to reach proj's crucial proj.db
"""
assert app.existsAndVisible("mainWindow")

# Arbitrary wait period to insure project fully loaded and rendered
time.sleep(4)

messagesCount = 0
for i in range(0, 10):
message = app.getStringProperty(
f"mainWindow/messageLog/messageItem_{i}/messageText", "text"
)
if message == "":
break
extra.append(extras.html("Message logs content: {}".format(message)))
messagesCount = messagesCount + 1
extra.append(extras.html("Message logs count: {}".format(messagesCount)))
assert messagesCount == 0

bounds = app.getBoundingBox("mainWindow/mapCanvas")
move_x = bounds[0] + bounds[2] / 2
move_y = bounds[1] + bounds[3] / 3

pyautogui.moveTo(move_x, move_y, duration=0.5)
pyautogui.click(interval=0.5)

bounds = app.getBoundingBox("mainWindow/featureForm")
move_x = bounds[0] + bounds[2] / 2
move_y = bounds[1] + 80

pyautogui.moveTo(move_x, move_y, duration=0.5)
pyautogui.click(interval=0.5)

app.takeScreenshot(
"mainWindow", os.path.join(screenshot_path, "test_image_attachment.png")
)
assert process_alive()
extra.append(extras.html('<img src="images/test_image_attachment.png"/>'))

assert screenshot_check("test_image_attachment", "test_image_attachment")


@pytest.mark.project_file("test_svg.qgz")
def test_svg(app, screenshot_path, screenshot_check, extra, process_alive):
"""
Expand Down Expand Up @@ -198,7 +245,7 @@ def test_postgis_ssl(app, screenshot_path, screenshot_check, extra, process_aliv
messagesCount = 0
for i in range(0, 10):
message = app.getStringProperty(
"mainWindow/messageLog/messageItem_{}/messageText".format(i), "text"
f"mainWindow/messageLog/messageItem_{i}/messageText", "text"
)
if message == "":
break
Expand Down
36 changes: 27 additions & 9 deletions test/test_urlutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,35 @@

#include <QDebug>
#include <QFileInfo>
#include <QTemporaryFile>
#include <QUrl>


TEST_CASE( "UrlUtils" )
{
// should be considered relative
REQUIRE( UrlUtils::isRelativeOrFileUrl( QStringLiteral( "path/to/file" ) ) );
REQUIRE( UrlUtils::isRelativeOrFileUrl( QStringLiteral( "/path/to/file" ) ) );
REQUIRE( UrlUtils::isRelativeOrFileUrl( QStringLiteral( "file:///path/to/file" ) ) );

// should NOT be considered relative
REQUIRE( !UrlUtils::isRelativeOrFileUrl( QStringLiteral( "http://osm.org" ) ) );
REQUIRE( !UrlUtils::isRelativeOrFileUrl( QStringLiteral( "http://osm.org/test?query=1" ) ) );
REQUIRE( !UrlUtils::isRelativeOrFileUrl( QStringLiteral( "https://osm.org/test?query=1" ) ) );
SECTION( "isRelativeOrFileUrl" )
{
// should be considered relative
REQUIRE( UrlUtils::isRelativeOrFileUrl( QStringLiteral( "path/to/file" ) ) );
REQUIRE( UrlUtils::isRelativeOrFileUrl( QStringLiteral( "/path/to/file" ) ) );
REQUIRE( UrlUtils::isRelativeOrFileUrl( QStringLiteral( "file:///path/to/file" ) ) );

// should NOT be considered relative
REQUIRE( !UrlUtils::isRelativeOrFileUrl( QStringLiteral( "http://osm.org" ) ) );
REQUIRE( !UrlUtils::isRelativeOrFileUrl( QStringLiteral( "http://osm.org/test?query=1" ) ) );
REQUIRE( !UrlUtils::isRelativeOrFileUrl( QStringLiteral( "https://osm.org/test?query=1" ) ) );
}

SECTION( "fromString" )
{
// a file that exists will be transformed into a file:// URL
QTemporaryFile tmpFile( QStringLiteral( "test.jpg" ) );
REQUIRE( UrlUtils::fromString( tmpFile.fileName() ).toString() == QUrl::fromLocalFile( tmpFile.fileName() ).toString() );

// a string that doesn't link to an existing file will not transform into a file:// URL
REQUIRE( UrlUtils::fromString( QStringLiteral( "/my/missing/file.txt" ) ).toString() == QStringLiteral( "/my/missing/file.txt" ) );

// a URL string (e.g. http(s)) will be handled as such
REQUIRE( UrlUtils::fromString( QStringLiteral( "https://www.opengis.ch/" ) ).toString() == QStringLiteral( "https://www.opengis.ch/" ) );
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/testdata/polygons.gpkg
Binary file not shown.
Binary file modified test/testdata/projection_dataset.gpkg
Binary file not shown.
Binary file added test/testdata/reserve.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/testdata/test_image_attachment.qgz
Binary file not shown.

1 comment on commit 6e7c2a4

@qfield-fairy
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.