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 }