Warning, file /office/calligra/braindump/src/SectionsIO.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  Copyright (c) 2009 Cyrille Berger <cberger@cberger.net>
0003  *
0004  * This library is free software; you can redistribute it and/or
0005  * modify it under the terms of the GNU Lesser General Public
0006  * License as published by the Free Software Foundation;
0007  * either version 2, or (at your option) any later version of the License.
0008  *
0009  * This library is distributed in the hope that it will be useful,
0010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012  * Lesser General Public License for more details.
0013  *
0014  * You should have received a copy of the GNU Lesser General Public License
0015  * along with this library; see the file COPYING.  If not, write to
0016  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018  */
0019 
0020 #include "SectionsIO.h"
0021 
0022 #include <QDomDocument>
0023 #include <QFileInfo>
0024 #include <QTimer>
0025 #include <QDebug>
0026 
0027 #include <KoStore.h>
0028 #include <KoOdf.h>
0029 #include <KoOdfWriteStore.h>
0030 #include <KoEmbeddedDocumentSaver.h>
0031 #include <KoGenStyles.h>
0032 #include <KoShapeSavingContext.h>
0033 #include <KoXmlWriter.h>
0034 #include <KoOdfReadStore.h>
0035 #include <KoXmlNS.h>
0036 #include <KoShapeLoadingContext.h>
0037 #include <KoOdfLoadingContext.h>
0038 
0039 #include "RootSection.h"
0040 #include "SectionGroup.h"
0041 #include "Section.h"
0042 #include "SectionContainer.h"
0043 #include "Layout.h"
0044 #include "LayoutFactoryRegistry.h"
0045 #include "Xml.h"
0046 
0047 SectionsIO::SectionsIO(RootSection* rootSection) : m_rootSection(rootSection), m_timer(new QTimer(this)), m_nextNumber(0)
0048 {
0049     m_timer->start(60 * 1000); // Every minute
0050     connect(m_timer, SIGNAL(timeout()), SLOT(save()));
0051     m_directory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/sections/";
0052     QDir().mkdir(m_directory);
0053 
0054     // Finally load
0055     load();
0056 }
0057 
0058 SectionsIO::~SectionsIO()
0059 {
0060 }
0061 
0062 void SectionsIO::push(Section* _section, PushMode _pushMode)
0063 {
0064     if(!m_sectionsToSave.contains(_section)) {
0065         m_sectionsToSave.push_back(_section);
0066     }
0067     if(_pushMode == RecursivePush) {
0068         foreach(Section * sec, _section->sections()) {
0069             push(sec, RecursivePush);
0070         }
0071     }
0072 }
0073 
0074 struct SectionsIO::SaveContext {
0075     enum Version {
0076         VERSION_1
0077     };
0078     Section* section;
0079     QString filename;
0080     bool saveSection(SectionsIO* sectionsIO);
0081     bool loadSection(SectionsIO* sectionsIO, Version version);
0082 };
0083 
0084 bool SectionsIO::SaveContext::saveSection(SectionsIO* sectionsIO)
0085 {
0086     struct Finally {
0087         Finally(KoStore *s) : store(s) { }
0088         ~Finally() {
0089             delete store;
0090         }
0091         KoStore *store;
0092     };
0093 
0094     QString fullFileName = sectionsIO->m_directory + filename;
0095     QString fullFileNameTmpNew = fullFileName + ".tmp_new/";
0096     QString fullFileNameTmpOld = fullFileName + ".tmp_old";
0097     QDir(fullFileNameTmpNew).removeRecursively();
0098 
0099     const char* mimeType = KoOdf::mimeType(KoOdf::Text);
0100 
0101     QDir().mkdir(fullFileNameTmpNew);
0102     KoStore* store = KoStore::createStore(fullFileNameTmpNew, KoStore::Write, mimeType, KoStore::Directory);
0103     Finally finaly(store);
0104 
0105     KoOdfWriteStore odfStore(store);
0106     KoEmbeddedDocumentSaver embeddedSaver;
0107 
0108     KoXmlWriter* manifestWriter = odfStore.manifestWriter(mimeType);
0109     KoXmlWriter* contentWriter = odfStore.contentWriter();
0110     KoXmlWriter* bodyWriter = odfStore.bodyWriter();
0111 
0112     if(!manifestWriter || !contentWriter || !bodyWriter) {
0113         return false;
0114     }
0115 
0116     KoGenStyles mainStyles;
0117     KoShapeSavingContext * context = new KoShapeSavingContext(*bodyWriter, mainStyles, embeddedSaver);
0118     context->addOption(KoShapeSavingContext::DrawId);
0119 
0120     bodyWriter->startElement("office:body");
0121     Xml::writeBraindumpNS(*bodyWriter);
0122     bodyWriter->startElement(KoOdf::bodyContentElement(KoOdf::Text, true));
0123 
0124     section->sectionContainer()->saveOdf(*context);
0125 
0126     bodyWriter->startElement("braindump:layout");
0127     bodyWriter->addAttribute("braindump:type", section->layout()->id());
0128     bodyWriter->endElement(); // braindump:layout
0129 
0130     bodyWriter->endElement(); // office:element
0131     bodyWriter->endElement(); // office:body
0132 
0133     mainStyles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, contentWriter);
0134 
0135     odfStore.closeContentWriter();
0136 
0137     //add manifest line for content.xml
0138     manifestWriter->addManifestEntry("content.xml", "text/xml");
0139 
0140 
0141     if(!mainStyles.saveOdfStylesDotXml(store, manifestWriter)) {
0142         return false;
0143     }
0144 
0145     if(!context->saveDataCenter(store, manifestWriter)) {
0146         qDebug() << "save data centers failed";
0147         return false;
0148     }
0149 
0150     // Save embedded objects
0151     KoDocumentBase::SavingContext documentContext(odfStore, embeddedSaver);
0152     if(!embeddedSaver.saveEmbeddedDocuments(documentContext)) {
0153         qDebug() << "save embedded documents failed";
0154         return false;
0155     }
0156 
0157     // Write out manifest file
0158     if(!odfStore.closeManifestWriter()) {
0159         return false;
0160     }
0161 
0162     delete store;
0163     finaly.store = 0;
0164     delete context;
0165 
0166     QDir(fullFileNameTmpOld).removeRecursively();
0167     QDir().rename(fullFileName, fullFileNameTmpOld);
0168     QDir().rename(fullFileNameTmpNew, fullFileName);
0169     QDir(fullFileNameTmpOld).removeRecursively();
0170 
0171     return true;
0172 }
0173 
0174 bool SectionsIO::SaveContext::loadSection(SectionsIO* sectionsIO, SectionsIO::SaveContext::Version version)
0175 {
0176     Q_UNUSED(version);
0177     // In case saving problem occurred, try to recover a directory either new or old
0178     QString fullFileName = sectionsIO->m_directory + filename;
0179     QString fullFileNameTmpNew = fullFileName + ".tmp_new/";
0180     QString fullFileNameTmpOld = fullFileName + ".tmp_old";
0181     if(!QFileInfo(fullFileName).exists()) {
0182         if(QFileInfo(fullFileNameTmpNew).exists()) {
0183             QDir().rename(fullFileNameTmpNew, fullFileName);
0184         } else if(QFileInfo(fullFileNameTmpOld).exists()) {
0185             QDir().rename(fullFileNameTmpOld, fullFileName);
0186         } else {
0187             return false;
0188         }
0189     }
0190     qDebug() << "Loading from " << fullFileName;
0191 
0192     const char* mimeType = KoOdf::mimeType(KoOdf::Text);
0193     KoStore* store = KoStore::createStore(fullFileName + '/', KoStore::Read, mimeType, KoStore::Directory);
0194     KoOdfReadStore odfStore(store);
0195 
0196     QString errorMessage;
0197     if(! odfStore.loadAndParse(errorMessage)) {
0198         qCritical() << "loading and parsing failed:" << errorMessage << endl;
0199         return false;
0200     }
0201 
0202     KoXmlElement content = odfStore.contentDoc().documentElement();
0203     KoXmlElement realBody(KoXml::namedItemNS(content, KoXmlNS::office, "body"));
0204 
0205     KoXmlElement body = KoXml::namedItemNS(realBody, KoXmlNS::office, KoOdf::bodyContentElement(KoOdf::Text, false));
0206 
0207     KoOdfLoadingContext loadingContext(odfStore.styles(), odfStore.store());
0208     KoShapeLoadingContext context(loadingContext, section->sectionContainer()->resourceManager());
0209 
0210     KoXmlElement element;
0211     QList<KoShape*> shapes;
0212     forEachElement(element, body) {
0213         qDebug() << "loading shape" << element.nodeName();
0214 
0215         if(element.nodeName() == "braindump:section") {
0216             section->sectionContainer()->loadOdf(element, context, shapes);
0217         } else if(element.nodeName() == "braindump:layout") {
0218             QString type = element.attribute("type");
0219             Layout* layout = LayoutFactoryRegistry::instance()->createLayout(type);
0220             if(layout) {
0221                 section->setLayout(layout);
0222             }
0223         }
0224     }
0225     section->layout()->addShapes(shapes);
0226     return true;
0227 }
0228 
0229 void SectionsIO::saveTheStructure(QDomDocument& doc, QDomElement& elt, SectionGroup* root, QList<SaveContext*>& contextToRemove)
0230 {
0231     foreach(Section * section, root->sections()) {
0232         SaveContext* context = m_contextes[section];
0233         if(context) {
0234             contextToRemove.removeAll(context);
0235         } else {
0236             context = new SaveContext;
0237             m_contextes[section] = context;
0238             context->section = section;
0239             context->filename = generateFileName();
0240         }
0241         Q_ASSERT(context);
0242         QDomElement celt = doc.createElement("Section");
0243         elt.appendChild(celt);
0244         celt.setAttribute("filename", context->filename);
0245         celt.setAttribute("name", section->name());
0246         saveTheStructure(doc, celt, section, contextToRemove);
0247     }
0248 }
0249 
0250 void SectionsIO::save()
0251 {
0252     qDebug() << "Start saving";
0253     if(m_sectionsToSave.isEmpty()) {
0254         qDebug() << "No section to save";
0255         return;
0256     }
0257     QList<SaveContext*> contextToRemove = m_contextes.values();
0258     // First: save the structure
0259     QDomDocument doc;
0260     QDomElement root = doc.createElement("RootElement");
0261     doc.appendChild(root);
0262     saveTheStructure(doc, root, m_rootSection, contextToRemove);
0263     QFile file(structureFileName());
0264     file.open(QIODevice::WriteOnly);
0265     file.write(doc.toString().toUtf8());
0266     file.close();
0267 
0268     // Second: save each section
0269     foreach(SaveContext * saveContext, m_contextes) {
0270         if(m_sectionsToSave.contains(saveContext->section)) {
0271             if(saveContext->saveSection(this)) {
0272                 qDebug() << "Successfully loaded: " << saveContext->section->name();
0273             } else {
0274                 qDebug() << "Saving failed"; // TODO: Report it
0275             }
0276         }
0277     }
0278     m_sectionsToSave.clear();
0279 
0280     // Last remove unused sections
0281     foreach(SaveContext * saveContext, contextToRemove) {
0282         QDir(m_directory + saveContext->filename).removeRecursively();
0283         m_contextes.remove(saveContext->section);
0284         delete saveContext;
0285     }
0286 }
0287 
0288 void SectionsIO::loadTheStructure(QDomElement& elt, SectionGroup* parent, RootSection* _rootSection)
0289 {
0290     QDomNode n = elt.firstChild();
0291     while(!n.isNull()) {
0292         QDomElement e = n.toElement(); // try to convert the node to an element.
0293         if(!e.isNull() && e.nodeName() == "Section") {
0294             Section* section = new Section(_rootSection);
0295             QString name = e.attribute("name", "");
0296             if(name.isEmpty()) {
0297                 name = SectionGroup::nextName();
0298             }
0299             section->setName(name);
0300             parent->insertSection(section);
0301             SaveContext* context = new SaveContext;
0302             context->filename = e.attribute("filename", "");
0303             context->section = section;
0304             m_contextes[section] = context;
0305             loadTheStructure(e, section, _rootSection);
0306         }
0307         n = n.nextSibling();
0308     }
0309 }
0310 
0311 void SectionsIO::load()
0312 {
0313     QDomDocument doc;
0314     QFile file(structureFileName());
0315     if(!file.open(QIODevice::ReadOnly))
0316         return;
0317     if(!doc.setContent(&file)) {
0318         file.close();
0319         return;
0320     }
0321     file.close();
0322 
0323     QDomElement docElem = doc.documentElement();
0324     if(docElem.nodeName() != "RootElement") return;
0325 
0326     loadTheStructure(docElem, m_rootSection, m_rootSection);
0327 
0328     // Second: load each section
0329     foreach(SaveContext * saveContext, m_contextes) {
0330         if(!saveContext->loadSection(this, SaveContext::VERSION_1)) {
0331             qDebug() << "Loading failed"; // TODO: Report it
0332         }
0333     }
0334 }
0335 
0336 QString SectionsIO::generateFileName()
0337 {
0338     for(; true; ++m_nextNumber) {
0339         QString filename = "section" + QString::number(m_nextNumber);
0340         if(!QFileInfo(m_directory + filename).exists() && !usedFileName(filename)) {
0341             return filename;
0342         }
0343     }
0344 }
0345 
0346 bool SectionsIO::usedFileName(const QString& filename)
0347 {
0348     foreach(SaveContext * context, m_contextes.values()) {
0349         if(context->filename == filename)
0350             return true;
0351     }
0352     return false;
0353 }
0354 
0355 QString SectionsIO::structureFileName()
0356 {
0357     return m_directory + "structure.xml";
0358 }