1 /***************************************************************************
2 copyright : (C) 2002 - 2008 by Scott Wheeler
3 email : wheeler@kde.org
4 ***************************************************************************/
6 /***************************************************************************
7 * This library is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU Lesser General Public License version *
9 * 2.1 as published by the Free Software Foundation. *
11 * This library is distributed in the hope that it will be useful, but *
12 * WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
14 * Lesser General Public License for more details. *
16 * You should have received a copy of the GNU Lesser General Public *
17 * License along with this library; if not, write to the Free Software *
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
21 * Alternatively, this file is available under the Mozilla Public *
22 * License Version 1.1. You may obtain a copy of the License at *
23 * http://www.mozilla.org/MPL/ *
24 ***************************************************************************/
38 # define ftruncate _chsize
52 using namespace TagLib;
56 typedef FileName FileNameHandle;
60 struct FileNameHandle : public std::string
62 FileNameHandle(FileName name) : std::string(name) {}
63 operator FileName () const { return c_str(); }
68 class File::FilePrivate
71 FilePrivate(FileName fileName);
80 static const uint bufferSize = 1024;
83 File::FilePrivate::FilePrivate(FileName fileName) :
90 // First try with read / write mode, if that fails, fall back to read only.
94 if(wcslen((const wchar_t *) fileName) > 0) {
96 file = _wfopen(name, L"rb+");
101 file = _wfopen(name, L"rb");
110 file = fopen(name, "rb+");
115 file = fopen(name, "rb");
118 debug("Could not open file " + String((const char *) name));
121 ////////////////////////////////////////////////////////////////////////////////
123 ////////////////////////////////////////////////////////////////////////////////
125 File::File(FileName file)
127 d = new FilePrivate(file);
137 FileName File::name() const
142 ByteVector File::readBlock(ulong length)
145 debug("File::readBlock() -- Invalid File");
146 return ByteVector::null;
150 return ByteVector::null;
152 if(length > FilePrivate::bufferSize &&
153 length > ulong(File::length()))
155 length = File::length();
158 ByteVector v(static_cast<uint>(length));
159 const int count = fread(v.data(), sizeof(char), length, d->file);
164 void File::writeBlock(const ByteVector &data)
170 debug("File::writeBlock() -- attempted to write to a file that is not writable");
174 fwrite(data.data(), sizeof(char), data.size(), d->file);
177 long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &before)
179 if(!d->file || pattern.size() > d->bufferSize)
182 // The position in the file that the current buffer starts at.
184 long bufferOffset = fromOffset;
187 // These variables are used to keep track of a partial match that happens at
188 // the end of a buffer.
190 int previousPartialMatch = -1;
191 int beforePreviousPartialMatch = -1;
193 // Save the location of the current read pointer. We will restore the
194 // position using seek() before all returns.
196 long originalPosition = tell();
198 // Start the search at the offset.
202 // This loop is the crux of the find method. There are three cases that we
203 // want to account for:
205 // (1) The previously searched buffer contained a partial match of the search
206 // pattern and we want to see if the next one starts with the remainder of
209 // (2) The search pattern is wholly contained within the current buffer.
211 // (3) The current buffer ends with a partial match of the pattern. We will
212 // note this for use in the next itteration, where we will check for the rest
215 // All three of these are done in two steps. First we check for the pattern
216 // and do things appropriately if a match (or partial match) is found. We
217 // then check for "before". The order is important because it gives priority
218 // to "real" matches.
220 for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) {
222 // (1) previous partial match
224 if(previousPartialMatch >= 0 && int(d->bufferSize) > previousPartialMatch) {
225 const int patternOffset = (d->bufferSize - previousPartialMatch);
226 if(buffer.containsAt(pattern, 0, patternOffset)) {
227 seek(originalPosition);
228 return bufferOffset - d->bufferSize + previousPartialMatch;
232 if(!before.isNull() && beforePreviousPartialMatch >= 0 && int(d->bufferSize) > beforePreviousPartialMatch) {
233 const int beforeOffset = (d->bufferSize - beforePreviousPartialMatch);
234 if(buffer.containsAt(before, 0, beforeOffset)) {
235 seek(originalPosition);
240 // (2) pattern contained in current buffer
242 long location = buffer.find(pattern);
244 seek(originalPosition);
245 return bufferOffset + location;
248 if(!before.isNull() && buffer.find(before) >= 0) {
249 seek(originalPosition);
255 previousPartialMatch = buffer.endsWithPartialMatch(pattern);
258 beforePreviousPartialMatch = buffer.endsWithPartialMatch(before);
260 bufferOffset += d->bufferSize;
263 // Since we hit the end of the file, reset the status before continuing.
267 seek(originalPosition);
273 long File::rfind(const ByteVector &pattern, long fromOffset, const ByteVector &before)
275 if(!d->file || pattern.size() > d->bufferSize)
278 // The position in the file that the current buffer starts at.
282 // These variables are used to keep track of a partial match that happens at
283 // the end of a buffer.
286 int previousPartialMatch = -1;
287 int beforePreviousPartialMatch = -1;
290 // Save the location of the current read pointer. We will restore the
291 // position using seek() before all returns.
293 long originalPosition = tell();
295 // Start the search at the offset.
298 if(fromOffset == 0) {
299 seek(-1 * int(d->bufferSize), End);
300 bufferOffset = tell();
303 seek(fromOffset + -1 * int(d->bufferSize), Beginning);
304 bufferOffset = tell();
307 // See the notes in find() for an explanation of this algorithm.
309 for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) {
311 // TODO: (1) previous partial match
313 // (2) pattern contained in current buffer
315 long location = buffer.rfind(pattern);
317 seek(originalPosition);
318 return bufferOffset + location;
321 if(!before.isNull() && buffer.find(before) >= 0) {
322 seek(originalPosition);
326 // TODO: (3) partial match
328 bufferOffset -= d->bufferSize;
332 // Since we hit the end of the file, reset the status before continuing.
336 seek(originalPosition);
341 void File::insert(const ByteVector &data, ulong start, ulong replace)
346 if(data.size() == replace) {
351 else if(data.size() < replace) {
354 removeBlock(start + data.size(), replace - data.size());
358 // Woohoo! Faster (about 20%) than id3lib at last. I had to get hardcore
359 // and avoid TagLib's high level API for rendering just copying parts of
360 // the file that don't contain tag data.
362 // Now I'll explain the steps in this ugliness:
364 // First, make sure that we're working with a buffer that is longer than
365 // the *differnce* in the tag sizes. We want to avoid overwriting parts
366 // that aren't yet in memory, so this is necessary.
368 ulong bufferLength = bufferSize();
370 while(data.size() - replace > bufferLength)
371 bufferLength += bufferSize();
373 // Set where to start the reading and writing.
375 long readPosition = start + replace;
376 long writePosition = start;
379 ByteVector aboutToOverwrite(static_cast<uint>(bufferLength));
381 // This is basically a special case of the loop below. Here we're just
382 // doing the same steps as below, but since we aren't using the same buffer
383 // size -- instead we're using the tag size -- this has to be handled as a
384 // special case. We're also using File::writeBlock() just for the tag.
385 // That's a bit slower than using char *'s so, we're only doing it here.
388 int bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file);
389 readPosition += bufferLength;
393 writePosition += data.size();
395 buffer = aboutToOverwrite;
397 // In case we've already reached the end of file...
399 buffer.resize(bytesRead);
401 // Ok, here's the main loop. We want to loop until the read fails, which
402 // means that we hit the end of the file.
404 while(!buffer.isEmpty()) {
406 // Seek to the current read position and read the data that we're about
407 // to overwrite. Appropriately increment the readPosition.
410 bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file);
411 aboutToOverwrite.resize(bytesRead);
412 readPosition += bufferLength;
414 // Check to see if we just read the last block. We need to call clear()
415 // if we did so that the last write succeeds.
417 if(ulong(bytesRead) < bufferLength)
420 // Seek to the write position and write our buffer. Increment the
424 fwrite(buffer.data(), sizeof(char), buffer.size(), d->file);
425 writePosition += buffer.size();
427 // Make the current buffer the data that we read in the beginning.
429 buffer = aboutToOverwrite;
431 // Again, we need this for the last write. We don't want to write garbage
432 // at the end of our file, so we need to set the buffer size to the amount
433 // that we actually read.
435 bufferLength = bytesRead;
439 void File::removeBlock(ulong start, ulong length)
444 ulong bufferLength = bufferSize();
446 long readPosition = start + length;
447 long writePosition = start;
449 ByteVector buffer(static_cast<uint>(bufferLength));
453 while(bytesRead != 0) {
455 bytesRead = fread(buffer.data(), sizeof(char), bufferLength, d->file);
456 readPosition += bytesRead;
458 // Check to see if we just read the last block. We need to call clear()
459 // if we did so that the last write succeeds.
461 if(bytesRead < bufferLength)
465 fwrite(buffer.data(), sizeof(char), bytesRead, d->file);
466 writePosition += bytesRead;
468 truncate(writePosition);
471 bool File::readOnly() const
476 bool File::isReadable(const char *file)
478 return access(file, R_OK) == 0;
481 bool File::isOpen() const
483 return (d->file != NULL);
486 bool File::isValid() const
488 return isOpen() && d->valid;
491 void File::seek(long offset, Position p)
494 debug("File::seek() -- trying to seek in a file that isn't opened.");
500 fseek(d->file, offset, SEEK_SET);
503 fseek(d->file, offset, SEEK_CUR);
506 fseek(d->file, offset, SEEK_END);
516 long File::tell() const
518 return ftell(d->file);
523 // Do some caching in case we do multiple calls.
531 long curpos = tell();
534 long endpos = tell();
536 seek(curpos, Beginning);
542 bool File::isWritable(const char *file)
544 return access(file, W_OK) == 0;
547 ////////////////////////////////////////////////////////////////////////////////
549 ////////////////////////////////////////////////////////////////////////////////
551 void File::setValid(bool valid)
556 void File::truncate(long length)
558 ftruncate(fileno(d->file), length);
561 TagLib::uint File::bufferSize()
563 return FilePrivate::bufferSize;