1 /* FileUtils.cpp */
2
3 /* Copyright (C) 2011-2020 Michael Lugmair (Lucio Carreras)
4 *
5 * This file is part of sayonara player
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "Utils/FileUtils.h"
22 #include "Utils/Utils.h"
23 #include "Utils/Algorithm.h"
24 #include "Utils/Logger/Logger.h"
25 #include "Utils/Language/Language.h"
26 #include "Utils/Set.h"
27
28 #include <QUrl>
29 #include <QDir>
30 #include <QFile>
31 #include <QFileInfo>
32 #include <QStringList>
33 #include <QCryptographicHash>
34
35 namespace Algorithm = ::Util::Algorithm;
36
cleanFilename(const QString & path)37 QString Util::File::cleanFilename(const QString& path)
38 {
39 return (path.trimmed().isEmpty()) ?
40 QString("") :
41 QDir::cleanPath(path);
42 }
43
removeFilesInDirectory(const QString & dirName)44 void Util::File::removeFilesInDirectory(const QString& dirName)
45 {
46 removeFilesInDirectory(dirName, QStringList());
47 }
48
removeFilesInDirectory(const QString & dirName,const QStringList & filters)49 void Util::File::removeFilesInDirectory(const QString& dirName, const QStringList& filters)
50 {
51 if(dirName.contains(".."))
52 {
53 return;
54 }
55
56 auto dir = QDir(dirName);
57 dir.setNameFilters(filters);
58
59 const auto fileInfoList = dir.entryInfoList
60 (
61 QDir::Filters(QDir::System | QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot)
62 );
63
64 for(const auto& fileInfo : fileInfoList)
65 {
66 const auto path = fileInfo.absoluteFilePath();
67
68 if(fileInfo.isSymLink())
69 {
70 QFile::remove(path);
71 }
72
73 else if(fileInfo.isDir())
74 {
75 removeFilesInDirectory(path);
76 QDir().rmdir(path);
77 }
78
79 else if(fileInfo.isFile())
80 {
81 QFile::remove(path);
82 }
83 }
84 }
85
deleteFiles(const QStringList & paths)86 void Util::File::deleteFiles(const QStringList& paths)
87 {
88 if(paths.isEmpty())
89 {
90 return;
91 }
92
93 spLog(Log::Develop, "Util::File") << "I will delete " << paths;
94 auto sortedPaths = paths;
95 Algorithm::sort(sortedPaths, [](const QString& str1, const QString& str2) {
96 return (str1.size() > str2.size());
97 });
98
99 for(const auto& path : Algorithm::AsConst(sortedPaths))
100 {
101 if(path.contains(".."))
102 {
103 continue;
104 }
105
106 const auto info = QFileInfo(path);
107 if(!info.exists())
108 {
109 continue;
110 }
111
112 if(info.isSymLink())
113 {
114 QFile::remove(info.absoluteFilePath());
115 }
116
117 else if(info.isDir())
118 {
119 removeFilesInDirectory(path);
120 QDir().rmdir(path);
121 }
122
123 else
124 {
125 QFile::remove(path);
126 }
127 }
128 }
129
getParentDirectory(const QString & filename)130 QString Util::File::getParentDirectory(const QString& filename)
131 {
132 const auto cleaned = cleanFilename(filename);
133 const auto index = cleaned.lastIndexOf(QDir::separator());
134
135 return (index > 0)
136 ? cleanFilename(cleaned.left(index))
137 : QDir::rootPath();
138 }
139
getFilenameOfPath(const QString & path)140 QString Util::File::getFilenameOfPath(const QString& path)
141 {
142 return QDir(cleanFilename(path)).dirName();
143 }
144
splitFilename(const QString & src,QString & path,QString & filename)145 void Util::File::splitFilename(const QString& src, QString& path, QString& filename)
146 {
147 path = Util::File::getParentDirectory(src);
148 filename = Util::File::getFilenameOfPath(src);
149 }
150
splitFilename(const QString & src)151 std::pair<QString, QString> Util::File::splitFilename(const QString& src)
152 {
153 std::pair<QString, QString> ret;
154 splitFilename(src, ret.first, ret.second);
155
156 return ret;
157 }
158
getParentDirectories(const QStringList & files)159 QStringList Util::File::getParentDirectories(const QStringList& files)
160 {
161 Util::Set<QString> folders;
162 for(const auto& file : files)
163 {
164 const auto folder = getParentDirectory(file);
165 folders.insert(folder);
166 }
167
168 return QStringList(folders.toList());
169 }
170
getAbsoluteFilename(const QString & filename)171 QString Util::File::getAbsoluteFilename(const QString& filename)
172 {
173 return QDir(filename).absolutePath();
174 }
175
getFilesizeString(Filesize filesize)176 QString Util::File::getFilesizeString(Filesize filesize)
177 {
178 Filesize kb = 1 << 10; // 1024
179 Filesize mb = kb << 10;
180 Filesize gb = mb << 10;
181
182 QString size;
183 if(filesize > gb)
184 {
185 size =
186 QString::number(filesize / gb) + "." + QString::number((filesize / mb) % gb).left(2) +
187 " " + Lang::get(Lang::GB);
188 }
189
190 else if(filesize > mb)
191 {
192 size =
193 QString::number(filesize / mb) + "." + QString::number((filesize / kb) % mb).left(2) +
194 " " + Lang::get(Lang::MB);
195 }
196
197 else
198 {
199 size = QString::number(filesize / kb) + " " + +" " + Lang::get(Lang::KB);
200 }
201
202 return size;
203 }
204
isUrl(const QString & str)205 bool Util::File::isUrl(const QString& str)
206 {
207 const auto url = QUrl(str);
208 return (url.isValid() && !url.scheme().isEmpty());
209 }
210
isWWW(const QString & str)211 bool Util::File::isWWW(const QString& str)
212 {
213 const auto schemes = QStringList{"http", "https", "ftp", "itpc", "feed"};
214 const auto url = QUrl(str);
215 const auto scheme = url.scheme().toLower();
216
217 return (url.isValid() && schemes.contains(scheme));
218 }
219
isDir(const QString & filename)220 bool Util::File::isDir(const QString& filename)
221 {
222 return QFileInfo(filename).isDir();
223 }
224
isFile(const QString & filename)225 bool Util::File::isFile(const QString& filename)
226 {
227 return QFileInfo(filename).isFile();
228 }
229
isSoundFile(const QString & filename)230 bool Util::File::isSoundFile(const QString& filename)
231 {
232 const auto extensions = Util::soundfileExtensions(true);
233 return Algorithm::contains(extensions, [&filename](const auto& extension) {
234 return (filename.endsWith(extension.rightRef(4), Qt::CaseInsensitive));
235 });
236 }
237
isPlaylistFile(const QString & filename)238 bool Util::File::isPlaylistFile(const QString& filename)
239 {
240 const auto extensions = Util::playlistExtensions(true);
241 return Algorithm::contains(extensions, [&filename](const auto& extension) {
242 return (filename.endsWith(extension.rightRef(4), Qt::CaseInsensitive));
243 });
244 }
245
isImageFile(const QString & filename)246 bool Util::File::isImageFile(const QString& filename)
247 {
248 const auto extensions = Util::imageExtensions(true);
249 return Algorithm::contains(extensions, [&filename](const auto& extension) {
250 return (filename.endsWith(extension.rightRef(4), Qt::CaseInsensitive));
251 });
252 }
253
createDirectories(const QString & path)254 bool Util::File::createDirectories(const QString& path)
255 {
256 return QDir().mkpath(path);
257 }
258
isAbsolute(const QString & filename)259 bool Util::File::isAbsolute(const QString& filename)
260 {
261 return QDir::isAbsolutePath(filename);
262 }
263
writeFile(const QByteArray & arr,const QString & filename)264 bool Util::File::writeFile(const QByteArray& arr, const QString& filename)
265 {
266 QFile f(filename);
267 if(!f.open(QFile::WriteOnly))
268 {
269 return false;
270 }
271
272 const auto bytesWritten = f.write(arr);
273
274 f.close();
275
276 return (bytesWritten >= arr.size());
277 }
278
readFileIntoByteArray(const QString & filename,QByteArray & content)279 bool Util::File::readFileIntoByteArray(const QString& filename, QByteArray& content)
280 {
281 QFile file(filename);
282 content.clear();
283
284 if(!file.open(QIODevice::ReadOnly))
285 {
286 return false;
287 }
288
289 while(!file.atEnd())
290 {
291 const auto arr = file.read(4096);
292 content.append(arr);
293 }
294
295 file.close();
296
297 return (content.size() > 0);
298 }
299
readFileIntoString(const QString & filename,QString & content)300 bool Util::File::readFileIntoString(const QString& filename, QString& content)
301 {
302 QFile file(filename);
303 content.clear();
304 if(!file.open(QIODevice::ReadOnly))
305 {
306 return false;
307 }
308
309 while(!file.atEnd())
310 {
311 content.append(QString::fromUtf8(file.readLine()));
312 }
313
314 file.close();
315
316 return !content.isEmpty();
317 }
318
319 // hallo.txta
320 // . = 5
getFileExtension(const QString & filename)321 QString Util::File::getFileExtension(const QString& filename)
322 {
323 const auto lastDot = filename.lastIndexOf(".");
324 const auto lastSep = filename.lastIndexOf(QDir::separator());
325
326 if(lastDot < 0 && lastDot < lastSep)
327 {
328 return QString();
329 }
330
331 const auto ext = filename.mid(lastDot + 1);
332 return (ext.size() > 4) ? QString() : ext;
333 }
334
checkFile(const QString & filepath)335 bool Util::File::checkFile(const QString& filepath)
336 {
337 return (isWWW(filepath) || exists(filepath));
338 }
339
createSymlink(const QString & source,const QString & target)340 bool Util::File::createSymlink(const QString& source, const QString& target)
341 {
342 QFileInfo targetInfo(target);
343 if(targetInfo.exists() && targetInfo.isSymLink())
344 {
345 Util::File::deleteFiles({target});
346 }
347
348 QFile f(source);
349 return f.link(target);
350 }
351
checkSymLink(const QString & symlinkPath)352 bool Util::File::checkSymLink(const QString& symlinkPath)
353 {
354 const auto info = QFileInfo(symlinkPath);
355 if(info.isSymLink())
356 {
357 if(Util::File::exists(info.symLinkTarget()))
358 {
359 return true;
360 }
361
362 Util::File::deleteFiles({symlinkPath});
363 }
364
365 return false;
366 }
367
getCommonDirectory(QString dir1,QString dir2)368 QString Util::File::getCommonDirectory(QString dir1, QString dir2)
369 {
370 while(dir1.compare(dir2) != 0)
371 {
372 while(dir1.size() > dir2.size())
373 {
374 auto d1 = QDir(dir1);
375 const auto canUp = d1.cdUp();
376 if(!canUp)
377 {
378 return QDir::rootPath();
379 }
380 dir1 = d1.absolutePath();
381 }
382
383 while(dir2.size() > dir1.size())
384 {
385 auto d2 = QDir(dir2);
386 const auto canUp = d2.cdUp();
387 if(!canUp)
388 {
389 return QDir::rootPath();
390 }
391
392 dir2 = d2.absolutePath();
393 }
394 }
395
396 return dir1;
397 }
398
getCommonDirectory(const QStringList & paths)399 QString Util::File::getCommonDirectory(const QStringList& paths)
400 {
401 if(paths.isEmpty())
402 {
403 return QDir::rootPath();
404 }
405
406 if(paths.size() == 1)
407 {
408 return QDir(paths[0]).absolutePath();
409 }
410
411 QString ret;
412 QStringList absolutePaths;
413 for(const auto& path : paths)
414 {
415 const auto filename = getAbsoluteFilename(path);
416 const auto info = QFileInfo(filename);
417
418 if(info.isFile() || info.isDir())
419 {
420 absolutePaths << QDir(filename).absolutePath();
421 }
422
423 else if(info.isRoot())
424 {
425 return QDir::rootPath();
426 }
427 }
428
429 if(absolutePaths.isEmpty())
430 {
431 return QDir::rootPath();
432 }
433
434 if(absolutePaths.size() == 1)
435 {
436 return absolutePaths[0];
437 }
438
439 ret = absolutePaths[0];
440
441 for(const auto& absolutePath : Algorithm::AsConst(absolutePaths))
442 {
443 ret = getCommonDirectory(ret, absolutePath);
444 }
445
446 return ret;
447 }
448
splitDirectories(const QString & path)449 QStringList Util::File::splitDirectories(const QString& path)
450 {
451 auto ret = path.split(QDir::separator());
452 ret.removeAll("");
453
454 return ret;
455 }
456
createDir(const QString & dirName)457 bool Util::File::createDir(const QString& dirName)
458 {
459 return (QDir(dirName).exists() || QDir().mkdir(dirName));
460 }
461
copyDir(const QString & sourceDirectory,const QString & targetDirectory,QString & newFilename)462 bool Util::File::copyDir(const QString& sourceDirectory, const QString& targetDirectory,
463 QString& newFilename)
464 {
465 newFilename.clear();
466
467 if(!canCopyDir(sourceDirectory, targetDirectory))
468 {
469 return false;
470 }
471
472 spLog(Log::Debug, "File") << "Copy " << sourceDirectory << " to " << targetDirectory;
473 spLog(Log::Debug, "File") << "Create dir: " << targetDirectory;
474 if(!createDir(targetDirectory))
475 {
476 return false;
477 }
478
479 const auto src = QDir(sourceDirectory);
480 const auto copyTo = QDir(targetDirectory).absoluteFilePath(src.dirName());
481
482 spLog(Log::Debug, "File") << "Create dir: " << copyTo;
483 if(!createDir(copyTo))
484 {
485 return false;
486 }
487
488 const auto srcInfos = src.entryInfoList(QStringList(),
489 (QDir::Files | QDir::Dirs |
490 QDir::Filter::NoDotAndDotDot));
491 for(const auto& info : srcInfos)
492 {
493 if(info.isDir())
494 {
495 QString nf;
496 const auto success = copyDir(info.filePath(), copyTo, nf);
497 if(!success)
498 {
499 return false;
500 }
501 }
502
503 else if(info.isSymLink())
504 {
505 newFilename = info.filePath();
506 newFilename.remove(sourceDirectory);
507 newFilename.prepend(copyTo);
508
509 const auto target = info.symLinkTarget();
510 createSymlink(info.symLinkTarget(), newFilename);
511 }
512
513 else
514 {
515 const auto oldFilename = info.filePath();
516
517 newFilename = oldFilename;
518 newFilename.remove(sourceDirectory);
519 newFilename.prepend(copyTo);
520
521 auto file = QFile(oldFilename);
522 spLog(Log::Debug, "File") << "Copy file " << oldFilename << " to " << newFilename;
523 file.copy(newFilename);
524 }
525 }
526
527 newFilename = QDir(targetDirectory).filePath(src.dirName());
528
529 return true;
530 }
531
moveDir(const QString & sourceDirectory,const QString & targetDirectory,QString & newFilename)532 bool Util::File::moveDir(const QString& sourceDirectory, const QString& targetDirectory,
533 QString& newFilename)
534 {
535 newFilename = QString();
536
537 QDir s(sourceDirectory);
538 QDir t(targetDirectory);
539
540 bool success = renameDir(sourceDirectory, t.filePath(s.dirName()));
541
542 if(success)
543 {
544 newFilename = t.filePath(s.dirName());
545 }
546
547 return success;
548 }
549
canCopyDir(const QString & srcDir,const QString & targetDirectory)550 bool Util::File::canCopyDir(const QString& srcDir, const QString& targetDirectory)
551 {
552 if(srcDir.isEmpty())
553 {
554 return false;
555 }
556
557 if(targetDirectory.isEmpty())
558 {
559 return false;
560 }
561
562 if(QString(targetDirectory + "/").startsWith(srcDir + "/"))
563 {
564 return false;
565 }
566
567 if(!exists(srcDir))
568 {
569 return false;
570 }
571
572 return true;
573 }
574
renameDir(const QString & srcDir,const QString & newDir)575 bool Util::File::renameDir(const QString& srcDir, const QString& newDir)
576 {
577 return QDir().rename(srcDir, newDir);
578 }
579
getMD5Sum(const QString & filename)580 QByteArray Util::File::getMD5Sum(const QString& filename)
581 {
582 QFile f(filename);
583 if(f.open(QFile::ReadOnly))
584 {
585 QCryptographicHash hash(QCryptographicHash::Md5);
586 if(hash.addData(&f))
587 {
588 return hash.result().toHex();
589 }
590 }
591
592 return QByteArray();
593 }
594
moveFiles(const QStringList & files,const QString & dir,QStringList & newNames)595 bool Util::File::moveFiles(const QStringList& files, const QString& dir, QStringList& newNames)
596 {
597 bool success = true;
598 for(const auto& file : files)
599 {
600 QString newName;
601 success = moveFile(file, dir, newName);
602 if(!success)
603 {
604 continue;
605 }
606
607 newNames << newName;
608 }
609
610 return success;
611 }
612
renameFile(const QString & oldName,const QString & newName)613 bool Util::File::renameFile(const QString& oldName, const QString& newName)
614 {
615 QFileInfo info(oldName);
616 if(!info.isFile())
617 {
618 return false;
619 }
620
621 return QFile(oldName).rename(newName);
622 }
623
copyFiles(const QStringList & files,const QString & dir,QStringList & newNames)624 bool Util::File::copyFiles(const QStringList& files, const QString& dir, QStringList& newNames)
625 {
626 bool success = true;
627 for(const auto& file : files)
628 {
629 QString newName;
630 success = copyFile(file, dir, newName);
631 if(!success)
632 {
633 continue;
634 }
635
636 newNames << newName;
637 }
638
639 return success;
640 }
641
moveFile(const QString & file,const QString & dir,QString & newName)642 bool Util::File::moveFile(const QString& file, const QString& dir, QString& newName)
643 {
644 if(copyFile(file, dir, newName))
645 {
646 return QFile(file).remove();
647 }
648
649 return false;
650 }
651
copyFile(const QString & file,const QString & dir,QString & newName)652 bool Util::File::copyFile(const QString& file, const QString& dir, QString& newName)
653 {
654 newName.clear();
655
656 if(!QFileInfo(dir).isDir())
657 {
658 return false;
659 }
660
661 if(!QFileInfo(file).isFile())
662 {
663 return false;
664 }
665
666 const auto pureFilename = getFilenameOfPath(file);
667 newName = QDir(dir).absoluteFilePath(pureFilename);
668
669 return QFile(file).copy(newName);
670 }
671
exists(const QString & filename)672 bool Util::File::exists(const QString& filename)
673 {
674 return (!filename.isEmpty()) && QFile::exists(filename);
675 }
676
isSamePath(const QString & filename1,const QString & filename2)677 bool Util::File::isSamePath(const QString& filename1, const QString& filename2)
678 {
679 const auto cleaned1 = cleanFilename(filename1);
680 const auto cleaned2 = cleanFilename(filename2);
681
682 #ifdef Q_OS_UNIX
683 return (cleaned1.compare(cleaned2) == 0);
684 #else
685 return (cleaned1.compare(cleaned2, Qt::CaseInsensitive) == 0);
686 #endif
687 }
688
isSubdir(const QString & dir,const QString & parentDir)689 bool Util::File::isSubdir(const QString& dir, const QString& parentDir)
690 {
691 if(isSamePath(dir, parentDir))
692 {
693 return false;
694 }
695
696 const auto cleanedDir = cleanFilename(dir);
697 const auto cleanedParentDir = cleanFilename(parentDir);
698
699 if(cleanedDir.isEmpty() || cleanedParentDir.isEmpty())
700 {
701 return false;
702 }
703
704 const QFileInfo info(cleanedDir);
705
706 QDir d1(cleanedDir);
707 if(info.exists() && info.isFile())
708 {
709 const auto d1String = Util::File::getParentDirectory(cleanedDir);
710 if(isSamePath(d1String, parentDir))
711 {
712 return true;
713 }
714
715 d1 = QDir(d1String);
716
717 }
718
719 const QDir d2(cleanedParentDir);
720
721 while(!d1.isRoot())
722 {
723 d1 = QDir(Util::File::getParentDirectory(d1.absolutePath()));
724 if(isSamePath(d1.absolutePath(), d2.absolutePath()))
725 {
726 return true;
727 }
728 }
729
730 return false;
731 }
732
invalidFilenameChars()733 QList<QChar> Util::File::invalidFilenameChars()
734 {
735 return
736 {
737 '*', '?', '/', '\\'
738 };
739 }
740