Download src/Gui/AutoSaver.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 15.2 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Gui/AutoSaver.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Gui/AutoSaver.cpp
-
curl -L -o AutoSaver.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Gui/AutoSaver.cpp
15.2 kB
| /*************************************************************************** | |
| * Copyright (c) 2015 Werner Mayer <wmayer[at]users.sourceforge.net> * | |
| * * | |
| * This file is part of the FreeCAD CAx development system. * | |
| * * | |
| * This library is free software; you can redistribute it and/or * | |
| * modify it under the terms of the GNU Library General Public * | |
| * License as published by the Free Software Foundation; either * | |
| * version 2 of the License, or (at your option) any later version. * | |
| * * | |
| * This library is distributed in the hope that it will be useful, * | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
| * GNU Library General Public License for more details. * | |
| * * | |
| * You should have received a copy of the GNU Library General Public * | |
| * License along with this library; see the file COPYING.LIB. If not, * | |
| * write to the Free Software Foundation, Inc., 59 Temple Place, * | |
| * Suite 330, Boston, MA 02111-1307, USA * | |
| * * | |
| ***************************************************************************/ | |
| FC_LOG_LEVEL_INIT("App", true, true) | |
| using namespace Gui; | |
| namespace sp = std::placeholders; | |
| AutoSaver* AutoSaver::self = nullptr; | |
| const int AutoSaveTimeout = 900000; | |
| AutoSaver::AutoSaver(QObject* parent) | |
| : QObject(parent) | |
| , timeout(AutoSaveTimeout) | |
| , compressed(true) | |
| { | |
| // NOLINTBEGIN | |
| App::GetApplication().signalNewDocument.connect( | |
| std::bind(&AutoSaver::slotCreateDocument, this, sp::_1) | |
| ); | |
| App::GetApplication().signalDeleteDocument.connect( | |
| std::bind(&AutoSaver::slotDeleteDocument, this, sp::_1) | |
| ); | |
| // NOLINTEND | |
| } | |
| AutoSaver::~AutoSaver() = default; | |
| AutoSaver* AutoSaver::instance() | |
| { | |
| if (!self) { | |
| self = new AutoSaver(QApplication::instance()); | |
| } | |
| return self; | |
| } | |
| void AutoSaver::renameFile(QString dirName, QString file, QString tmpFile) | |
| { | |
| FC_LOG("auto saver rename " << tmpFile.toUtf8().constData() << " -> " << file.toUtf8().constData()); | |
| QDir dir(dirName); | |
| dir.remove(file); | |
| if (!dir.rename(tmpFile, file)) { | |
| FC_ERR( | |
| "Failed to rename autosave file " << tmpFile.toStdString() << " to " | |
| << file.toStdString() << "\n" | |
| ); | |
| } | |
| } | |
| void AutoSaver::setTimeout(int ms) | |
| { | |
| timeout = Base::clamp<int>(ms, 0, 3600000); // between 0 and 60 min | |
| // go through the attached documents and apply the new timeout | |
| for (auto& it : saverMap) { | |
| if (it.second->timerId > 0) { | |
| killTimer(it.second->timerId); | |
| } | |
| int id = timeout > 0 ? startTimer(timeout) : 0; | |
| it.second->timerId = id; | |
| } | |
| } | |
| void AutoSaver::setCompressed(bool on) | |
| { | |
| this->compressed = on; | |
| } | |
| void AutoSaver::slotCreateDocument(const App::Document& Doc) | |
| { | |
| std::string name = Doc.getName(); | |
| int id = timeout > 0 ? startTimer(timeout) : 0; | |
| AutoSaveProperty* as = new AutoSaveProperty(&Doc); | |
| as->timerId = id; | |
| if (!this->compressed) { | |
| std::string dirName = Doc.TransientDir.getValue(); | |
| dirName += "/fc_recovery_files"; | |
| Base::FileInfo fi(dirName); | |
| fi.createDirectory(); | |
| as->dirName = dirName; | |
| } | |
| saverMap.insert(std::make_pair(name, as)); | |
| } | |
| void AutoSaver::slotDeleteDocument(const App::Document& Doc) | |
| { | |
| std::string name = Doc.getName(); | |
| std::map<std::string, AutoSaveProperty*>::iterator it = saverMap.find(name); | |
| if (it != saverMap.end()) { | |
| if (it->second->timerId > 0) { | |
| killTimer(it->second->timerId); | |
| } | |
| delete it->second; | |
| saverMap.erase(it); | |
| } | |
| } | |
| void AutoSaver::saveDocument(const std::string& name, AutoSaveProperty& saver) | |
| { | |
| Gui::WaitCursor wc; | |
| App::Document* doc = App::GetApplication().getDocument(name.c_str()); | |
| if (doc && !doc->testStatus(App::Document::PartialDoc) | |
| && !doc->testStatus(App::Document::TempDoc)) { | |
| // Set the document's current transient directory | |
| std::string dirName = doc->TransientDir.getValue(); | |
| dirName += "/fc_recovery_files"; | |
| saver.dirName = dirName; | |
| // Write recovery meta file | |
| QFile file(QStringLiteral("%1/fc_recovery_file.xml") | |
| .arg(QString::fromUtf8(doc->TransientDir.getValue()))); | |
| if (file.open(QFile::WriteOnly)) { | |
| QTextStream str(&file); | |
| str.setCodec("UTF-8"); | |
| str << "<?xml version='1.0' encoding='utf-8'?>\n" | |
| << "<AutoRecovery SchemaVersion=\"1\">\n"; | |
| str << " <Status>Created</Status>\n"; | |
| str << " <Label>" << QString::fromUtf8(doc->Label.getValue()) | |
| << "</Label>\n"; // store the document's current label | |
| str << " <FileName>" << QString::fromUtf8(doc->FileName.getValue()) | |
| << "</FileName>\n"; // store the document's current filename | |
| str << "</AutoRecovery>\n"; | |
| file.close(); | |
| } | |
| // make sure to tmp. disable saving thumbnails because this causes trouble if the | |
| // associated 3d view is not active | |
| Base::Reference<ParameterGrp> hGrp = App::GetApplication().GetParameterGroupByPath( | |
| "User parameter:BaseApp/Preferences/Document" | |
| ); | |
| bool save = hGrp->GetBool("SaveThumbnail", true); | |
| hGrp->SetBool("SaveThumbnail", false); | |
| getMainWindow()->showMessage(tr("Wait until the auto-recovery file has been saved…"), 5000); | |
| // qApp->processEvents(); | |
| Base::TimeElapsed startTime; | |
| // open extra scope to close ZipWriter properly | |
| { | |
| if (!this->compressed) { | |
| RecoveryWriter writer(saver); | |
| // We will be using thread pool if not compressed. | |
| // So, always force binary format because ASCII | |
| // is not reentrant. See PropertyPartShape::SaveDocFile | |
| writer.setMode("BinaryBrep"); | |
| writer.putNextEntry("Document.xml"); | |
| doc->Save(writer); | |
| // Special handling for Gui document. | |
| doc->signalSaveDocument(writer); | |
| // write additional files | |
| writer.writeFiles(); | |
| } | |
| // only create the file if something has changed | |
| else if (!saver.touched.empty()) { | |
| std::string fn = doc->TransientDir.getValue(); | |
| fn += "/fc_recovery_file.fcstd"; | |
| Base::FileInfo tmp(fn); | |
| Base::ofstream file(tmp, std::ios::out | std::ios::binary); | |
| if (file.is_open()) { | |
| Base::ZipWriter writer(file); | |
| if (hGrp->GetBool("SaveBinaryBrep", true)) { | |
| writer.setMode("BinaryBrep"); | |
| } | |
| writer.setComment("AutoRecovery file"); | |
| writer.setLevel(1); // apparently the fastest compression | |
| writer.putNextEntry("Document.xml"); | |
| doc->Save(writer); | |
| // Special handling for Gui document. | |
| doc->signalSaveDocument(writer); | |
| // write additional files | |
| writer.writeFiles(); | |
| } | |
| } | |
| } | |
| Base::Console().log( | |
| "Save auto-recovery file in %fs\n", | |
| Base::TimeElapsed::diffTimeF(startTime, Base::TimeElapsed()) | |
| ); | |
| hGrp->SetBool("SaveThumbnail", save); | |
| } | |
| } | |
| void AutoSaver::timerEvent(QTimerEvent* event) | |
| { | |
| int id = event->timerId(); | |
| for (auto& it : saverMap) { | |
| if (it.second->timerId == id) { | |
| try { | |
| saveDocument(it.first, *it.second); | |
| it.second->touched.clear(); | |
| break; | |
| } | |
| catch (...) { | |
| Base::Console().error("Failed to auto-save document '%s'\n", it.first.c_str()); | |
| } | |
| } | |
| } | |
| } | |
| // ---------------------------------------------------------------------------- | |
| AutoSaveProperty::AutoSaveProperty(const App::Document* doc) | |
| : timerId(-1) | |
| { | |
| // NOLINTBEGIN | |
| documentNew = const_cast<App::Document*>(doc)->signalNewObject.connect( | |
| std::bind(&AutoSaveProperty::slotNewObject, this, sp::_1) | |
| ); | |
| documentMod = const_cast<App::Document*>(doc)->signalChangedObject.connect( | |
| std::bind(&AutoSaveProperty::slotChangePropertyData, this, sp::_2) | |
| ); | |
| // NOLINTEND | |
| } | |
| AutoSaveProperty::~AutoSaveProperty() | |
| { | |
| documentNew.disconnect(); | |
| documentMod.disconnect(); | |
| } | |
| void AutoSaveProperty::slotNewObject(const App::DocumentObject& obj) | |
| { | |
| std::vector<App::Property*> props; | |
| obj.getPropertyList(props); | |
| // if an object was deleted and then restored by an undo then add all properties | |
| // because this might be the data files which we may want to re-write | |
| for (const auto& prop : props) { | |
| slotChangePropertyData(*prop); | |
| } | |
| } | |
| void AutoSaveProperty::slotChangePropertyData(const App::Property& prop) | |
| { | |
| std::stringstream str; | |
| str << static_cast<const void*>(&prop) << std::ends; | |
| std::string address = str.str(); | |
| this->touched.insert(address); | |
| } | |
| // ---------------------------------------------------------------------------- | |
| RecoveryWriter::RecoveryWriter(AutoSaveProperty& saver) | |
| : Base::FileWriter(saver.dirName.c_str()) | |
| , saver(saver) | |
| {} | |
| RecoveryWriter::~RecoveryWriter() = default; | |
| bool RecoveryWriter::shouldWrite(const std::string& name, const Base::Persistence* object) const | |
| { | |
| // Property files of a view provider can always be written because | |
| // these are rather small files. | |
| if (object->isDerivedFrom<App::Property>()) { | |
| const auto* prop = static_cast<const App::Property*>(object); | |
| const App::PropertyContainer* parent = prop->getContainer(); | |
| if (parent && parent->isDerivedFrom<Gui::ViewProvider>()) { | |
| return true; | |
| } | |
| } | |
| else if (object->isDerivedFrom<Gui::Document>()) { | |
| return true; | |
| } | |
| // These are the addresses of touched properties of a document object. | |
| std::stringstream str; | |
| str << static_cast<const void*>(object) << std::ends; | |
| std::string address = str.str(); | |
| // Check if the property will be exported to the same file. If the file has changed or if the | |
| // property hasn't been yet exported then (re-)write the file. | |
| std::map<std::string, std::string>::iterator it = saver.fileMap.find(address); | |
| if (it == saver.fileMap.end() || it->second != name) { | |
| saver.fileMap[address] = name; | |
| return true; | |
| } | |
| std::set<std::string>::const_iterator jt = saver.touched.find(address); | |
| return (jt != saver.touched.end()); | |
| } | |
| namespace Gui | |
| { | |
| class RecoveryRunnable: public QRunnable | |
| { | |
| public: | |
| RecoveryRunnable( | |
| const std::set<std::string>& modes, | |
| const char* dir, | |
| const char* file, | |
| const App::Property* p | |
| ) | |
| : prop(p->Copy()) | |
| , writer(dir) | |
| { | |
| writer.setModes(modes); | |
| dirName = QString::fromUtf8(dir); | |
| fileName = QString::fromUtf8(file); | |
| tmpName = QStringLiteral("%1.tmp%2").arg(fileName).arg(rand()); | |
| writer.putNextEntry(tmpName.toUtf8().constData()); | |
| } | |
| ~RecoveryRunnable() override | |
| { | |
| delete prop; | |
| } | |
| void run() override | |
| { | |
| try { | |
| prop->SaveDocFile(writer); | |
| writer.close(); | |
| // We could have renamed the file in this thread. However, there is | |
| // still chance of crash when we deleted the original and before rename | |
| // the new file. So we ask the main thread to do it. There is still | |
| // possibility of crash caused by thread other than the main, but | |
| // that's the best we can do for now. | |
| QMetaObject::invokeMethod( | |
| AutoSaver::instance(), | |
| "renameFile", | |
| Qt::QueuedConnection, | |
| Q_ARG(QString, dirName), | |
| Q_ARG(QString, fileName), | |
| Q_ARG(QString, tmpName) | |
| ); | |
| } | |
| catch (const Base::Exception& e) { | |
| Base::Console().warning("Exception in auto-saving: %s\n", e.what()); | |
| } | |
| catch (const std::exception& e) { | |
| Base::Console().warning("C++ exception in auto-saving: %s\n", e.what()); | |
| } | |
| catch (...) { | |
| Base::Console().warning("Unknown exception in auto-saving\n"); | |
| } | |
| } | |
| private: | |
| App::Property* prop; | |
| Base::FileWriter writer; | |
| QString dirName; | |
| QString fileName; | |
| QString tmpName; | |
| }; | |
| } // namespace Gui | |
| void RecoveryWriter::writeFiles() | |
| { | |
| // use a while loop because it is possible that while | |
| // processing the files new ones can be added | |
| size_t index = 0; | |
| this->FileStream.close(); | |
| while (index < FileList.size()) { | |
| FileEntry entry = FileList.begin()[index]; | |
| if (shouldWrite(entry.FileName, entry.Object)) { | |
| std::string filePath = entry.FileName; | |
| std::string::size_type pos = 0; | |
| while ((pos = filePath.find('/', pos)) != std::string::npos) { | |
| std::string dirName = DirName + "/" + filePath.substr(0, pos); | |
| pos++; | |
| Base::FileInfo fi(dirName); | |
| fi.createDirectory(); | |
| } | |
| // For properties a copy can be created and then this can be written to disk in a thread | |
| if (entry.Object->isDerivedFrom<App::Property>()) { | |
| const auto* prop = static_cast<const App::Property*>(entry.Object); | |
| QThreadPool::globalInstance()->start( | |
| new RecoveryRunnable(getModes(), DirName.c_str(), entry.FileName.c_str(), prop) | |
| ); | |
| } | |
| else { | |
| std::string fileName = DirName + "/" + entry.FileName; | |
| this->FileStream.open(fileName.c_str(), std::ios::out | std::ios::binary); | |
| entry.Object->SaveDocFile(*this); | |
| this->FileStream.close(); | |
| } | |
| } | |
| index++; | |
| } | |
| } | |