1 /***************************************************************************
2  *   Copyright (C) 2003 by Sébastien Laoût                                 *
3  *   slaout@linux62.org                                                    *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20 
21 #include "formatimporter.h"
22 
23 #include <QtCore/QString>
24 #include <QtCore/QStringList>
25 #include <QtCore/QDir>
26 #include <QtCore/QTextStream>
27 #include <QtXml/QDomDocument>
28 #include <QLocale>
29 #include <QApplication>
30 
31 #include <KMessageBox>
32 #include <KIO/CopyJob>
33 #include <KLocalizedString>
34 
35 #include "notecontent.h"
36 #include "notefactory.h"
37 #include "bnpview.h"
38 #include "basketscene.h"
39 #include "global.h"
40 #include "xmlwork.h"
41 #include "tools.h"
42 
43 
44 
shouldImportBaskets()45 bool FormatImporter::shouldImportBaskets()
46 {
47     // We should import if the application have not successfully loaded any basket...
48     if (Global::bnpView->topLevelItemCount() >= 0)
49         return false;
50 
51     // ... And there is at least one folder in the save folder, with a ".basket" file inside that folder.
52     QDir dir(Global::savesFolder(), QString::null, QDir::Name | QDir::IgnoreCase, QDir::Dirs | QDir::NoSymLinks);
53     QStringList list = dir.entryList();
54     for (QStringList::Iterator it = list.begin(); it != list.end(); ++it)
55         if (*it != "." && *it != ".." && dir.exists(Global::savesFolder() + *it + "/.basket"))
56             return true;
57 
58     return false;
59 }
60 
copyFolder(const QString & folder,const QString & newFolder)61 void FormatImporter::copyFolder(const QString &folder, const QString &newFolder)
62 {
63     copyFinished = false;
64     KIO::CopyJob *copyJob = KIO::copyAs(QUrl::fromLocalFile(folder), QUrl::fromLocalFile(newFolder), KIO::HideProgressInfo);
65     connect(copyJob, &KIO::CopyJob::copyingDone, this, &FormatImporter::slotCopyingDone);
66     while (!copyFinished)
67         qApp->processEvents();
68 }
69 
moveFolder(const QString & folder,const QString & newFolder)70 void FormatImporter::moveFolder(const QString &folder, const QString &newFolder)
71 {
72     copyFinished = false;
73     KIO::CopyJob *copyJob = KIO::moveAs(QUrl::fromLocalFile(folder), QUrl::fromLocalFile(newFolder), KIO::HideProgressInfo);
74     connect(copyJob, &KIO::CopyJob::copyingDone, this, &FormatImporter::slotCopyingDone);
75     while (!copyFinished)
76         qApp->processEvents();
77 }
78 
slotCopyingDone(KIO::Job *)79 void FormatImporter::slotCopyingDone(KIO::Job *)
80 {
81 //  qDebug() << "Copy finished of " + from.path() + " to " + to.path();
82     copyFinished = true;
83 }
84 
importBaskets()85 void FormatImporter::importBaskets()
86 {
87     qDebug() << "Import Baskets: Preparing...";
88 
89     // Some preliminary preparations (create the destination folders and the basket tree file):
90     QDir dirPrep;
91     dirPrep.mkdir(Global::savesFolder());
92     dirPrep.mkdir(Global::basketsFolder());
93     QDomDocument document("basketTree");
94     QDomElement root = document.createElement("basketTree");
95     document.appendChild(root);
96 
97     // First up, establish a list of every baskets, ensure the old order (if any), and count them.
98     QStringList baskets;
99 
100     // Read the 0.5.0 baskets order:
101     QDomDocument *doc = XMLWork::openFile("container", Global::savesFolder() + "container.baskets");
102     if (doc != 0) {
103         QDomElement docElem = doc->documentElement();
104         QDomElement basketsElem = XMLWork::getElement(docElem, "baskets");
105         QDomNode n = basketsElem.firstChild();
106         while (!n.isNull()) {
107             QDomElement e = n.toElement();
108             if ((!e.isNull()) && e.tagName() == "basket")
109                 baskets.append(e.text());
110             n = n.nextSibling();
111         }
112     }
113 
114     // Then load the baskets that weren't loaded (import < 0.5.0 ones):
115     QDir dir(Global::savesFolder(), QString::null, QDir::Name | QDir::IgnoreCase, QDir::Dirs | QDir::NoSymLinks);
116     QStringList list = dir.entryList();
117     if (list.count() > 2) // Pass "." and ".."
118         for (QStringList::Iterator it = list.begin(); it != list.end(); ++it) // For each folder
119             if (*it != "." && *it != ".." && dir.exists(Global::savesFolder() + *it + "/.basket")) // If it can be a basket folder
120                 if (!(baskets.contains((*it) + "/")) && baskets.contains(*it))   // And if it is not already in the imported baskets list
121                     baskets.append(*it);
122 
123     qDebug() << "Import Baskets: Found " << baskets.count() << " baskets to import.";
124 
125     // Import every baskets:
126     int i = 0;
127     for (QStringList::iterator it = baskets.begin(); it != baskets.end(); ++it) {
128         ++i;
129         qDebug() << "Import Baskets: Importing basket " << i << " of " << baskets.count() << "...";
130 
131         // Move the folder to the new repository (normal basket) or copy the folder (mirorred folder):
132         QString folderName = *it;
133         if (folderName.startsWith('/')) { // It was a folder mirror:
134             KMessageBox::information(0, i18n("<p>Folder mirroring is not possible anymore (see <a href='http://basket-notepads.github.io'>basket-notepads.github.io</a> for more information).</p>"
135                                              "<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 "
136                                              "modifying one will not modify the other anymore as they are now separate entities.</p>", folderName), i18n("Folder Mirror Import"),
137                                      "", KMessageBox::AllowLink);
138             // Also modify folderName to be only the folder name and not the full path anymore:
139             QString newFolderName = folderName;
140             if (newFolderName.endsWith('/'))
141                 newFolderName = newFolderName.left(newFolderName.length() - 1);
142             newFolderName = newFolderName.mid(newFolderName.lastIndexOf('/') + 1);
143             newFolderName = Tools::fileNameForNewFile(newFolderName, Global::basketsFolder());
144             FormatImporter f;
145             f.copyFolder(folderName, Global::basketsFolder() + newFolderName);
146             folderName = newFolderName;
147         } else
148             dir.rename(Global::savesFolder() + folderName, Global::basketsFolder() + folderName); // Move the folder
149 
150         // Import the basket structure file and get the properties (to add them in the tree basket-properties cache):
151         QDomElement properties = importBasket(folderName);
152 
153         // Add it to the XML document:
154         QDomElement basketElement = document.createElement("basket");
155         root.appendChild(basketElement);
156         basketElement.setAttribute("folderName", folderName);
157         basketElement.appendChild(properties);
158     }
159 
160     // Finalize (write to disk and delete now useless files):
161     qDebug() << "Import Baskets: Finalizing...";
162 
163     QFile file(Global::basketsFolder() + "baskets.xml");
164     if (file.open(QIODevice::WriteOnly)) {
165         QTextStream stream(&file);
166         stream.setCodec("UTF-8");
167         QString xml = document.toString();
168         stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
169         stream << xml;
170         file.close();
171     }
172 
173     Tools::deleteRecursively(Global::savesFolder() + ".tmp");
174     dir.remove(Global::savesFolder() + "container.baskets");
175 
176     qDebug() << "Import Baskets: Finished.";
177 }
178 
importBasket(const QString & folderName)179 QDomElement FormatImporter::importBasket(const QString &folderName)
180 {
181     // Load the XML file:
182     QDomDocument *document = XMLWork::openFile("basket", Global::basketsFolder() + folderName + "/.basket");
183     if (!document) {
184         qDebug() << "Import Baskets: Failed to read the basket file!";
185         return QDomElement();
186     }
187     QDomElement docElem = document->documentElement();
188 
189     // Import properties (change <background color=""> to <appearance backgroundColor="">, and figure out if is a checklist or not):
190     QDomElement properties = XMLWork::getElement(docElem, "properties");
191     QDomElement background = XMLWork::getElement(properties, "background");
192     QColor backgroundColor = QColor(background.attribute("color"));
193     if (backgroundColor.isValid() && (backgroundColor != qApp->palette().color(QPalette::Base))) { // Use the default color if it was already that color:
194         QDomElement appearance = document->createElement("appearance");
195         appearance.setAttribute("backgroundColor", backgroundColor.name());
196         properties.appendChild(appearance);
197     }
198     QDomElement disposition = document->createElement("disposition");
199     disposition.setAttribute("mindMap",     "false");
200     disposition.setAttribute("columnCount", "1");
201     disposition.setAttribute("free",        "false");
202     bool isCheckList = XMLWork::trueOrFalse(XMLWork::getElementText(properties, "showCheckBoxes"), false);
203 
204     // Insert all notes in a group (column): 1/ rename "items" to "group", 2/ add "notes" to root, 3/ move "group" into "notes"
205     QDomElement column = XMLWork::getElement(docElem, "items");
206     column.setTagName("group");
207     QDomElement notes = document->createElement("notes");
208     notes.appendChild(column);
209     docElem.appendChild(notes);
210 
211     // Import notes from older representations:
212     QDomNode n = column.firstChild();
213     while (! n.isNull()) {
214         QDomElement e = n.toElement();
215         if (!e.isNull()) {
216             e.setTagName("note");
217             QDomElement content = XMLWork::getElement(e, "content");
218             // Add Check tag:
219             if (isCheckList) {
220                 bool isChecked = XMLWork::trueOrFalse(e.attribute("checked", "false"));
221                 XMLWork::addElement(*document, e, "tags", (isChecked ? "todo_done" : "todo_unchecked"));
222             }
223             // Import annotations as folded groups:
224             QDomElement parentE = column;
225             QString annotations = XMLWork::getElementText(e, "annotations", "");
226             if (!annotations.isEmpty()) {
227                 QDomElement annotGroup = document->createElement("group");
228                 column.insertBefore(annotGroup, e);
229                 annotGroup.setAttribute("folded", "true");
230                 annotGroup.appendChild(e);
231                 parentE = annotGroup;
232                 // Create the text note and add it to the DOM tree:
233                 QDomElement annotNote = document->createElement("note");
234                 annotNote.setAttribute("type", "text");
235                 annotGroup.appendChild(annotNote);
236                 QString annotFileName = Tools::fileNameForNewFile("annotations1.txt", BasketScene::fullPathForFolderName(folderName));
237                 QString annotFullPath = BasketScene::fullPathForFolderName(folderName) + "/" + annotFileName;
238                 QFile file(annotFullPath);
239                 if (file.open(QIODevice::WriteOnly)) {
240                     QTextStream stream(&file);
241                     stream << annotations;
242                     file.close();
243                 }
244                 XMLWork::addElement(*document, annotNote, "content", annotFileName);
245                 n = annotGroup;
246             }
247             // Import Launchers from 0.3.x, 0.4.0 and 0.5.0-alphas:
248             QString runCommand = e.attribute("runcommand"); // Keep compatibility with 0.4.0 and 0.5.0-alphas versions
249             runCommand = XMLWork::getElementText(e, "action", runCommand); // Keep compatibility with 0.3.x versions
250             if (! runCommand.isEmpty()) {   // An import should be done
251                 // Prepare the launcher note:
252                 QString title = content.attribute("title", "");
253                 QString icon  = content.attribute("icon",  "");
254                 if (title.isEmpty()) title = runCommand;
255                 if (icon.isEmpty())  icon  = NoteFactory::iconForCommand(runCommand);
256                 // Import the launcher note:
257                 // Adapted version of "QString launcherName = NoteFactory::createNoteLauncherFile(runCommand, title, icon, this)":
258                 QString launcherContent = QString(
259                                               "[Desktop Entry]\n"
260                                               "Exec=%1\n"
261                                               "Name=%2\n"
262                                               "Icon=%3\n"
263                                               "Encoding=UTF-8\n"
264                                               "Type=Application\n").arg(runCommand, title, icon.isEmpty() ? QString("exec") : icon);
265                 QString launcherFileName = Tools::fileNameForNewFile("launcher.desktop", Global::basketsFolder() + folderName /*+ "/"*/);
266                 QString launcherFullPath = Global::basketsFolder() + folderName /*+ "/"*/ + launcherFileName;
267                 QFile file(launcherFullPath);
268                 if (file.open(QIODevice::WriteOnly)) {
269                     QTextStream stream(&file);
270                     stream.setCodec("UTF-8");
271                     stream << launcherContent;
272                     file.close();
273                 }
274                 // Add the element to the DOM:
275                 QDomElement launcherElem = document->createElement("note");
276                 parentE.insertBefore(launcherElem, e);
277                 launcherElem.setAttribute("type", "launcher");
278                 XMLWork::addElement(*document, launcherElem, "content", launcherFileName);
279             }
280             // Import unknown ns to 0.6.0:
281             if (e.attribute("type") == "unknow")
282                 e.setAttribute("type", "unknown");
283             // Import links from version < 0.5.0:
284             if (!content.attribute("autotitle").isEmpty() && content.attribute("autoTitle").isEmpty())
285                 content.setAttribute("autoTitle", content.attribute("autotitle"));
286             if (!content.attribute("autoicon").isEmpty() && content.attribute("autoIcon").isEmpty())
287                 content.setAttribute("autoIcon", content.attribute("autoicon"));
288         }
289         n = n.nextSibling();
290     }
291 
292     // Save the resulting XML file:
293     QFile file(Global::basketsFolder() + folderName + "/.basket");
294     if (file.open(QIODevice::WriteOnly)) {
295         QTextStream stream(&file);
296         stream.setCodec("UTF-8");
297 //      QString xml = document->toString();
298 //      stream << "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
299 //      stream << xml;
300         stream << document->toString(); // Document is ALREADY using UTF-8
301         file.close();
302     } else
303         qDebug() << "Import Baskets: Failed to save the basket file!";
304 
305     // Return the newly created properties (to put in the basket tree):
306     return properties;
307 }
308 
309