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