+ implement file based auto-save & recovery mechanism

This commit is contained in:
wmayer 2015-09-19 01:13:33 +02:00
parent 8db1280a4d
commit 848f9c4d46
7 changed files with 192 additions and 73 deletions

View File

@ -1702,6 +1702,7 @@ void Application::runApplication(void)
if (!hDocGrp->GetBool("AutoSaveEnabled", true))
timeout = 0;
AutoSaver::instance()->setTimeout(timeout * 60000);
AutoSaver::instance()->setCompressed(hDocGrp->GetBool("AutoSaveCompressed", false));
// set toolbar icon size
ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("General");
@ -1915,7 +1916,7 @@ void Application::checkForPreviousCrashes()
countDeletedDocs++;
}
// search for the existance of a recovery file
else if (doc_dir.exists(QLatin1String("fc_recovery_file.fcstd"))) {
else if (doc_dir.exists(QLatin1String("fc_recovery_file.xml"))) {
// store the transient directory in case it's not empty
restoreDocFiles << *it;
}

View File

@ -52,7 +52,7 @@ using namespace Gui;
AutoSaver* AutoSaver::self = 0;
AutoSaver::AutoSaver(QObject* parent)
: QObject(parent), timeout(900000)
: QObject(parent), timeout(900000), compressed(false)
{
App::GetApplication().signalNewDocument.connect(boost::bind(&AutoSaver::slotCreateDocument, this, _1));
App::GetApplication().signalDeleteDocument.connect(boost::bind(&AutoSaver::slotDeleteDocument, this, _1));
@ -82,17 +82,25 @@ void AutoSaver::setTimeout(int ms)
}
}
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;
std::string dirName = Doc.TransientDir.getValue();
dirName += "/fc_recovery_files";
Base::FileInfo fi(dirName);
fi.createDirectory();
as->dirName = dirName;
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));
}
@ -113,6 +121,11 @@ void AutoSaver::saveDocument(const std::string& name, AutoSaveProperty& saver)
Gui::WaitCursor wc;
App::Document* doc = App::GetApplication().getDocument(name.c_str());
if (doc) {
// 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(QString::fromLatin1("%1/fc_recovery_file.xml")
.arg(QString::fromUtf8(doc->TransientDir.getValue())));
@ -128,10 +141,6 @@ void AutoSaver::saveDocument(const std::string& name, AutoSaveProperty& saver)
file.close();
}
std::string fn = doc->TransientDir.getValue();
fn += "/fc_recovery_file.fcstd";
Base::FileInfo tmp(fn);
// 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
@ -139,25 +148,18 @@ void AutoSaver::saveDocument(const std::string& name, AutoSaveProperty& saver)
bool save = hGrp->GetBool("SaveThumbnail",false);
hGrp->SetBool("SaveThumbnail",false);
//Gui::StatusWidget* sw = new Gui::StatusWidget(qApp->activeWindow());
//sw->setStatusText(tr("Please wait until the AutoRecovery file has been saved..."));
//sw->show();
getMainWindow()->showMessage(tr("Please wait until the AutoRecovery file has been saved..."), 5000);
qApp->processEvents();
//qApp->processEvents();
// open extra scope to close ZipWriter properly
Base::StopWatch watch;
watch.start();
{
Base::ofstream file(tmp, std::ios::out | std::ios::binary);
if (file.is_open()) {
Base::ZipWriter writer(file);
//RecoveryWriter writer(saver);
if (!this->compressed) {
RecoveryWriter writer(saver);
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);
@ -168,10 +170,31 @@ void AutoSaver::saveDocument(const std::string& name, AutoSaveProperty& saver)
// write additional files
writer.writeFiles();
}
}
else {
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");
//sw->hide();
//sw->deleteLater();
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();
}
}
}
std::string str = watch.toString(watch.elapsed());
Base::Console().Log("Save AutoRecovery file: %s\n", str.c_str());
@ -243,16 +266,6 @@ RecoveryWriter::~RecoveryWriter()
{
}
void RecoveryWriter::setLevel(int)
{
// not implemented
}
void RecoveryWriter::setComment(const char*)
{
// not implemented
}
bool RecoveryWriter::shouldWrite(const std::string& name, const Base::Persistence *object) const
{
// Property files of a view provider can always be written because

View File

@ -77,6 +77,10 @@ public:
Sets the timeout in milliseconds. A value of 0 means that no timer is used.
*/
void setTimeout(int ms);
/*!
Enables or disables to create compreesed recovery files.
*/
void setCompressed(bool on);
protected:
void slotCreateDocument(const App::Document& Doc);
@ -86,6 +90,7 @@ protected:
private:
int timeout; /*!< Timeout in milliseconds */
bool compressed;
std::map<std::string, AutoSaveProperty*> saverMap;
};
@ -95,9 +100,6 @@ public:
RecoveryWriter(AutoSaveProperty&);
virtual ~RecoveryWriter();
void setLevel(int);
void setComment(const char*);
/*!
This method can be re-implemented in sub-classes to avoid
to write out certain objects. The default implementation

View File

@ -37,7 +37,7 @@
using namespace Gui::Dialog;
// taken from the script doctools.py
const char* doctools =
std::string DlgProjectUtility::doctools =
"import os,sys,string\n"
"import xml.sax\n"
"import xml.sax.handler\n"

View File

@ -25,6 +25,7 @@
#define GUI_DIALOG_DLGPROJECTUTILITY_H
#include <QDialog>
#include <string>
namespace Gui { namespace Dialog {
@ -42,6 +43,7 @@ private Q_SLOTS:
void on_createButton_clicked();
protected:
static std::string doctools;
Ui_DlgProjectUtility* ui;
};

View File

@ -34,6 +34,7 @@
# include <QDebug>
# include <QDir>
# include <QFile>
# include <QFileInfo>
# include <QHeaderView>
# include <QPushButton>
# include <QTextStream>
@ -41,6 +42,7 @@
# include <QMap>
# include <QList>
# include <QVector>
# include <sstream>
#endif
#include "DocumentRecovery.h"
@ -60,6 +62,74 @@
using namespace Gui;
using namespace Gui::Dialog;
// taken from the script doctools.py
std::string DocumentRecovery::doctools =
"import os,sys,string\n"
"import xml.sax\n"
"import xml.sax.handler\n"
"import xml.sax.xmlreader\n"
"import zipfile\n"
"\n"
"# SAX handler to parse the Document.xml\n"
"class DocumentHandler(xml.sax.handler.ContentHandler):\n"
" def __init__(self, dirname):\n"
" self.files = []\n"
" self.dirname = dirname\n"
"\n"
" def startElement(self, name, attributes):\n"
" item=attributes.get(\"file\")\n"
" if item != None:\n"
" self.files.append(os.path.join(self.dirname,str(item)))\n"
"\n"
" def characters(self, data):\n"
" return\n"
"\n"
" def endElement(self, name):\n"
" return\n"
"\n"
"def extractDocument(filename, outpath):\n"
" zfile=zipfile.ZipFile(filename)\n"
" files=zfile.namelist()\n"
"\n"
" for i in files:\n"
" data=zfile.read(i)\n"
" dirs=i.split(\"/\")\n"
" if len(dirs) > 1:\n"
" dirs.pop()\n"
" curpath=outpath\n"
" for j in dirs:\n"
" curpath=curpath+\"/\"+j\n"
" os.mkdir(curpath)\n"
" output=open(outpath+\"/\"+i,\'wb\')\n"
" output.write(data)\n"
" output.close()\n"
"\n"
"def createDocument(filename, outpath):\n"
" files=getFilesList(filename)\n"
" dirname=os.path.dirname(filename)\n"
" guixml=os.path.join(dirname,\"GuiDocument.xml\")\n"
" if os.path.exists(guixml):\n"
" files.extend(getFilesList(guixml))\n"
" compress=zipfile.ZipFile(outpath,\'w\',zipfile.ZIP_DEFLATED)\n"
" for i in files:\n"
" dirs=os.path.split(i)\n"
" #print i, dirs[-1]\n"
" compress.write(i,dirs[-1],zipfile.ZIP_DEFLATED)\n"
" compress.close()\n"
"\n"
"def getFilesList(filename):\n"
" dirname=os.path.dirname(filename)\n"
" handler=DocumentHandler(dirname)\n"
" parser=xml.sax.make_parser()\n"
" parser.setContentHandler(handler)\n"
" parser.parse(filename)\n"
"\n"
" files=[]\n"
" files.append(filename)\n"
" files.extend(iter(handler.files))\n"
" return files\n"
;
namespace Gui { namespace Dialog {
class DocumentRecoveryPrivate
@ -113,6 +183,7 @@ DocumentRecovery::DocumentRecovery(const QList<QFileInfo>& dirs, QWidget* parent
item->setText(0, info.label);
item->setToolTip(0, info.tooltip);
item->setText(1, tr("Not yet recovered"));
item->setToolTip(1, info.projectFile);
d_ptr->ui.treeWidget->addTopLevelItem(item);
}
}
@ -128,6 +199,21 @@ bool DocumentRecovery::foundDocuments() const
return (!d->recoveryInfo.isEmpty());
}
QString DocumentRecovery::createProjectFile(const QString& documentXml)
{
QString source = documentXml;
QFileInfo fi(source);
QString dest = fi.dir().absoluteFilePath(QString::fromLatin1("fc_recovery_file.fcstd"));
std::stringstream str;
str << doctools << "\n";
str << "createDocument(\"" << (const char*)source.toUtf8()
<< "\", \"" << (const char*)dest.toUtf8() << "\")";
Application::Instance->runPythonCode(str.str().c_str());
return dest;
}
void DocumentRecovery::closeEvent(QCloseEvent* e)
{
Q_D(DocumentRecovery);
@ -151,6 +237,9 @@ void DocumentRecovery::accept()
try {
QString file = it->projectFile;
QFileInfo fi(file);
if (fi.fileName() == QLatin1String("Document.xml"))
file = createProjectFile(it->projectFile);
App::Document* document = App::GetApplication().newDocument();
documentName = document->getName();
document->FileName.setValue(file.toUtf8().constData());
@ -253,48 +342,57 @@ DocumentRecoveryPrivate::Info DocumentRecoveryPrivate::getRecoveryInfo(const QFi
info.status = DocumentRecoveryPrivate::Unknown;
info.label = qApp->translate("StdCmdNew","Unnamed");
QString file;
QDir doc_dir(fi.absoluteFilePath());
QDir rec_dir(doc_dir.absoluteFilePath(QLatin1String("fc_recovery_files")));
// compressed recovery file
if (doc_dir.exists(QLatin1String("fc_recovery_file.fcstd"))) {
info.status = DocumentRecoveryPrivate::Created;
QString file = doc_dir.absoluteFilePath(QLatin1String("fc_recovery_file.fcstd"));
info.projectFile = file;
info.tooltip = fi.fileName();
file = doc_dir.absoluteFilePath(QLatin1String("fc_recovery_file.fcstd"));
}
// separate files for recovery
else if (rec_dir.exists(QLatin1String("Document.xml"))) {
file = rec_dir.absoluteFilePath(QLatin1String("Document.xml"));
}
// when the Xml meta exists get some relevant information
info.xmlFile = doc_dir.absoluteFilePath(QLatin1String("fc_recovery_file.xml"));
if (doc_dir.exists(QLatin1String("fc_recovery_file.xml"))) {
XmlConfig cfg = readXmlFile(info.xmlFile);
info.status = DocumentRecoveryPrivate::Created;
info.projectFile = file;
info.tooltip = fi.fileName();
if (cfg.contains(QString::fromLatin1("Label"))) {
info.label = cfg[QString::fromLatin1("Label")];
}
// when the Xml meta exists get some relevant information
info.xmlFile = doc_dir.absoluteFilePath(QLatin1String("fc_recovery_file.xml"));
if (doc_dir.exists(QLatin1String("fc_recovery_file.xml"))) {
XmlConfig cfg = readXmlFile(info.xmlFile);
if (cfg.contains(QString::fromLatin1("FileName"))) {
info.fileName = cfg[QString::fromLatin1("FileName")];
}
if (cfg.contains(QString::fromLatin1("Label"))) {
info.label = cfg[QString::fromLatin1("Label")];
}
if (cfg.contains(QString::fromLatin1("Status"))) {
QString status = cfg[QString::fromLatin1("Status")];
if (status == QLatin1String("Deprecated"))
if (cfg.contains(QString::fromLatin1("FileName"))) {
info.fileName = cfg[QString::fromLatin1("FileName")];
}
if (cfg.contains(QString::fromLatin1("Status"))) {
QString status = cfg[QString::fromLatin1("Status")];
if (status == QLatin1String("Deprecated"))
info.status = DocumentRecoveryPrivate::Overage;
else if (status == QLatin1String("Success"))
info.status = DocumentRecoveryPrivate::Success;
else if (status == QLatin1String("Failure"))
info.status = DocumentRecoveryPrivate::Failure;
}
if (info.status == DocumentRecoveryPrivate::Created) {
// compare the modification dates
QFileInfo fileInfo(info.fileName);
if (!info.fileName.isEmpty() && fileInfo.exists()) {
QDateTime dateRecv = QFileInfo(file).lastModified();
QDateTime dateProj = fileInfo.lastModified();
if (dateRecv < dateProj) {
info.status = DocumentRecoveryPrivate::Overage;
else if (status == QLatin1String("Success"))
info.status = DocumentRecoveryPrivate::Success;
else if (status == QLatin1String("Failure"))
info.status = DocumentRecoveryPrivate::Failure;
}
if (info.status == DocumentRecoveryPrivate::Created) {
// compare the modification dates
QFileInfo fileInfo(info.fileName);
if (!info.fileName.isEmpty() && fileInfo.exists()) {
QDateTime dateRecv = QFileInfo(file).lastModified();
QDateTime dateProj = fileInfo.lastModified();
if (dateRecv < dateProj) {
info.status = DocumentRecoveryPrivate::Overage;
writeRecoveryInfo(info);
qWarning() << "Ignore recovery file " << file.toUtf8()
<< " because it is older than the project file" << info.fileName.toUtf8() << "\n";
}
writeRecoveryInfo(info);
qWarning() << "Ignore recovery file " << file.toUtf8()
<< " because it is older than the project file" << info.fileName.toUtf8() << "\n";
}
}
}

View File

@ -28,6 +28,7 @@
#include <QScopedPointer>
#include <QList>
#include <QFileInfo>
#include <string>
namespace Gui { namespace Dialog {
@ -49,8 +50,10 @@ public:
protected:
void closeEvent(QCloseEvent*);
QString createProjectFile(const QString&);
private:
static std::string doctools;
QScopedPointer<DocumentRecoveryPrivate> d_ptr;
Q_DISABLE_COPY(DocumentRecovery)
Q_DECLARE_PRIVATE(DocumentRecovery)