Merge pull request #3111 from janbar/epg_timeslot
[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 }
55
56 /*!
57  * Destroys this ByteVectorStream instance.
58  */
59 TagLibVFSStream::~TagLibVFSStream()
60 {
61   m_file.Close();
62 }
63
64 /*!
65  * Returns the file name in the local file system encoding.
66  */
67 FileName TagLibVFSStream::name() const
68 {
69   return m_strFileName.c_str();
70 }
71
72 /*!
73  * Reads a block of size \a length at the current get pointer.
74  */
75 ByteVector TagLibVFSStream::readBlock(TagLib::ulong length)
76 {
77   ByteVector byteVector(static_cast<TagLib::uint>(length));
78   byteVector.resize(m_file.Read(byteVector.data(), length));
79   return byteVector;
80 }
81
82 /*!
83  * Attempts to write the block \a data at the current get pointer.  If the
84  * file is currently only opened read only -- i.e. readOnly() returns true --
85  * this attempts to reopen the file in read/write mode.
86  *
87  * \note This should be used instead of using the streaming output operator
88  * for a ByteVector.  And even this function is significantly slower than
89  * doing output with a char[].
90  */
91 void TagLibVFSStream::writeBlock(const ByteVector &data)
92 {
93   m_file.Write(data.data(), data.size());
94 }
95
96 /*!
97  * Insert \a data at position \a start in the file overwriting \a replace
98  * bytes of the original content.
99  *
100  * \note This method is slow since it requires rewriting all of the file
101  * after the insertion point.
102  */
103 void TagLibVFSStream::insert(const ByteVector &data, TagLib::ulong start, TagLib::ulong replace)
104 {
105   if (data.size() == replace)
106   {
107     seek(start);
108     writeBlock(data);
109     return;
110   }
111   else if (data.size() < replace)
112   {
113     seek(start);
114     writeBlock(data);
115     removeBlock(start + data.size(), replace - data.size());
116   }
117
118   // Woohoo!  Faster (about 20%) than id3lib at last.  I had to get hardcore
119   // and avoid TagLib's high level API for rendering just copying parts of
120   // the file that don't contain tag data.
121   //
122   // Now I'll explain the steps in this ugliness:
123
124   // First, make sure that we're working with a buffer that is longer than
125   // the *differnce* in the tag sizes.  We want to avoid overwriting parts
126   // that aren't yet in memory, so this is necessary.
127   TagLib::ulong bufferLength = bufferSize();
128
129   while (data.size() - replace > bufferLength)
130     bufferLength += bufferSize();
131
132   // Set where to start the reading and writing.
133   long readPosition = start + replace;
134   long writePosition = start;
135   ByteVector buffer;
136   ByteVector aboutToOverwrite(static_cast<TagLib::uint>(bufferLength));
137
138   // This is basically a special case of the loop below.  Here we're just
139   // doing the same steps as below, but since we aren't using the same buffer
140   // size -- instead we're using the tag size -- this has to be handled as a
141   // special case.  We're also using File::writeBlock() just for the tag.
142   // That's a bit slower than using char *'s so, we're only doing it here.
143   seek(readPosition);
144   int bytesRead = m_file.Read(aboutToOverwrite.data(), bufferLength);
145   readPosition += bufferLength;
146
147   seek(writePosition);
148   writeBlock(data);
149   writePosition += data.size();
150
151   buffer = aboutToOverwrite;
152   buffer.resize(bytesRead);
153
154   // Ok, here's the main loop.  We want to loop until the read fails, which
155   // means that we hit the end of the file.
156   while (!buffer.isEmpty())
157   {
158     // Seek to the current read position and read the data that we're about
159     // to overwrite.  Appropriately increment the readPosition.
160     seek(readPosition);
161     bytesRead = m_file.Read(aboutToOverwrite.data(), bufferLength);
162     aboutToOverwrite.resize(bytesRead);
163     readPosition += bufferLength;
164
165     // Check to see if we just read the last block.  We need to call clear()
166     // if we did so that the last write succeeds.
167     if (TagLib::ulong(bytesRead) < bufferLength)
168       clear();
169
170     // Seek to the write position and write our buffer.  Increment the
171     // writePosition.
172     seek(writePosition);
173     m_file.Write(buffer.data(), buffer.size());
174     writePosition += buffer.size();
175
176     buffer = aboutToOverwrite;
177     bufferLength = bytesRead;
178   }
179 }
180
181 /*!
182  * Removes a block of the file starting a \a start and continuing for
183  * \a length bytes.
184  *
185  * \note This method is slow since it involves rewriting all of the file
186  * after the removed portion.
187  */
188 void TagLibVFSStream::removeBlock(TagLib::ulong start, TagLib::ulong length)
189 {
190   TagLib::ulong bufferLength = bufferSize();
191
192   long readPosition = start + length;
193   long writePosition = start;
194
195   ByteVector buffer(static_cast<TagLib::uint>(bufferLength));
196
197   TagLib::ulong bytesRead = 1;
198
199   while(bytesRead != 0)
200   {
201     seek(readPosition);
202     bytesRead = m_file.Read(buffer.data(), bufferLength);
203     readPosition += bytesRead;
204
205     // Check to see if we just read the last block.  We need to call clear()
206     // if we did so that the last write succeeds.
207     if(bytesRead < bufferLength)
208       clear();
209
210     seek(writePosition);
211     m_file.Write(buffer.data(), bytesRead);
212     writePosition += bytesRead;
213   }
214   truncate(writePosition);
215 }
216
217 /*!
218  * Returns true if the file is read only (or if the file can not be opened).
219  */
220 bool TagLibVFSStream::readOnly() const
221 {
222   return m_bIsReadOnly;
223 }
224
225 /*!
226  * Since the file can currently only be opened as an argument to the
227  * constructor (sort-of by design), this returns if that open succeeded.
228  */
229 bool TagLibVFSStream::isOpen() const
230 {
231   return m_bIsOpen;
232 }
233
234 /*!
235  * Move the I/O pointer to \a offset in the file from position \a p.  This
236  * defaults to seeking from the beginning of the file.
237  *
238  * \see Position
239  */
240 void TagLibVFSStream::seek(long offset, Position p)
241 {
242   switch(p)
243   {
244     case Beginning:
245       m_file.Seek(offset, SEEK_SET);
246       break;
247     case Current:
248       m_file.Seek(offset, SEEK_CUR);
249       break;
250     case End:
251       m_file.Seek(offset, SEEK_END);
252       break;
253   }
254 }
255
256 /*!
257  * Reset the end-of-file and error flags on the file.
258  */
259 void TagLibVFSStream::clear()
260 {
261 }
262
263 /*!
264  * Returns the current offset within the file.
265  */
266 long TagLibVFSStream::tell() const
267 {
268   int64_t pos = m_file.GetPosition();
269   if(pos > LONG_MAX)
270     return -1;
271   else
272     return (long)pos;
273 }
274
275 /*!
276  * Returns the length of the file.
277  */
278 long TagLibVFSStream::length()
279 {
280   return (long)m_file.GetLength();
281 }
282
283 /*!
284  * Truncates the file to a \a length.
285  */
286 void TagLibVFSStream::truncate(long length)
287 {
288   m_file.Truncate(length);
289 }