File indexing completed on 2022-09-27 16:29:13

0001 /**
0002  * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût <slaout@linux62.org>
0003  * SPDX-License-Identifier: GPL-2.0-or-later
0004  */
0005 
0006 #include "formatimporter.h"
0007 
0008 #include <QApplication>
0009 #include <QLocale>
0010 #include <QtCore/QDir>
0011 #include <QtCore/QString>
0012 #include <QtCore/QStringList>
0013 #include <QtCore/QTextStream>
0014 #include <QtXml/QDomDocument>
0015 
0016 #include <KIO/CopyJob>
0017 #include <KLocalizedString>
0018 #include <KMessageBox>
0019 
0020 #include "basketscene.h"
0021 #include "bnpview.h"
0022 #include "global.h"
0023 #include "notecontent.h"
0024 #include "notefactory.h"
0025 #include "tools.h"
0026 #include "xmlwork.h"
0027 
0028 bool FormatImporter::shouldImportBaskets()
0029 {
0030     // We should import if the application have not successfully loaded any basket...
0031     if (Global::bnpView->topLevelItemCount() >= 0)
0032         return false;
0033 
0034     // ... And there is at least one folder in the save folder, with a ".basket" file inside that folder.
0035     QDir dir(Global::savesFolder(), QString(), QDir::Name | QDir::IgnoreCase, QDir::Dirs | QDir::NoSymLinks);
0036     QStringList list = dir.entryList();
0037     for (QStringList::Iterator it = list.begin(); it != list.end(); ++it)
0038         if (*it != "." && *it != ".." && dir.exists(Global::savesFolder() + *it + "/.basket"))
0039             return true;
0040 
0041     return false;
0042 }
0043 
0044 void FormatImporter::copyFolder(const QString &folder, const QString &newFolder)
0045 {
0046     copyFinished = false;
0047     KIO::CopyJob *copyJob = KIO::copyAs(QUrl::fromLocalFile(folder), QUrl::fromLocalFile(newFolder), KIO::HideProgressInfo);
0048     connect(copyJob, &KIO::CopyJob::copyingDone, this, &FormatImporter::slotCopyingDone);
0049     while (!copyFinished)
0050         qApp->processEvents();
0051 }
0052 
0053 void FormatImporter::moveFolder(const QString &folder, const QString &newFolder)
0054 {
0055     copyFinished = false;
0056     KIO::CopyJob *copyJob = KIO::moveAs(QUrl::fromLocalFile(folder), QUrl::fromLocalFile(newFolder), KIO::HideProgressInfo);
0057     connect(copyJob, &KIO::CopyJob::copyingDone, this, &FormatImporter::slotCopyingDone);
0058     while (!copyFinished)
0059         qApp->processEvents();
0060 }
0061 
0062 void FormatImporter::slotCopyingDone(KIO::Job *)
0063 {
0064     //  qDebug() << "Copy finished of " + from.path() + " to " + to.path();
0065     copyFinished = true;
0066 }
0067 
0068 void FormatImporter::importBaskets()
0069 {
0070     qDebug() << "Import Baskets: Preparing...";
0071 
0072     // Some preliminary preparations (create the destination folders and the basket tree file):
0073     QDir dirPrep;
0074     dirPrep.mkdir(Global::savesFolder());
0075     dirPrep.mkdir(Global::basketsFolder());
0076     QDomDocument document("basketTree");
0077     QDomElement root = document.createElement("basketTree");
0078     document.appendChild(root);
0079 
0080     // First up, establish a list of every baskets, ensure the old order (if any), and count them.
0081     QStringList baskets;
0082 
0083     // Read the 0.5.0 baskets order:
0084     QDomDocument *doc = XMLWork::openFile("container", Global::savesFolder() + "container.baskets");
0085     if (doc != nullptr) {
0086         QDomElement docElem = doc->documentElement();
0087         QDomElement basketsElem = XMLWork::getElement(docElem, "baskets");
0088         QDomNode n = basketsElem.firstChild();
0089         while (!n.isNull()) {
0090             QDomElement e = n.toElement();
0091             if ((!e.isNull()) && e.tagName() == "basket")
0092                 baskets.append(e.text());
0093             n = n.nextSibling();
0094         }
0095     }
0096 
0097     // Then load the baskets that weren't loaded (import < 0.5.0 ones):
0098     QDir dir(Global::savesFolder(), QString(), QDir::Name | QDir::IgnoreCase, QDir::Dirs | QDir::NoSymLinks);
0099     QStringList list = dir.entryList();
0100     if (list.count() > 2)                                                                          // Pass "." and ".."
0101         for (QStringList::Iterator it = list.begin(); it != list.end(); ++it)                      // For each folder
0102             if (*it != "." && *it != ".." && dir.exists(Global::savesFolder() + *it + "/.basket")) // If it can be a basket folder
0103                 if (!(baskets.contains((*it) + '/')) && baskets.contains(*it))                     // And if it is not already in the imported baskets list
0104                     baskets.append(*it);
0105 
0106     qDebug() << "Import Baskets: Found " << baskets.count() << " baskets to import.";
0107 
0108     // Import every baskets:
0109     int i = 0;
0110     for (QStringList::iterator it = baskets.begin(); it != baskets.end(); ++it) {
0111         ++i;
0112         qDebug() << "Import Baskets: Importing basket " << i << " of " << baskets.count() << "...";
0113 
0114         // Move the folder to the new repository (normal basket) or copy the folder (mirrored folder):
0115         QString folderName = *it;
0116         if (folderName.startsWith('/')) { // It was a folder mirror:
0117             KMessageBox::information(nullptr,
0118                                      i18n("<p>Folder mirroring is not possible anymore (see <a href='https://basket-notepads.github.io'>basket-notepads.github.io</a> for more information).</p>"
0119                                           "<p>The folder <b>%1</b> has been copied for the basket needs. You can either delete this folder or delete the basket, or use both. But remember that "
0120                                           "modifying one will not modify the other anymore as they are now separate entities.</p>",
0121                                           folderName),
0122                                      i18n("Folder Mirror Import"),
0123                                      QString(),
0124                                      KMessageBox::AllowLink);
0125             // Also modify folderName to be only the folder name and not the full path anymore:
0126             QString newFolderName = folderName;
0127             if (newFolderName.endsWith('/'))
0128                 newFolderName = newFolderName.left(newFolderName.length() - 1);
0129             newFolderName = newFolderName.mid(newFolderName.lastIndexOf('/') + 1);
0130             newFolderName = Tools::fileNameForNewFile(newFolderName, Global::basketsFolder());
0131             FormatImporter f;
0132             f.copyFolder(folderName, Global::basketsFolder() + newFolderName);
0133             folderName = newFolderName;
0134         } else
0135             dir.rename(Global::savesFolder() + folderName, Global::basketsFolder() + folderName); // Move the folder
0136 
0137         // Import the basket structure file and get the properties (to add them in the tree basket-properties cache):
0138         QDomElement properties = importBasket(folderName);
0139 
0140         // Add it to the XML document:
0141         QDomElement basketElement = document.createElement("basket");
0142         root.appendChild(basketElement);
0143         basketElement.setAttribute("folderName", folderName);
0144         basketElement.appendChild(properties);
0145     }
0146 
0147     // Finalize (write to disk and delete now useless files):
0148     qDebug() << "Import Baskets: Finalizing...";
0149 
0150     QFile file(Global::basketsFolder() + "baskets.xml");
0151     if (file.open(QIODevice::WriteOnly)) {
0152         QTextStream stream(&file);
0153         stream.setCodec("UTF-8");
0154         QString xml = document.toString();
0155         stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
0156         stream << xml;
0157         file.close();
0158     }
0159 
0160     Tools::deleteRecursively(Global::savesFolder() + ".tmp");
0161     dir.remove(Global::savesFolder() + "container.baskets");
0162 
0163     qDebug() << "Import Baskets: Finished.";
0164 }
0165 
0166 QDomElement FormatImporter::importBasket(const QString &folderName)
0167 {
0168     // Load the XML file:
0169     QDomDocument *document = XMLWork::openFile("basket", Global::basketsFolder() + folderName + "/.basket");
0170     if (!document) {
0171         qDebug() << "Import Baskets: Failed to read the basket file!";
0172         return QDomElement();
0173     }
0174     QDomElement docElem = document->documentElement();
0175 
0176     // Import properties (change <background color=""> to <appearance backgroundColor="">, and figure out if is a checklist or not):
0177     QDomElement properties = XMLWork::getElement(docElem, "properties");
0178     QDomElement background = XMLWork::getElement(properties, "background");
0179     QColor backgroundColor = QColor(background.attribute("color"));
0180     if (backgroundColor.isValid() && (backgroundColor != qApp->palette().color(QPalette::Base))) { // Use the default color if it was already that color:
0181         QDomElement appearance = document->createElement("appearance");
0182         appearance.setAttribute("backgroundColor", backgroundColor.name());
0183         properties.appendChild(appearance);
0184     }
0185     QDomElement disposition = document->createElement("disposition");
0186     disposition.setAttribute("mindMap", "false");
0187     disposition.setAttribute("columnCount", "1");
0188     disposition.setAttribute("free", "false");
0189     bool isCheckList = XMLWork::trueOrFalse(XMLWork::getElementText(properties, "showCheckBoxes"), false);
0190 
0191     // Insert all notes in a group (column): 1/ rename "items" to "group", 2/ add "notes" to root, 3/ move "group" into "notes"
0192     QDomElement column = XMLWork::getElement(docElem, "items");
0193     column.setTagName("group");
0194     QDomElement notes = document->createElement("notes");
0195     notes.appendChild(column);
0196     docElem.appendChild(notes);
0197 
0198     // Import notes from older representations:
0199     QDomNode n = column.firstChild();
0200     while (!n.isNull()) {
0201         QDomElement e = n.toElement();
0202         if (!e.isNull()) {
0203             e.setTagName("note");
0204             QDomElement content = XMLWork::getElement(e, "content");
0205             // Add Check tag:
0206             if (isCheckList) {
0207                 bool isChecked = XMLWork::trueOrFalse(e.attribute("checked", "false"));
0208                 XMLWork::addElement(*document, e, "tags", (isChecked ? "todo_done" : "todo_unchecked"));
0209             }
0210             // Import annotations as folded groups:
0211             QDomElement parentE = column;
0212             QString annotations = XMLWork::getElementText(e, "annotations", QString());
0213             if (!annotations.isEmpty()) {
0214                 QDomElement annotGroup = document->createElement("group");
0215                 column.insertBefore(annotGroup, e);
0216                 annotGroup.setAttribute("folded", "true");
0217                 annotGroup.appendChild(e);
0218                 parentE = annotGroup;
0219                 // Create the text note and add it to the DOM tree:
0220                 QDomElement annotNote = document->createElement("note");
0221                 annotNote.setAttribute("type", "text");
0222                 annotGroup.appendChild(annotNote);
0223                 QString annotFileName = Tools::fileNameForNewFile("annotations1.txt", BasketScene::fullPathForFolderName(folderName));
0224                 QString annotFullPath = BasketScene::fullPathForFolderName(folderName) + '/' + annotFileName;
0225                 QFile file(annotFullPath);
0226                 if (file.open(QIODevice::WriteOnly)) {
0227                     QTextStream stream(&file);
0228                     stream << annotations;
0229                     file.close();
0230                 }
0231                 XMLWork::addElement(*document, annotNote, "content", annotFileName);
0232                 n = annotGroup;
0233             }
0234             // Import Launchers from 0.3.x, 0.4.0 and 0.5.0-alphas:
0235             QString runCommand = e.attribute("runcommand");                // Keep compatibility with 0.4.0 and 0.5.0-alphas versions
0236             runCommand = XMLWork::getElementText(e, "action", runCommand); // Keep compatibility with 0.3.x versions
0237             if (!runCommand.isEmpty()) {                                   // An import should be done
0238                 // Prepare the launcher note:
0239                 QString title = content.attribute("title", QString());
0240                 QString icon = content.attribute("icon", QString());
0241                 if (title.isEmpty())
0242                     title = runCommand;
0243                 if (icon.isEmpty())
0244                     icon = NoteFactory::iconForCommand(runCommand);
0245                 // Import the launcher note:
0246                 // Adapted version of "QString launcherName = NoteFactory::createNoteLauncherFile(runCommand, title, icon, this)":
0247                 QString launcherContent = QString(
0248                                               "[Desktop Entry]\n"
0249                                               "Exec=%1\n"
0250                                               "Name=%2\n"
0251                                               "Icon=%3\n"
0252                                               "Encoding=UTF-8\n"
0253                                               "Type=Application\n")
0254                                               .arg(runCommand, title, icon.isEmpty() ? QString("exec") : icon);
0255                 QString launcherFileName = Tools::fileNameForNewFile("launcher.desktop", Global::basketsFolder() + folderName /*+ "/"*/);
0256                 QString launcherFullPath = Global::basketsFolder() + folderName /*+ "/"*/ + launcherFileName;
0257                 QFile file(launcherFullPath);
0258                 if (file.open(QIODevice::WriteOnly)) {
0259                     QTextStream stream(&file);
0260                     stream.setCodec("UTF-8");
0261                     stream << launcherContent;
0262                     file.close();
0263                 }
0264                 // Add the element to the DOM:
0265                 QDomElement launcherElem = document->createElement("note");
0266                 parentE.insertBefore(launcherElem, e);
0267                 launcherElem.setAttribute("type", "launcher");
0268                 XMLWork::addElement(*document, launcherElem, "content", launcherFileName);
0269             }
0270             // Import unknown ns to 0.6.0:
0271             if (e.attribute("type") == "unknow")
0272                 e.setAttribute("type", "unknown");
0273             // Import links from version < 0.5.0:
0274             if (!content.attribute("autotitle").isEmpty() && content.attribute("autoTitle").isEmpty())
0275                 content.setAttribute("autoTitle", content.attribute("autotitle"));
0276             if (!content.attribute("autoicon").isEmpty() && content.attribute("autoIcon").isEmpty())
0277                 content.setAttribute("autoIcon", content.attribute("autoicon"));
0278         }
0279         n = n.nextSibling();
0280     }
0281 
0282     // Save the resulting XML file:
0283     QFile file(Global::basketsFolder() + folderName + "/.basket");
0284     if (file.open(QIODevice::WriteOnly)) {
0285         QTextStream stream(&file);
0286         stream.setCodec("UTF-8");
0287         //      QString xml = document->toString();
0288         //      stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
0289         //      stream << xml;
0290         stream << document->toString(); // Document is ALREADY using UTF-8
0291         file.close();
0292     } else
0293         qDebug() << "Import Baskets: Failed to save the basket file!";
0294 
0295     // Return the newly created properties (to put in the basket tree):
0296     return properties;
0297 }