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