1 /*******************************************************************
2
3 Part of the Fritzing project - http://fritzing.org
4 Copyright (c) 2007-2014 Fachhochschule Potsdam - http://fh-potsdam.de
5
6 Fritzing is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Fritzing is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Fritzing. If not, see <http://www.gnu.org/licenses/>.
18
19 ********************************************************************
20
21 $Revision: 7004 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-04-29 13:10:59 +0200 (Mo, 29. Apr 2013) $
24
25 ********************************************************************/
26
27 #include "folderutils.h"
28 #include "lockmanager.h"
29 #include "textutils.h"
30 #include <QDesktopServices>
31 #include <QCoreApplication>
32 #include <QSettings>
33 #include <QTextStream>
34 #include <QUuid>
35 #include <QCryptographicHash>
36 #include <QProcess>
37 #include <QDesktopServices>
38 #include <QUrl>
39 #include <QFileInfo>
40
41 #include "../debugdialog.h"
42 #ifdef QUAZIP_INSTALLED
43 #include <quazip/quazip.h>
44 #include <quazip/quazipfile.h>
45 #else
46 #include "../lib/quazip/quazip.h"
47 #include "../lib/quazip/quazipfile.h"
48 #endif
49 #include "../lib/qtsysteminfo/QtSystemInfo.h"
50
51
52 FolderUtils* FolderUtils::singleton = NULL;
53 QString FolderUtils::m_openSaveFolder = "";
54
FolderUtils()55 FolderUtils::FolderUtils() {
56 m_openSaveFolder = ___emptyString___;
57 m_folders
58 << "/bins"
59 << "/partfactory"
60 << "/parts/user" << "/parts/contrib"
61 << "/parts/svg/user/icon" << "/parts/svg/user/breadboard" << "/parts/svg/user/schematic" << "/parts/svg/user/pcb"
62 << "/parts/svg/contrib/icon" << "/parts/svg/contrib/breadboard" << "/parts/svg/contrib/schematic" << "/parts/svg/contrib/pcb"
63 << "/backup"
64 << "/fzz";
65 }
66
~FolderUtils()67 FolderUtils::~FolderUtils() {
68 }
69
70 // finds a subfolder of the application directory searching backward up the tree
getApplicationSubFolder(QString search)71 QDir * FolderUtils::getApplicationSubFolder(QString search) {
72 if (singleton == NULL) {
73 singleton = new FolderUtils();
74 }
75
76 QString path = singleton->applicationDirPath();
77 path += "/" + search;
78 //DebugDialog::debug(QString("path %1").arg(path) );
79 QDir* dir= new QDir(path);
80 while (!dir->exists()) {
81 // if we're running from the debug or release folder, go up one to find things
82 dir->cdUp();
83 dir->cdUp();
84 if (dir->isRoot()) return NULL; // didn't find the search folder
85
86 dir->setPath(dir->absolutePath() + "/" + search);
87 }
88
89 return dir;
90 }
91
getApplicationSubFolderPath(QString search)92 QString FolderUtils::getApplicationSubFolderPath(QString search) {
93 if (singleton == NULL) {
94 singleton = new FolderUtils();
95 }
96
97 QDir * dir = getApplicationSubFolder(search);
98 if (dir == NULL) return "";
99
100 QString result = dir->path();
101 delete dir;
102 return result;
103 }
104
getUserDataStorePath(QString folder)105 QString FolderUtils::getUserDataStorePath(QString folder) {
106 QString settingsFile = QSettings(QSettings::IniFormat,QSettings::UserScope,"Fritzing","Fritzing").fileName();
107 return QFileInfo(settingsFile).dir()
108 .absolutePath()+(folder.isEmpty()?"":QString("/")+folder);
109 }
110
getUserDataStoreFolders()111 const QStringList & FolderUtils::getUserDataStoreFolders() {
112 if (singleton == NULL) {
113 singleton = new FolderUtils();
114 }
115
116 return singleton->userDataStoreFolders();
117 }
118
createFolderAnCdIntoIt(QDir & dir,QString newFolder)119 bool FolderUtils::createFolderAnCdIntoIt(QDir &dir, QString newFolder) {
120 if(!dir.mkdir(newFolder)) return false;
121 if(!dir.cd(newFolder)) return false;
122
123 return true;
124 }
125
setApplicationPath(const QString & path)126 bool FolderUtils::setApplicationPath(const QString & path)
127 {
128 if (singleton == NULL) {
129 singleton = new FolderUtils();
130 }
131
132 return singleton->setApplicationPath2(path);
133 }
134
cleanup()135 void FolderUtils::cleanup() {
136 if (singleton) {
137 delete singleton;
138 singleton = NULL;
139 }
140 }
141
getLibraryPath()142 const QString FolderUtils::getLibraryPath()
143 {
144 if (singleton == NULL) {
145 singleton = new FolderUtils();
146 }
147
148 return singleton->libraryPath();
149 }
150
151
libraryPath()152 const QString FolderUtils::libraryPath()
153 {
154 #ifdef Q_OS_MAC
155 // mac plugins are always in the bundle
156 return QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../lib");
157 #endif
158
159 return QDir::cleanPath(applicationDirPath() + "/lib");
160 }
161
applicationDirPath()162 const QString FolderUtils::applicationDirPath() {
163 if (m_appPath.isEmpty()) {
164 #ifdef Q_OS_WIN
165 return QCoreApplication::applicationDirPath();
166 #else
167 // look in standard Fritzing location (applicationDirPath and parent folders) then in standard linux locations
168 QStringList candidates;
169 candidates.append(QCoreApplication::applicationDirPath());
170 QDir dir(QCoreApplication::applicationDirPath());
171 if (dir.cdUp()) {
172 candidates.append(dir.absolutePath());
173 if (dir.cdUp()) {
174 candidates.append(dir.absolutePath());
175 if (dir.cdUp()) {
176 candidates.append(dir.absolutePath());
177 }
178 }
179 }
180
181 #ifdef PKGDATADIR
182 candidates.append(QLatin1String(PKGDATADIR));
183 #else
184 candidates.append("/usr/share/fritzing");
185 candidates.append("/usr/local/share/fritzing");
186 #endif
187 candidates.append(QDir::homePath() + "/.local/share/fritzing");
188 foreach (QString candidate, candidates) {
189 //DebugDialog::debug(QString("candidate:%1").arg(candidate));
190 QDir dir(candidate);
191 if (!dir.exists("parts")) continue;
192
193 if (dir.exists("bins")) {
194 m_appPath = candidate;
195 return m_appPath;
196 }
197
198 }
199
200 m_appPath = QCoreApplication::applicationDirPath();
201 DebugDialog::debug("data folders not found");
202
203 #endif
204 }
205
206 return m_appPath;
207 }
208
setApplicationPath2(const QString & path)209 bool FolderUtils::setApplicationPath2(const QString & path)
210 {
211 QDir dir(path);
212 if (!dir.exists()) return false;
213
214 m_appPath = path;
215 return true;
216 }
217
userDataStoreFolders()218 const QStringList & FolderUtils::userDataStoreFolders() {
219 return m_folders;
220 }
221
setOpenSaveFolder(const QString & path)222 void FolderUtils::setOpenSaveFolder(const QString& path) {
223 setOpenSaveFolderAux(path);
224 QSettings settings;
225 settings.setValue("openSaveFolder", m_openSaveFolder);
226 }
227
setOpenSaveFolderAux(const QString & path)228 void FolderUtils::setOpenSaveFolderAux(const QString& path)
229 {
230 QFileInfo fileInfo(path);
231 if(fileInfo.isDir()) {
232 m_openSaveFolder = path;
233 } else {
234 m_openSaveFolder = fileInfo.path().remove(fileInfo.fileName());
235 }
236 }
237
238
openSaveFolder()239 const QString FolderUtils::openSaveFolder() {
240 if(m_openSaveFolder.isEmpty()) {
241 QSettings settings;
242 QString tempFolder = settings.value("openSaveFolder").toString();
243 if (!tempFolder.isEmpty()) {
244 QFileInfo fileInfo(tempFolder);
245 if (fileInfo.exists()) {
246 m_openSaveFolder = tempFolder;
247 return m_openSaveFolder;
248 }
249 else {
250 settings.remove("openSaveFolder");
251 }
252 }
253
254 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
255 DebugDialog::debug(QString("default save location: %1").arg(QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation)));
256 return QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
257 #else
258 DebugDialog::debug(QString("default save location: %1").arg(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)));
259 return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
260 #endif
261 } else {
262 return m_openSaveFolder;
263 }
264 }
265
266
getOpenFileName(QWidget * parent,const QString & caption,const QString & dir,const QString & filter,QString * selectedFilter,QFileDialog::Options options)267 QString FolderUtils::getOpenFileName( QWidget * parent, const QString & caption, const QString & dir, const QString & filter, QString * selectedFilter, QFileDialog::Options options )
268 {
269 QString result = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options);
270 if (!result.isNull()) {
271 setOpenSaveFolder(result);
272 }
273 return result;
274 }
275
getOpenFileNames(QWidget * parent,const QString & caption,const QString & dir,const QString & filter,QString * selectedFilter,QFileDialog::Options options)276 QStringList FolderUtils::getOpenFileNames( QWidget * parent, const QString & caption, const QString & dir, const QString & filter, QString * selectedFilter, QFileDialog::Options options )
277 {
278 QStringList result = QFileDialog::getOpenFileNames(parent, caption, dir, filter, selectedFilter, options);
279 if (result.count() > 0) {
280 setOpenSaveFolder(result.at(0));
281 }
282 return result;
283 }
284
getSaveFileName(QWidget * parent,const QString & caption,const QString & dir,const QString & filter,QString * selectedFilter,QFileDialog::Options options)285 QString FolderUtils::getSaveFileName( QWidget * parent, const QString & caption, const QString & dir, const QString & filter, QString * selectedFilter, QFileDialog::Options options )
286 {
287 //DebugDialog::debug(QString("getopenfilename %1 %2 %3 %4").arg(caption).arg(dir).arg(filter).arg(*selectedFilter));
288 QString result = QFileDialog::getSaveFileName(parent, caption, dir, filter, selectedFilter, options);
289 if (!result.isNull()) {
290 setOpenSaveFolder(result);
291 }
292 return result;
293 }
294
isEmptyFileName(const QString & fileName,const QString & untitledFileName)295 bool FolderUtils::isEmptyFileName(const QString &fileName, const QString &untitledFileName) {
296 return (fileName.isEmpty() || fileName.isNull() || fileName.startsWith(untitledFileName));
297 }
298
replicateDir(QDir srcDir,QDir targDir)299 void FolderUtils::replicateDir(QDir srcDir, QDir targDir) {
300 // copy all files from srcDir source to tagDir
301 QStringList files = srcDir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot);
302 for(int i=0; i < files.size(); i++) {
303 QFile tempFile(srcDir.path() + "/" +files.at(i));
304 DebugDialog::debug(QObject::tr("Copying file %1").arg(tempFile.fileName()));
305 QFileInfo fi(files.at(i));
306 QString newFilePath = targDir.path() + "/" + fi.fileName();
307 if(QFileInfo(tempFile.fileName()).isDir()) {
308 QDir newTargDir = QDir(newFilePath);
309 newTargDir.mkpath(newTargDir.absolutePath());
310 newTargDir.cd(files.at(i));
311 replicateDir(QDir(tempFile.fileName()),newTargDir);
312 } else {
313 if(!tempFile.copy(newFilePath)) {
314 DebugDialog::debug(QObject::tr("File %1 already exists: it won't be overwritten").arg(newFilePath));
315 }
316 }
317 }
318 }
319
320 // NOTE: This function cannot remove directories that have non-empty name filters set on it.
rmdir(const QString & dirPath)321 void FolderUtils::rmdir(const QString &dirPath) {
322 QDir dir = QDir(dirPath);
323 rmdir(dir);
324 }
325
rmdir(QDir & dir)326 void FolderUtils::rmdir(QDir & dir) {
327 //DebugDialog::debug(QString("removing folder: %1").arg(dir.path()));
328
329 QStringList files = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot);
330 for(int i=0; i < files.size(); i++) {
331 QFile tempFile(dir.path() + "/" +files.at(i));
332 //DebugDialog::debug(QString("removing from original folder: %1").arg(tempFile.fileName()));
333 if(QFileInfo(tempFile.fileName()).isDir()) {
334 QDir dir = QDir(tempFile.fileName());
335 rmdir(dir);
336 } else {
337 tempFile.remove(tempFile.fileName());
338 }
339 }
340 dir.rmdir(dir.path());
341 }
342
createZipAndSaveTo(const QDir & dirToCompress,const QString & filepath,const QStringList & skipSuffixes)343 bool FolderUtils::createZipAndSaveTo(const QDir &dirToCompress, const QString &filepath, const QStringList & skipSuffixes) {
344 DebugDialog::debug("zipping "+dirToCompress.path()+" into "+filepath);
345
346 QString tempZipFile = QDir::temp().path()+"/"+TextUtils::getRandText()+".zip";
347 DebugDialog::debug("temp file: "+tempZipFile);
348 QuaZip zip(tempZipFile);
349 if(!zip.open(QuaZip::mdCreate)) {
350 qWarning("zip.open(): %d", zip.getZipError());
351 return false;
352 }
353
354 QFileInfoList files=dirToCompress.entryInfoList();
355 QFile inFile;
356 QuaZipFile outFile(&zip);
357 char c;
358
359 QString currFolderBU = QDir::currentPath();
360 QDir::setCurrent(dirToCompress.path());
361 foreach(QFileInfo file, files) {
362 if(!file.isFile()||file.fileName()==filepath) continue;
363 if (file.fileName().contains(LockManager::LockedFileName)) continue;
364
365 bool skip = false;
366 foreach (QString suffix, skipSuffixes) {
367 if (file.fileName().endsWith(suffix)) {
368 skip = true;
369 break;
370 }
371 }
372 if (skip) continue;
373
374 //#pragma message("remove fzz check")
375 //if (file.fileName().endsWith(".fzz")) continue;
376
377 inFile.setFileName(file.fileName());
378
379 if(!inFile.open(QIODevice::ReadOnly)) {
380 qWarning("inFile.open(): %s", inFile.errorString().toLocal8Bit().constData());
381 return false;
382 }
383 if(!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(inFile.fileName(), inFile.fileName()))) {
384 qWarning("outFile.open(): %d", outFile.getZipError());
385 return false;
386 }
387
388 while(inFile.getChar(&c)&&outFile.putChar(c)){}
389
390 if(outFile.getZipError()!=UNZ_OK) {
391 qWarning("outFile.putChar(): %d", outFile.getZipError());
392 return false;
393 }
394 outFile.close();
395 if(outFile.getZipError()!=UNZ_OK) {
396 qWarning("outFile.close(): %d", outFile.getZipError());
397 return false;
398 }
399 inFile.close();
400 }
401 zip.close();
402 QDir::setCurrent(currFolderBU);
403
404 if(QFileInfo(filepath).exists()) {
405 // if we're here the usr has already accepted to overwrite
406 QFile::remove(filepath);
407 }
408 QFile file(tempZipFile);
409 FolderUtils::slamCopy(file, filepath);
410 file.remove();
411
412 if(zip.getZipError()!=0) {
413 qWarning("zip.close(): %d", zip.getZipError());
414 return false;
415 }
416 return true;
417 }
418
419
unzipTo(const QString & filepath,const QString & dirToDecompress,QString & error)420 bool FolderUtils::unzipTo(const QString &filepath, const QString &dirToDecompress, QString & error) {
421 static QChar badCharacters[] = { '\\', '/', ':', '*', '?', '"', '<', '>', '|' };
422 static QChar underscore('_');
423
424 QuaZip zip(filepath);
425 if(!zip.open(QuaZip::mdUnzip)) {
426 error = QString("zip.open(): %d").arg(zip.getZipError());
427 DebugDialog::debug(error);
428 return false;
429 }
430
431 zip.setFileNameCodec("IBM866");
432 DebugDialog::debug(QString("unzipping %1 entries from %2").arg(zip.getEntriesCount()).arg(filepath));
433 QuaZipFileInfo info;
434 QuaZipFile file(&zip);
435 QFile out;
436 QString name;
437 char c;
438 for(bool more=zip.goToFirstFile(); more; more=zip.goToNextFile()) {
439 if(!zip.getCurrentFileInfo(&info)) {
440 error = QString("getCurrentFileInfo(): %d\n").arg(zip.getZipError());
441 DebugDialog::debug(error);
442 return false;
443 }
444
445 if(!file.open(QIODevice::ReadOnly)) {
446 error = QString("file.open(): %d").arg(file.getZipError());
447 DebugDialog::debug(error);
448 return false;
449 }
450 name=file.getActualFileName();
451 if(file.getZipError()!=UNZ_OK) {
452 error = QString("file.getFileName(): %d").arg(file.getZipError());
453 DebugDialog::debug(error);
454 return false;
455 }
456
457 out.setFileName(dirToDecompress+"/"+name);
458 // this will fail if "name" contains subdirectories, but we don't mind that
459 if(!out.open(QIODevice::WriteOnly)) {
460 for (int i = 0; i < name.length(); i++) {
461 if (name[i].unicode() < 32) {
462 name.replace(i, 1, &underscore, 1);
463 }
464 else for (unsigned int j = 0; j < (sizeof(badCharacters) / sizeof(QChar)); j++) {
465 if (name[i] == badCharacters[j]) {
466 name.replace(i, 1, &underscore, 1);
467 break;
468 }
469 }
470 }
471 out.setFileName(dirToDecompress+"/"+name);
472 if(!out.open(QIODevice::WriteOnly)) {
473 error = QString("out.open(): %s").arg(out.errorString().toLocal8Bit().constData());
474 DebugDialog::debug(error);
475 return false;
476 }
477 }
478
479 // Slow like hell (on GNU/Linux at least), but it is not my fault.
480 // Not ZIP/UNZIP package's fault either.
481 // The slowest thing here is out.putChar(c).
482 // TODO: now that out.putChar has been replaced with a buffered write, is it still slow under Linux?
483
484 #define BUFFERSIZE 1024
485 char buffer[BUFFERSIZE];
486 int ix = 0;
487 while(file.getChar(&c)) {
488 buffer[ix++] = c;
489 if (ix == BUFFERSIZE) {
490 out.write(buffer, ix);
491 ix = 0;
492 }
493 }
494 if (ix > 0) {
495 out.write(buffer, ix);
496 }
497
498 out.close();
499 if(file.getZipError()!=UNZ_OK) {
500 error = QString("file.getFileName(): %d").arg(file.getZipError());
501 DebugDialog::debug(error);
502 return false;
503 }
504 if(!file.atEnd()) {
505 error = "read all but not EOF";
506 DebugDialog::debug(error);
507 return false;
508 }
509 file.close();
510 if(file.getZipError()!=UNZ_OK) {
511 error = QString("file.close(): %d").arg(file.getZipError());
512 DebugDialog::debug(error);
513 return false;
514 }
515 }
516 zip.close();
517 if(zip.getZipError()!=UNZ_OK) {
518 error = QString("zip.close(): %d").arg(zip.getZipError());
519 DebugDialog::debug(error);
520 return false;
521 }
522 return true;
523 }
524
collectFiles(const QDir & parent,QStringList & filters,QStringList & files,bool recursive)525 void FolderUtils::collectFiles(const QDir & parent, QStringList & filters, QStringList & files, bool recursive)
526 {
527 QFileInfoList fileInfoList = parent.entryInfoList(filters, QDir::Files | QDir::Hidden | QDir::NoSymLinks);
528 foreach (QFileInfo fileInfo, fileInfoList) {
529 files.append(fileInfo.absoluteFilePath());
530 }
531
532 if (recursive) {
533 QFileInfoList dirList = parent.entryInfoList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::NoSymLinks);
534 foreach (QFileInfo dirInfo, dirList) {
535 QDir dir(dirInfo.filePath());
536 //DebugDialog::debug(QString("looking in backup dir %1").arg(dir.absolutePath()));
537
538 collectFiles(dir, filters, files, recursive);
539 }
540 }
541 }
542
543
544
makePartFolderHierarchy(const QString & prefixFolder,const QString & destFolder)545 void FolderUtils::makePartFolderHierarchy(const QString & prefixFolder, const QString & destFolder) {
546 QDir dir(prefixFolder);
547
548 dir.mkdir(destFolder);
549 dir.mkdir("svg");
550 dir.cd("svg");
551 dir.mkdir(destFolder);
552 dir.cd(destFolder);
553 dir.mkdir("icon");
554 dir.mkdir("breadboard");
555 dir.mkdir("schematic");
556 dir.mkdir("pcb");
557 }
558
copyBin(const QString & dest,const QString & source)559 void FolderUtils::copyBin(const QString & dest, const QString & source) {
560 if(QFileInfo(dest).exists()) return;
561
562 // this copy action, is not working on windows, because is a resources file
563 if(!QFile(source).copy(dest)) {
564 #ifdef Q_OS_WIN // may not be needed from qt 4.5.2 on
565 DebugDialog::debug("Failed to copy a file from the resources");
566 QDir binsFolder = QFileInfo(dest).dir().absolutePath();
567 QStringList binFiles = binsFolder.entryList(QDir::AllEntries | QDir::NoDotAndDotDot);
568 foreach(QString binName, binFiles) {
569 if(binName.startsWith("qt_temp.")) {
570 QString filePath = binsFolder.absoluteFilePath(binName);
571 bool success = QFile(filePath).rename(dest);
572 Q_UNUSED(success);
573 break;
574 }
575 }
576 #endif
577 }
578 QFlags<QFile::Permission> ps = QFile::permissions(dest);
579 QFile::setPermissions(
580 dest,
581 QFile::WriteOwner | QFile::WriteUser | ps
582 #ifdef Q_OS_WIN
583 | QFile::WriteOther | QFile::WriteGroup
584 #endif
585
586 );
587 }
588
slamCopy(QFile & file,const QString & dest)589 bool FolderUtils::slamCopy(QFile & file, const QString & dest) {
590 QFileInfo info(file);
591 if (info.absoluteFilePath() == dest) {
592 // source = dest
593 return true;
594 }
595
596 bool result = file.copy(dest);
597 if (result) return result;
598
599 file.remove(dest);
600 return file.copy(dest);
601 }
602
showInFolder(const QString & path)603 void FolderUtils::showInFolder(const QString & path)
604 {
605 // http://stackoverflow.com/questions/3490336/how-to-reveal-in-finder-or-show-in-explorer-with-qt
606 // http://stackoverflow.com/questions/9581330/change-selection-in-explorer-window
607 // Mac, Windows support folder or file.
608 #if defined(Q_OS_WIN)
609 const QString explorer = "explorer.exe";
610 QString param = QLatin1String("/e,/select,");
611 param += QDir::toNativeSeparators(path);
612 QProcess::startDetached(explorer, QStringList(param));
613 #elif defined(Q_OS_MAC)
614 QStringList scriptArgs;
615 scriptArgs << QLatin1String("-e")
616 << QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"")
617 .arg(path);
618 QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs);
619 scriptArgs.clear();
620 scriptArgs << QLatin1String("-e")
621 << QLatin1String("tell application \"Finder\" to activate");
622 QProcess::execute("/usr/bin/osascript", scriptArgs);
623 #else
624 QDesktopServices::openUrl( QUrl::fromLocalFile( QFileInfo(path).absolutePath() ) );
625 #endif
626 }
627