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., 51 Franklin Street, Fifth Floor, Boston, MA *
19 * 02110-1301 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 "tfilestream.h"
27 #include "tstring.h"
28 #include "tdebug.h"
29
30 #ifdef _WIN32
31 # include <windows.h>
32 #else
33 # include <stdio.h>
34 # include <unistd.h>
35 #endif
36
37 using namespace TagLib;
38
39 namespace
40 {
41 #ifdef _WIN32
42
43 // Uses Win32 native API instead of POSIX API to reduce the resource consumption.
44
45 typedef FileName FileNameHandle;
46 typedef HANDLE FileHandle;
47
48 const FileHandle InvalidFileHandle = INVALID_HANDLE_VALUE;
49
openFile(const FileName & path,bool readOnly)50 FileHandle openFile(const FileName &path, bool readOnly)
51 {
52 const DWORD access = readOnly ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE);
53
54 #if defined (PLATFORM_WINRT)
55 return CreateFile2(path.wstr().c_str(), access, FILE_SHARE_READ, OPEN_EXISTING, NULL);
56 #else
57 return CreateFileW(path.wstr().c_str(), access, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
58 #endif
59 }
60
openFile(const int fileDescriptor,bool readOnly)61 FileHandle openFile(const int fileDescriptor, bool readOnly)
62 {
63 return InvalidFileHandle;
64 }
65
closeFile(FileHandle file)66 void closeFile(FileHandle file)
67 {
68 CloseHandle(file);
69 }
70
readFile(FileHandle file,ByteVector & buffer)71 size_t readFile(FileHandle file, ByteVector &buffer)
72 {
73 DWORD length;
74 if(ReadFile(file, buffer.data(), static_cast<DWORD>(buffer.size()), &length, NULL))
75 return static_cast<size_t>(length);
76 else
77 return 0;
78 }
79
writeFile(FileHandle file,const ByteVector & buffer)80 size_t writeFile(FileHandle file, const ByteVector &buffer)
81 {
82 DWORD length;
83 if(WriteFile(file, buffer.data(), static_cast<DWORD>(buffer.size()), &length, NULL))
84 return static_cast<size_t>(length);
85 else
86 return 0;
87 }
88
89 #else // _WIN32
90
91 struct FileNameHandle : public std::string
92 {
93 FileNameHandle(FileName name) : std::string(name) {}
94 operator FileName () const { return c_str(); }
95 };
96
97 typedef FILE* FileHandle;
98
99 const FileHandle InvalidFileHandle = 0;
100
101 FileHandle openFile(const FileName &path, bool readOnly)
102 {
103 return fopen(path, readOnly ? "rb" : "rb+");
104 }
105
106 FileHandle openFile(const int fileDescriptor, bool readOnly)
107 {
108 return fdopen(fileDescriptor, readOnly ? "rb" : "rb+");
109 }
110
111 void closeFile(FileHandle file)
112 {
113 fclose(file);
114 }
115
116 size_t readFile(FileHandle file, ByteVector &buffer)
117 {
118 return fread(buffer.data(), sizeof(char), buffer.size(), file);
119 }
120
121 size_t writeFile(FileHandle file, const ByteVector &buffer)
122 {
123 return fwrite(buffer.data(), sizeof(char), buffer.size(), file);
124 }
125
126 #endif // _WIN32
127 }
128
129 class FileStream::FileStreamPrivate
130 {
131 public:
FileStreamPrivate(const FileName & fileName)132 FileStreamPrivate(const FileName &fileName)
133 : file(InvalidFileHandle)
134 , name(fileName)
135 , readOnly(true)
136 {
137 }
138
139 FileHandle file;
140 FileNameHandle name;
141 bool readOnly;
142 };
143
144 ////////////////////////////////////////////////////////////////////////////////
145 // public members
146 ////////////////////////////////////////////////////////////////////////////////
147
FileStream(FileName fileName,bool openReadOnly)148 FileStream::FileStream(FileName fileName, bool openReadOnly)
149 : d(new FileStreamPrivate(fileName))
150 {
151 // First try with read / write mode, if that fails, fall back to read only.
152
153 if(!openReadOnly)
154 d->file = openFile(fileName, false);
155
156 if(d->file != InvalidFileHandle)
157 d->readOnly = false;
158 else
159 d->file = openFile(fileName, true);
160
161 if(d->file == InvalidFileHandle)
162 # ifdef _WIN32
163 debug("Could not open file " + fileName.toString());
164 # else
165 debug("Could not open file " + String(static_cast<const char *>(d->name)));
166 # endif
167 }
168
FileStream(int fileDescriptor,bool openReadOnly)169 FileStream::FileStream(int fileDescriptor, bool openReadOnly)
170 : d(new FileStreamPrivate(""))
171 {
172 // First try with read / write mode, if that fails, fall back to read only.
173
174 if(!openReadOnly)
175 d->file = openFile(fileDescriptor, false);
176
177 if(d->file != InvalidFileHandle)
178 d->readOnly = false;
179 else
180 d->file = openFile(fileDescriptor, true);
181
182 if(d->file == InvalidFileHandle)
183 debug("Could not open file using file descriptor");
184 }
185
~FileStream()186 FileStream::~FileStream()
187 {
188 if(isOpen())
189 closeFile(d->file);
190
191 delete d;
192 }
193
name() const194 FileName FileStream::name() const
195 {
196 return d->name;
197 }
198
readBlock(unsigned long length)199 ByteVector FileStream::readBlock(unsigned long length)
200 {
201 if(!isOpen()) {
202 debug("FileStream::readBlock() -- invalid file.");
203 return ByteVector();
204 }
205
206 if(length == 0)
207 return ByteVector();
208
209 const unsigned long streamLength = static_cast<unsigned long>(FileStream::length());
210 if(length > bufferSize() && length > streamLength)
211 length = streamLength;
212
213 ByteVector buffer(static_cast<unsigned int>(length));
214
215 const size_t count = readFile(d->file, buffer);
216 buffer.resize(static_cast<unsigned int>(count));
217
218 return buffer;
219 }
220
writeBlock(const ByteVector & data)221 void FileStream::writeBlock(const ByteVector &data)
222 {
223 if(!isOpen()) {
224 debug("FileStream::writeBlock() -- invalid file.");
225 return;
226 }
227
228 if(readOnly()) {
229 debug("FileStream::writeBlock() -- read only file.");
230 return;
231 }
232
233 writeFile(d->file, data);
234 }
235
insert(const ByteVector & data,unsigned long start,unsigned long replace)236 void FileStream::insert(const ByteVector &data, unsigned long start, unsigned long replace)
237 {
238 if(!isOpen()) {
239 debug("FileStream::insert() -- invalid file.");
240 return;
241 }
242
243 if(readOnly()) {
244 debug("FileStream::insert() -- read only file.");
245 return;
246 }
247
248 if(data.size() == replace) {
249 seek(start);
250 writeBlock(data);
251 return;
252 }
253 else if(data.size() < replace) {
254 seek(start);
255 writeBlock(data);
256 removeBlock(start + data.size(), replace - data.size());
257 return;
258 }
259
260 // Woohoo! Faster (about 20%) than id3lib at last. I had to get hardcore
261 // and avoid TagLib's high level API for rendering just copying parts of
262 // the file that don't contain tag data.
263 //
264 // Now I'll explain the steps in this ugliness:
265
266 // First, make sure that we're working with a buffer that is longer than
267 // the *differnce* in the tag sizes. We want to avoid overwriting parts
268 // that aren't yet in memory, so this is necessary.
269
270 unsigned long bufferLength = bufferSize();
271
272 while(data.size() - replace > bufferLength)
273 bufferLength += bufferSize();
274
275 // Set where to start the reading and writing.
276
277 long readPosition = start + replace;
278 long writePosition = start;
279
280 ByteVector buffer = data;
281 ByteVector aboutToOverwrite(static_cast<unsigned int>(bufferLength));
282
283 while(true) {
284 // Seek to the current read position and read the data that we're about
285 // to overwrite. Appropriately increment the readPosition.
286
287 seek(readPosition);
288 const unsigned int bytesRead = static_cast<unsigned int>(readFile(d->file, aboutToOverwrite));
289 aboutToOverwrite.resize(bytesRead);
290 readPosition += bufferLength;
291
292 // Check to see if we just read the last block. We need to call clear()
293 // if we did so that the last write succeeds.
294
295 if(bytesRead < bufferLength)
296 clear();
297
298 // Seek to the write position and write our buffer. Increment the
299 // writePosition.
300
301 seek(writePosition);
302 writeBlock(buffer);
303
304 // We hit the end of the file.
305
306 if(bytesRead == 0)
307 break;
308
309 writePosition += buffer.size();
310
311 // Make the current buffer the data that we read in the beginning.
312
313 buffer = aboutToOverwrite;
314 }
315 }
316
removeBlock(unsigned long start,unsigned long length)317 void FileStream::removeBlock(unsigned long start, unsigned long length)
318 {
319 if(!isOpen()) {
320 debug("FileStream::removeBlock() -- invalid file.");
321 return;
322 }
323
324 unsigned long bufferLength = bufferSize();
325
326 long readPosition = start + length;
327 long writePosition = start;
328
329 ByteVector buffer(static_cast<unsigned int>(bufferLength));
330
331 for(unsigned int bytesRead = -1; bytesRead != 0;) {
332 seek(readPosition);
333 bytesRead = static_cast<unsigned int>(readFile(d->file, buffer));
334 readPosition += bytesRead;
335
336 // Check to see if we just read the last block. We need to call clear()
337 // if we did so that the last write succeeds.
338
339 if(bytesRead < buffer.size()) {
340 clear();
341 buffer.resize(bytesRead);
342 }
343
344 seek(writePosition);
345 writeFile(d->file, buffer);
346
347 writePosition += bytesRead;
348 }
349
350 truncate(writePosition);
351 }
352
readOnly() const353 bool FileStream::readOnly() const
354 {
355 return d->readOnly;
356 }
357
isOpen() const358 bool FileStream::isOpen() const
359 {
360 return (d->file != InvalidFileHandle);
361 }
362
seek(long offset,Position p)363 void FileStream::seek(long offset, Position p)
364 {
365 if(!isOpen()) {
366 debug("FileStream::seek() -- invalid file.");
367 return;
368 }
369
370 #ifdef _WIN32
371
372 if(p != Beginning && p != Current && p != End) {
373 debug("FileStream::seek() -- Invalid Position value.");
374 return;
375 }
376
377 LARGE_INTEGER liOffset;
378 liOffset.QuadPart = offset;
379
380 if(!SetFilePointerEx(d->file, liOffset, NULL, static_cast<DWORD>(p))) {
381 debug("FileStream::seek() -- Failed to set the file pointer.");
382 }
383
384 #else
385
386 int whence;
387 switch(p) {
388 case Beginning:
389 whence = SEEK_SET;
390 break;
391 case Current:
392 whence = SEEK_CUR;
393 break;
394 case End:
395 whence = SEEK_END;
396 break;
397 default:
398 debug("FileStream::seek() -- Invalid Position value.");
399 return;
400 }
401
402 fseek(d->file, offset, whence);
403
404 #endif
405 }
406
clear()407 void FileStream::clear()
408 {
409 #ifdef _WIN32
410
411 // NOP
412
413 #else
414
415 clearerr(d->file);
416
417 #endif
418 }
419
tell() const420 long FileStream::tell() const
421 {
422 #ifdef _WIN32
423
424 const LARGE_INTEGER zero = {};
425 LARGE_INTEGER position;
426
427 if(SetFilePointerEx(d->file, zero, &position, FILE_CURRENT) &&
428 position.QuadPart <= LONG_MAX) {
429 return static_cast<long>(position.QuadPart);
430 }
431 else {
432 debug("FileStream::tell() -- Failed to get the file pointer.");
433 return 0;
434 }
435
436 #else
437
438 return ftell(d->file);
439
440 #endif
441 }
442
length()443 long FileStream::length()
444 {
445 if(!isOpen()) {
446 debug("FileStream::length() -- invalid file.");
447 return 0;
448 }
449
450 #ifdef _WIN32
451
452 LARGE_INTEGER fileSize;
453
454 if(GetFileSizeEx(d->file, &fileSize) && fileSize.QuadPart <= LONG_MAX) {
455 return static_cast<long>(fileSize.QuadPart);
456 }
457 else {
458 debug("FileStream::length() -- Failed to get the file size.");
459 return 0;
460 }
461
462 #else
463
464 const long curpos = tell();
465
466 seek(0, End);
467 const long endpos = tell();
468
469 seek(curpos, Beginning);
470
471 return endpos;
472
473 #endif
474 }
475
476 ////////////////////////////////////////////////////////////////////////////////
477 // protected members
478 ////////////////////////////////////////////////////////////////////////////////
479
truncate(long length)480 void FileStream::truncate(long length)
481 {
482 #ifdef _WIN32
483
484 const long currentPos = tell();
485
486 seek(length);
487
488 if(!SetEndOfFile(d->file)) {
489 debug("FileStream::truncate() -- Failed to truncate the file.");
490 }
491
492 seek(currentPos);
493
494 #else
495
496 const int error = ftruncate(fileno(d->file), length);
497 if(error != 0)
498 debug("FileStream::truncate() -- Coundn't truncate the file.");
499
500 #endif
501 }
502
bufferSize()503 unsigned int FileStream::bufferSize()
504 {
505 return 1024;
506 }
507