Chris@57
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@57
|
2
|
Chris@57
|
3 /*
|
Chris@57
|
4 EasyMercurial
|
Chris@57
|
5
|
Chris@57
|
6 Based on HgExplorer by Jari Korhonen
|
Chris@57
|
7 Copyright (c) 2010 Jari Korhonen
|
Chris@644
|
8 Copyright (c) 2013 Chris Cannam
|
Chris@644
|
9 Copyright (c) 2013 Queen Mary, University of London
|
Chris@57
|
10
|
Chris@57
|
11 This program is free software; you can redistribute it and/or
|
Chris@57
|
12 modify it under the terms of the GNU General Public License as
|
Chris@57
|
13 published by the Free Software Foundation; either version 2 of the
|
Chris@57
|
14 License, or (at your option) any later version. See the file
|
Chris@57
|
15 COPYING included with this distribution for more information.
|
Chris@57
|
16 */
|
jtkorhonen@0
|
17
|
Chris@62
|
18 #include "common.h"
|
Chris@62
|
19 #include "debug.h"
|
jtkorhonen@0
|
20
|
Chris@62
|
21 #include <QFileInfo>
|
Chris@62
|
22 #include <QProcessEnvironment>
|
Chris@62
|
23 #include <QStringList>
|
Chris@77
|
24 #include <QDir>
|
Chris@172
|
25 #include <QRegExp>
|
Chris@678
|
26 #include <QFont>
|
Chris@678
|
27 #include <QFontMetrics>
|
jtkorhonen@0
|
28
|
Chris@62
|
29 #include <sys/types.h>
|
Chris@76
|
30
|
Chris@76
|
31 #ifdef Q_OS_WIN32
|
Chris@76
|
32 #define _WIN32_WINNT 0x0500
|
Chris@166
|
33 #define SECURITY_WIN32
|
Chris@76
|
34 #include <windows.h>
|
Chris@76
|
35 #include <security.h>
|
Chris@455
|
36 #include <process.h>
|
Chris@76
|
37 #else
|
Chris@78
|
38 #include <errno.h>
|
Chris@62
|
39 #include <pwd.h>
|
Chris@78
|
40 #include <unistd.h>
|
Chris@80
|
41 #include <sys/ioctl.h>
|
Chris@80
|
42 #include <sys/types.h>
|
Chris@80
|
43 #include <sys/stat.h>
|
Chris@80
|
44 #include <fcntl.h>
|
Chris@105
|
45 #include <signal.h>
|
Chris@443
|
46 #include <stdio.h>
|
Chris@76
|
47 #endif
|
Chris@62
|
48
|
Chris@172
|
49 QString findInPath(QString name, QString installPath, bool executableRequired)
|
Chris@62
|
50 {
|
Chris@77
|
51 bool found = false;
|
Chris@62
|
52 if (name != "") {
|
Chris@172
|
53 if (name[0] != '/'
|
Chris@172
|
54 #ifdef Q_OS_WIN32
|
Chris@178
|
55 && (QRegExp("^\\w:").indexIn(name) != 0)
|
Chris@172
|
56 #endif
|
Chris@178
|
57 ) {
|
Chris@77
|
58 #ifdef Q_OS_WIN32
|
Chris@77
|
59 QChar pathSep = ';';
|
Chris@77
|
60 #else
|
Chris@77
|
61 QChar pathSep = ':';
|
Chris@77
|
62 #endif
|
Chris@62
|
63 name = QFileInfo(name).fileName();
|
Chris@62
|
64 QString path =
|
Chris@62
|
65 QProcessEnvironment::systemEnvironment().value("PATH");
|
Chris@214
|
66 DEBUG << "findInPath: seeking location for binary " << name
|
Chris@74
|
67 << ": system path is " << path << endl;
|
Chris@172
|
68 if (installPath != "") {
|
Chris@214
|
69 DEBUG << "findInPath: install path is " << installPath
|
Chris@172
|
70 << ", adding to system path" << endl;
|
Chris@172
|
71 //!!! path = path + pathSep + installPath;
|
Chris@172
|
72 path = installPath + pathSep + path;
|
Chris@172
|
73 }
|
Chris@74
|
74 #ifndef Q_OS_WIN32
|
Chris@178
|
75 //!!!
|
Chris@74
|
76 path = path + ":/usr/local/bin";
|
Chris@74
|
77 DEBUG << "... adding /usr/local/bin just in case (fix and add settings dlg please)"
|
Chris@74
|
78 << endl;
|
Chris@74
|
79 #endif
|
Chris@62
|
80 QStringList elements = path.split(pathSep, QString::SkipEmptyParts);
|
Chris@62
|
81 foreach (QString element, elements) {
|
Chris@77
|
82 QString full = QDir(element).filePath(name);
|
Chris@62
|
83 QFileInfo fi(full);
|
Chris@214
|
84 DEBUG << "findInPath: looking at " << full << endl;
|
Chris@172
|
85 if (fi.exists() && fi.isFile()) {
|
Chris@214
|
86 DEBUG << "findInPath: it's a file" << endl;
|
Chris@172
|
87 if (!executableRequired || fi.isExecutable()) {
|
Chris@172
|
88 name = full;
|
Chris@214
|
89 DEBUG << "findInPath: found at " << name << endl;
|
Chris@172
|
90 found = true;
|
Chris@172
|
91 break;
|
Chris@172
|
92 }
|
Chris@62
|
93 }
|
Chris@62
|
94 }
|
Chris@214
|
95 } else {
|
Chris@214
|
96 // absolute path given
|
Chris@214
|
97 QFileInfo fi(name);
|
Chris@214
|
98 DEBUG << "findInPath: looking at absolute path " << name << endl;
|
Chris@214
|
99 if (fi.exists() && fi.isFile()) {
|
Chris@214
|
100 DEBUG << "findInPath: it's a file" << endl;
|
Chris@214
|
101 if (!executableRequired || fi.isExecutable()) {
|
Chris@214
|
102 DEBUG << "findInPath: found at " << name << endl;
|
Chris@214
|
103 found = true;
|
Chris@214
|
104 }
|
Chris@214
|
105 }
|
Chris@62
|
106 }
|
Chris@62
|
107 }
|
Chris@77
|
108 #ifdef Q_OS_WIN32
|
Chris@77
|
109 if (!found) {
|
Chris@77
|
110 if (!name.endsWith(".exe")) {
|
Chris@172
|
111 return findInPath(name + ".exe", installPath, executableRequired);
|
Chris@77
|
112 }
|
Chris@77
|
113 }
|
Chris@77
|
114 #endif
|
Chris@213
|
115 if (found) {
|
Chris@213
|
116 return name;
|
Chris@213
|
117 } else {
|
Chris@213
|
118 return "";
|
Chris@213
|
119 }
|
Chris@62
|
120 }
|
jtkorhonen@0
|
121
|
Chris@62
|
122 #ifdef Q_OS_WIN32
|
Chris@62
|
123 QString getUserRealName()
|
Chris@62
|
124 {
|
Chris@76
|
125 TCHAR buf[1024];
|
Chris@76
|
126 long unsigned int maxlen = 1000;
|
Chris@62
|
127 LPTSTR info = buf;
|
jtkorhonen@0
|
128
|
Chris@76
|
129 if (!GetUserNameEx(NameDisplay, info, &maxlen)) {
|
Chris@76
|
130 DEBUG << "GetUserNameEx failed: " << GetLastError() << endl;
|
Chris@62
|
131 return "";
|
Chris@62
|
132 }
|
jtkorhonen@0
|
133
|
Chris@76
|
134 #ifdef UNICODE
|
Chris@76
|
135 return QString::fromUtf16((const unsigned short *)info);
|
Chris@76
|
136 #else
|
Chris@76
|
137 return QString::fromLocal8Bit(info);
|
Chris@76
|
138 #endif
|
Chris@62
|
139 }
|
luisf@71
|
140 #else
|
luisf@71
|
141 #ifdef Q_OS_MAC
|
Chris@62
|
142 // Nothing here: definition is in common_osx.mm
|
Chris@62
|
143 #else
|
Chris@62
|
144 QString getUserRealName()
|
Chris@62
|
145 {
|
Chris@62
|
146 const int maxlen = 1023;
|
Chris@62
|
147 char buf[maxlen + 2];
|
jtkorhonen@0
|
148
|
Chris@62
|
149 if (getlogin_r(buf, maxlen)) return "";
|
jtkorhonen@0
|
150
|
Chris@62
|
151 struct passwd *p = getpwnam(buf);
|
Chris@62
|
152 if (!p) return "";
|
Chris@62
|
153
|
Chris@62
|
154 QString s(p->pw_gecos);
|
Chris@62
|
155 if (s != "") s = s.split(',')[0];
|
Chris@62
|
156 return s;
|
Chris@62
|
157 }
|
Chris@62
|
158 #endif
|
luisf@71
|
159 #endif
|
jtkorhonen@0
|
160
|
Chris@78
|
161 void loseControllingTerminal()
|
Chris@78
|
162 {
|
Chris@78
|
163 #ifndef Q_OS_WIN32
|
Chris@80
|
164
|
Chris@80
|
165 if (!isatty(0)) {
|
Chris@80
|
166 DEBUG << "stdin is not a terminal" << endl;
|
Chris@80
|
167 } else {
|
Chris@80
|
168 DEBUG << "stdin is a terminal, detaching from it" << endl;
|
Chris@80
|
169 if (ioctl(0, TIOCNOTTY, NULL) < 0) {
|
Chris@80
|
170 perror("ioctl failed");
|
Chris@83
|
171 DEBUG << "ioctl for TIOCNOTTY on stdin failed (errno = " << errno << ")" << endl;
|
Chris@78
|
172 } else {
|
Chris@83
|
173 DEBUG << "ioctl for TIOCNOTTY on stdin succeeded" << endl;
|
Chris@83
|
174 return;
|
Chris@78
|
175 }
|
Chris@80
|
176 }
|
Chris@80
|
177
|
Chris@80
|
178 int ttyfd = open("/dev/tty", O_RDWR);
|
Chris@80
|
179 if (ttyfd < 0) {
|
Chris@80
|
180 DEBUG << "failed to open controlling terminal" << endl;
|
Chris@80
|
181 } else {
|
Chris@80
|
182 if (ioctl(ttyfd, TIOCNOTTY, NULL) < 0) {
|
Chris@80
|
183 perror("ioctl failed");
|
Chris@83
|
184 DEBUG << "ioctl for TIOCNOTTY on controlling terminal failed (errno = " << errno << ")" << endl;
|
Chris@80
|
185 } else {
|
Chris@83
|
186 DEBUG << "ioctl for TIOCNOTTY on controlling terminal succeeded" << endl;
|
Chris@83
|
187 return;
|
Chris@78
|
188 }
|
Chris@78
|
189 }
|
Chris@80
|
190
|
Chris@78
|
191 #endif
|
Chris@78
|
192 }
|
Chris@79
|
193
|
Chris@105
|
194 void installSignalHandlers()
|
Chris@105
|
195 {
|
Chris@105
|
196 #ifndef Q_OS_WIN32
|
Chris@105
|
197 sigset_t sgnals;
|
Chris@105
|
198 sigemptyset (&sgnals);
|
Chris@105
|
199 sigaddset(&sgnals, SIGHUP);
|
Chris@105
|
200 sigaddset(&sgnals, SIGCONT);
|
Chris@105
|
201 pthread_sigmask(SIG_BLOCK, &sgnals, 0);
|
Chris@105
|
202 #endif
|
Chris@105
|
203 }
|
Chris@105
|
204
|
Chris@455
|
205 ProcessStatus
|
Chris@455
|
206 GetProcessStatus(int pid)
|
Chris@455
|
207 {
|
Chris@455
|
208 #ifdef _WIN32
|
Chris@455
|
209 HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
|
Chris@455
|
210 if (!handle) {
|
Chris@455
|
211 return ProcessNotRunning;
|
Chris@455
|
212 } else {
|
Chris@455
|
213 CloseHandle(handle);
|
Chris@455
|
214 return ProcessRunning;
|
Chris@455
|
215 }
|
Chris@455
|
216 #else
|
Chris@455
|
217 if (kill(getpid(), 0) == 0) {
|
Chris@455
|
218 if (kill(pid, 0) == 0) {
|
Chris@455
|
219 return ProcessRunning;
|
Chris@455
|
220 } else {
|
Chris@455
|
221 return ProcessNotRunning;
|
Chris@455
|
222 }
|
Chris@455
|
223 } else {
|
Chris@455
|
224 return UnknownProcessStatus;
|
Chris@455
|
225 }
|
Chris@455
|
226 #endif
|
Chris@455
|
227 }
|
Chris@455
|
228
|
Chris@79
|
229 FolderStatus getFolderStatus(QString path)
|
Chris@79
|
230 {
|
Chris@85
|
231 if (path != "/" && path.endsWith("/")) {
|
Chris@85
|
232 path = path.left(path.length()-1);
|
Chris@85
|
233 }
|
Chris@84
|
234 DEBUG << "getFolderStatus: " << path << endl;
|
Chris@79
|
235 QFileInfo fi(path);
|
Chris@79
|
236 if (fi.exists()) {
|
Chris@84
|
237 DEBUG << "exists" << endl;
|
Chris@79
|
238 QDir dir(path);
|
Chris@79
|
239 if (!dir.exists()) { // returns false for files
|
Chris@84
|
240 DEBUG << "not directory" << endl;
|
Chris@79
|
241 return FolderIsFile;
|
Chris@79
|
242 }
|
Chris@79
|
243 if (QDir(dir.filePath(".hg")).exists()) {
|
Chris@84
|
244 DEBUG << "has repo" << endl;
|
Chris@79
|
245 return FolderHasRepo;
|
Chris@79
|
246 }
|
Chris@79
|
247 return FolderExists;
|
Chris@79
|
248 } else {
|
Chris@79
|
249 QDir parent = fi.dir();
|
Chris@79
|
250 if (parent.exists()) {
|
Chris@84
|
251 DEBUG << "parent exists" << endl;
|
Chris@79
|
252 return FolderParentExists;
|
Chris@79
|
253 }
|
Chris@79
|
254 return FolderUnknown;
|
Chris@79
|
255 }
|
Chris@79
|
256 }
|
Chris@79
|
257
|
Chris@79
|
258 QString getContainingRepoFolder(QString path)
|
Chris@79
|
259 {
|
Chris@79
|
260 if (getFolderStatus(path) == FolderHasRepo) return "";
|
Chris@79
|
261
|
Chris@79
|
262 QFileInfo me(path);
|
Chris@79
|
263 QFileInfo parent(me.dir().absolutePath());
|
Chris@79
|
264
|
Chris@79
|
265 while (me != parent) {
|
Chris@79
|
266 QString parentPath = parent.filePath();
|
Chris@79
|
267 if (getFolderStatus(parentPath) == FolderHasRepo) {
|
Chris@79
|
268 return parentPath;
|
Chris@79
|
269 }
|
Chris@79
|
270 me = parent;
|
Chris@79
|
271 parent = me.dir().absolutePath();
|
Chris@79
|
272 }
|
Chris@79
|
273
|
Chris@79
|
274 return "";
|
Chris@79
|
275 }
|
Chris@79
|
276
|
Chris@79
|
277 QString xmlEncode(QString s)
|
Chris@79
|
278 {
|
Chris@79
|
279 s
|
Chris@79
|
280 .replace("&", "&")
|
Chris@79
|
281 .replace("<", "<")
|
Chris@79
|
282 .replace(">", ">")
|
Chris@79
|
283 .replace("\"", """)
|
Chris@79
|
284 .replace("'", "'");
|
Chris@79
|
285
|
Chris@79
|
286 return s;
|
Chris@79
|
287 }
|
Chris@408
|
288
|
Chris@408
|
289 QString uniDecode(QString s)
|
Chris@408
|
290 {
|
Chris@408
|
291 QString d;
|
Chris@408
|
292 for (int i = 0; i < s.length(); ++i) {
|
Chris@412
|
293 // backslash escaped with another backslash: replace with a
|
Chris@410
|
294 // single backslash and skip on
|
Chris@411
|
295 if (i+1 < s.length() && s[i] == '\\' && s[i+1] == '\\') {
|
Chris@411
|
296 d += '\\';
|
Chris@411
|
297 i += 1;
|
Chris@410
|
298 continue;
|
Chris@410
|
299 }
|
Chris@410
|
300 // unescaped backslash followed by u and at least four more
|
Chris@410
|
301 // chars: replace with Unicode character
|
Chris@408
|
302 if (i+5 < s.length() && s[i] == '\\' && s[i+1] == 'u') {
|
Chris@408
|
303 QString uni = s.mid(i+2, 4);
|
Chris@663
|
304 QByteArray ba = QByteArray::fromHex(uni.toLatin1());
|
Chris@408
|
305 d += QChar(ba[1], ba[0]);
|
Chris@408
|
306 i += 5;
|
Chris@410
|
307 continue;
|
Chris@408
|
308 }
|
Chris@410
|
309 // default case: leave alone
|
Chris@410
|
310 d += s[i];
|
Chris@408
|
311 }
|
Chris@408
|
312 return d;
|
Chris@408
|
313 }
|
Chris@443
|
314
|
Chris@443
|
315 QByteArray randomKey()
|
Chris@443
|
316 {
|
Chris@443
|
317 static int len = 16;
|
Chris@443
|
318
|
Chris@443
|
319 QByteArray ba;
|
Chris@443
|
320 ba.resize(len);
|
Chris@443
|
321 char *buffer = ba.data();
|
Chris@443
|
322
|
Chris@443
|
323 #ifdef Q_OS_WIN32
|
Chris@443
|
324
|
Chris@443
|
325 HCRYPTPROV provider = 0;
|
Chris@443
|
326 if (!CryptAcquireContextW(&provider, 0, 0,
|
Chris@443
|
327 PROV_RSA_FULL,
|
Chris@443
|
328 CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) {
|
Chris@443
|
329 return QByteArray();
|
Chris@443
|
330 }
|
Chris@443
|
331
|
chris@447
|
332 if (!CryptGenRandom(provider, len, (BYTE *)buffer)) {
|
Chris@443
|
333 CryptReleaseContext(provider, 0);
|
Chris@443
|
334 return QByteArray();
|
Chris@443
|
335 }
|
Chris@443
|
336
|
Chris@443
|
337 CryptReleaseContext(provider, 0);
|
Chris@443
|
338
|
Chris@443
|
339 #else
|
Chris@443
|
340
|
Chris@443
|
341 FILE *rf = fopen("/dev/urandom", "r");
|
Chris@443
|
342 if (!rf) {
|
Chris@443
|
343 return QByteArray();
|
Chris@443
|
344 }
|
Chris@443
|
345
|
Chris@443
|
346 for (int i = 0; i < len; ++i) {
|
Chris@443
|
347 buffer[i] = fgetc(rf);
|
Chris@443
|
348 }
|
Chris@443
|
349
|
Chris@443
|
350 fclose(rf);
|
Chris@443
|
351
|
Chris@443
|
352 #endif
|
Chris@443
|
353
|
Chris@443
|
354 return ba;
|
Chris@443
|
355 }
|
Chris@443
|
356
|
Chris@678
|
357 int
|
Chris@678
|
358 scalePixelSize(int pixels)
|
Chris@678
|
359 {
|
Chris@678
|
360 static double ratio = 0.0;
|
Chris@678
|
361 if (ratio == 0.0) {
|
Chris@678
|
362 double baseEm;
|
Chris@678
|
363 #ifdef Q_OS_MAC
|
Chris@678
|
364 baseEm = 17.0;
|
Chris@678
|
365 #else
|
Chris@678
|
366 baseEm = 15.0;
|
Chris@678
|
367 #endif
|
Chris@678
|
368 double em = QFontMetrics(QFont()).height();
|
Chris@678
|
369 ratio = em / baseEm;
|
Chris@678
|
370 }
|
Chris@678
|
371
|
Chris@678
|
372 int scaled = int(pixels * ratio + 0.5);
|
Chris@678
|
373 if (pixels != 0 && scaled == 0) scaled = 1;
|
Chris@678
|
374 return scaled;
|
Chris@678
|
375 }
|
Chris@678
|
376
|