040d160d949a5af3509226f49c782b98c6964222
[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   switch(p)
244   {
245     case Beginning:
246       m_file.Seek(offset, SEEK_SET);
247       break;
248     case Current:
249       m_file.Seek(offset, SEEK_CUR);
250       break;
251     case End:
252       m_file.Seek(offset, SEEK_END);
253       break;
254   }
255 }
256
257 /*!
258  * Reset the end-of-file and error flags on the file.
259  */
260 void TagLibVFSStream::clear()
261 {
262 }
263
264 /*!
265  * Returns the current offset within the file.
266  */
267 long TagLibVFSStream::tell() const
268 {
269   int64_t pos = m_file.GetPosition();
270   if(pos > LONG_MAX)
271     return -1;
272   else
273     return (long)pos;
274 }
275
276 /*!
277  * Returns the length of the file.
278  */
279 long TagLibVFSStream::length()
280 {
281   return (long)m_file.GetLength();
282 }
283
284 /*!
285  * Truncates the file to a \a length.
286  */
287 void TagLibVFSStream::truncate(long length)
288 {
289   m_file.Truncate(length);
290 }