+ implement file based auto-save & recovery mechanism
This commit is contained in:
parent
8db1280a4d
commit
848f9c4d46
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user