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