~ubuntu-branches/ubuntu/utopic/ardour3/utopic

« back to all changes in this revision

Viewing changes to libs/taglib/taglib/toolkit/tfile.cpp

  • Committer: Package Import Robot
  • Author(s): Felipe Sateler
  • Date: 2013-09-21 19:05:02 UTC
  • Revision ID: package-import@ubuntu.com-20130921190502-8gsftrku6jnzhd7v
Tags: upstream-3.4~dfsg
ImportĀ upstreamĀ versionĀ 3.4~dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/***************************************************************************
 
2
    copyright            : (C) 2002 - 2008 by Scott Wheeler
 
3
    email                : wheeler@kde.org
 
4
 ***************************************************************************/
 
5
 
 
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.                     *
 
10
 *                                                                         *
 
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.                       *
 
15
 *                                                                         *
 
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  *
 
19
 *   USA                                                                   *
 
20
 *                                                                         *
 
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
 ***************************************************************************/
 
25
 
 
26
#include "tfile.h"
 
27
#include "tstring.h"
 
28
#include "tdebug.h"
 
29
 
 
30
#include <stdio.h>
 
31
#include <string.h>
 
32
#include <sys/stat.h>
 
33
 
 
34
#ifdef _WIN32
 
35
# include <wchar.h>
 
36
# include <windows.h>
 
37
# include <io.h>
 
38
# define ftruncate _chsize
 
39
#else
 
40
# include <unistd.h>
 
41
#endif
 
42
 
 
43
#include <stdlib.h>
 
44
 
 
45
#ifndef R_OK
 
46
# define R_OK 4
 
47
#endif
 
48
#ifndef W_OK
 
49
# define W_OK 2
 
50
#endif
 
51
 
 
52
using namespace TagLib;
 
53
 
 
54
#ifdef _WIN32
 
55
 
 
56
typedef FileName FileNameHandle;
 
57
 
 
58
#else
 
59
 
 
60
struct FileNameHandle : public std::string
 
61
{
 
62
  FileNameHandle(FileName name) : std::string(name) {}
 
63
  operator FileName () const { return c_str(); }
 
64
};
 
65
 
 
66
#endif
 
67
 
 
68
class File::FilePrivate
 
69
{
 
70
public:
 
71
  FilePrivate(FileName fileName);
 
72
 
 
73
  FILE *file;
 
74
 
 
75
  FileNameHandle name;
 
76
 
 
77
  bool readOnly;
 
78
  bool valid;
 
79
  ulong size;
 
80
  static const uint bufferSize = 1024;
 
81
};
 
82
 
 
83
File::FilePrivate::FilePrivate(FileName fileName) :
 
84
  file(0),
 
85
  name(fileName),
 
86
  readOnly(true),
 
87
  valid(true),
 
88
  size(0)
 
89
{
 
90
  // First try with read / write mode, if that fails, fall back to read only.
 
91
 
 
92
#ifdef _WIN32
 
93
 
 
94
  if(wcslen((const wchar_t *) fileName) > 0) {
 
95
 
 
96
    file = _wfopen(name, L"rb+");
 
97
 
 
98
    if(file)
 
99
      readOnly = false;
 
100
    else
 
101
      file = _wfopen(name, L"rb");
 
102
 
 
103
    if(file)
 
104
      return;
 
105
 
 
106
  }
 
107
 
 
108
#endif
 
109
 
 
110
  file = fopen(name, "rb+");
 
111
 
 
112
  if(file)
 
113
    readOnly = false;
 
114
  else
 
115
    file = fopen(name, "rb");
 
116
 
 
117
  if(!file) {
 
118
    debug("Could not open file " + String((const char *) name));
 
119
  }
 
120
}
 
121
 
 
122
////////////////////////////////////////////////////////////////////////////////
 
123
// public members
 
124
////////////////////////////////////////////////////////////////////////////////
 
125
 
 
126
File::File(FileName file)
 
127
{
 
128
  d = new FilePrivate(file);
 
129
}
 
130
 
 
131
File::~File()
 
132
{
 
133
  if(d->file)
 
134
    fclose(d->file);
 
135
  delete d;
 
136
}
 
137
 
 
138
FileName File::name() const
 
139
{
 
140
  return d->name;
 
141
}
 
142
 
 
143
ByteVector File::readBlock(ulong length)
 
144
{
 
145
  if(!d->file) {
 
146
    debug("File::readBlock() -- Invalid File");
 
147
    return ByteVector::null;
 
148
  }
 
149
 
 
150
  if(length == 0)
 
151
    return ByteVector::null;
 
152
 
 
153
  if(length > FilePrivate::bufferSize &&
 
154
     length > ulong(File::length()))
 
155
  {
 
156
    length = File::length();
 
157
  }
 
158
 
 
159
  ByteVector v(static_cast<uint>(length));
 
160
  const int count = fread(v.data(), sizeof(char), length, d->file);
 
161
  v.resize(count);
 
162
  return v;
 
163
}
 
164
 
 
165
void File::writeBlock(const ByteVector &data)
 
166
{
 
167
  if(!d->file)
 
168
    return;
 
169
 
 
170
  if(d->readOnly) {
 
171
    debug("File::writeBlock() -- attempted to write to a file that is not writable");
 
172
    return;
 
173
  }
 
174
 
 
175
  fwrite(data.data(), sizeof(char), data.size(), d->file);
 
176
}
 
177
 
 
178
long File::find(const ByteVector &pattern, long fromOffset, const ByteVector &before)
 
179
{
 
180
  if(!d->file || pattern.size() > d->bufferSize)
 
181
      return -1;
 
182
 
 
183
  // The position in the file that the current buffer starts at.
 
184
 
 
185
  long bufferOffset = fromOffset;
 
186
  ByteVector buffer;
 
187
 
 
188
  // These variables are used to keep track of a partial match that happens at
 
189
  // the end of a buffer.
 
190
 
 
191
  int previousPartialMatch = -1;
 
192
  int beforePreviousPartialMatch = -1;
 
193
 
 
194
  // Save the location of the current read pointer.  We will restore the
 
195
  // position using seek() before all returns.
 
196
 
 
197
  long originalPosition = tell();
 
198
 
 
199
  // Start the search at the offset.
 
200
 
 
201
  seek(fromOffset);
 
202
 
 
203
  // This loop is the crux of the find method.  There are three cases that we
 
204
  // want to account for:
 
205
  //
 
206
  // (1) The previously searched buffer contained a partial match of the search
 
207
  // pattern and we want to see if the next one starts with the remainder of
 
208
  // that pattern.
 
209
  //
 
210
  // (2) The search pattern is wholly contained within the current buffer.
 
211
  //
 
212
  // (3) The current buffer ends with a partial match of the pattern.  We will
 
213
  // note this for use in the next itteration, where we will check for the rest
 
214
  // of the pattern.
 
215
  //
 
216
  // All three of these are done in two steps.  First we check for the pattern
 
217
  // and do things appropriately if a match (or partial match) is found.  We
 
218
  // then check for "before".  The order is important because it gives priority
 
219
  // to "real" matches.
 
220
 
 
221
  for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) {
 
222
 
 
223
    // (1) previous partial match
 
224
 
 
225
    if(previousPartialMatch >= 0 && int(d->bufferSize) > previousPartialMatch) {
 
226
      const int patternOffset = (d->bufferSize - previousPartialMatch);
 
227
      if(buffer.containsAt(pattern, 0, patternOffset)) {
 
228
        seek(originalPosition);
 
229
        return bufferOffset - d->bufferSize + previousPartialMatch;
 
230
      }
 
231
    }
 
232
 
 
233
    if(!before.isNull() && beforePreviousPartialMatch >= 0 && int(d->bufferSize) > beforePreviousPartialMatch) {
 
234
      const int beforeOffset = (d->bufferSize - beforePreviousPartialMatch);
 
235
      if(buffer.containsAt(before, 0, beforeOffset)) {
 
236
        seek(originalPosition);
 
237
        return -1;
 
238
      }
 
239
    }
 
240
 
 
241
    // (2) pattern contained in current buffer
 
242
 
 
243
    long location = buffer.find(pattern);
 
244
    if(location >= 0) {
 
245
      seek(originalPosition);
 
246
      return bufferOffset + location;
 
247
    }
 
248
 
 
249
    if(!before.isNull() && buffer.find(before) >= 0) {
 
250
      seek(originalPosition);
 
251
      return -1;
 
252
    }
 
253
 
 
254
    // (3) partial match
 
255
 
 
256
    previousPartialMatch = buffer.endsWithPartialMatch(pattern);
 
257
 
 
258
    if(!before.isNull())
 
259
      beforePreviousPartialMatch = buffer.endsWithPartialMatch(before);
 
260
 
 
261
    bufferOffset += d->bufferSize;
 
262
  }
 
263
 
 
264
  // Since we hit the end of the file, reset the status before continuing.
 
265
 
 
266
  clear();
 
267
 
 
268
  seek(originalPosition);
 
269
 
 
270
  return -1;
 
271
}
 
272
 
 
273
 
 
274
long File::rfind(const ByteVector &pattern, long fromOffset, const ByteVector &before)
 
275
{
 
276
  if(!d->file || pattern.size() > d->bufferSize)
 
277
      return -1;
 
278
 
 
279
  // The position in the file that the current buffer starts at.
 
280
 
 
281
  ByteVector buffer;
 
282
 
 
283
  // These variables are used to keep track of a partial match that happens at
 
284
  // the end of a buffer.
 
285
 
 
286
  /*
 
287
  int previousPartialMatch = -1;
 
288
  int beforePreviousPartialMatch = -1;
 
289
  */
 
290
 
 
291
  // Save the location of the current read pointer.  We will restore the
 
292
  // position using seek() before all returns.
 
293
 
 
294
  long originalPosition = tell();
 
295
 
 
296
  // Start the search at the offset.
 
297
 
 
298
  long bufferOffset;
 
299
  if(fromOffset == 0) {
 
300
    seek(-1 * int(d->bufferSize), End);
 
301
    bufferOffset = tell();
 
302
  }
 
303
  else {
 
304
    seek(fromOffset + -1 * int(d->bufferSize), Beginning);
 
305
    bufferOffset = tell();
 
306
  }
 
307
 
 
308
  // See the notes in find() for an explanation of this algorithm.
 
309
 
 
310
  for(buffer = readBlock(d->bufferSize); buffer.size() > 0; buffer = readBlock(d->bufferSize)) {
 
311
 
 
312
    // TODO: (1) previous partial match
 
313
 
 
314
    // (2) pattern contained in current buffer
 
315
 
 
316
    long location = buffer.rfind(pattern);
 
317
    if(location >= 0) {
 
318
      seek(originalPosition);
 
319
      return bufferOffset + location;
 
320
    }
 
321
 
 
322
    if(!before.isNull() && buffer.find(before) >= 0) {
 
323
      seek(originalPosition);
 
324
      return -1;
 
325
    }
 
326
 
 
327
    // TODO: (3) partial match
 
328
 
 
329
    bufferOffset -= d->bufferSize;
 
330
    seek(bufferOffset);
 
331
  }
 
332
 
 
333
  // Since we hit the end of the file, reset the status before continuing.
 
334
 
 
335
  clear();
 
336
 
 
337
  seek(originalPosition);
 
338
 
 
339
  return -1;
 
340
}
 
341
 
 
342
void File::insert(const ByteVector &data, ulong start, ulong replace)
 
343
{
 
344
  if(!d->file)
 
345
    return;
 
346
 
 
347
  if(data.size() == replace) {
 
348
    seek(start);
 
349
    writeBlock(data);
 
350
    return;
 
351
  }
 
352
  else if(data.size() < replace) {
 
353
      seek(start);
 
354
      writeBlock(data);
 
355
      removeBlock(start + data.size(), replace - data.size());
 
356
      return;
 
357
  }
 
358
 
 
359
  // Woohoo!  Faster (about 20%) than id3lib at last.  I had to get hardcore
 
360
  // and avoid TagLib's high level API for rendering just copying parts of
 
361
  // the file that don't contain tag data.
 
362
  //
 
363
  // Now I'll explain the steps in this ugliness:
 
364
 
 
365
  // First, make sure that we're working with a buffer that is longer than
 
366
  // the *differnce* in the tag sizes.  We want to avoid overwriting parts
 
367
  // that aren't yet in memory, so this is necessary.
 
368
 
 
369
  ulong bufferLength = bufferSize();
 
370
 
 
371
  while(data.size() - replace > bufferLength)
 
372
    bufferLength += bufferSize();
 
373
 
 
374
  // Set where to start the reading and writing.
 
375
 
 
376
  long readPosition = start + replace;
 
377
  long writePosition = start;
 
378
 
 
379
  ByteVector buffer;
 
380
  ByteVector aboutToOverwrite(static_cast<uint>(bufferLength));
 
381
 
 
382
  // This is basically a special case of the loop below.  Here we're just
 
383
  // doing the same steps as below, but since we aren't using the same buffer
 
384
  // size -- instead we're using the tag size -- this has to be handled as a
 
385
  // special case.  We're also using File::writeBlock() just for the tag.
 
386
  // That's a bit slower than using char *'s so, we're only doing it here.
 
387
 
 
388
  seek(readPosition);
 
389
  int bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file);
 
390
  readPosition += bufferLength;
 
391
 
 
392
  seek(writePosition);
 
393
  writeBlock(data);
 
394
  writePosition += data.size();
 
395
 
 
396
  buffer = aboutToOverwrite;
 
397
 
 
398
  // In case we've already reached the end of file...
 
399
 
 
400
  buffer.resize(bytesRead);
 
401
 
 
402
  // Ok, here's the main loop.  We want to loop until the read fails, which
 
403
  // means that we hit the end of the file.
 
404
 
 
405
  while(!buffer.isEmpty()) {
 
406
 
 
407
    // Seek to the current read position and read the data that we're about
 
408
    // to overwrite.  Appropriately increment the readPosition.
 
409
 
 
410
    seek(readPosition);
 
411
    bytesRead = fread(aboutToOverwrite.data(), sizeof(char), bufferLength, d->file);
 
412
    aboutToOverwrite.resize(bytesRead);
 
413
    readPosition += bufferLength;
 
414
 
 
415
    // Check to see if we just read the last block.  We need to call clear()
 
416
    // if we did so that the last write succeeds.
 
417
 
 
418
    if(ulong(bytesRead) < bufferLength)
 
419
      clear();
 
420
 
 
421
    // Seek to the write position and write our buffer.  Increment the
 
422
    // writePosition.
 
423
 
 
424
    seek(writePosition);
 
425
    fwrite(buffer.data(), sizeof(char), buffer.size(), d->file);
 
426
    writePosition += buffer.size();
 
427
 
 
428
    // Make the current buffer the data that we read in the beginning.
 
429
 
 
430
    buffer = aboutToOverwrite;
 
431
 
 
432
    // Again, we need this for the last write.  We don't want to write garbage
 
433
    // at the end of our file, so we need to set the buffer size to the amount
 
434
    // that we actually read.
 
435
 
 
436
    bufferLength = bytesRead;
 
437
  }
 
438
}
 
439
 
 
440
void File::removeBlock(ulong start, ulong length)
 
441
{
 
442
  if(!d->file)
 
443
    return;
 
444
 
 
445
  ulong bufferLength = bufferSize();
 
446
 
 
447
  long readPosition = start + length;
 
448
  long writePosition = start;
 
449
 
 
450
  ByteVector buffer(static_cast<uint>(bufferLength));
 
451
 
 
452
  ulong bytesRead = 1;
 
453
 
 
454
  while(bytesRead != 0) {
 
455
    seek(readPosition);
 
456
    bytesRead = fread(buffer.data(), sizeof(char), bufferLength, d->file);
 
457
    readPosition += bytesRead;
 
458
 
 
459
    // Check to see if we just read the last block.  We need to call clear()
 
460
    // if we did so that the last write succeeds.
 
461
 
 
462
    if(bytesRead < bufferLength)
 
463
      clear();
 
464
 
 
465
    seek(writePosition);
 
466
    fwrite(buffer.data(), sizeof(char), bytesRead, d->file);
 
467
    writePosition += bytesRead;
 
468
  }
 
469
  truncate(writePosition);
 
470
}
 
471
 
 
472
bool File::readOnly() const
 
473
{
 
474
  return d->readOnly;
 
475
}
 
476
 
 
477
bool File::isReadable(const char *file)
 
478
{
 
479
  return access(file, R_OK) == 0;
 
480
}
 
481
 
 
482
bool File::isOpen() const
 
483
{
 
484
  return (d->file != NULL);
 
485
}
 
486
 
 
487
bool File::isValid() const
 
488
{
 
489
  return isOpen() && d->valid;
 
490
}
 
491
 
 
492
void File::seek(long offset, Position p)
 
493
{
 
494
  if(!d->file) {
 
495
    debug("File::seek() -- trying to seek in a file that isn't opened.");
 
496
    return;
 
497
  }
 
498
 
 
499
  switch(p) {
 
500
  case Beginning:
 
501
    fseek(d->file, offset, SEEK_SET);
 
502
    break;
 
503
  case Current:
 
504
    fseek(d->file, offset, SEEK_CUR);
 
505
    break;
 
506
  case End:
 
507
    fseek(d->file, offset, SEEK_END);
 
508
    break;
 
509
  }
 
510
}
 
511
 
 
512
void File::clear()
 
513
{
 
514
  clearerr(d->file);
 
515
}
 
516
 
 
517
long File::tell() const
 
518
{
 
519
  return ftell(d->file);
 
520
}
 
521
 
 
522
long File::length()
 
523
{
 
524
  // Do some caching in case we do multiple calls.
 
525
 
 
526
  if(d->size > 0)
 
527
    return d->size;
 
528
 
 
529
  if(!d->file)
 
530
    return 0;
 
531
 
 
532
  long curpos = tell();
 
533
 
 
534
  seek(0, End);
 
535
  long endpos = tell();
 
536
 
 
537
  seek(curpos, Beginning);
 
538
 
 
539
  d->size = endpos;
 
540
  return endpos;
 
541
}
 
542
 
 
543
bool File::isWritable(const char *file)
 
544
{
 
545
  return access(file, W_OK) == 0;
 
546
}
 
547
 
 
548
////////////////////////////////////////////////////////////////////////////////
 
549
// protected members
 
550
////////////////////////////////////////////////////////////////////////////////
 
551
 
 
552
void File::setValid(bool valid)
 
553
{
 
554
  d->valid = valid;
 
555
}
 
556
 
 
557
void File::truncate(long length)
 
558
{
 
559
  ftruncate(fileno(d->file), length);
 
560
}
 
561
 
 
562
TagLib::uint File::bufferSize()
 
563
{
 
564
  return FilePrivate::bufferSize;
 
565
}