# HG changeset patch # User Chris Cannam # Date 1301416223 -3600 # Node ID 920e3880f7b4137177f08aff40921f56024ae169 # Parent a1ae2c1f80ab80e75ab635e57f8c0e91c266725e * Add TempWriteFile abstraction, use it when exporting audio to avoid clobbering existing file before export is complete diff -r a1ae2c1f80ab -r 920e3880f7b4 base/TempWriteFile.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/TempWriteFile.cpp Tue Mar 29 17:30:23 2011 +0100 @@ -0,0 +1,69 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "TempWriteFile.h" + +#include "Exceptions.h" + +#include +#include +#include + +TempWriteFile::TempWriteFile(QString target) : + m_target(target) +{ + QTemporaryFile temp(m_target + "."); + temp.setAutoRemove(false); + temp.open(); // creates the file and opens it atomically + if (temp.error()) { + std::cerr << "TempWriteFile: Failed to create temporary file in directory of " << m_target.toStdString() << ": " << temp.errorString().toStdString() << std::endl; + throw FileOperationFailed(temp.fileName(), "creation"); + } + + m_temp = temp.fileName(); + temp.close(); +} + +TempWriteFile::~TempWriteFile() +{ + if (m_temp != "") { + QDir dir(QFileInfo(m_temp).dir()); + dir.remove(m_temp); + } +} + +QString +TempWriteFile::getTemporaryFilename() +{ + return m_temp; +} + +void +TempWriteFile::moveToTarget() +{ + if (m_temp == "") return; + + QDir dir(QFileInfo(m_temp).dir()); + // According to http://doc.trolltech.com/4.4/qdir.html#rename + // some systems fail, if renaming over an existing file. + // Therefore, delete first the existing file. + if (dir.exists(m_target)) dir.remove(m_target); + if (!dir.rename(m_temp, m_target)) { + std::cerr << "TempWriteFile: Failed to rename temporary file " << m_temp.toStdString() << " to target " << m_target.toStdString() << std::endl; + throw FileOperationFailed(m_temp, "rename"); + } + + m_temp = ""; +} + diff -r a1ae2c1f80ab -r 920e3880f7b4 base/TempWriteFile.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/TempWriteFile.h Tue Mar 29 17:30:23 2011 +0100 @@ -0,0 +1,60 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _TEMP_WRITE_FILE_H_ +#define _TEMP_WRITE_FILE_H_ + +#include + +/** + * A class that manages the creation of a temporary file with a given + * prefix and the renaming of that file to the prefix after use. For + * use when saving a file over an existing one, to avoid clobbering + * the original before the save is complete. + */ + +class TempWriteFile +{ +public: + TempWriteFile(QString targetFileName); // may throw FileOperationFailed + + /** + * Destroy the temporary file object. If moveToTarget has not + * been called, the associated temporary file will be deleted + * without being copied to the target location. + */ + ~TempWriteFile(); + + /** + * Return the name of the temporary file. Unless the constructor + * threw an exception, this file will have been created already + * (but it will not be open). + * + * (If moveToTarget has already been called, return an empty + * string.) + */ + QString getTemporaryFilename(); + + /** + * Rename the temporary file to the target filename. + */ + void moveToTarget(); + +protected: + QString m_target; + QString m_temp; +}; + + +#endif diff -r a1ae2c1f80ab -r 920e3880f7b4 data/fileio/WavFileWriter.cpp --- a/data/fileio/WavFileWriter.cpp Wed Mar 09 11:48:01 2011 +0000 +++ b/data/fileio/WavFileWriter.cpp Tue Mar 29 17:30:23 2011 +0100 @@ -17,6 +17,8 @@ #include "model/DenseTimeValueModel.h" #include "base/Selection.h" +#include "base/TempWriteFile.h" +#include "base/Exceptions.h" #include @@ -28,19 +30,28 @@ m_path(path), m_sampleRate(sampleRate), m_channels(channels), + m_temp(0), m_file(0) { SF_INFO fileInfo; fileInfo.samplerate = m_sampleRate; fileInfo.channels = m_channels; fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; - - m_file = sf_open(m_path.toLocal8Bit(), SFM_WRITE, &fileInfo); - if (!m_file) { - std::cerr << "WavFileWriter: Failed to open file (" - << sf_strerror(m_file) << ")" << std::endl; - m_error = QString("Failed to open audio file '%1' for writing") - .arg(m_path); + + try { + m_temp = new TempWriteFile(m_path); + m_file = sf_open(m_temp->getTemporaryFilename().toLocal8Bit(), + SFM_WRITE, &fileInfo); + if (!m_file) { + std::cerr << "WavFileWriter: Failed to open file (" + << sf_strerror(m_file) << ")" << std::endl; + m_error = QString("Failed to open audio file '%1' for writing") + .arg(m_temp->getTemporaryFilename()); + } + } catch (FileOperationFailed &f) { + m_error = f.what(); + m_temp = 0; + m_file = 0; } } @@ -65,18 +76,23 @@ WavFileWriter::writeModel(DenseTimeValueModel *source, MultiSelection *selection) { + if (!m_temp) { + m_error = QString("Failed to write model to audio file: No file open"); + return false; + } + if (source->getChannelCount() != m_channels) { std::cerr << "WavFileWriter::writeModel: Wrong number of channels (" << source->getChannelCount() << " != " << m_channels << ")" << std::endl; m_error = QString("Failed to write model to audio file '%1'") - .arg(m_path); + .arg(m_temp->getTemporaryFilename()); return false; } if (!m_file) { m_error = QString("Failed to write model to audio file '%1': File not open") - .arg(m_path); + .arg(m_temp->getTemporaryFilename()); return false; } @@ -129,9 +145,14 @@ bool WavFileWriter::writeSamples(float **samples, size_t count) { + if (!m_temp) { + m_error = QString("Failed to write model to audio file: No file open"); + return false; + } + if (!m_file) { m_error = QString("Failed to write model to audio file '%1': File not open") - .arg(m_path); + .arg(m_temp->getTemporaryFilename()); return false; } @@ -161,6 +182,9 @@ sf_close(m_file); m_file = 0; } + m_temp->moveToTarget(); + delete m_temp; + m_temp = 0; return true; } diff -r a1ae2c1f80ab -r 920e3880f7b4 data/fileio/WavFileWriter.h --- a/data/fileio/WavFileWriter.h Wed Mar 09 11:48:01 2011 +0000 +++ b/data/fileio/WavFileWriter.h Tue Mar 29 17:30:23 2011 +0100 @@ -22,6 +22,7 @@ class DenseTimeValueModel; class MultiSelection; +class TempWriteFile; class WavFileWriter { @@ -46,6 +47,7 @@ QString m_path; size_t m_sampleRate; size_t m_channels; + TempWriteFile *m_temp; SNDFILE *m_file; QString m_error; }; diff -r a1ae2c1f80ab -r 920e3880f7b4 svcore.pro --- a/svcore.pro Wed Mar 09 11:48:01 2011 +0000 +++ b/svcore.pro Tue Mar 29 17:30:23 2011 +0100 @@ -50,6 +50,7 @@ base/StorageAdviser.h \ base/StringBits.h \ base/TempDirectory.h \ + base/TempWriteFile.h \ base/TextMatcher.h \ base/Thread.h \ base/UnitDatabase.h \ @@ -79,6 +80,7 @@ base/StorageAdviser.cpp \ base/StringBits.cpp \ base/TempDirectory.cpp \ + base/TempWriteFile.cpp \ base/TextMatcher.cpp \ base/Thread.cpp \ base/UnitDatabase.cpp \