1 #include <QString>
2 #include <QLatin1String>
3 #include <QDir>
4 #include <QFile>
5 #include <QSettings>
6 #include <QLocale>
7 #include <QDesktopServices>
8 #include <QMessageBox>
9 #ifdef HAVE_QT5
10 #include <QStandardPaths>
11 #endif
12 
13 #ifdef Q_OS_UNIX
14 #include <sys/stat.h> // chmod
15 #endif
16 
17 #ifdef Q_OS_WIN
18 #include <windows.h>
19 #include <shellapi.h>
20 #include <shlobj.h>
21 #endif
22 
23 #ifdef Q_OS_MAC
24 #include <sys/stat.h> // chmod
25 #include <CoreServices/CoreServices.h>
26 #endif
27 
28 #include "psiapplication.h"
29 #include "applicationinfo.h"
30 #include "systeminfo.h"
31 #include "profiles.h"
32 #include "homedirmigration.h"
33 #include "activeprofiles.h"
34 #include "translationmanager.h"
35 #ifdef HAVE_CONFIG
36 #include "config.h"
37 #endif
38 
39 #define xstr(a) str(a)
40 #define str(a) #a
41 
42 // Constants. These should be moved to a more 'dynamically changeable'
43 // place (like an external file loaded through the resources system)
44 // Should also be overridable through an optional file.
45 //
46 // PROG_SNAME - read as small name, system name, soname, short name, fs name
47 
48 #define PROG_NAME "Psi"
49 #define PROG_SNAME "psi"
50 #define PROG_VERSION PSI_VERSION
51 //#define PROG_VERSION "0.15-dev" " (" __DATE__ ")" //CVS Builds are dated
52 //#define PROG_VERSION "0.15";
53 #define PROG_CAPS_NODE "https://psi-im.org"
54 #define PROG_IPC_NAME "org.psi-im.Psi"	// must not contain '\\' character on Windows
55 #define PROG_OPTIONS_NS "http://psi-im.org/options"
56 #define PROG_STORAGE_NS "http://psi-im.org/storage"
57 #define PROG_FILECACHE_NS "http://psi-im.org/filecache"
58 #ifdef Q_OS_MAC
59 #define PROG_APPCAST_URL "http://psi-im.org/appcast/psi-mac.xml"
60 #else
61 #define PROG_APPCAST_URL ""
62 #endif
63 
name()64 QString ApplicationInfo::name()
65 {
66 	return PROG_NAME;
67 }
68 
sname()69 QLatin1String ApplicationInfo::sname()
70 {
71 	return QLatin1String(PROG_SNAME);
72 }
73 
version()74 QString ApplicationInfo::version()
75 {
76 	return PROG_VERSION;
77 }
78 
capsNode()79 QString ApplicationInfo::capsNode()
80 {
81 	return PROG_CAPS_NODE;
82 }
83 
osName()84 QString ApplicationInfo::osName()
85 {
86 	return SystemInfo::instance()->os();
87 }
88 
IPCName()89 QString ApplicationInfo::IPCName()
90 {
91 	return PROG_IPC_NAME;
92 }
93 
getAppCastURL()94 QString ApplicationInfo::getAppCastURL()
95 {
96 	return PROG_APPCAST_URL;
97 }
98 
optionsNS()99 QString ApplicationInfo::optionsNS()
100 {
101 	return PROG_OPTIONS_NS;
102 }
103 
storageNS()104 QString ApplicationInfo::storageNS()
105 {
106 	return PROG_STORAGE_NS;
107 }
108 
fileCacheNS()109 QString ApplicationInfo::fileCacheNS()
110 {
111 	return PROG_FILECACHE_NS;
112 }
113 
getCertificateStoreDirs()114 QStringList ApplicationInfo::getCertificateStoreDirs()
115 {
116 	QStringList l;
117 	l += ApplicationInfo::resourcesDir() + "/certs";
118 	l += ApplicationInfo::homeDir(ApplicationInfo::DataLocation) + "/certs";
119 	return l;
120 }
121 
dataDirs()122 QStringList ApplicationInfo::dataDirs()
123 {
124 	const static QStringList dirs = QStringList() << ":" << "." << homeDir(DataLocation)
125 												  << resourcesDir();
126 	return  dirs;
127 }
128 
getCertificateStoreSaveDir()129 QString ApplicationInfo::getCertificateStoreSaveDir()
130 {
131 	QDir certsave(homeDir(DataLocation) + "/certs");
132 	if(!certsave.exists()) {
133 		QDir home(homeDir(DataLocation));
134 		home.mkdir("certs");
135 	}
136 
137 	return certsave.path();
138 }
139 
resourcesDir()140 QString ApplicationInfo::resourcesDir()
141 {
142 #if defined(Q_OS_WIN)
143 	return qApp->applicationDirPath();
144 #elif defined(Q_OS_MAC)
145 	// FIXME: Clean this up (remko)
146 	// System routine locates resource files. We "know" that Psi.icns is
147 	// in the Resources directory.
148 	QString resourcePath;
149 	CFBundleRef mainBundle = CFBundleGetMainBundle();
150 	CFStringRef resourceCFStringRef = CFStringCreateWithCString(NULL,
151 									"application.icns", kCFStringEncodingASCII);
152 	CFURLRef resourceURLRef = CFBundleCopyResourceURL(mainBundle,
153 											resourceCFStringRef, NULL, NULL);
154 	if (resourceURLRef) {
155 		CFStringRef resourcePathStringRef = CFURLCopyFileSystemPath(
156 					resourceURLRef, kCFURLPOSIXPathStyle);
157 		const char* resourcePathCString = CFStringGetCStringPtr(
158 					resourcePathStringRef, kCFStringEncodingASCII);
159 		if (resourcePathCString) {
160 			resourcePath = resourcePathCString;
161 		}
162 		else { // CFStringGetCStringPtr failed; use fallback conversion
163 			CFIndex bufferLength = CFStringGetLength(resourcePathStringRef) + 1;
164 			char* resourcePathCString = new char[ bufferLength ];
165 			Boolean conversionSuccess = CFStringGetCString(
166 						resourcePathStringRef, resourcePathCString,
167 						bufferLength, kCFStringEncodingASCII);
168 			if (conversionSuccess) {
169 				resourcePath = resourcePathCString;
170 			}
171 			delete [] resourcePathCString;  // I own this
172 		}
173 		CFRelease( resourcePathStringRef ); // I own this
174 	}
175 	// Remove the tail component of the path
176 	if (! resourcePath.isNull()) {
177 		QFileInfo fileInfo(resourcePath);
178 		resourcePath = fileInfo.absolutePath();
179 	}
180 	return resourcePath;
181 #else
182 	return PSI_DATADIR;
183 #endif
184 }
185 
libDir()186 QString ApplicationInfo::libDir()
187 {
188 #if defined(Q_OS_UNIX)
189 	return PSI_LIBDIR;
190 #else
191 	return QString();
192 #endif
193 }
194 
195 /** \brief return psi's private read write data directory
196   * unix+mac: $HOME/.psi
197   * environment variable "PSIDATADIR" overrides
198   */
homeDir(ApplicationInfo::HomedirType type)199 QString ApplicationInfo::homeDir(ApplicationInfo::HomedirType type)
200 {
201 	static QString configDir_;
202 	static QString dataDir_;
203 	static QString cacheDir_;
204 
205 	if (configDir_.isEmpty()) {
206 		// Try the environment override first
207 		configDir_ = QString::fromLocal8Bit(getenv("PSIDATADIR"));
208 
209 		if (configDir_.isEmpty()) {
210 #if defined Q_OS_WIN
211 			QString base = QFileInfo(QCoreApplication::applicationFilePath()).fileName()
212 					.toLower().indexOf("portable") == -1?
213 						"" : QCoreApplication::applicationDirPath();
214 			if (base.isEmpty()) {
215 				wchar_t path[MAX_PATH];
216 				if (SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, path) == S_OK) {
217 					configDir_ = QString::fromWCharArray(path) + "\\" + name();
218 				} else {
219 					configDir_ = QDir::homePath() + "/" + name();
220 				}
221 				dataDir_ = configDir_;
222 				// prefer non-roaming data location for cache which is default for qds:DataLocation
223 #ifdef HAVE_QT5
224 				cacheDir_ = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
225 #else
226 				cacheDir_ = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
227 #endif
228 			} else {
229 				configDir_ = dataDir_ = cacheDir_ = base + "/" + name();
230 			}
231 			// temporary store for later processing
232 			QDir configDir(configDir_);
233 			QDir cacheDir(cacheDir_);
234 			QDir dataDir(dataDir_);
235 #elif defined Q_OS_MAC
236 			QDir configDir(QDir::homePath() + "/Library/Application Support/" + name());
237 			QDir cacheDir(QDir::homePath() + "/Library/Caches/" + name());
238 			QDir dataDir(configDir);
239 #elif defined HAVE_FREEDESKTOP
240 #ifndef HAVE_QT5
241 			QString XdgConfigHome = QString::fromLocal8Bit(getenv("XDG_CONFIG_HOME"));
242 			QString XdgDataHome = QString::fromLocal8Bit(getenv("XDG_DATA_HOME"));
243 			QString XdgCacheHome = QString::fromLocal8Bit(getenv("XDG_CACHE_HOME"));
244 #else
245 			QString XdgConfigHome(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation));
246 			QString XdgDataHome(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation));
247 			QString XdgCacheHome(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation));
248 #endif
249 			if (XdgConfigHome.isEmpty()) {
250 				XdgConfigHome = QDir::homePath() + "/.config";
251 			}
252 			if (XdgDataHome.isEmpty()) {
253 				XdgDataHome = QDir::homePath() + "/.local/share";
254 			}
255 			if (XdgCacheHome.isEmpty()) {
256 				XdgCacheHome = QDir::homePath() + "/.cache";
257 			}
258 			QDir configDir(XdgConfigHome + "/" + sname());
259 			QDir dataDir(XdgDataHome + "/" + sname());
260 			QDir cacheDir(XdgCacheHome + "/" + sname());
261 
262 			// migrate mix-cased to lowercase, if needed
263 
264 			QDir configDirOld(XdgConfigHome + "/" + name());
265 			QDir dataDirOld(XdgDataHome + "/" + name());
266 			QDir cacheDirOld(XdgCacheHome + "/" + name());
267 
268 			bool ok = true;
269 			if (ok && !configDir.exists() && configDirOld.exists()) {
270 				configDirOld = QDir(XdgConfigHome);
271 				ok = configDirOld.rename(name(), sname());
272 			}
273 			if (ok && !dataDir.exists() && dataDirOld.exists()) {
274 				dataDirOld = QDir(XdgDataHome);
275 				ok = dataDirOld.rename(name(), sname());
276 			}
277 			if (ok && !cacheDir.exists() && cacheDirOld.exists()) {
278 				cacheDirOld = QDir(XdgCacheHome);
279 				ok = cacheDirOld.rename(name(), sname());
280 			}
281 
282 			if(!ok)
283 			{
284 				QMessageBox::information(0, QObject::tr("Conversion Error"), QObject::tr("Configuration data for a previous version of Psi was found, but it was not possible to convert it to work with the current version. Ensure you have appropriate permission and that another copy of Psi is not running, and try again."));
285 				exit(0);
286 			}
287 #endif
288 			configDir_ = configDir.path();
289 			cacheDir_ = cacheDir.path();
290 			dataDir_ = dataDir.path();
291 
292 			// To prevent from multiple startup of import  wizard
293 			if (ActiveProfiles::instance()->isActive("import_wizard")) {
294 				exit(0);
295 			}
296 
297 			if (!configDir.exists() && !dataDir.exists() && !cacheDir.exists()) {
298 				HomeDirMigration dlg;
299 
300 				if (dlg.checkOldHomeDir()) {
301 					ActiveProfiles::instance()->setThisProfile("import_wizard");
302 					QSettings s(dlg.oldHomeDir() + "/psirc", QSettings::IniFormat);
303 					QString lastLang = s.value("last_lang", QString()).toString();
304 					if(lastLang.isEmpty()) {
305 						lastLang = QLocale().name().section('_', 0, 0);
306 					}
307 					TranslationManager::instance()->loadTranslation(lastLang);
308 					dlg.exec();
309 					ActiveProfiles::instance()->unsetThisProfile();
310 				}
311 			}
312 			if (!dataDir.exists()) {
313 				dataDir.mkpath(".");
314 			}
315 			if (!cacheDir.exists()) {
316 				cacheDir.mkpath(".");
317 			}
318 		}
319 		else {
320 			cacheDir_ = configDir_;
321 			dataDir_ = configDir_;
322 		}
323 	}
324 
325 	QString ret;
326 	switch(type) {
327 	case ApplicationInfo::ConfigLocation:
328 		ret = configDir_;
329 		break;
330 
331 	case ApplicationInfo::DataLocation:
332 		ret = dataDir_;
333 		break;
334 
335 	case ApplicationInfo::CacheLocation:
336 		ret = cacheDir_;
337 		break;
338 	}
339 	return ret;
340 }
341 
makeSubhomePath(const QString & path,ApplicationInfo::HomedirType type)342 QString ApplicationInfo::makeSubhomePath(const QString &path, ApplicationInfo::HomedirType type)
343 {
344 	if (path.indexOf("..") == -1) { // ensure its in home dir
345 		QDir dir(homeDir(type) + "/" + path);
346 		if (!dir.exists()) {
347 			dir.mkpath(".");
348 		}
349 		return dir.path();
350 	}
351 	return QString();
352 }
353 
makeSubprofilePath(const QString & path,ApplicationInfo::HomedirType type)354 QString ApplicationInfo::makeSubprofilePath(const QString &path, ApplicationInfo::HomedirType type)
355 {
356 	if (path.indexOf("..") == -1) { // ensure its in profile dir
357 		QDir dir(pathToProfile(activeProfile, type) + "/" + path);
358 		if (!dir.exists()) {
359 			dir.mkpath(".");
360 		}
361 		return dir.path();
362 	}
363 	return QString();
364 }
365 
historyDir()366 QString ApplicationInfo::historyDir()
367 {
368 	return makeSubprofilePath("history", ApplicationInfo::DataLocation);
369 }
370 
vCardDir()371 QString ApplicationInfo::vCardDir()
372 {
373 	return makeSubprofilePath("vcard", ApplicationInfo::CacheLocation);
374 }
375 
bobDir()376 QString ApplicationInfo::bobDir()
377 {
378 	return makeSubhomePath("bob", ApplicationInfo::CacheLocation);
379 }
380 
profilesDir(ApplicationInfo::HomedirType type)381 QString ApplicationInfo::profilesDir(ApplicationInfo::HomedirType type)
382 {
383 	return makeSubhomePath("profiles", type);
384 }
385 
currentProfileDir(ApplicationInfo::HomedirType type)386 QString ApplicationInfo::currentProfileDir(ApplicationInfo::HomedirType type)
387 {
388 	return pathToProfile(activeProfile, type);
389 }
390 
desktopFile()391 QString ApplicationInfo::desktopFile()
392 {
393 	QString dFile;
394 	const QString _desktopFile(xstr(APP_PREFIX) "/share/applications/" xstr(APP_BIN_NAME) ".desktop");
395 	QFile f(_desktopFile);
396 	if(f.open(QIODevice::ReadOnly)) {
397 		dFile = QString::fromUtf8(f.readAll());
398 	}
399 	return dFile;
400 }
401