diff --git a/lib/ui/stdmenus.inc b/lib/ui/stdmenus.inc index 5b0602961f..56aeb90a2a 100644 --- a/lib/ui/stdmenus.inc +++ b/lib/ui/stdmenus.inc @@ -41,7 +41,7 @@ Menuset Item "Save|S" "buffer-write" Item "Save As...|A" "buffer-write-as" Item "Save All|l" "buffer-write-all" - Item "Revert|R" "buffer-reload" + Item "Revert to saved|R" "buffer-reload" Submenu "Version Control|V" "file_vc" Separator Submenu "Import|I" "file_import" diff --git a/src/Buffer.cpp b/src/Buffer.cpp index d55b596147..e5de38c482 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -103,6 +103,7 @@ using std::pair; using std::stack; using std::vector; using std::string; +using std::time_t; namespace lyx { @@ -131,6 +132,7 @@ using support::split; using support::subst; using support::tempName; using support::trim; +using support::sum; namespace Alert = frontend::Alert; namespace os = support::os; @@ -192,13 +194,18 @@ public: /// Container for all sort of Buffer dependant errors. map errorLists; + + /// timestamp and checksum used to test if the file has been externally + /// modified. (Used to properly enable 'File->Revert to saved', bug 4114). + time_t timestamp_; + unsigned long checksum_; }; Buffer::Impl::Impl(Buffer & parent, FileName const & file, bool readonly_) : lyx_clean(true), bak_clean(true), unnamed(false), read_only(readonly_), filename(file), file_fully_loaded(false), inset(params), - toc_backend(&parent) + timestamp_(0), checksum_(0), toc_backend(&parent) { inset.setAutoBreakRows(true); lyxvc.buffer(&parent); @@ -755,6 +762,9 @@ Buffer::ReadStatus Buffer::readFile(Lexer & lex, FileName const & filename, //MacroTable::localMacros().clear(); pimpl_->file_fully_loaded = true; + // save the timestamp and checksum of disk file + pimpl_->timestamp_ = fs::last_write_time(filename.toFilesystemEncoding()); + pimpl_->checksum_ = sum(filename); return success; } @@ -789,9 +799,22 @@ bool Buffer::save() const } } + // ask if the disk file has been externally modified (use checksum method) + if (fs::exists(encodedFilename) && isExternallyModified(checksum_method)) { + docstring const file = makeDisplayPath(fileName(), 20); + docstring text = bformat(_("Document %1$s has been externally modified. Are you sure " + "you want to overwrite this file?"), file); + int const ret = Alert::prompt(_("Overwrite modified file?"), + text, 1, 1, _("&Overwrite"), _("&Cancel")); + if (ret == 1) + return false; + } + if (writeFile(pimpl_->filename)) { markClean(); removeAutosaveFile(fileName()); + pimpl_->timestamp_ = fs::last_write_time(pimpl_->filename.toFilesystemEncoding()); + pimpl_->checksum_ = sum(pimpl_->filename); return true; } else { // Saving failed, so backup is not backup @@ -1556,6 +1579,16 @@ bool Buffer::isBakClean() const } +bool Buffer::isExternallyModified(CheckMethod method) const +{ + BOOST_ASSERT(fs::exists(pimpl_->filename.toFilesystemEncoding())); + // if method == timestamp, check timestamp before checksum + return (method == checksum_method + || pimpl_->timestamp_ != fs::last_write_time(pimpl_->filename.toFilesystemEncoding())) + && pimpl_->checksum_ != sum(pimpl_->filename); +} + + void Buffer::markClean() const { if (!pimpl_->lyx_clean) { diff --git a/src/Buffer.h b/src/Buffer.h index 2ce9ae6508..218f472744 100644 --- a/src/Buffer.h +++ b/src/Buffer.h @@ -83,6 +83,22 @@ public: wrongversion ///< The version of the file does not match ours }; + /// Method to check if a file is externally modified, used by + /// isExternallyModified() + /** + * timestamp is fast but inaccurate. For example, the granularity + * of timestamp on a FAT filesystem is 2 second. Also, various operations + * may touch the timestamp of a file even when its content is unchanged. + * + * checksum is accurate but slow, which can be a problem when it is + * frequently used, or used for a large file on a slow (network) file + * system. + */ + enum CheckMethod { + checksum_method, ///< Use file check sum + timestamp_method, ///< Use timestamp, and checksum if timestamp has changed + }; + /** Constructor \param file \param b optional \c false by default @@ -210,6 +226,9 @@ public: /// bool isDepClean(std::string const & name) const; + /// whether or not disk file has been externally modified + bool isExternallyModified(CheckMethod method) const; + /// mark the main lyx file as not needing saving void markClean() const; diff --git a/src/LyXFunc.cpp b/src/LyXFunc.cpp index 075dd6c427..e724ed0c78 100644 --- a/src/LyXFunc.cpp +++ b/src/LyXFunc.cpp @@ -496,7 +496,8 @@ FuncStatus LyXFunc::getStatus(FuncRequest const & cmd) const enable = buf->lyxvc().inUse(); break; case LFUN_BUFFER_RELOAD: - enable = !buf->isUnnamed() && !buf->isClean(); + enable = !buf->isUnnamed() && fs::exists(buf->fileName()) + && (!buf->isClean() || buf->isExternallyModified(Buffer::timestamp_method)); break; case LFUN_INSET_SETTINGS: {