mirror of https://github.com/KLayout/klayout.git
Issue 720 (#721)
* Implemented a backup scheme for the file writer. * Also consider backup files when the writer fails. * Removed test exception * Added config option for number of backups.
This commit is contained in:
parent
ca05a83021
commit
a0cc5fa52a
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>606</width>
|
||||
<height>130</height>
|
||||
<width>611</width>
|
||||
<height>271</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
|
@ -67,6 +67,55 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Keep Backup Files</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>When saving files, the specified number of backups are kept. Backup files are formed by adding ".1", ".2" etc. to the original file name. ".1" is the most recent one. On saving, the files will be shuffled and ".1" becomes ".2", ".2 becomes ".3" up to the specified number of backup files.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>252</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Number of backup files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="keep_backups"/>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>(0 for "no backups)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ static const std::string cfg_synchronized_views ("synchronized-views");
|
|||
static const std::string cfg_edit_mode ("edit-mode");
|
||||
static const std::string cfg_custom_macro_paths ("custom-macro-paths");
|
||||
static const std::string cfg_mru ("mru");
|
||||
static const std::string cfg_mru_layer_properties ("mru-layer-properties");
|
||||
static const std::string cfg_mru_sessions ("mru-sessions");
|
||||
static const std::string cfg_mru_bookmarks ("mru-bookmarks");
|
||||
static const std::string cfg_keep_backups ("keep-backups");
|
||||
static const std::string cfg_technologies ("technology-data");
|
||||
static const std::string cfg_key_bindings ("key-bindings");
|
||||
static const std::string cfg_menu_items_hidden ("menu-items-hidden");
|
||||
|
|
|
|||
|
|
@ -190,6 +190,10 @@ MainConfigPage7::setup (lay::PluginRoot *root)
|
|||
bool en = true;
|
||||
root->config_get (cfg_layout_file_watcher_enabled, en);
|
||||
mp_ui->check_for_updates->setChecked (en);
|
||||
|
||||
int kb = 0;
|
||||
root->config_get (cfg_keep_backups, kb);
|
||||
mp_ui->keep_backups->setValue (kb);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -197,6 +201,7 @@ MainConfigPage7::commit (lay::PluginRoot *root)
|
|||
{
|
||||
try {
|
||||
root->config_set (cfg_layout_file_watcher_enabled, mp_ui->check_for_updates->isChecked ());
|
||||
root->config_set (cfg_keep_backups, mp_ui->keep_backups->value ());
|
||||
} catch (...) { }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -426,6 +426,7 @@ MainWindow::MainWindow (QApplication *app, lay::Plugin *plugin_parent, const cha
|
|||
m_mode (std::numeric_limits<unsigned int>::max ()),
|
||||
mp_setup_form (0),
|
||||
m_open_mode (0),
|
||||
m_keep_backups (0),
|
||||
m_disable_tab_selected (false),
|
||||
m_exited (false),
|
||||
dm_do_update_menu (this, &MainWindow::do_update_menu),
|
||||
|
|
@ -1606,6 +1607,14 @@ MainWindow::configure (const std::string &name, const std::string &value)
|
|||
|
||||
return true;
|
||||
|
||||
} else if (name == cfg_keep_backups) {
|
||||
|
||||
int kb = 0;
|
||||
tl::from_string (value, kb);
|
||||
m_keep_backups = kb;
|
||||
|
||||
return false; // not taken - let others set it too.
|
||||
|
||||
} else if (name == cfg_micron_digits) {
|
||||
|
||||
unsigned int d = 5;
|
||||
|
|
@ -3160,7 +3169,7 @@ MainWindow::cm_save_current_cell_as ()
|
|||
}
|
||||
}
|
||||
|
||||
cv->save_as (fn, om, options, false /*don't update*/);
|
||||
cv->save_as (fn, om, options, false /*don't update*/, m_keep_backups);
|
||||
|
||||
add_mru (fn, cv->tech_name ());
|
||||
|
||||
|
|
@ -3239,7 +3248,7 @@ MainWindow::do_save (bool as)
|
|||
break;
|
||||
}
|
||||
|
||||
current_view ()->save_as ((unsigned int) cv_index, fn, om, options, true);
|
||||
current_view ()->save_as ((unsigned int) cv_index, fn, om, options, true, m_keep_backups);
|
||||
add_mru (fn, current_view ()->cellview (cv_index)->tech_name ());
|
||||
|
||||
}
|
||||
|
|
@ -3284,7 +3293,7 @@ MainWindow::cm_save_all ()
|
|||
}
|
||||
}
|
||||
|
||||
view (view_index)->save_as (cv_index, fn, om, options, true);
|
||||
view (view_index)->save_as (cv_index, fn, om, options, true, m_keep_backups);
|
||||
add_mru (fn, current_view ()->cellview (cv_index)->tech_name ());
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -894,6 +894,7 @@ private:
|
|||
SettingsForm *mp_setup_form;
|
||||
std::vector <lay::LayoutView *> mp_views;
|
||||
int m_open_mode;
|
||||
int m_keep_backups;
|
||||
std::vector<std::pair<std::string, std::string> > m_mru;
|
||||
QStatusBar *mp_status_bar;
|
||||
QStackedWidget *mp_main_stack_widget;
|
||||
|
|
|
|||
|
|
@ -316,14 +316,14 @@ static void delete_layers2 (lay::LayoutView *view, unsigned int index, const std
|
|||
|
||||
static void save_as1 (lay::LayoutView *view, unsigned int index, const std::string &filename, const db::SaveLayoutOptions &options)
|
||||
{
|
||||
view->save_as (index, filename, tl::OutputStream::OM_Auto, options, true);
|
||||
view->save_as (index, filename, tl::OutputStream::OM_Auto, options, true, 0);
|
||||
}
|
||||
|
||||
static void save_as2 (lay::LayoutView *view, unsigned int index, const std::string &filename, bool /*gzip*/, const db::SaveLayoutOptions &options)
|
||||
{
|
||||
// because set_format_from_name always returns true now, we ignore the gzip option -
|
||||
// it's probably used only in that context.
|
||||
view->save_as (index, filename, tl::OutputStream::OM_Auto, options, true);
|
||||
view->save_as (index, filename, tl::OutputStream::OM_Auto, options, true, 0);
|
||||
}
|
||||
|
||||
#if defined(HAVE_QTBINDINGS)
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ LayoutHandle::update_save_options (db::SaveLayoutOptions &options)
|
|||
}
|
||||
|
||||
void
|
||||
LayoutHandle::save_as (const std::string &fn, tl::OutputStream::OutputStreamMode om, const db::SaveLayoutOptions &options, bool update)
|
||||
LayoutHandle::save_as (const std::string &fn, tl::OutputStream::OutputStreamMode om, const db::SaveLayoutOptions &options, bool update, int keep_backups)
|
||||
{
|
||||
if (update) {
|
||||
|
||||
|
|
@ -314,8 +314,13 @@ LayoutHandle::save_as (const std::string &fn, tl::OutputStream::OutputStreamMode
|
|||
{
|
||||
// The write needs to be finished before the file watcher gets the new modification time
|
||||
db::Writer writer (options);
|
||||
tl::OutputStream stream (fn, om);
|
||||
writer.write (*mp_layout, stream);
|
||||
tl::OutputStream stream (fn, om, false, keep_backups);
|
||||
try {
|
||||
writer.write (*mp_layout, stream);
|
||||
} catch (...) {
|
||||
stream.reject ();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
if (update) {
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ public:
|
|||
* Save the layout under the given file name and with the given options.
|
||||
* If update is true, this method updates the cell view's filename, title, save options and dirty flag.
|
||||
*/
|
||||
void save_as (const std::string &filename, tl::OutputStream::OutputStreamMode om, const db::SaveLayoutOptions &options, bool update = true);
|
||||
void save_as (const std::string &filename, tl::OutputStream::OutputStreamMode om, const db::SaveLayoutOptions &options, bool update = true, int keep_backups = 0);
|
||||
|
||||
/**
|
||||
* @brief Sets the save options and a flag indicating whether they are valid
|
||||
|
|
|
|||
|
|
@ -2078,12 +2078,12 @@ LayoutView::signal_selection_changed ()
|
|||
}
|
||||
|
||||
void
|
||||
LayoutView::save_as (unsigned int index, const std::string &filename, tl::OutputStream::OutputStreamMode om, const db::SaveLayoutOptions &options, bool update)
|
||||
LayoutView::save_as (unsigned int index, const std::string &filename, tl::OutputStream::OutputStreamMode om, const db::SaveLayoutOptions &options, bool update, int keep_backups)
|
||||
{
|
||||
tl_assert (index < cellviews ());
|
||||
|
||||
tl::SelfTimer timer (tl::verbosity () >= 11, tl::to_string (QObject::tr ("Saving")));
|
||||
cellview (index)->save_as (filename, om, options, update);
|
||||
cellview (index)->save_as (filename, om, options, update, keep_backups);
|
||||
|
||||
cellview_changed (index);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -775,7 +775,7 @@ public:
|
|||
* @brief Save the given cellview into the given file (with options)
|
||||
* If "update" is true, the cell view's properties will be updated (options, filename etc.).
|
||||
*/
|
||||
void save_as (unsigned int index, const std::string &filename, tl::OutputStream::OutputStreamMode om, const db::SaveLayoutOptions &options, bool update);
|
||||
void save_as (unsigned int index, const std::string &filename, tl::OutputStream::OutputStreamMode om, const db::SaveLayoutOptions &options, bool update, int keep_backups);
|
||||
|
||||
/**
|
||||
* @brief Implementation of the undo operations
|
||||
|
|
|
|||
|
|
@ -435,6 +435,21 @@ bool mkpath (const std::string &p)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool rename_file (const std::string &path, const std::string &new_name)
|
||||
{
|
||||
// resolve relative names in new_name
|
||||
std::string new_path = new_name;
|
||||
if (! tl::is_absolute (new_path)) {
|
||||
new_path = tl::combine_path (tl::dirname (path), new_name);
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
return _wrename (tl::to_wstring (path).c_str (), tl::to_wstring (new_path).c_str ()) == 0;
|
||||
#else
|
||||
return rename (tl::to_local (path).c_str (), tl::to_local (new_path).c_str ()) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool rm_file (const std::string &path)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
|
|
|
|||
|
|
@ -125,6 +125,11 @@ bool TL_PUBLIC is_dir (const std::string &s);
|
|||
*/
|
||||
std::vector<std::string> TL_PUBLIC dir_entries (const std::string &s, bool with_files = true, bool with_dirs = true, bool without_dotfiles = false);
|
||||
|
||||
/**
|
||||
* @brief Rename the given file
|
||||
*/
|
||||
bool TL_PUBLIC rename_file (const std::string &path, const std::string &new_name);
|
||||
|
||||
/**
|
||||
* @brief Removes the given file and returns true on success
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
#include "tlDeflate.h"
|
||||
#include "tlAssert.h"
|
||||
#include "tlFileUtils.h"
|
||||
#include "tlLog.h"
|
||||
|
||||
#include "tlException.h"
|
||||
#include "tlString.h"
|
||||
|
|
@ -692,7 +693,15 @@ OutputStream::OutputStream (OutputStreamBase &delegate, bool as_text)
|
|||
mp_buffer = new char[m_buffer_capacity];
|
||||
}
|
||||
|
||||
OutputStream::OutputStreamMode
|
||||
OutputStream::OutputStream (OutputStreamBase *delegate, bool as_text)
|
||||
: m_pos (0), mp_delegate (delegate), m_owns_delegate (true), m_as_text (as_text)
|
||||
{
|
||||
m_buffer_capacity = 16384;
|
||||
m_buffer_pos = 0;
|
||||
mp_buffer = new char[m_buffer_capacity];
|
||||
}
|
||||
|
||||
OutputStream::OutputStreamMode
|
||||
OutputStream::output_mode_from_filename (const std::string &abstract_path, OutputStream::OutputStreamMode om)
|
||||
{
|
||||
if (om == OM_Auto) {
|
||||
|
|
@ -707,16 +716,16 @@ OutputStream::output_mode_from_filename (const std::string &abstract_path, Outpu
|
|||
}
|
||||
|
||||
static
|
||||
OutputStreamBase *create_file_stream (const std::string &path, OutputStream::OutputStreamMode om)
|
||||
OutputStreamBase *create_file_stream (const std::string &path, OutputStream::OutputStreamMode om, int keep_backups)
|
||||
{
|
||||
if (om == OutputStream::OM_Zlib) {
|
||||
return new OutputZLibFile (path);
|
||||
return new OutputZLibFile (path, keep_backups);
|
||||
} else {
|
||||
return new OutputFile (path);
|
||||
return new OutputFile (path, keep_backups);
|
||||
}
|
||||
}
|
||||
|
||||
OutputStream::OutputStream (const std::string &abstract_path, OutputStreamMode om, bool as_text)
|
||||
OutputStream::OutputStream (const std::string &abstract_path, OutputStreamMode om, bool as_text, int keep_backups)
|
||||
: m_pos (0), mp_delegate (0), m_owns_delegate (false), m_as_text (as_text), m_path (abstract_path)
|
||||
{
|
||||
// Determine output mode
|
||||
|
|
@ -728,9 +737,9 @@ OutputStream::OutputStream (const std::string &abstract_path, OutputStreamMode o
|
|||
} else if (ex.test ("pipe:")) {
|
||||
mp_delegate = new OutputPipe (ex.get ());
|
||||
} else if (ex.test ("file:")) {
|
||||
mp_delegate = create_file_stream (ex.get (), om);
|
||||
mp_delegate = create_file_stream (ex.get (), om, keep_backups);
|
||||
} else {
|
||||
mp_delegate = create_file_stream (abstract_path, om);
|
||||
mp_delegate = create_file_stream (abstract_path, om, keep_backups);
|
||||
}
|
||||
|
||||
m_owns_delegate = true;
|
||||
|
|
@ -742,21 +751,42 @@ OutputStream::OutputStream (const std::string &abstract_path, OutputStreamMode o
|
|||
|
||||
OutputStream::~OutputStream ()
|
||||
{
|
||||
close ();
|
||||
try {
|
||||
close ();
|
||||
} catch (...) {
|
||||
// no recursive exceptions
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OutputStream::close ()
|
||||
{
|
||||
flush ();
|
||||
try {
|
||||
|
||||
flush ();
|
||||
|
||||
if (mp_delegate && m_owns_delegate) {
|
||||
delete mp_delegate;
|
||||
mp_delegate = 0;
|
||||
}
|
||||
if (mp_buffer) {
|
||||
delete[] mp_buffer;
|
||||
mp_buffer = 0;
|
||||
}
|
||||
|
||||
} catch (...) {
|
||||
|
||||
if (mp_delegate && m_owns_delegate) {
|
||||
delete mp_delegate;
|
||||
mp_delegate = 0;
|
||||
}
|
||||
if (mp_buffer) {
|
||||
delete[] mp_buffer;
|
||||
mp_buffer = 0;
|
||||
}
|
||||
|
||||
throw;
|
||||
|
||||
if (mp_delegate && m_owns_delegate) {
|
||||
delete mp_delegate;
|
||||
mp_delegate = 0;
|
||||
}
|
||||
if (mp_buffer) {
|
||||
delete[] mp_buffer;
|
||||
mp_buffer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -867,11 +897,110 @@ OutputStream::seek (size_t pos)
|
|||
m_pos = pos;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// OutputFileBase implementation
|
||||
|
||||
OutputFileBase::OutputFileBase (const std::string &path, int keep_backups)
|
||||
: m_keep_backups (keep_backups), m_path (path), m_has_error (false)
|
||||
{
|
||||
if (tl::file_exists (path)) {
|
||||
m_backup_path = path + ".~backup";
|
||||
if (tl::file_exists (m_backup_path)) {
|
||||
if (! tl::rm_file (m_backup_path)) {
|
||||
tl::warn << tl::sprintf (tl::to_string (tr ("Could not create backup file: unable to remove existing file '%s'")), m_backup_path);
|
||||
m_backup_path = std::string ();
|
||||
}
|
||||
}
|
||||
if (! m_backup_path.empty ()) {
|
||||
if (! tl::rename_file (path, m_backup_path)) {
|
||||
tl::warn << tl::sprintf (tl::to_string (tr ("Could not create backup file: unable to rename original file '%s' to backup file")), path, m_backup_path);
|
||||
m_backup_path = std::string ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OutputFileBase::~OutputFileBase ()
|
||||
{
|
||||
if (! m_backup_path.empty ()) {
|
||||
|
||||
if (m_has_error) {
|
||||
|
||||
if (! tl::rm_file (m_path)) {
|
||||
tl::warn << tl::sprintf (tl::to_string (tr ("Could not restore backup file: unable to remove file '%s'")), m_path);
|
||||
} else if (! tl::rename_file (m_backup_path, m_path)) {
|
||||
tl::warn << tl::sprintf (tl::to_string (tr ("Could not restore backup file: unable to rename file '%s' back to '%s'")), m_backup_path, m_path);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (m_keep_backups == 0) {
|
||||
|
||||
if (! tl::rm_file (m_backup_path)) {
|
||||
tl::warn << tl::sprintf (tl::to_string (tr ("Could not remove backup file '%s'")), m_backup_path);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// shuffle backup files
|
||||
int n = 1;
|
||||
for ( ; m_keep_backups < 0 || n < m_keep_backups; ++n) {
|
||||
std::string p = m_path + "." + tl::to_string (n);
|
||||
if (! tl::file_exists (p)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (n > 0) {
|
||||
std::string p = m_path + "." + tl::to_string (n);
|
||||
std::string pprev = n > 1 ? (m_path + "." + tl::to_string (n - 1)) : m_backup_path;
|
||||
if (tl::file_exists (p)) {
|
||||
if (! tl::rm_file (p)) {
|
||||
tl::warn << tl::sprintf (tl::to_string (tr ("Error shuffling backup files: unable to remove file '%s'")), p);
|
||||
}
|
||||
}
|
||||
if (! tl::rename_file (pprev, p)) {
|
||||
tl::warn << tl::sprintf (tl::to_string (tr ("Error shuffling backup files: unable to rename file '%s' to '%s'")), pprev, p);
|
||||
}
|
||||
--n;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OutputFileBase::seek (size_t s)
|
||||
{
|
||||
try {
|
||||
seek_file (s);
|
||||
} catch (...) {
|
||||
reject ();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void OutputFileBase::write (const char *b, size_t n)
|
||||
{
|
||||
try {
|
||||
write_file (b, n);
|
||||
} catch (...) {
|
||||
reject ();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void OutputFileBase::reject ()
|
||||
{
|
||||
m_has_error = true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// OutputFile implementation
|
||||
|
||||
OutputFile::OutputFile (const std::string &path)
|
||||
: m_fd (-1)
|
||||
OutputFile::OutputFile (const std::string &path, int keep_backups)
|
||||
: OutputFileBase (path, keep_backups), m_fd (-1)
|
||||
{
|
||||
m_source = path;
|
||||
#if defined(_WIN32)
|
||||
|
|
@ -898,11 +1027,11 @@ OutputFile::~OutputFile ()
|
|||
close (m_fd);
|
||||
#endif
|
||||
m_fd = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
OutputFile::seek (size_t s)
|
||||
void
|
||||
OutputFile::seek_file (size_t s)
|
||||
{
|
||||
tl_assert (m_fd >= 0);
|
||||
#if defined(_WIN64)
|
||||
|
|
@ -914,8 +1043,8 @@ OutputFile::seek (size_t s)
|
|||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
OutputFile::write (const char *b, size_t n)
|
||||
void
|
||||
OutputFile::write_file (const char *b, size_t n)
|
||||
{
|
||||
tl_assert (m_fd >= 0);
|
||||
#if defined(_WIN32)
|
||||
|
|
@ -931,8 +1060,8 @@ OutputFile::write (const char *b, size_t n)
|
|||
// ---------------------------------------------------------------
|
||||
// OutputZLibFile implementation
|
||||
|
||||
OutputZLibFile::OutputZLibFile (const std::string &path)
|
||||
: mp_d (new ZLibFilePrivate ())
|
||||
OutputZLibFile::OutputZLibFile (const std::string &path, int keep_backups)
|
||||
: OutputFileBase (path, keep_backups), mp_d (new ZLibFilePrivate ())
|
||||
{
|
||||
m_source = path;
|
||||
#if defined(_WIN32)
|
||||
|
|
@ -960,7 +1089,7 @@ OutputZLibFile::~OutputZLibFile ()
|
|||
}
|
||||
|
||||
void
|
||||
OutputZLibFile::write (const char *b, size_t n)
|
||||
OutputZLibFile::write_file (const char *b, size_t n)
|
||||
{
|
||||
tl_assert (mp_d->zs != NULL);
|
||||
int ret = gzwrite (mp_d->zs, (char *) b, (unsigned int) n);
|
||||
|
|
|
|||
|
|
@ -703,6 +703,14 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Rejects the output - for delegates supporting unrolling, this means the original file is restored
|
||||
*/
|
||||
virtual void reject ()
|
||||
{
|
||||
// ... the default implementation does not support this feature ..
|
||||
}
|
||||
|
||||
private:
|
||||
// No copying
|
||||
OutputStreamBase (const OutputStreamBase &);
|
||||
|
|
@ -831,13 +839,81 @@ private:
|
|||
std::ostringstream m_stream;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A base for file writer delegates
|
||||
*
|
||||
* This class mainly provides safety services for the file writer.
|
||||
* When writing a file, it will keep a backup until the file actually
|
||||
* has been written. This way, a network or disk full error will not
|
||||
* comprimise the file's content. The backup file name is created
|
||||
* by appending ".~backup" to the original file path.
|
||||
*
|
||||
* In addition, a specified number or persistent backup files can
|
||||
* be kept. The first backup will be called like the original file
|
||||
* with ".1" appended. The second will be called ".2" etc.
|
||||
* The backups will be shuffled, so ".1" is always the most recent
|
||||
* one while older ones get bigger numbers.
|
||||
*
|
||||
* The number of backups can be specified with "keep_backups". If this
|
||||
* count is zero, no backups will be kept. If the count is larger than
|
||||
* zero, the specified maximum number of backups is kept. If less than
|
||||
* zero, an infinite number of backups is made. But beware: shuffling
|
||||
* will become increasingly expensive.
|
||||
*/
|
||||
class TL_PUBLIC OutputFileBase
|
||||
: public OutputStreamBase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*
|
||||
* @param path The (relative) path of the file to write
|
||||
* @param keep_backups The number of backups to keep (0: none, -1: infinite)
|
||||
*/
|
||||
OutputFileBase (const std::string &path, int keep_backups);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
virtual ~OutputFileBase ();
|
||||
|
||||
/**
|
||||
* @brief Seek to the specified position
|
||||
*
|
||||
* Writing continues at that position after a seek.
|
||||
*/
|
||||
virtual void seek (size_t s);
|
||||
|
||||
/**
|
||||
* @brief Write to a file
|
||||
*
|
||||
* Implements the basic write method.
|
||||
* Will throw a FileWriteErrorException if an error occurs.
|
||||
*/
|
||||
virtual void write (const char *b, size_t n);
|
||||
|
||||
/**
|
||||
* @brief Unrolls the output
|
||||
*/
|
||||
virtual void reject ();
|
||||
|
||||
protected:
|
||||
virtual void seek_file (size_t s) = 0;
|
||||
virtual void write_file (const char *b, size_t n) = 0;
|
||||
|
||||
private:
|
||||
int m_keep_backups;
|
||||
std::string m_backup_path, m_path;
|
||||
bool m_has_error;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A zlib output file delegate
|
||||
*
|
||||
* Implements the writer for a zlib stream
|
||||
*/
|
||||
class TL_PUBLIC OutputZLibFile
|
||||
: public OutputStreamBase
|
||||
: public OutputFileBase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
|
|
@ -847,9 +923,10 @@ public:
|
|||
* object. open() will throw a FileOpenErrorException if
|
||||
* an error occurs.
|
||||
*
|
||||
* @param path The (relative) path of the file to open
|
||||
* @param path The (relative) path of the file to write
|
||||
* @param keep_backups The number of backups to keep (0: none, -1: infinite)
|
||||
*/
|
||||
OutputZLibFile (const std::string &path);
|
||||
OutputZLibFile (const std::string &path, int keep_backups);
|
||||
|
||||
/**
|
||||
* @brief Close the file
|
||||
|
|
@ -858,13 +935,19 @@ public:
|
|||
*/
|
||||
virtual ~OutputZLibFile ();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Write to a file
|
||||
*
|
||||
* Implements the basic write method.
|
||||
* Will throw a ZLibWriteErrorException if an error occurs.
|
||||
*/
|
||||
virtual void write (const char *b, size_t n);
|
||||
virtual void write_file (const char *b, size_t n);
|
||||
|
||||
/**
|
||||
* @brief The seek operation isn't implemented for zlib files
|
||||
*/
|
||||
virtual void seek_file (size_t /*s*/) { }
|
||||
|
||||
private:
|
||||
// No copying
|
||||
|
|
@ -881,7 +964,7 @@ private:
|
|||
* Implements the writer for ordinary files.
|
||||
*/
|
||||
class TL_PUBLIC OutputFile
|
||||
: public OutputStreamBase
|
||||
: public OutputFileBase
|
||||
{
|
||||
public:
|
||||
/**
|
||||
|
|
@ -891,10 +974,10 @@ public:
|
|||
* object. open() will throw a FileOpenErrorException if
|
||||
* an error occurs.
|
||||
*
|
||||
* @param path The (relative) path of the file to open
|
||||
* @param read True, if the file should be read, false on write.
|
||||
* @param path The (relative) path of the file to write
|
||||
* @param keep_backups The number of backups to keep (0: none, -1: infinite)
|
||||
*/
|
||||
OutputFile (const std::string &path);
|
||||
OutputFile (const std::string &path, int keep_backups = 0);
|
||||
|
||||
/**
|
||||
* @brief Close the file
|
||||
|
|
@ -903,20 +986,21 @@ public:
|
|||
*/
|
||||
virtual ~OutputFile ();
|
||||
|
||||
/**
|
||||
* @brief Returns a value indicating whether that stream supports seek
|
||||
*/
|
||||
virtual bool supports_seek ()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Seek to the specified position
|
||||
*
|
||||
* Writing continues at that position after a seek.
|
||||
*/
|
||||
virtual void seek (size_t s);
|
||||
|
||||
/**
|
||||
* @brief Returns a value indicating whether that stream supports seek
|
||||
*/
|
||||
bool supports_seek ()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
virtual void seek_file (size_t s);
|
||||
|
||||
/**
|
||||
* @brief Write to a file
|
||||
|
|
@ -924,7 +1008,7 @@ public:
|
|||
* Implements the basic write method.
|
||||
* Will throw a FileWriteErrorException if an error occurs.
|
||||
*/
|
||||
virtual void write (const char *b, size_t n);
|
||||
virtual void write_file (const char *b, size_t n);
|
||||
|
||||
private:
|
||||
// No copying
|
||||
|
|
@ -1030,13 +1114,20 @@ public:
|
|||
*/
|
||||
OutputStream (OutputStreamBase &delegate, bool as_text = false);
|
||||
|
||||
/**
|
||||
* @brief Default constructor
|
||||
*
|
||||
* This constructor takes a delegate object. The stream will own the delegate.
|
||||
*/
|
||||
OutputStream (OutputStreamBase *delegate, bool as_text = false);
|
||||
|
||||
/**
|
||||
* @brief Open an output stream with the given path and stream mode
|
||||
*
|
||||
* This will automatically create a delegate object and delete it later.
|
||||
* If "as_text" is true, the output will be formatted with the system's line separator.
|
||||
*/
|
||||
OutputStream (const std::string &abstract_path, OutputStreamMode om = OM_Auto, bool as_text = false);
|
||||
OutputStream (const std::string &abstract_path, OutputStreamMode om = OM_Auto, bool as_text = false, int keep_backups = 0);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
|
|
@ -1120,6 +1211,16 @@ public:
|
|||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Rejects the output - for delegates which support backup, this means the original file is restored
|
||||
*/
|
||||
void reject () const
|
||||
{
|
||||
if (mp_delegate) {
|
||||
mp_delegate->reject ();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a value indicating whether that stream supports seek
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -676,3 +676,82 @@ TEST (17)
|
|||
EXPECT_EQ (tl::is_same_file (yfile, tl::combine_path (dpath, "../d/y")), true);
|
||||
}
|
||||
|
||||
// rename_file
|
||||
TEST (18)
|
||||
{
|
||||
std::string tp = tl::absolute_file_path (tmp_file ());
|
||||
std::string xfile = tl::combine_path (tp, "x");
|
||||
std::string yfile = tl::combine_path (tp, "y");
|
||||
std::string zfile = tl::combine_path (tl::combine_path (tp, "dir"), "z");
|
||||
|
||||
tl::mkpath (tl::combine_path (tp, "dir"));
|
||||
|
||||
{
|
||||
tl::OutputStream os (xfile);
|
||||
os << "hello, world!\n";
|
||||
}
|
||||
|
||||
EXPECT_EQ (tl::file_exists (xfile), true);
|
||||
EXPECT_EQ (tl::file_exists (yfile), false);
|
||||
EXPECT_EQ (tl::file_exists (zfile), false);
|
||||
|
||||
{
|
||||
tl::InputStream is (xfile);
|
||||
EXPECT_EQ (is.read_all (), "hello, world!\n");
|
||||
}
|
||||
|
||||
tl::rename_file (xfile, yfile);
|
||||
|
||||
EXPECT_EQ (tl::file_exists (xfile), false);
|
||||
EXPECT_EQ (tl::file_exists (yfile), true);
|
||||
EXPECT_EQ (tl::file_exists (zfile), false);
|
||||
|
||||
{
|
||||
tl::InputStream is (yfile);
|
||||
EXPECT_EQ (is.read_all (), "hello, world!\n");
|
||||
}
|
||||
|
||||
tl::rename_file (yfile, "x");
|
||||
|
||||
EXPECT_EQ (tl::file_exists (xfile), true);
|
||||
EXPECT_EQ (tl::file_exists (yfile), false);
|
||||
EXPECT_EQ (tl::file_exists (zfile), false);
|
||||
|
||||
{
|
||||
tl::InputStream is (xfile);
|
||||
EXPECT_EQ (is.read_all (), "hello, world!\n");
|
||||
}
|
||||
|
||||
tl::rename_file (xfile, zfile);
|
||||
|
||||
EXPECT_EQ (tl::file_exists (xfile), false);
|
||||
EXPECT_EQ (tl::file_exists (yfile), false);
|
||||
EXPECT_EQ (tl::file_exists (zfile), true);
|
||||
|
||||
{
|
||||
tl::InputStream is (zfile);
|
||||
EXPECT_EQ (is.read_all (), "hello, world!\n");
|
||||
}
|
||||
|
||||
tl::rename_file (zfile, xfile);
|
||||
|
||||
EXPECT_EQ (tl::file_exists (xfile), true);
|
||||
EXPECT_EQ (tl::file_exists (yfile), false);
|
||||
EXPECT_EQ (tl::file_exists (zfile), false);
|
||||
|
||||
{
|
||||
tl::InputStream is (xfile);
|
||||
EXPECT_EQ (is.read_all (), "hello, world!\n");
|
||||
}
|
||||
|
||||
tl::rename_file (xfile, tl::combine_path ("dir", "z"));
|
||||
|
||||
EXPECT_EQ (tl::file_exists (xfile), false);
|
||||
EXPECT_EQ (tl::file_exists (yfile), false);
|
||||
EXPECT_EQ (tl::file_exists (zfile), true);
|
||||
|
||||
{
|
||||
tl::InputStream is (zfile);
|
||||
EXPECT_EQ (is.read_all (), "hello, world!\n");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,3 +162,235 @@ TEST(TextInputStream)
|
|||
EXPECT_EQ (tis.read_all (), "Hello, world!\nWith another line\n\nseparated by a LFCR and CRLF.");
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class BrokenOutputStream
|
||||
: public tl::OutputFile
|
||||
{
|
||||
public:
|
||||
BrokenOutputStream (const std::string &path, int keep_backups)
|
||||
: tl::OutputFile (path, keep_backups)
|
||||
{ }
|
||||
|
||||
void write_file(const char *b, size_t n)
|
||||
{
|
||||
for (const char *p = b; p < b + n; ++p) {
|
||||
if (*p == '!') {
|
||||
throw tl::Exception ("Bang!");
|
||||
}
|
||||
}
|
||||
tl::OutputFile::write (b, n);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
TEST(SafeOutput)
|
||||
{
|
||||
std::string tp = tmp_file ("x");
|
||||
|
||||
{
|
||||
tl::OutputStream os (tp);
|
||||
os << "blabla\n";
|
||||
}
|
||||
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
|
||||
{
|
||||
tl::OutputStream os (tp);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), true);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
os << "Hello, world!\n";
|
||||
}
|
||||
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
|
||||
{
|
||||
tl::InputStream is (tp);
|
||||
EXPECT_EQ (is.read_all (), "Hello, world!\n");
|
||||
}
|
||||
|
||||
try {
|
||||
BrokenOutputStream broken (tp, 0);
|
||||
tl::OutputStream os (broken);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), true);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
os << "Hi!\n";
|
||||
os.flush (); // raises the exception
|
||||
EXPECT_EQ (true, false);
|
||||
} catch (...) {
|
||||
// '!' raises an exception
|
||||
}
|
||||
|
||||
// The original content is restored now
|
||||
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
|
||||
{
|
||||
tl::InputStream is (tp);
|
||||
EXPECT_EQ (is.read_all (), "Hello, world!\n");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
BrokenOutputStream *broken = new BrokenOutputStream (tp, 0);
|
||||
tl::OutputStream os (broken);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), true);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
os << "Hi!\n";
|
||||
os.flush (); // raises the exception
|
||||
EXPECT_EQ (true, false);
|
||||
} catch (...) {
|
||||
// '!' raises an exception
|
||||
}
|
||||
|
||||
// The original content is restored now
|
||||
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
|
||||
{
|
||||
tl::InputStream is (tp);
|
||||
EXPECT_EQ (is.read_all (), "Hello, world!\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(Backups)
|
||||
{
|
||||
std::string tp = tmp_file ("x");
|
||||
|
||||
{
|
||||
tl::OutputStream os (tp, tl::OutputStream::OM_Auto, false, 2);
|
||||
os << "1\n";
|
||||
}
|
||||
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".1"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".2"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".3"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
|
||||
{
|
||||
tl::InputStream is (tp);
|
||||
EXPECT_EQ (is.read_all (), "1\n");
|
||||
}
|
||||
|
||||
{
|
||||
tl::OutputStream os (tp, tl::OutputStream::OM_Auto, false, 2);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), true);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
os << "2\n";
|
||||
}
|
||||
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".1"), true);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".2"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".3"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
|
||||
{
|
||||
tl::InputStream is (tp);
|
||||
EXPECT_EQ (is.read_all (), "2\n");
|
||||
}
|
||||
|
||||
{
|
||||
tl::InputStream is (tp + ".1");
|
||||
EXPECT_EQ (is.read_all (), "1\n");
|
||||
}
|
||||
|
||||
{
|
||||
tl::OutputStream os (tp, tl::OutputStream::OM_Auto, false, 2);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), true);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
os << "3\n";
|
||||
}
|
||||
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".1"), true);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".2"), true);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".3"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
|
||||
{
|
||||
tl::InputStream is (tp);
|
||||
EXPECT_EQ (is.read_all (), "3\n");
|
||||
}
|
||||
|
||||
{
|
||||
tl::InputStream is (tp + ".1");
|
||||
EXPECT_EQ (is.read_all (), "2\n");
|
||||
}
|
||||
|
||||
{
|
||||
tl::InputStream is (tp + ".2");
|
||||
EXPECT_EQ (is.read_all (), "1\n");
|
||||
}
|
||||
|
||||
{
|
||||
tl::OutputStream os (tp, tl::OutputStream::OM_Auto, false, 2);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), true);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
os << "4\n";
|
||||
}
|
||||
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".1"), true);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".2"), true);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".3"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
|
||||
{
|
||||
tl::InputStream is (tp);
|
||||
EXPECT_EQ (is.read_all (), "4\n");
|
||||
}
|
||||
|
||||
{
|
||||
tl::InputStream is (tp + ".1");
|
||||
EXPECT_EQ (is.read_all (), "3\n");
|
||||
}
|
||||
|
||||
{
|
||||
tl::InputStream is (tp + ".2");
|
||||
EXPECT_EQ (is.read_all (), "2\n");
|
||||
}
|
||||
|
||||
try {
|
||||
BrokenOutputStream broken (tp, 2);
|
||||
tl::OutputStream os (broken);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), true);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
os << "5!\n";
|
||||
os.flush (); // raises the exception
|
||||
EXPECT_EQ (true, false);
|
||||
} catch (...) {
|
||||
// '!' raises an exception
|
||||
}
|
||||
|
||||
EXPECT_EQ (tl::file_exists (tp + ".~backup"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".1"), true);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".2"), true);
|
||||
EXPECT_EQ (tl::file_exists (tp + ".3"), false);
|
||||
EXPECT_EQ (tl::file_exists (tp), true);
|
||||
|
||||
{
|
||||
tl::InputStream is (tp);
|
||||
EXPECT_EQ (is.read_all (), "4\n");
|
||||
}
|
||||
|
||||
{
|
||||
tl::InputStream is (tp + ".1");
|
||||
EXPECT_EQ (is.read_all (), "3\n");
|
||||
}
|
||||
|
||||
{
|
||||
tl::InputStream is (tp + ".2");
|
||||
EXPECT_EQ (is.read_all (), "2\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue