Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgspathresolver.cpp
3 : : --------------------------------------
4 : : Date : February 2017
5 : : Copyright : (C) 2017 by Martin Dobias
6 : : Email : wonder dot sk at gmail dot com
7 : : ***************************************************************************
8 : : * *
9 : : * This program is free software; you can redistribute it and/or modify *
10 : : * it under the terms of the GNU General Public License as published by *
11 : : * the Free Software Foundation; either version 2 of the License, or *
12 : : * (at your option) any later version. *
13 : : * *
14 : : ***************************************************************************/
15 : :
16 : : #include "qgspathresolver.h"
17 : : #include "qgslocalizeddatapathregistry.h"
18 : :
19 : : #include "qgis.h"
20 : : #include "qgsapplication.h"
21 : : #include <QFileInfo>
22 : : #include <QUrl>
23 : : #include <QUuid>
24 : :
25 : :
26 : : typedef std::vector< std::pair< QString, std::function< QString( const QString & ) > > > CustomResolvers;
27 : 55 : Q_GLOBAL_STATIC( CustomResolvers, sCustomResolvers )
28 : :
29 : 589 : QgsPathResolver::QgsPathResolver( const QString &baseFileName )
30 : 589 : : mBaseFileName( baseFileName )
31 : : {
32 : 589 : }
33 : :
34 : :
35 : 40 : QString QgsPathResolver::readPath( const QString &f ) const
36 : : {
37 : 40 : QString filename = f;
38 : :
39 : 40 : const CustomResolvers customResolvers = *sCustomResolvers();
40 : 40 : for ( const auto &resolver : customResolvers )
41 : 0 : filename = resolver.second( filename );
42 : :
43 : 40 : if ( filename.isEmpty() )
44 : 0 : return QString();
45 : :
46 : 40 : QString src = filename;
47 : 40 : if ( src.startsWith( QLatin1String( "inbuilt:" ) ) )
48 : : {
49 : : // strip away "inbuilt:" prefix, replace with actual inbuilt data folder path
50 : 0 : return QgsApplication::pkgDataPath() + QStringLiteral( "/resources" ) + src.mid( 8 );
51 : : }
52 : :
53 : 40 : if ( src.startsWith( QLatin1String( "localized:" ) ) )
54 : : {
55 : : // strip away "localized:" prefix, replace with actual inbuilt data folder path
56 : 0 : return QgsApplication::localizedDataPathRegistry()->globalPath( src.mid( 10 ) ) ;
57 : : }
58 : :
59 : 40 : if ( mBaseFileName.isNull() )
60 : : {
61 : 40 : return src;
62 : : }
63 : :
64 : : // if this is a VSIFILE, remove the VSI prefix and append to final result
65 : 0 : QString vsiPrefix = qgsVsiPrefix( src );
66 : 0 : if ( ! vsiPrefix.isEmpty() )
67 : : {
68 : : // unfortunately qgsVsiPrefix returns prefix also for files like "/x/y/z.gz"
69 : : // so we need to check if we really have the prefix
70 : 0 : if ( src.startsWith( QLatin1String( "/vsi" ), Qt::CaseInsensitive ) )
71 : 0 : src.remove( 0, vsiPrefix.size() );
72 : : else
73 : 0 : vsiPrefix.clear();
74 : 0 : }
75 : :
76 : : // relative path should always start with ./ or ../
77 : 0 : if ( !src.startsWith( QLatin1String( "./" ) ) && !src.startsWith( QLatin1String( "../" ) ) )
78 : : {
79 : : #if defined(Q_OS_WIN)
80 : : if ( src.startsWith( "\\\\" ) ||
81 : : src.startsWith( "//" ) ||
82 : : ( src[0].isLetter() && src[1] == ':' ) )
83 : : {
84 : : // UNC or absolute path
85 : : return vsiPrefix + src;
86 : : }
87 : : #else
88 : 0 : if ( src[0] == '/' )
89 : : {
90 : : // absolute path
91 : 0 : return vsiPrefix + src;
92 : : }
93 : : #endif
94 : :
95 : : // so this one isn't absolute, but also doesn't start // with ./ or ../.
96 : : // That means that it was saved with an earlier version of "relative path support",
97 : : // where the source file had to exist and only the project directory was stripped
98 : : // from the filename.
99 : :
100 : 0 : QFileInfo pfi( mBaseFileName );
101 : 0 : QString home = pfi.absolutePath();
102 : 0 : if ( home.isEmpty() )
103 : 0 : return vsiPrefix + src;
104 : :
105 : 0 : QFileInfo fi( home + '/' + src );
106 : :
107 : 0 : if ( !fi.exists() )
108 : : {
109 : 0 : return vsiPrefix + src;
110 : : }
111 : : else
112 : : {
113 : 0 : return vsiPrefix + fi.canonicalFilePath();
114 : : }
115 : 0 : }
116 : :
117 : 0 : QString srcPath = src;
118 : 0 : QString projPath = mBaseFileName;
119 : :
120 : 0 : if ( projPath.isEmpty() )
121 : : {
122 : 0 : return vsiPrefix + src;
123 : : }
124 : :
125 : : #if defined(Q_OS_WIN)
126 : : srcPath.replace( '\\', '/' );
127 : : projPath.replace( '\\', '/' );
128 : :
129 : : bool uncPath = projPath.startsWith( "//" );
130 : : #endif
131 : :
132 : : // Make sure the path is absolute (see GH #33200)
133 : 0 : projPath = QFileInfo( projPath ).absoluteFilePath();
134 : :
135 : : #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
136 : : QStringList srcElems = srcPath.split( '/', QString::SkipEmptyParts );
137 : : QStringList projElems = projPath.split( '/', QString::SkipEmptyParts );
138 : : #else
139 : 0 : QStringList srcElems = srcPath.split( '/', Qt::SkipEmptyParts );
140 : 0 : QStringList projElems = projPath.split( '/', Qt::SkipEmptyParts );
141 : : #endif
142 : :
143 : : #if defined(Q_OS_WIN)
144 : : if ( uncPath )
145 : : {
146 : : projElems.insert( 0, "" );
147 : : projElems.insert( 0, "" );
148 : : }
149 : : #endif
150 : :
151 : : // remove project file element
152 : 0 : projElems.removeLast();
153 : :
154 : : // append source path elements
155 : 0 : projElems << srcElems;
156 : 0 : projElems.removeAll( QStringLiteral( "." ) );
157 : :
158 : : // resolve ..
159 : : int pos;
160 : 0 : while ( ( pos = projElems.indexOf( QLatin1String( ".." ) ) ) > 0 )
161 : : {
162 : : // remove preceding element and ..
163 : 0 : projElems.removeAt( pos - 1 );
164 : 0 : projElems.removeAt( pos - 1 );
165 : : }
166 : :
167 : : #if !defined(Q_OS_WIN)
168 : : // make path absolute
169 : 0 : projElems.prepend( QString() );
170 : : #endif
171 : :
172 : 0 : return vsiPrefix + projElems.join( QLatin1Char( '/' ) );
173 : 40 : }
174 : :
175 : 0 : QString QgsPathResolver::setPathPreprocessor( const std::function<QString( const QString & )> &processor )
176 : : {
177 : 0 : QString id = QUuid::createUuid().toString();
178 : 0 : sCustomResolvers()->emplace_back( std::make_pair( id, processor ) );
179 : 0 : return id;
180 : 0 : }
181 : :
182 : 0 : bool QgsPathResolver::removePathPreprocessor( const QString &id )
183 : : {
184 : 0 : const size_t prevCount = sCustomResolvers()->size();
185 : 0 : sCustomResolvers()->erase( std::remove_if( sCustomResolvers()->begin(), sCustomResolvers()->end(), [id]( std::pair< QString, std::function< QString( const QString & ) > > &a )
186 : : {
187 : 0 : return a.first == id;
188 : 0 : } ), sCustomResolvers()->end() );
189 : 0 : return prevCount != sCustomResolvers()->size();
190 : 0 : }
191 : :
192 : 0 : QString QgsPathResolver::writePath( const QString &src ) const
193 : : {
194 : 0 : if ( src.isEmpty() )
195 : : {
196 : 0 : return src;
197 : : }
198 : :
199 : 0 : QString localizedPath = QgsApplication::localizedDataPathRegistry()->localizedPath( src );
200 : 0 : if ( !localizedPath.isEmpty() )
201 : 0 : return QStringLiteral( "localized:" ) + localizedPath;
202 : :
203 : 0 : if ( src.startsWith( QgsApplication::pkgDataPath() + QStringLiteral( "/resources" ) ) )
204 : : {
205 : : // replace inbuilt data folder path with "inbuilt:" prefix
206 : 0 : return QStringLiteral( "inbuilt:" ) + src.mid( QgsApplication::pkgDataPath().length() + 10 );
207 : : }
208 : :
209 : 0 : if ( mBaseFileName.isEmpty() )
210 : : {
211 : 0 : return src;
212 : : }
213 : :
214 : : // Get projPath even if project has not been created yet
215 : 0 : QFileInfo pfi( QFileInfo( mBaseFileName ).path() );
216 : 0 : QString projPath = pfi.canonicalFilePath();
217 : :
218 : : // If project directory doesn't exit, fallback to absoluteFilePath : symbolic
219 : : // links won't be handled correctly, but that's OK as the path is "virtual".
220 : 0 : if ( projPath.isEmpty() )
221 : 0 : projPath = pfi.absoluteFilePath();
222 : :
223 : 0 : if ( projPath.isEmpty() )
224 : : {
225 : 0 : return src;
226 : : }
227 : :
228 : : // Check if it is a publicSource uri and clean it
229 : 0 : QUrl url { src };
230 : 0 : QString srcPath { src };
231 : 0 : QString urlQuery;
232 : :
233 : 0 : if ( url.isLocalFile( ) )
234 : : {
235 : 0 : srcPath = url.path();
236 : 0 : urlQuery = url.query();
237 : 0 : }
238 : :
239 : 0 : QFileInfo srcFileInfo( srcPath );
240 : 0 : if ( srcFileInfo.exists() )
241 : 0 : srcPath = srcFileInfo.canonicalFilePath();
242 : :
243 : : // if this is a VSIFILE, remove the VSI prefix and append to final result
244 : 0 : QString vsiPrefix = qgsVsiPrefix( src );
245 : 0 : if ( ! vsiPrefix.isEmpty() )
246 : : {
247 : 0 : srcPath.remove( 0, vsiPrefix.size() );
248 : 0 : }
249 : :
250 : : #if defined( Q_OS_WIN )
251 : : const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
252 : :
253 : : srcPath.replace( '\\', '/' );
254 : :
255 : : if ( srcPath.startsWith( "//" ) )
256 : : {
257 : : // keep UNC prefix
258 : : srcPath = "\\\\" + srcPath.mid( 2 );
259 : : }
260 : :
261 : : projPath.replace( '\\', '/' );
262 : : if ( projPath.startsWith( "//" ) )
263 : : {
264 : : // keep UNC prefix
265 : : projPath = "\\\\" + projPath.mid( 2 );
266 : : }
267 : : #else
268 : 0 : const Qt::CaseSensitivity cs = Qt::CaseSensitive;
269 : : #endif
270 : :
271 : : #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
272 : : QStringList projElems = projPath.split( '/', QString::SkipEmptyParts );
273 : : QStringList srcElems = srcPath.split( '/', QString::SkipEmptyParts );
274 : : #else
275 : 0 : QStringList projElems = projPath.split( '/', Qt::SkipEmptyParts );
276 : 0 : QStringList srcElems = srcPath.split( '/', Qt::SkipEmptyParts );
277 : : #endif
278 : :
279 : 0 : projElems.removeAll( QStringLiteral( "." ) );
280 : 0 : srcElems.removeAll( QStringLiteral( "." ) );
281 : :
282 : : // remove common part
283 : 0 : int n = 0;
284 : 0 : while ( !srcElems.isEmpty() &&
285 : 0 : !projElems.isEmpty() &&
286 : 0 : srcElems[0].compare( projElems[0], cs ) == 0 )
287 : : {
288 : 0 : srcElems.removeFirst();
289 : 0 : projElems.removeFirst();
290 : 0 : n++;
291 : : }
292 : :
293 : 0 : if ( n == 0 )
294 : : {
295 : : // no common parts; might not even be a file
296 : 0 : return src;
297 : : }
298 : :
299 : 0 : if ( !projElems.isEmpty() )
300 : : {
301 : : // go up to the common directory
302 : 0 : for ( int i = 0; i < projElems.size(); i++ )
303 : : {
304 : 0 : srcElems.insert( 0, QStringLiteral( ".." ) );
305 : 0 : }
306 : 0 : }
307 : : else
308 : : {
309 : : // let it start with . nevertheless,
310 : : // so relative path always start with either ./ or ../
311 : 0 : srcElems.insert( 0, QStringLiteral( "." ) );
312 : : }
313 : :
314 : : // Append url query if any
315 : 0 : QString returnPath { vsiPrefix + srcElems.join( QLatin1Char( '/' ) ) };
316 : 0 : if ( ! urlQuery.isEmpty() )
317 : : {
318 : 0 : returnPath.append( '?' );
319 : 0 : returnPath.append( urlQuery );
320 : 0 : }
321 : 0 : return returnPath;
322 : 0 : }
|