File indexing completed on 2024-04-07 15:23:33

0001 /*
0002 *  Copyright 2017  Smith AR <audoban@openmailbox.org>
0003 *                  Michail Vourlakos <mvourlakos@gmail.com>
0004 *
0005 *  This file is part of Latte-Dock
0006 *
0007 *  Latte-Dock is free software; you can redistribute it and/or
0008 *  modify it under the terms of the GNU General Public License as
0009 *  published by the Free Software Foundation; either version 2 of
0010 *  the License, or (at your option) any later version.
0011 *
0012 *  Latte-Dock is distributed in the hope that it will be useful,
0013 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0014 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0015 *  GNU General Public License for more details.
0016 *
0017 *  You should have received a copy of the GNU General Public License
0018 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
0019 */
0020 
0021 #include "importer.h"
0022 
0023 // local
0024 #include "manager.h"
0025 #include "../lattecorona.h"
0026 #include "../screenpool.h"
0027 #include "../layout/abstractlayout.h"
0028 #include "../settings/universalsettings.h"
0029 #include "../liblatte2/types.h"
0030 
0031 // Qt
0032 #include <QFile>
0033 #include <QTemporaryDir>
0034 
0035 // KDE
0036 #include <KArchive/KTar>
0037 #include <KArchive/KArchiveEntry>
0038 #include <KArchive/KArchiveDirectory>
0039 #include <KConfigGroup>
0040 #include <KLocalizedString>
0041 #include <KNotification>
0042 
0043 namespace Latte {
0044 namespace Layouts {
0045 
0046 Importer::Importer(QObject *parent)
0047     : QObject(parent)
0048 {
0049     m_manager = qobject_cast<Layouts::Manager *>(parent);
0050 }
0051 
0052 Importer::~Importer()
0053 {
0054 }
0055 
0056 bool Importer::updateOldConfiguration()
0057 {
0058     QFile oldAppletsFile(QDir::homePath() + "/.config/lattedock-appletsrc");
0059 
0060     if (!oldAppletsFile.exists()) {
0061         return false;
0062     }
0063 
0064     //! import standard old configuration and create the relevant layouts
0065     importOldLayout(QDir::homePath() + "/.config/lattedock-appletsrc", i18n("My Layout"));
0066     importOldLayout(QDir::homePath() + "/.config/lattedock-appletsrc", i18n("Alternative"), true);
0067 
0068     QFile extFile(QDir::homePath() + "/.config/lattedockextrc");
0069 
0070     //! import also the old user layouts into the new architecture
0071     if (extFile.exists()) {
0072         KSharedConfigPtr extFileConfig = KSharedConfig::openConfig(extFile.fileName());
0073         KConfigGroup externalSettings = KConfigGroup(extFileConfig, "External");
0074         QStringList userLayouts = externalSettings.readEntry("userLayouts", QStringList());
0075 
0076         for(const auto &userConfig : userLayouts) {
0077             qDebug() << "user layout : " << userConfig;
0078             importOldConfiguration(userConfig);
0079         }
0080     }
0081 
0082     m_manager->corona()->universalSettings()->setVersion(2);
0083     m_manager->corona()->universalSettings()->setCurrentLayoutName(i18n("My Layout"));
0084 
0085     return true;
0086 }
0087 
0088 bool Importer::importOldLayout(QString oldAppletsPath, QString newName, bool alternative, QString exportDirectory)
0089 {
0090     QString newLayoutPath = layoutCanBeImported(oldAppletsPath, newName, exportDirectory);
0091     qDebug() << "New Layout Should be created: " << newLayoutPath;
0092 
0093     KSharedConfigPtr oldFile = KSharedConfig::openConfig(oldAppletsPath);
0094     KSharedConfigPtr newFile = KSharedConfig::openConfig(newLayoutPath);
0095 
0096     KConfigGroup containments = KConfigGroup(oldFile, "Containments");
0097     KConfigGroup copiedContainments = KConfigGroup(newFile, "Containments");
0098 
0099     QList<int> systrays;
0100 
0101     bool atLeastOneContainmentWasFound{false};
0102 
0103     //! first copy the latte containments that correspond to the correct session
0104     //! and find also the systrays that should be copied also
0105     for(const auto &containmentId : containments.groupList()) {
0106         KConfigGroup containmentGroup = containments.group(containmentId);
0107 
0108         QString plugin = containmentGroup.readEntry("plugin", QString());
0109         Types::SessionType session = (Types::SessionType)containmentGroup.readEntry("session", (int)Types::DefaultSession);
0110 
0111         bool shouldImport = false;
0112 
0113         if (plugin == "org.kde.latte.containment" && session == Types::DefaultSession && !alternative) {
0114             qDebug() << containmentId << " - " << plugin << " - " << session;
0115             shouldImport = true;
0116         } else if (plugin == "org.kde.latte.containment" && session == Types::AlternativeSession && alternative) {
0117             qDebug() << containmentId << " - " << plugin << " - " << session;
0118             shouldImport = true;
0119         }
0120 
0121         // this latte containment should be imported
0122         if (shouldImport) {
0123             auto applets = containments.group(containmentId).group("Applets");
0124 
0125             for(const auto &applet : applets.groupList()) {
0126                 KConfigGroup appletSettings = applets.group(applet).group("Configuration");
0127 
0128                 int systrayId = appletSettings.readEntry("SystrayContainmentId", "-1").toInt();
0129 
0130                 if (systrayId != -1) {
0131                     systrays.append(systrayId);
0132                     qDebug() << "systray was found in the containment...";
0133                     break;
0134                 }
0135             }
0136 
0137             KConfigGroup newContainment = copiedContainments.group(containmentId);
0138             containmentGroup.copyTo(&newContainment);
0139             atLeastOneContainmentWasFound = true;
0140         }
0141     }
0142 
0143     //! not even one latte containment was found for that layout so we must break
0144     //! the code here
0145     if (!atLeastOneContainmentWasFound) {
0146         return false;
0147     }
0148 
0149     //! copy also the systrays that were discovered
0150     for(const auto &containmentId : containments.groupList()) {
0151         int cId = containmentId.toInt();
0152 
0153         if (systrays.contains(cId)) {
0154             KConfigGroup containmentGroup = containments.group(containmentId);
0155             KConfigGroup newContainment = copiedContainments.group(containmentId);
0156             containmentGroup.copyTo(&newContainment);
0157         }
0158     }
0159 
0160     copiedContainments.sync();
0161 
0162     KConfigGroup oldGeneralSettings = KConfigGroup(oldFile, "General");
0163 
0164     QStringList layoutLaunchers;
0165 
0166     if (!alternative) {
0167         layoutLaunchers = oldGeneralSettings.readEntry("globalLaunchers_default", QStringList());
0168     } else {
0169         layoutLaunchers = oldGeneralSettings.readEntry("globalLaunchers_alternative", QStringList());
0170     }
0171 
0172     //! update also the layout settings correctly
0173     Layout::AbstractLayout newLayout(this, newLayoutPath, newName);
0174     newLayout.setVersion(2);
0175     newLayout.setLaunchers(layoutLaunchers);
0176 
0177     //newLayout.setShowInMenu(true);
0178 
0179     if (alternative) {
0180         newLayout.setColor("purple");
0181     } else {
0182         newLayout.setColor("blue");
0183     }
0184 
0185     return true;
0186 }
0187 
0188 QStringList Importer::standardPaths(bool localfirst)
0189 {
0190     QStringList paths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
0191 
0192     if (localfirst) {
0193         return paths;
0194     } else {
0195         QStringList reversed;
0196 
0197         for (int i=paths.count()-1; i>=0; i--) {
0198             reversed << paths[i];
0199         }
0200 
0201         return reversed;
0202     }
0203 }
0204 
0205 QStringList Importer::standardPathsFor(QString subPath, bool localfirst)
0206 {
0207     QStringList paths = standardPaths(localfirst);
0208 
0209     QString separator = subPath.startsWith("/") ? "" : "/";
0210 
0211     for (int i=0; i<paths.count(); ++i) {
0212         paths[i] = paths[i] + separator + subPath;
0213     }
0214 
0215     return paths;
0216 }
0217 
0218 
0219 QString Importer::standardPath(QString subPath, bool localfirst)
0220 {
0221     QStringList paths = standardPaths(localfirst);
0222 
0223     for(const auto &pt : paths) {
0224         QString ptF = pt + "/" +subPath;
0225         if (QFileInfo(ptF).exists()) {
0226             return ptF;
0227         }
0228     }
0229 
0230     //! in any case that above fails
0231     if (QFileInfo("/usr/share/"+subPath).exists()) {
0232         return "/usr/share/"+subPath;
0233     }
0234 
0235     return "";
0236 }
0237 
0238 QString Importer::layoutCanBeImported(QString oldAppletsPath, QString newName, QString exportDirectory)
0239 {
0240     QFile oldAppletsrc(oldAppletsPath);
0241 
0242     //! old file doesn't exist
0243     if (!oldAppletsrc.exists()) {
0244         return QString();
0245     }
0246 
0247     KSharedConfigPtr lConfig = KSharedConfig::openConfig(oldAppletsPath);
0248     KConfigGroup m_layoutGroup = KConfigGroup(lConfig, "LayoutSettings");
0249     int layoutVersion = m_layoutGroup.readEntry("version", 1);
0250 
0251     //! old file layout appears to not be old as its version is >=2
0252     if (layoutVersion >= 2) {
0253         return QString();
0254     }
0255 
0256     QDir layoutDir(exportDirectory.isNull() ? QDir::homePath() + "/.config/latte" : exportDirectory);
0257 
0258     if (!layoutDir.exists() && exportDirectory.isNull()) {
0259         QDir(QDir::homePath() + "/.config").mkdir("latte");
0260     }
0261 
0262     //! set up the new layout name
0263     if (newName.isEmpty()) {
0264         int extension = oldAppletsrc.fileName().lastIndexOf(".latterc");
0265 
0266         if (extension > 0) {
0267             //! remove the last 8 characters that contain the extension
0268             newName = oldAppletsrc.fileName().remove(extension, 8);
0269         } else {
0270             newName = oldAppletsrc.fileName();
0271         }
0272     }
0273 
0274     QString newLayoutPath = layoutDir.absolutePath() + "/" + newName + ".layout.latte";
0275     QFile newLayoutFile(newLayoutPath);
0276 
0277     QStringList filter;
0278     filter.append(QString(newName + "*.layout.latte"));
0279     QStringList files = layoutDir.entryList(filter, QDir::Files | QDir::NoSymLinks);
0280 
0281     //! if the newLayout already exists provide a newName that doesn't
0282     if (files.count() >= 1) {
0283         int newCounter = files.count() + 1;
0284 
0285         newLayoutPath = layoutDir.absolutePath() + "/" + newName + "-" + QString::number(newCounter) + ".layout.latte";
0286     }
0287 
0288     return newLayoutPath;
0289 }
0290 
0291 bool Importer::importOldConfiguration(QString oldConfigPath, QString newName)
0292 {
0293     QFile oldConfigFile(oldConfigPath);
0294 
0295     if (!oldConfigFile.exists()) {
0296         return false;
0297     }
0298 
0299     KTar archive(oldConfigPath, QStringLiteral("application/x-tar"));
0300     archive.open(QIODevice::ReadOnly);
0301 
0302     if (!archive.isOpen()) {
0303         return false;
0304     }
0305 
0306     auto rootDir = archive.directory();
0307     QTemporaryDir uniqueTempDir;
0308     QDir tempDir{uniqueTempDir.path()};
0309 
0310     qDebug() << "temp layout directory : " << tempDir.absolutePath();
0311 
0312     if (rootDir) {
0313         if (!tempDir.exists())
0314             tempDir.mkpath(tempDir.absolutePath());
0315 
0316         for(const auto &name : rootDir->entries()) {
0317             auto fileEntry = rootDir->file(name);
0318 
0319             if (fileEntry && (fileEntry->name() == "lattedockrc"
0320                               || fileEntry->name() == "lattedock-appletsrc")) {
0321                 if (!fileEntry->copyTo(tempDir.absolutePath())) {
0322                     qInfo() << i18nc("import/export config", "The extracted file could not be copied!!!");
0323                     archive.close();
0324                     return false;
0325                 }
0326             } else {
0327                 qInfo() << i18nc("import/export config", "The file has a wrong format!!!");
0328                 archive.close();
0329                 return false;
0330             }
0331         }
0332     } else {
0333         qInfo() << i18nc("import/export config", "The temp directory could not be created!!!");
0334         archive.close();
0335         return false;
0336     }
0337 
0338     //! only if the above has passed we must process the files
0339     QString appletsPath(tempDir.absolutePath() + "/lattedock-appletsrc");
0340     QString screensPath(tempDir.absolutePath() + "/lattedockrc");
0341 
0342     if (!QFile(appletsPath).exists() || !QFile(screensPath).exists()) {
0343         return false;
0344     }
0345 
0346 
0347     if (newName.isEmpty()) {
0348         int lastSlash = oldConfigPath.lastIndexOf("/");
0349         newName = oldConfigPath.remove(0, lastSlash + 1);
0350 
0351         int ext = newName.lastIndexOf(".latterc");
0352         newName = newName.remove(ext, 8);
0353     }
0354 
0355     if (!importOldLayout(appletsPath, newName)) {
0356         return false;
0357     }
0358 
0359     //! the old configuration contains also screen values, these must be updated also
0360     KSharedConfigPtr oldScreensConfig = KSharedConfig::openConfig(screensPath);
0361     KConfigGroup m_screensGroup = KConfigGroup(oldScreensConfig, "ScreenConnectors");
0362 
0363     //restore the known ids to connector mappings
0364     for(const QString &key : m_screensGroup.keyList()) {
0365         QString connector = m_screensGroup.readEntry(key, QString());
0366         int id = key.toInt();
0367 
0368         if (id >= 10 && !m_manager->corona()->screenPool()->knownIds().contains(id)) {
0369             m_manager->corona()->screenPool()->insertScreenMapping(id, connector);
0370         }
0371     }
0372 
0373     return true;
0374 }
0375 
0376 bool Importer::exportFullConfiguration(QString file)
0377 {
0378     if (QFile::exists(file) && !QFile::remove(file)) {
0379         return false;
0380     }
0381 
0382     KTar archive(file, QStringLiteral("application/x-tar"));
0383 
0384     if (!archive.open(QIODevice::WriteOnly)) {
0385         return false;
0386     }
0387 
0388     archive.addLocalFile(QString(QDir::homePath() + "/.config/lattedockrc"), QStringLiteral("lattedockrc"));
0389 
0390     for(const auto &layoutName : availableLayouts()) {
0391         archive.addLocalFile(layoutFilePath(layoutName), QString("latte/" + layoutName + ".layout.latte"));
0392     }
0393 
0394     //archive.addLocalDirectory(QString(QDir::homePath() + "/.config/latte"), QStringLiteral("latte"));
0395 
0396     archive.close();
0397 
0398     return true;
0399 }
0400 
0401 Importer::LatteFileVersion Importer::fileVersion(QString file)
0402 {
0403     if (!QFile::exists(file))
0404         return UnknownFileType;
0405 
0406     if (file.endsWith(".layout.latte")) {
0407         KSharedConfigPtr lConfig = KSharedConfig::openConfig(QFileInfo(file).absoluteFilePath());
0408         KConfigGroup layoutGroup = KConfigGroup(lConfig, "LayoutSettings");
0409         int version = layoutGroup.readEntry("version", 1);
0410 
0411         if (version == 2)
0412             return Importer::LayoutVersion2;
0413         else
0414             return Importer::UnknownFileType;
0415     }
0416 
0417     if (!file.endsWith(".latterc")) {
0418         return Importer::UnknownFileType;
0419     }
0420 
0421     KTar archive(file, QStringLiteral("application/x-tar"));
0422     archive.open(QIODevice::ReadOnly);
0423 
0424     //! if the file isnt a tar archive
0425     if (!archive.isOpen()) {
0426         return Importer::UnknownFileType;
0427     }
0428 
0429     QTemporaryDir archiveTempDir;
0430 
0431     bool version1rc = false;
0432     bool version1applets = false;
0433 
0434     bool version2rc = false;
0435     bool version2LatteDir = false;
0436     bool version2layout = false;
0437 
0438     archive.directory()->copyTo(archiveTempDir.path());
0439 
0440 
0441     //rc file
0442     QString rcFile(archiveTempDir.path() + "/lattedockrc");
0443 
0444     if (QFile(rcFile).exists()) {
0445         KSharedConfigPtr lConfig = KSharedConfig::openConfig(rcFile);
0446         KConfigGroup universalGroup = KConfigGroup(lConfig, "UniversalSettings");
0447         int version = universalGroup.readEntry("version", 1);
0448 
0449         if (version == 1) {
0450             version1rc = true;
0451         } else if (version == 2) {
0452             version2rc = true;
0453         }
0454     }
0455 
0456     //applets file
0457     QString appletsFile(archiveTempDir.path() + "/lattedock-appletsrc");
0458 
0459     if (QFile(appletsFile).exists() && version1rc) {
0460         KSharedConfigPtr lConfig = KSharedConfig::openConfig(appletsFile);
0461         KConfigGroup generalGroup = KConfigGroup(lConfig, "LayoutSettings");
0462         int version = generalGroup.readEntry("version", 1);
0463 
0464         if (version == 1) {
0465             version1applets = true;
0466         } else if (version == 2) {
0467             version2layout = true;
0468         }
0469     }
0470 
0471     //latte directory
0472     QString latteDir(archiveTempDir.path() + "/latte");
0473 
0474     if (QDir(latteDir).exists()) {
0475         version2LatteDir = true;
0476     }
0477 
0478     if (version1applets && version1applets) {
0479         return ConfigVersion1;
0480     } else if (version2rc && version2LatteDir) {
0481         return ConfigVersion2;
0482     }
0483 
0484     return Importer::UnknownFileType;
0485 }
0486 
0487 bool Importer::importHelper(QString fileName)
0488 {
0489     LatteFileVersion version = fileVersion(fileName);
0490 
0491     if ((version != ConfigVersion1) && (version != ConfigVersion2)) {
0492         return false;
0493     }
0494 
0495     KTar archive(fileName, QStringLiteral("application/x-tar"));
0496     archive.open(QIODevice::ReadOnly);
0497 
0498     if (!archive.isOpen()) {
0499         return false;
0500     }
0501 
0502     QString latteDirPath(QDir::homePath() + "/.config/latte");
0503     QDir latteDir(latteDirPath);
0504 
0505     if (latteDir.exists()) {
0506         latteDir.removeRecursively();
0507     }
0508 
0509     archive.directory()->copyTo(QString(QDir::homePath() + "/.config"));
0510 
0511     return true;
0512 }
0513 
0514 QString Importer::importLayoutHelper(QString fileName)
0515 {
0516     LatteFileVersion version = fileVersion(fileName);
0517 
0518     if (version != LayoutVersion2) {
0519         return QString();
0520     }
0521 
0522     QString newLayoutName = Layout::AbstractLayout::layoutName(fileName);
0523     newLayoutName = uniqueLayoutName(newLayoutName);
0524 
0525     QString newPath = QDir::homePath() + "/.config/latte/" + newLayoutName + ".layout.latte";
0526     QFile(fileName).copy(newPath);
0527 
0528     QFileInfo newFileInfo(newPath);
0529 
0530     if (newFileInfo.exists() && !newFileInfo.isWritable()) {
0531         QFile(newPath).setPermissions(QFileDevice::ReadUser | QFileDevice::WriteUser | QFileDevice::ReadGroup | QFileDevice::ReadOther);
0532     }
0533 
0534     return newLayoutName;
0535 
0536 }
0537 
0538 QStringList Importer::availableLayouts()
0539 {
0540     QDir layoutDir(QDir::homePath() + "/.config/latte");
0541     QStringList filter;
0542     filter.append(QString("*.layout.latte"));
0543     QStringList files = layoutDir.entryList(filter, QDir::Files | QDir::NoSymLinks);
0544 
0545     QStringList layoutNames;
0546 
0547     for(const auto &file : files) {
0548         layoutNames.append(Layout::AbstractLayout::layoutName(file));
0549     }
0550 
0551     return layoutNames;
0552 }
0553 
0554 
0555 QString Importer::nameOfConfigFile(const QString &fileName)
0556 {
0557     int lastSlash = fileName.lastIndexOf("/");
0558     QString tempLayoutFile = fileName;
0559     QString layoutName = tempLayoutFile.remove(0, lastSlash + 1);
0560 
0561     int ext = layoutName.lastIndexOf(".latterc");
0562     layoutName = layoutName.remove(ext, 8);
0563 
0564     return layoutName;
0565 }
0566 
0567 bool Importer::layoutExists(QString layoutName)
0568 {
0569     return QFile::exists(layoutFilePath(layoutName));
0570 }
0571 
0572 
0573 QString Importer::layoutFilePath(QString layoutName)
0574 {
0575     return QString(QDir::homePath() + "/.config/latte/" + layoutName + ".layout.latte");
0576 }
0577 
0578 QString Importer::uniqueLayoutName(QString name)
0579 {
0580     int pos_ = name.lastIndexOf(QRegExp(QString("[-][0-9]+")));
0581 
0582     if (layoutExists(name) && pos_ > 0) {
0583         name = name.left(pos_);
0584     }
0585 
0586     int i = 2;
0587 
0588     QString namePart = name;
0589 
0590     while (layoutExists(name)) {
0591         name = namePart + "-" + QString::number(i);
0592         i++;
0593     }
0594 
0595     return name;
0596 }
0597 
0598 QStringList Importer::checkRepairMultipleLayoutsLinkedFile()
0599 {
0600     QString linkedFilePath = QDir::homePath() + "/.config/latte/" + Layout::AbstractLayout::MultipleLayoutsName + ".layout.latte";
0601     KSharedConfigPtr filePtr = KSharedConfig::openConfig(linkedFilePath);
0602     KConfigGroup linkedContainments = KConfigGroup(filePtr, "Containments");
0603 
0604     //! layoutName and its Containments
0605     QHash<QString, QStringList> linkedLayoutContainmentGroups;
0606 
0607     for(const auto &cId : linkedContainments.groupList()) {
0608         QString layoutName = linkedContainments.group(cId).readEntry("layoutId", QString());
0609 
0610         if (!layoutName.isEmpty()) {
0611             qDebug() << layoutName;
0612             linkedLayoutContainmentGroups[layoutName].append(cId);
0613             linkedContainments.group(cId).writeEntry("layoutId", QString());
0614         }
0615     }
0616 
0617     QStringList updatedLayouts;
0618 
0619     for(const auto &layoutName : linkedLayoutContainmentGroups.uniqueKeys()) {
0620         if (layoutName != Layout::AbstractLayout::MultipleLayoutsName && layoutExists(layoutName)) {
0621             updatedLayouts << layoutName;
0622             KSharedConfigPtr layoutFilePtr = KSharedConfig::openConfig(layoutFilePath(layoutName));
0623             KConfigGroup origLayoutContainments = KConfigGroup(layoutFilePtr, "Containments");
0624 
0625             //Clear old containments
0626             origLayoutContainments.deleteGroup();
0627 
0628             //Update containments
0629             for(const auto &cId : linkedLayoutContainmentGroups[layoutName]) {
0630                 KConfigGroup newContainment = origLayoutContainments.group(cId);
0631                 linkedContainments.group(cId).copyTo(&newContainment);
0632                 linkedContainments.group(cId).deleteGroup();
0633             }
0634 
0635             origLayoutContainments.sync();
0636         }
0637     }
0638 
0639     //! clear all remaining ghost containments
0640     for(const auto &cId : linkedContainments.groupList()) {
0641         linkedContainments.group(cId).deleteGroup();
0642     }
0643 
0644     linkedContainments.sync();
0645 
0646     return updatedLayouts;
0647 }
0648 
0649 }
0650 }