aboutsummaryrefslogtreecommitdiffstats
path: root/taglib/toolkit/tfilestream.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'taglib/toolkit/tfilestream.cpp')
-rw-r--r--taglib/toolkit/tfilestream.cpp380
1 files changed, 380 insertions, 0 deletions
diff --git a/taglib/toolkit/tfilestream.cpp b/taglib/toolkit/tfilestream.cpp
new file mode 100644
index 00000000..ce2bf11f
--- /dev/null
+++ b/taglib/toolkit/tfilestream.cpp
@@ -0,0 +1,380 @@
+/***************************************************************************
+ copyright : (C) 2002 - 2008 by Scott Wheeler
+ email : wheeler@kde.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * This library is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU Lesser General Public License version *
+ * 2.1 as published by the Free Software Foundation. *
+ * *
+ * This library is distributed in the hope that it will be useful, but *
+ * WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+ * Lesser General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU Lesser General Public *
+ * License along with this library; if not, write to the Free Software *
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA *
+ * 02110-1301 USA *
+ * *
+ * Alternatively, this file is available under the Mozilla Public *
+ * License Version 1.1. You may obtain a copy of the License at *
+ * http://www.mozilla.org/MPL/ *
+ ***************************************************************************/
+
+#include "tfilestream.h"
+#include "tstring.h"
+#include "tdebug.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+# include <wchar.h>
+# include <windows.h>
+# include <io.h>
+# define ftruncate _chsize
+#else
+# include <unistd.h>
+#endif
+
+#include <stdlib.h>
+
+#ifndef R_OK
+# define R_OK 4
+#endif
+#ifndef W_OK
+# define W_OK 2
+#endif
+
+using namespace TagLib;
+
+#ifdef _WIN32
+
+typedef FileName FileNameHandle;
+
+#else
+
+struct FileNameHandle : public std::string
+{
+ FileNameHandle(FileName name) : std::string(name) {}
+ operator FileName () const { return c_str(); }
+};
+
+#endif
+
+class FileStream::FileStreamPrivate
+{
+public:
+ FileStreamPrivate(FileName fileName);
+
+ FILE *file;
+
+ FileNameHandle name;
+
+ bool readOnly;
+ ulong size;
+ static const uint bufferSize = 1024;
+};
+
+FileStream::FileStreamPrivate::FileStreamPrivate(FileName fileName) :
+ file(0),
+ name(fileName),
+ readOnly(true),
+ size(0)
+{
+ // First try with read / write mode, if that fails, fall back to read only.
+
+#ifdef _WIN32
+
+ if(wcslen((const wchar_t *) fileName) > 0) {
+
+ file = _wfopen(name, L"rb+");
+
+ if(file)
+ readOnly = false;
+ else
+ file = _wfopen(name, L"rb");
+
+ if(file)
+ return;
+
+ }
+
+#endif
+
+ file = fopen(name, "rb+");
+
+ if(file)
+ readOnly = false;
+ else
+ file = fopen(name, "rb");
+
+ if(!file)
+ {
+ debug("Could not open file " + String((const char *) name));
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// public members
+////////////////////////////////////////////////////////////////////////////////
+
+FileStream::FileStream(FileName file)
+{
+ d = new FileStreamPrivate(file);
+}
+
+FileStream::~FileStream()
+{
+ if(d->file)
+ fclose(d->file);
+ delete d;
+}
+
+FileName FileStream::name() const
+{
+ return d->name;
+}
+
+ByteVector FileStream::readBlock(ulong length)
+{
+ if(!d->file) {
+ debug("FileStream::readBlock() -- Invalid File");
+ return ByteVector::null;
+ }
+
+ if(length == 0)
+ return ByteVector::null;
+
+ if(length > FileStreamPrivate::bufferSize &&
+ length > ulong(FileStream::length()))
+ {
+ length = FileStream::length();
+ }
+
+ ByteVector v(static_cast<uint>(length));
+ const int count = fread(v.data(), sizeof(char), length, d->file);
+ v.resize(count);
+ return v;
+}
+
+void FileStream::writeBlock(const ByteVector &data)
+{
+ if(!d->file)
+ return;
+
+ if(d->readOnly) {
+ debug("File::writeBlock() -- attempted to write to a file that is not writable");
+ return;
+ }
+
+ fwrite(data.data(), sizeof(char), data.size(), d->file);
+}
+
+void FileStream::insert(const ByteVector &data, ulong start, ulong replace)
+{
+ if(!d->file)
+ return;
+
+ if(data.size() == replace) {
+ seek(start);
+ writeBlock(data);
+ return;
+ }
+ else if(data.size() < replace) {
+ seek(start);
+ writeBlock(data);
+ removeBlock(start + data.size(), replace - data.size());
+ return;
+ }
+
+ // Woohoo! Faster (about 20%) than id3lib at last. I had to get hardcore
+ // and avoid TagLib's high level API for rendering just copying parts of
+ // the file that don't contain tag data.
+ //
+ // Now I'll explain the steps in this ugliness:
+
+ // First, make sure that we're working with a buffer that is longer than
+ // the *differnce* in the tag sizes. We want to avoid overwriting parts
+ // that aren't yet in memory, so this is necessary.
+
+ ulong bufferLength = bufferSize();
+
+ while(data.size() - replace > bufferLength)
+ bufferLength += bufferSize();
+
+ // Set where to start the reading and writing.
+
+ long readPosition = start + replace;
+ long writePosition = start;
+
+ ByteVector buffer;
+ ByteVector aboutToOverwrite(static_cast<uint>(bufferLength));
+
+ // This is basically a special case of the loop below. Here we're just
+ // doing the same steps as below, but since we aren't using the same buffer
+ // size -- instead we're using the tag size -- this has to be handled as a
+ // special case. We're also using File::writeBlock() just for the tag.
+ // That's a bit slower than using char *'s so, we're only doing it here.
+
+ seek(readPosition);
+ int bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file);
+ readPosition += bufferLength;
+
+ seek(writePosition);
+ writeBlock(data);
+ writePosition += data.size();
+
+ buffer = aboutToOverwrite;
+
+ // In case we've already reached the end of file...
+
+ buffer.resize(bytesRead);
+
+ // Ok, here's the main loop. We want to loop until the read fails, which
+ // means that we hit the end of the file.
+
+ while(!buffer.isEmpty()) {
+
+ // Seek to the current read position and read the data that we're about
+ // to overwrite. Appropriately increment the readPosition.
+
+ seek(readPosition);
+ bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file);
+ aboutToOverwrite.resize(bytesRead);
+ readPosition += bufferLength;
+
+ // Check to see if we just read the last block. We need to call clear()
+ // if we did so that the last write succeeds.
+
+ if(ulong(bytesRead) < bufferLength)
+ clear();
+
+ // Seek to the write position and write our buffer. Increment the
+ // writePosition.
+
+ seek(writePosition);
+ fwrite(buffer.data(), sizeof(char), buffer.size(), d->file);
+ writePosition += buffer.size();
+
+ // Make the current buffer the data that we read in the beginning.
+
+ buffer = aboutToOverwrite;
+
+ // Again, we need this for the last write. We don't want to write garbage
+ // at the end of our file, so we need to set the buffer size to the amount
+ // that we actually read.
+
+ bufferLength = bytesRead;
+ }
+}
+
+void FileStream::removeBlock(ulong start, ulong length)
+{
+ if(!d->file)
+ return;
+
+ ulong bufferLength = bufferSize();
+
+ long readPosition = start + length;
+ long writePosition = start;
+
+ ByteVector buffer(static_cast<uint>(bufferLength));
+
+ ulong bytesRead = 1;
+
+ while(bytesRead != 0) {
+ seek(readPosition);
+ bytesRead = fread(buffer.data(), sizeof(char), bufferLength, d->file);
+ readPosition += bytesRead;
+
+ // Check to see if we just read the last block. We need to call clear()
+ // if we did so that the last write succeeds.
+
+ if(bytesRead < bufferLength)
+ clear();
+
+ seek(writePosition);
+ fwrite(buffer.data(), sizeof(char), bytesRead, d->file);
+ writePosition += bytesRead;
+ }
+ truncate(writePosition);
+}
+
+bool FileStream::readOnly() const
+{
+ return d->readOnly;
+}
+
+bool FileStream::isOpen() const
+{
+ return (d->file != NULL);
+}
+
+void FileStream::seek(long offset, Position p)
+{
+ if(!d->file) {
+ debug("File::seek() -- trying to seek in a file that isn't opened.");
+ return;
+ }
+
+ switch(p) {
+ case Beginning:
+ fseek(d->file, offset, SEEK_SET);
+ break;
+ case Current:
+ fseek(d->file, offset, SEEK_CUR);
+ break;
+ case End:
+ fseek(d->file, offset, SEEK_END);
+ break;
+ }
+}
+
+void FileStream::clear()
+{
+ clearerr(d->file);
+}
+
+long FileStream::tell() const
+{
+ return ftell(d->file);
+}
+
+long FileStream::length()
+{
+ // Do some caching in case we do multiple calls.
+
+ if(d->size > 0)
+ return d->size;
+
+ if(!d->file)
+ return 0;
+
+ long curpos = tell();
+
+ seek(0, End);
+ long endpos = tell();
+
+ seek(curpos, Beginning);
+
+ d->size = endpos;
+ return endpos;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// protected members
+////////////////////////////////////////////////////////////////////////////////
+
+void FileStream::truncate(long length)
+{
+ ftruncate(fileno(d->file), length);
+}
+
+TagLib::uint FileStream::bufferSize()
+{
+ return FileStreamPrivate::bufferSize;
+}