ctkLocationManager.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. /*=============================================================================
  2. Library: CTK
  3. Copyright (c) German Cancer Research Center,
  4. Division of Medical and Biological Informatics
  5. Licensed under the Apache License, Version 2.0 (the "License");
  6. you may not use this file except in compliance with the License.
  7. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. =============================================================================*/
  15. #include "ctkLocationManager_p.h"
  16. #include "ctkPluginFrameworkProperties_p.h"
  17. #include "ctkBasicLocation_p.h"
  18. #include "ctkException.h"
  19. #include "ctkPluginFrameworkLauncher.h"
  20. #include "ctkPluginConstants.h"
  21. #include <QCoreApplication>
  22. #include <QSettings>
  23. #include <QFileInfo>
  24. #include <QDir>
  25. #include <QUrl>
  26. namespace {
  27. // Constants for configuration location discovery
  28. static const QString CTK = "commontk";
  29. static const QString PRODUCT_SITE_MARKER = ".commontkproduct";
  30. static const QString PRODUCT_SITE_ID = "id";
  31. static const QString PRODUCT_SITE_VERSION = "version";
  32. static const QString CONFIG_DIR = "configuration";
  33. // Data mode constants for user, configuration and data locations.
  34. static const QString NONE = "@none";
  35. static const QString NO_DEFAULT = "@noDefault";
  36. static const QString USER_HOME = "@user.home";
  37. static const QString USER_DIR = "@user.dir";
  38. // Placeholder for hashcode of installation directory
  39. static const QString INSTALL_HASH_PLACEHOLDER = "@install.hash";
  40. static const QString INSTANCE_DATA_AREA_PREFIX = ".metadata/.plugins/";
  41. static QScopedPointer<ctkBasicLocation> installLocation;
  42. static QScopedPointer<ctkBasicLocation> configurationLocation;
  43. static QScopedPointer<ctkBasicLocation> userLocation;
  44. static QScopedPointer<ctkBasicLocation> instanceLocation;
  45. static QScopedPointer<ctkBasicLocation> ctkHomeLocation;
  46. static bool CanWrite(const QUrl& location);
  47. static bool CanWrite(const QFileInfo& location);
  48. static QString SubstituteVar(const QString& source, const QString& var, const QString& prop);
  49. static QString GetInstallDirHash();
  50. static QString ComputeDefaultUserAreaLocation(const QString& pathAppendage);
  51. static bool isInitialized = false;
  52. //----------------------------------------------------------------------------
  53. static QUrl BuildUrl(const QString& location, bool trailingSlash)
  54. {
  55. QUrl result(location,QUrl::StrictMode);
  56. if (!result.isValid() || result.scheme().compare("file", Qt::CaseInsensitive) != 0)
  57. {
  58. result = QUrl::fromLocalFile(location);
  59. }
  60. bool isFile = result.isValid() && result.scheme().compare("file", Qt::CaseInsensitive) == 0;
  61. if (isFile)
  62. {
  63. QString path = result.path();
  64. if (trailingSlash == path.endsWith('/'))
  65. {
  66. return result;
  67. }
  68. path = trailingSlash ? path + '/' : path.left(path.size()-1);
  69. result.setPath(path);
  70. }
  71. return result;
  72. }
  73. //----------------------------------------------------------------------------
  74. static QString ComputeCTKHomeLocation()
  75. {
  76. // TODO MacOS do we need to walk out of the application bundle?
  77. return QCoreApplication::applicationDirPath();
  78. }
  79. //----------------------------------------------------------------------------
  80. ctkBasicLocation* BuildLocation(const QString& property, const QUrl& defaultLocation,
  81. const QString& userDefaultAppendage, bool readOnlyDefault,
  82. bool computeReadOnly, const QString& dataAreaPrefix)
  83. {
  84. QString location = ctkPluginFrameworkProperties::clearProperty(property).toString();
  85. // the user/product may specify a non-default readOnly setting
  86. QVariant userReadOnlySetting = ctkPluginFrameworkProperties::getProperty(property + ctkLocationManager::READ_ONLY_AREA_SUFFIX);
  87. bool readOnly = (userReadOnlySetting.isNull() ? readOnlyDefault : userReadOnlySetting.toBool());
  88. // if the instance location is not set, predict where the workspace will be and
  89. // put the instance area inside the workspace meta area.
  90. if (location.isNull())
  91. {
  92. return new ctkBasicLocation(property, defaultLocation,
  93. !userReadOnlySetting.isNull() || !computeReadOnly ? readOnly : !CanWrite(defaultLocation),
  94. dataAreaPrefix);
  95. }
  96. QString trimmedLocation = location.trimmed();
  97. if (trimmedLocation.compare(NONE, Qt::CaseInsensitive) == 0)
  98. {
  99. return NULL;
  100. }
  101. if (trimmedLocation.compare(NO_DEFAULT, Qt::CaseInsensitive) == 0)
  102. {
  103. return new ctkBasicLocation(property, QUrl(), readOnly, dataAreaPrefix);
  104. }
  105. if (trimmedLocation.startsWith(USER_HOME))
  106. {
  107. QString base = SubstituteVar(location, USER_HOME, ctkPluginFrameworkLauncher::PROP_USER_HOME);
  108. location = QFileInfo(QDir(base), userDefaultAppendage).absoluteFilePath();
  109. }
  110. else if (trimmedLocation.startsWith(USER_DIR))
  111. {
  112. QString base = SubstituteVar(location, USER_DIR, ctkPluginFrameworkLauncher::PROP_USER_DIR);
  113. location = QFileInfo(QDir(base), userDefaultAppendage).absoluteFilePath();
  114. }
  115. int idx = location.indexOf(INSTALL_HASH_PLACEHOLDER);
  116. if (idx == 0)
  117. {
  118. throw ctkRuntimeException("The location cannot start with '" + INSTALL_HASH_PLACEHOLDER + "': " + location);
  119. }
  120. else if (idx > 0)
  121. {
  122. location = location.left(idx) + GetInstallDirHash() + location.mid(idx + INSTALL_HASH_PLACEHOLDER.size());
  123. }
  124. QUrl url = BuildUrl(location, true);
  125. ctkBasicLocation* result = NULL;
  126. if (url.isValid())
  127. {
  128. result = new ctkBasicLocation(property, QUrl(), !userReadOnlySetting.isNull() || !computeReadOnly ? readOnly : !CanWrite(url), dataAreaPrefix);
  129. result->set(url, false);
  130. }
  131. return result;
  132. }
  133. //----------------------------------------------------------------------------
  134. static QString SubstituteVar(const QString& source, const QString& var, const QString& prop)
  135. {
  136. QString value = ctkPluginFrameworkProperties::getProperty(prop).toString();
  137. return value + source.mid(var.size());
  138. }
  139. //----------------------------------------------------------------------------
  140. static QUrl ComputeInstallConfigurationLocation()
  141. {
  142. QString property = ctkPluginFrameworkProperties::getProperty(ctkPluginFrameworkLauncher::PROP_INSTALL_AREA).toString();
  143. if (!property.isEmpty())
  144. {
  145. return BuildUrl(property, true);
  146. }
  147. return QUrl();
  148. }
  149. //----------------------------------------------------------------------------
  150. static QUrl ComputeSharedConfigurationLocation()
  151. {
  152. QVariant property = ctkPluginFrameworkProperties::getProperty(ctkPluginFrameworkLauncher::PROP_SHARED_CONFIG_AREA);
  153. if (property.isNull())
  154. {
  155. return QUrl();
  156. }
  157. QUrl sharedConfigurationURL = BuildUrl(property.toString(), true);
  158. if (!sharedConfigurationURL.isValid())
  159. {
  160. return QUrl();
  161. }
  162. if (sharedConfigurationURL.path().startsWith("/"))
  163. {
  164. // absolute
  165. return sharedConfigurationURL;
  166. }
  167. QUrl installURL = installLocation->getUrl();
  168. if (sharedConfigurationURL.scheme() != installURL.scheme())
  169. {
  170. // different protocol
  171. return sharedConfigurationURL;
  172. }
  173. QString sharedConfigPath = QDir::cleanPath(installURL.path() + '/' + sharedConfigurationURL.path());
  174. sharedConfigurationURL = installURL;
  175. sharedConfigurationURL.setPath(sharedConfigPath);
  176. ctkPluginFrameworkProperties::setProperty(ctkPluginFrameworkLauncher::PROP_SHARED_CONFIG_AREA, sharedConfigurationURL.toString());
  177. return QUrl();
  178. }
  179. //----------------------------------------------------------------------------
  180. static QString ComputeDefaultConfigurationLocation()
  181. {
  182. // 1) We store the config state relative to the 'ctk' directory if possible
  183. // 2) If this directory is read-only
  184. // we store the state in <user.home>/.commontk/<application-id>_<version> where <user.home>
  185. // is unique for each local user, and <application-id> is the one
  186. // defined in .eclipseproduct marker file. If .eclipseproduct does not
  187. // exist, use "eclipse" as the application-id.
  188. QUrl installURL = ComputeInstallConfigurationLocation();
  189. if (installURL.isValid() && installURL.scheme() == "file")
  190. {
  191. QDir installDir(installURL.toLocalFile());
  192. QFileInfo defaultConfigDir(installDir, CONFIG_DIR);
  193. if (!defaultConfigDir.exists())
  194. {
  195. installDir.mkpath(defaultConfigDir.absoluteFilePath());
  196. }
  197. if (defaultConfigDir.exists() && CanWrite(defaultConfigDir))
  198. {
  199. return defaultConfigDir.absoluteFilePath();
  200. }
  201. }
  202. // We can't write in the CTK install dir so try for some place in the user's home dir
  203. return ComputeDefaultUserAreaLocation(CONFIG_DIR);
  204. }
  205. //----------------------------------------------------------------------------
  206. static bool CanWrite(const QFileInfo& location)
  207. {
  208. if (location.isWritable() == false)
  209. {
  210. return false;
  211. }
  212. if (!location.isDir())
  213. {
  214. return false;
  215. }
  216. return true;
  217. }
  218. //----------------------------------------------------------------------------
  219. static bool CanWrite(const QUrl& location)
  220. {
  221. if (location.isValid() && location.scheme() == "file")
  222. {
  223. QFileInfo locationDir(location.toLocalFile());
  224. if (!locationDir.exists())
  225. {
  226. QDir().mkpath(locationDir.absoluteFilePath());
  227. }
  228. if (locationDir.exists() && CanWrite(locationDir))
  229. return true;
  230. }
  231. return false;
  232. }
  233. //----------------------------------------------------------------------------
  234. static QString ComputeDefaultUserAreaLocation(const QString& pathAppendage)
  235. {
  236. // we store the state in <user.home>/.commontk/<application-id>_<version> where <user.home>
  237. // is unique for each local user, and <application-id> is the one
  238. // defined in .commontkproduct marker file. If .commontkproduct does not
  239. // exist, use "commontk" as the application-id.
  240. QString installProperty = ctkPluginFrameworkProperties::getProperty(ctkPluginFrameworkLauncher::PROP_INSTALL_AREA).toString();
  241. QUrl installURL = BuildUrl(installProperty, true);
  242. if (!installURL.isValid())
  243. {
  244. return QString::null;
  245. }
  246. QFileInfo installDir(installURL.toLocalFile());
  247. QString installDirHash = GetInstallDirHash();
  248. QString appName = "." + CTK;
  249. QFileInfo ctkProduct(QDir(installDir.absoluteFilePath()), PRODUCT_SITE_MARKER);
  250. if (ctkProduct.exists())
  251. {
  252. QSettings props(ctkProduct.absoluteFilePath(), QSettings::IniFormat);
  253. QString appId = props.value(PRODUCT_SITE_ID).toString();
  254. if (appId.trimmed().isEmpty())
  255. {
  256. appId = CTK;
  257. }
  258. QString appVersion = props.value(PRODUCT_SITE_VERSION).toString();
  259. if (appVersion.trimmed().isEmpty())
  260. {
  261. appVersion = "";
  262. }
  263. appName += "/" + appId + "_" + appVersion + "_" + installDirHash;
  264. }
  265. else
  266. {
  267. // add the hash to help prevent collisions
  268. appName += "/" + installDirHash;
  269. }
  270. QString userHome = ctkPluginFrameworkProperties::getProperty(ctkPluginFrameworkLauncher::PROP_USER_HOME).toString();
  271. return QFileInfo(QDir(userHome), appName + "/" + pathAppendage).absoluteFilePath();
  272. }
  273. /**
  274. * Return hash code identifying an absolute installation path
  275. * @return hash code as String
  276. */
  277. //----------------------------------------------------------------------------
  278. static QString GetInstallDirHash()
  279. {
  280. // compute an install dir hash to prevent configuration area collisions with other eclipse installs
  281. QString installProperty = ctkPluginFrameworkProperties::getProperty(ctkPluginFrameworkLauncher::PROP_INSTALL_AREA).toString();
  282. QUrl installURL = BuildUrl(installProperty, true);
  283. if (!installURL.isValid())
  284. {
  285. return "";
  286. }
  287. QFileInfo installDir(installURL.toLocalFile());
  288. QString stringToHash = installDir.canonicalFilePath();
  289. if (stringToHash.isEmpty())
  290. {
  291. // fall back to absolute path
  292. stringToHash = installDir.absoluteFilePath();
  293. }
  294. uint hashCode = qHash(stringToHash);
  295. return QString::number(hashCode);
  296. }
  297. }
  298. const QString ctkLocationManager::READ_ONLY_AREA_SUFFIX = ".readOnly";
  299. // configuration area file/dir names
  300. const QString ctkLocationManager::CONFIG_FILE = "config.ini";
  301. const QString ctkLocationManager::CTK_PROPERTIES = "ctk.properties";
  302. //----------------------------------------------------------------------------
  303. void ctkLocationManager::initializeLocations()
  304. {
  305. if (isInitialized) return;
  306. // set the ctk storage area if it exists
  307. QVariant osgiStorage = ctkPluginFrameworkProperties::getProperty(ctkPluginConstants::FRAMEWORK_STORAGE);
  308. if (!osgiStorage.isNull())
  309. {
  310. ctkPluginFrameworkProperties::setProperty(ctkPluginFrameworkLauncher::PROP_CONFIG_AREA, osgiStorage);
  311. }
  312. // do install location initialization first since others may depend on it
  313. // assumes that the property is already set
  314. installLocation.reset(BuildLocation(ctkPluginFrameworkLauncher::PROP_INSTALL_AREA, QUrl(), "", true, false, QString::null));
  315. // TODO not sure what the data area prefix should be here for the user area
  316. QScopedPointer<ctkBasicLocation> temp(BuildLocation(ctkPluginFrameworkLauncher::PROP_USER_AREA_DEFAULT,
  317. QUrl(), "", false, false, QString::null));
  318. QUrl defaultLocation = temp ? temp->getUrl() : QUrl();
  319. if (!defaultLocation.isValid())
  320. {
  321. defaultLocation = BuildUrl(QFileInfo(QDir(ctkPluginFrameworkProperties::getProperty(ctkPluginFrameworkLauncher::PROP_USER_HOME).toString()), "user").absoluteFilePath(), true);
  322. }
  323. userLocation.reset(BuildLocation(ctkPluginFrameworkLauncher::PROP_USER_AREA, defaultLocation, "", false, false, QString::null));
  324. temp.reset(BuildLocation(ctkPluginFrameworkLauncher::PROP_INSTANCE_AREA_DEFAULT, QUrl(), "", false, false, INSTANCE_DATA_AREA_PREFIX));
  325. defaultLocation = temp ? temp->getUrl() : QUrl();
  326. if (!defaultLocation.isValid())
  327. {
  328. defaultLocation = BuildUrl(QFileInfo(QDir(ctkPluginFrameworkProperties::getProperty(ctkPluginFrameworkLauncher::PROP_USER_DIR).toString()), "workspace").absoluteFilePath(), true);
  329. }
  330. instanceLocation.reset(BuildLocation(ctkPluginFrameworkLauncher::PROP_INSTANCE_AREA, defaultLocation, "", false, false, INSTANCE_DATA_AREA_PREFIX));
  331. //mungeConfigurationLocation();
  332. // compute a default but it is very unlikely to be used since main will have computed everything
  333. temp.reset(BuildLocation(ctkPluginFrameworkLauncher::PROP_CONFIG_AREA_DEFAULT, QUrl(), "", false, false, QString::null));
  334. defaultLocation = temp ? temp->getUrl() : QUrl();
  335. if (!defaultLocation.isValid() && ctkPluginFrameworkProperties::getProperty(ctkPluginFrameworkLauncher::PROP_CONFIG_AREA).isNull())
  336. {
  337. // only compute the default if the configuration area property is not set
  338. defaultLocation = BuildUrl(ComputeDefaultConfigurationLocation(), true);
  339. }
  340. configurationLocation.reset(BuildLocation(ctkPluginFrameworkLauncher::PROP_CONFIG_AREA, defaultLocation, "", false, false, QString::null));
  341. // get the parent location based on the system property. This will have been set on the
  342. // way in either by the caller/user or by main. There will be no parent location if we are not
  343. // cascaded.
  344. QUrl parentLocation = ComputeSharedConfigurationLocation();
  345. if (parentLocation.isValid() && parentLocation != configurationLocation->getUrl())
  346. {
  347. ctkBasicLocation* parent = new ctkBasicLocation(QString::null, parentLocation, true, QString::null);
  348. configurationLocation->setParent(parent);
  349. }
  350. //initializeDerivedConfigurationLocations();
  351. if (ctkPluginFrameworkProperties::getProperty(ctkPluginFrameworkLauncher::PROP_HOME_LOCATION_AREA).isNull())
  352. {
  353. QString blueberryHomeLocationPath = ComputeCTKHomeLocation();
  354. if (!blueberryHomeLocationPath.isEmpty())
  355. {
  356. ctkPluginFrameworkProperties::setProperty(ctkPluginFrameworkLauncher::PROP_HOME_LOCATION_AREA, blueberryHomeLocationPath);
  357. }
  358. }
  359. // if ctk.home.location is not set then default to ctk.install.area
  360. if (ctkPluginFrameworkProperties::getProperty(ctkPluginFrameworkLauncher::PROP_HOME_LOCATION_AREA).isNull() &&
  361. !ctkPluginFrameworkProperties::getProperty(ctkPluginFrameworkLauncher::PROP_INSTALL_AREA).isNull())
  362. {
  363. ctkPluginFrameworkProperties::setProperty(ctkPluginFrameworkLauncher::PROP_HOME_LOCATION_AREA,
  364. ctkPluginFrameworkProperties::getProperty(ctkPluginFrameworkLauncher::PROP_INSTALL_AREA));
  365. }
  366. ctkHomeLocation.reset(BuildLocation(ctkPluginFrameworkLauncher::PROP_HOME_LOCATION_AREA, QUrl(), "", true, true, QString::null));
  367. isInitialized = true;
  368. }
  369. //----------------------------------------------------------------------------
  370. ctkBasicLocation* ctkLocationManager::getUserLocation()
  371. {
  372. return userLocation.data();
  373. }
  374. //----------------------------------------------------------------------------
  375. ctkBasicLocation* ctkLocationManager::getConfigurationLocation()
  376. {
  377. return configurationLocation.data();
  378. }
  379. //----------------------------------------------------------------------------
  380. ctkBasicLocation* ctkLocationManager::getInstallLocation()
  381. {
  382. return installLocation.data();
  383. }
  384. //----------------------------------------------------------------------------
  385. ctkBasicLocation* ctkLocationManager::getInstanceLocation()
  386. {
  387. return instanceLocation.data();
  388. }
  389. //----------------------------------------------------------------------------
  390. ctkBasicLocation* ctkLocationManager::getCTKHomeLocation()
  391. {
  392. return ctkHomeLocation.data();
  393. }