changeset 674:920e3880f7b4

* Add TempWriteFile abstraction, use it when exporting audio to avoid clobbering existing file before export is complete
author Chris Cannam
date Tue, 29 Mar 2011 17:30:23 +0100
parents a1ae2c1f80ab
children 341e4e1a6ed3
files base/TempWriteFile.cpp base/TempWriteFile.h data/fileio/WavFileWriter.cpp data/fileio/WavFileWriter.h svcore.pro
diffstat 5 files changed, 167 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- /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 <QTemporaryFile>
+#include <QDir>
+#include <iostream>
+
+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 = "";
+}
+    
--- /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 <QTemporaryFile>
+
+/**
+ * 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
--- 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 <QFileInfo>
 
@@ -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;
 }
 
--- 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;
 };
--- 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 \