TagLibVFSStream::seek: fix parsing files with broken structure (specified length...
[vuplus_xbmc] / xbmc / music / tags / TagLibVFSStream.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20 #include "limits.h"
21 #include "TagLibVFSStream.h"
22 #include "filesystem/File.h"
23 #include "utils/StdString.h"
24 #include "utils/log.h"
25 #include <taglib/tiostream.h>
26
27 using namespace XFILE;
28 using namespace TagLib;
29 using namespace MUSIC_INFO;
30 using namespace std;
31
32 #ifdef TARGET_WINDOWS
33 #pragma comment(lib, "tag.lib")
34 #endif
35
36 /*!
37  * Construct a File object and opens the \a file.  \a file should be a
38  * be an XBMC Vfile.
39  */
40 TagLibVFSStream::TagLibVFSStream(const string& strFileName, bool readOnly)
41 {
42   m_bIsOpen = true;
43   if (readOnly)
44   {
45     if (!m_file.Open(strFileName))
46       m_bIsOpen = false;
47   }
48   else
49   {
50     if (!m_file.OpenForWrite(strFileName))
51       m_bIsOpen = false;
52   }
53   m_strFileName = strFileName;
54   m_bIsReadOnly = readOnly || !m_bIsOpen;
55 }
56
57 /*!
58  * Destroys this ByteVectorStream instance.
59  */
60 TagLibVFSStream::~TagLibVFSStream()
61 {
62   m_file.Close();
63 }
64
65 /*!
66  * Returns the file name in the local file system encoding.
67  */
68 FileName TagLibVFSStream::name() const
69 {
70   return m_strFileName.c_str();
71 }
72
73 /*!
74  * Reads a block of size \a length at the current get pointer.
75  */
76 ByteVector TagLibVFSStream::readBlock(TagLib::ulong length)
77 {
78   ByteVector byteVector(static_cast<TagLib::uint>(length));
79   byteVector.resize(m_file.Read(byteVector.data(), length));
80   return byteVector;
81 }
82
83 /*!
84  * Attempts to write the block \a data at the current get pointer.  If the
85  * file is currently only opened read only -- i.e. readOnly() returns true --
86  * this attempts to reopen the file in read/write mode.
87  *
88  * \note This should be used instead of using the streaming output operator
89  * for a ByteVector.  And even this function is significantly slower than
90  * doing output with a char[].
91  */
92 void TagLibVFSStream::writeBlock(const ByteVector &data)
93 {
94   m_file.Write(data.data(), data.size());
95 }
96
97 /*!
98  * Insert \a data at position \a start in the file overwriting \a replace
99  * bytes of the original content.
100  *
101  * \note This method is slow since it requires rewriting all of the file
102  * after the insertion point.
103  */
104 void TagLibVFSStream::insert(const ByteVector &data, TagLib::ulong start, TagLib::ulong replace)
105 {
106   if (data.size() == replace)
107   {
108     seek(start);
109     writeBlock(data);
110     return;
111   }
112   else if (data.size() < replace)
113   {
114     seek(start);
115     writeBlock(data);
116     removeBlock(start + data.size(), replace - data.size());
117   }
118
119   // Woohoo!  Faster (about 20%) than id3lib at last.  I had to get hardcore
120   // and avoid TagLib's high level API for rendering just copying parts of
121   // the file that don't contain tag data.
122   //
123   // Now I'll explain the steps in this ugliness:
124
125   // First, make sure that we're working with a buffer that is longer than
126   // the *differnce* in the tag sizes.  We want to avoid overwriting parts
127   // that aren't yet in memory, so this is necessary.
128   TagLib::ulong bufferLength = bufferSize();
129
130   while (data.size() - replace > bufferLength)
131     bufferLength += bufferSize();
132
133   // Set where to start the reading and writing.
134   long readPosition = start + replace;
135   long writePosition = start;
136   ByteVector buffer;
137   ByteVector aboutToOverwrite(static_cast<TagLib::uint>(bufferLength));
138
139   // This is basically a special case of the loop below.  Here we're just
140   // doing the same steps as below, but since we aren't using the same buffer
141   // size -- instead we're using the tag size -- this has to be handled as a
142   // special case.  We're also using File::writeBlock() just for the tag.
143   // That's a bit slower than using char *'s so, we're only doing it here.
144   seek(readPosition);
145   int bytesRead = m_file.Read(aboutToOverwrite.data(), bufferLength);
146   readPosition += bufferLength;
147
148   seek(writePosition);
149   writeBlock(data);
150   writePosition += data.size();
151
152   buffer = aboutToOverwrite;
153   buffer.resize(bytesRead);
154
155   // Ok, here's the main loop.  We want to loop until the read fails, which
156   // means that we hit the end of the file.
157   while (!buffer.isEmpty())
158   {
159     // Seek to the current read position and read the data that we're about
160     // to overwrite.  Appropriately increment the readPosition.
161     seek(readPosition);
162     bytesRead = m_file.Read(aboutToOverwrite.data(), bufferLength);
163     aboutToOverwrite.resize(bytesRead);
164     readPosition += bufferLength;
165
166     // Check to see if we just read the last block.  We need to call clear()
167     // if we did so that the last write succeeds.
168     if (TagLib::ulong(bytesRead) < bufferLength)
169       clear();
170
171     // Seek to the write position and write our buffer.  Increment the
172     // writePosition.
173     seek(writePosition);
174     m_file.Write(buffer.data(), buffer.size());
175     writePosition += buffer.size();
176
177     buffer = aboutToOverwrite;
178     bufferLength = bytesRead;
179   }
180 }
181
182 /*!
183  * Removes a block of the file starting a \a start and continuing for
184  * \a length bytes.
185  *
186  * \note This method is slow since it involves rewriting all of the file
187  * after the removed portion.
188  */
189 void TagLibVFSStream::removeBlock(TagLib::ulong start, TagLib::ulong length)
190 {
191   TagLib::ulong bufferLength = bufferSize();
192
193   long readPosition = start + length;
194   long writePosition = start;
195
196   ByteVector buffer(static_cast<TagLib::uint>(bufferLength));
197
198   TagLib::ulong bytesRead = 1;
199
200   while(bytesRead != 0)
201   {
202     seek(readPosition);
203     bytesRead = m_file.Read(buffer.data(), bufferLength);
204     readPosition += bytesRead;
205
206     // Check to see if we just read the last block.  We need to call clear()
207     // if we did so that the last write succeeds.
208     if(bytesRead < bufferLength)
209       clear();
210
211     seek(writePosition);
212     m_file.Write(buffer.data(), bytesRead);
213     writePosition += bytesRead;
214   }
215   truncate(writePosition);
216 }
217
218 /*!
219  * Returns true if the file is read only (or if the file can not be opened).
220  */
221 bool TagLibVFSStream::readOnly() const
222 {
223   return m_bIsReadOnly;
224 }
225
226 /*!
227  * Since the file can currently only be opened as an argument to the
228  * constructor (sort-of by design), this returns if that open succeeded.
229  */
230 bool TagLibVFSStream::isOpen() const
231 {
232   return m_bIsOpen;
233 }
234
235 /*!
236  * Move the I/O pointer to \a offset in the file from position \a p.  This
237  * defaults to seeking from the beginning of the file.
238  *
239  * \see Position
240  */
241 void TagLibVFSStream::seek(long offset, Position p)
242 {
243   const long fileLen = length();
244   if (m_bIsReadOnly && fileLen > 0)
245   {
246     long startPos;
247     if (p == Beginning)
248       startPos = 0;
249     else if (p == Current)
250       startPos = tell();
251     else if (p == End)
252       startPos = fileLen;
253     else
254       return; // wrong Position value
255     
256     // When parsing some broken files, taglib may try to seek above end of file.
257     // If underlying VFS does not move I/O pointer in this case, taglib will parse
258     // same part of file several times and ends with error. To prevent this
259     // situation, force seek to last valid position so VFS move I/O pointer.
260     if (startPos >= 0)
261     {
262       if (offset < 0 && startPos + offset < 0)
263       {
264         m_file.Seek(0, SEEK_SET);
265         return;
266       }
267       if (offset > 0 && startPos + offset > fileLen)
268       {
269         m_file.Seek(fileLen, SEEK_SET);
270         return;
271       }
272     }
273   }
274
275   switch(p)
276   {
277     case Beginning:
278       m_file.Seek(offset, SEEK_SET);
279       break;
280     case Current:
281       m_file.Seek(offset, SEEK_CUR);
282       break;
283     case End:
284       m_file.Seek(offset, SEEK_END);
285       break;
286   }
287 }
288
289 /*!
290  * Reset the end-of-file and error flags on the file.
291  */
292 void TagLibVFSStream::clear()
293 {
294 }
295
296 /*!
297  * Returns the current offset within the file.
298  */
299 long TagLibVFSStream::tell() const
300 {
301   int64_t pos = m_file.GetPosition();
302   if(pos > LONG_MAX)
303     return -1;
304   else
305     return (long)pos;
306 }
307
308 /*!
309  * Returns the length of the file.
310  */
311 long TagLibVFSStream::length()
312 {
313   return (long)m_file.GetLength();
314 }
315
316 /*!
317  * Truncates the file to a \a length.
318  */
319 void TagLibVFSStream::truncate(long length)
320 {
321   m_file.Truncate(length);
322 }