Added TagLib (with AUTORS and COPYING files)
[someplayer] / src / taglib / toolkit / tfile.cpp
diff --git a/src/taglib/toolkit/tfile.cpp b/src/taglib/toolkit/tfile.cpp
new file mode 100644 (file)
index 0000000..7090a64
--- /dev/null
@@ -0,0 +1,564 @@
+/***************************************************************************
+    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
+ *   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 "tfile.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 File::FilePrivate
+{
+public:
+  FilePrivate(FileName fileName);
+
+  FILE *file;
+
+  FileNameHandle name;
+
+  bool readOnly;
+  bool valid;
+  ulong size;
+  static const uint bufferSize = 1024;
+};
+
+File::FilePrivate::FilePrivate(FileName fileName) :
+  file(0),
+  name(fileName),
+  readOnly(true),
+  valid(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
+////////////////////////////////////////////////////////////////////////////////
+
+File::File(FileName file)
+{
+  d = new FilePrivate(file);
+}
+
+File::~File()
+{
+  if(d->file)
+    fclose(d->file);
+  delete d;
+}
+
+FileName File::name() const
+{
+  return d->name;
+}
+
+ByteVector File::readBlock(ulong length)
+{
+  if(!d->file) {
+    debug("File::readBlock() -- Invalid File");
+    return ByteVector::null;
+  }
+
+  if(length == 0)
+    return ByteVector::null;
+
+  if(length > FilePrivate::bufferSize &&
+     length > ulong(File::length()))
+  {
+    length = File::length();
+  }
+
+  ByteVector v(static_cast<uint>(length));
+  const int count = fread(v.data(), sizeof(char), length, d->file);
+  v.resize(count);
+  return v;
+}
+
+void File::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);
+}
+
+long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &before)
+{
+  if(!d->file || pattern.size() > d->bufferSize)
+      return -1;
+
+  // The position in the file that the current buffer starts at.
+
+  long bufferOffset = fromOffset;
+  ByteVector buffer;
+
+  // These variables are used to keep track of a partial match that happens at
+  // the end of a buffer.
+
+  int previousPartialMatch = -1;
+  int beforePreviousPartialMatch = -1;
+
+  // Save the location of the current read pointer.  We will restore the
+  // position using seek() before all returns.
+
+  long originalPosition = tell();
+
+  // Start the search at the offset.
+
+  seek(fromOffset);
+
+  // This loop is the crux of the find method.  There are three cases that we
+  // want to account for:
+  //
+  // (1) The previously searched buffer contained a partial match of the search
+  // pattern and we want to see if the next one starts with the remainder of
+  // that pattern.
+  //
+  // (2) The search pattern is wholly contained within the current buffer.
+  //
+  // (3) The current buffer ends with a partial match of the pattern.  We will
+  // note this for use in the next itteration, where we will check for the rest
+  // of the pattern.
+  //
+  // All three of these are done in two steps.  First we check for the pattern
+  // and do things appropriately if a match (or partial match) is found.  We
+  // then check for "before".  The order is important because it gives priority
+  // to "real" matches.
+
+  for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) {
+
+    // (1) previous partial match
+
+    if(previousPartialMatch >= 0 && int(d->bufferSize) > previousPartialMatch) {
+      const int patternOffset = (d->bufferSize - previousPartialMatch);
+      if(buffer.containsAt(pattern, 0, patternOffset)) {
+        seek(originalPosition);
+        return bufferOffset - d->bufferSize + previousPartialMatch;
+      }
+    }
+
+    if(!before.isNull() && beforePreviousPartialMatch >= 0 && int(d->bufferSize) > beforePreviousPartialMatch) {
+      const int beforeOffset = (d->bufferSize - beforePreviousPartialMatch);
+      if(buffer.containsAt(before, 0, beforeOffset)) {
+        seek(originalPosition);
+        return -1;
+      }
+    }
+
+    // (2) pattern contained in current buffer
+
+    long location = buffer.find(pattern);
+    if(location >= 0) {
+      seek(originalPosition);
+      return bufferOffset + location;
+    }
+
+    if(!before.isNull() && buffer.find(before) >= 0) {
+      seek(originalPosition);
+      return -1;
+    }
+
+    // (3) partial match
+
+    previousPartialMatch = buffer.endsWithPartialMatch(pattern);
+
+    if(!before.isNull())
+      beforePreviousPartialMatch = buffer.endsWithPartialMatch(before);
+
+    bufferOffset += d->bufferSize;
+  }
+
+  // Since we hit the end of the file, reset the status before continuing.
+
+  clear();
+
+  seek(originalPosition);
+
+  return -1;
+}
+
+
+long File::rfind(const ByteVector &pattern, long fromOffset, const ByteVector &before)
+{
+  if(!d->file || pattern.size() > d->bufferSize)
+      return -1;
+
+  // The position in the file that the current buffer starts at.
+
+  ByteVector buffer;
+
+  // These variables are used to keep track of a partial match that happens at
+  // the end of a buffer.
+
+  /*
+  int previousPartialMatch = -1;
+  int beforePreviousPartialMatch = -1;
+  */
+
+  // Save the location of the current read pointer.  We will restore the
+  // position using seek() before all returns.
+
+  long originalPosition = tell();
+
+  // Start the search at the offset.
+
+  long bufferOffset;
+  if(fromOffset == 0) {
+    seek(-1 * int(d->bufferSize), End);
+    bufferOffset = tell();
+  }
+  else {
+    seek(fromOffset + -1 * int(d->bufferSize), Beginning);
+    bufferOffset = tell();
+  }
+
+  // See the notes in find() for an explanation of this algorithm.
+
+  for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) {
+
+    // TODO: (1) previous partial match
+
+    // (2) pattern contained in current buffer
+
+    long location = buffer.rfind(pattern);
+    if(location >= 0) {
+      seek(originalPosition);
+      return bufferOffset + location;
+    }
+
+    if(!before.isNull() && buffer.find(before) >= 0) {
+      seek(originalPosition);
+      return -1;
+    }
+
+    // TODO: (3) partial match
+
+    bufferOffset -= d->bufferSize;
+    seek(bufferOffset);
+  }
+
+  // Since we hit the end of the file, reset the status before continuing.
+
+  clear();
+
+  seek(originalPosition);
+
+  return -1;
+}
+
+void File::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 File::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 File::readOnly() const
+{
+  return d->readOnly;
+}
+
+bool File::isReadable(const char *file)
+{
+  return access(file, R_OK) == 0;
+}
+
+bool File::isOpen() const
+{
+  return (d->file != NULL);
+}
+
+bool File::isValid() const
+{
+  return isOpen() && d->valid;
+}
+
+void File::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 File::clear()
+{
+  clearerr(d->file);
+}
+
+long File::tell() const
+{
+  return ftell(d->file);
+}
+
+long File::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;
+}
+
+bool File::isWritable(const char *file)
+{
+  return access(file, W_OK) == 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// protected members
+////////////////////////////////////////////////////////////////////////////////
+
+void File::setValid(bool valid)
+{
+  d->valid = valid;
+}
+
+void File::truncate(long length)
+{
+  ftruncate(fileno(d->file), length);
+}
+
+TagLib::uint File::bufferSize()
+{
+  return FilePrivate::bufferSize;
+}