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 <tbytevectorlist.h>
27 #include <tmap.h>
28 #include <tstring.h>
29 #include <tdebug.h>
30 
31 #include "oggfile.h"
32 #include "oggpage.h"
33 #include "oggpageheader.h"
34 
35 using namespace TagLib;
36 
37 namespace
38 {
39   // Returns the first packet index of the right next page to the given one.
nextPacketIndex(const Ogg::Page * page)40   unsigned int nextPacketIndex(const Ogg::Page *page)
41   {
42     if(page->header()->lastPacketCompleted())
43       return page->firstPacketIndex() + page->packetCount();
44     else
45       return page->firstPacketIndex() + page->packetCount() - 1;
46   }
47 }
48 
49 class Ogg::File::FilePrivate
50 {
51 public:
FilePrivate()52   FilePrivate() :
53     firstPageHeader(0),
54     lastPageHeader(0)
55   {
56     pages.setAutoDelete(true);
57   }
58 
~FilePrivate()59   ~FilePrivate()
60   {
61     delete firstPageHeader;
62     delete lastPageHeader;
63   }
64 
65   unsigned int streamSerialNumber;
66   List<Page *> pages;
67   PageHeader *firstPageHeader;
68   PageHeader *lastPageHeader;
69   Map<unsigned int, ByteVector> dirtyPackets;
70 };
71 
72 ////////////////////////////////////////////////////////////////////////////////
73 // public members
74 ////////////////////////////////////////////////////////////////////////////////
75 
~File()76 Ogg::File::~File()
77 {
78   delete d;
79 }
80 
packet(unsigned int i)81 ByteVector Ogg::File::packet(unsigned int i)
82 {
83   // Check to see if we're called setPacket() for this packet since the last
84   // save:
85 
86   if(d->dirtyPackets.contains(i))
87     return d->dirtyPackets[i];
88 
89   // If we haven't indexed the page where the packet we're interested in starts,
90   // begin reading pages until we have.
91 
92   if(!readPages(i)) {
93     debug("Ogg::File::packet() -- Could not find the requested packet.");
94     return ByteVector();
95   }
96 
97   // Look for the first page in which the requested packet starts.
98 
99   List<Page *>::ConstIterator it = d->pages.begin();
100   while((*it)->containsPacket(i) == Page::DoesNotContainPacket)
101     ++it;
102 
103   // If the packet is completely contained in the first page that it's in.
104 
105   // If the packet is *not* completely contained in the first page that it's a
106   // part of then that packet trails off the end of the page.  Continue appending
107   // the pages' packet data until we hit a page that either does not end with the
108   // packet that we're fetching or where the last packet is complete.
109 
110   ByteVector packet = (*it)->packets()[i - (*it)->firstPacketIndex()];
111 
112   while(nextPacketIndex(*it) <= i) {
113     ++it;
114     packet.append((*it)->packets().front());
115   }
116 
117   return packet;
118 }
119 
setPacket(unsigned int i,const ByteVector & p)120 void Ogg::File::setPacket(unsigned int i, const ByteVector &p)
121 {
122   if(!readPages(i)) {
123     debug("Ogg::File::setPacket() -- Could not set the requested packet.");
124     return;
125   }
126 
127   d->dirtyPackets[i] = p;
128 }
129 
firstPageHeader()130 const Ogg::PageHeader *Ogg::File::firstPageHeader()
131 {
132   if(!d->firstPageHeader) {
133     const long firstPageHeaderOffset = find("OggS");
134     if(firstPageHeaderOffset < 0)
135       return 0;
136 
137     d->firstPageHeader = new PageHeader(this, firstPageHeaderOffset);
138   }
139 
140   return d->firstPageHeader->isValid() ? d->firstPageHeader : 0;
141 }
142 
lastPageHeader()143 const Ogg::PageHeader *Ogg::File::lastPageHeader()
144 {
145   if(!d->lastPageHeader) {
146     const long lastPageHeaderOffset = rfind("OggS");
147     if(lastPageHeaderOffset < 0)
148       return 0;
149 
150     d->lastPageHeader = new PageHeader(this, lastPageHeaderOffset);
151   }
152 
153   return d->lastPageHeader->isValid() ? d->lastPageHeader : 0;
154 }
155 
save()156 bool Ogg::File::save()
157 {
158   if(readOnly()) {
159     debug("Ogg::File::save() - Cannot save to a read only file.");
160     return false;
161   }
162 
163   Map<unsigned int, ByteVector>::ConstIterator it;
164   for(it = d->dirtyPackets.begin(); it != d->dirtyPackets.end(); ++it)
165     writePacket(it->first, it->second);
166 
167   d->dirtyPackets.clear();
168 
169   return true;
170 }
171 
172 ////////////////////////////////////////////////////////////////////////////////
173 // protected members
174 ////////////////////////////////////////////////////////////////////////////////
175 
File(FileName file)176 Ogg::File::File(FileName file) :
177   TagLib::File(file),
178   d(new FilePrivate())
179 {
180 }
181 
File(IOStream * stream)182 Ogg::File::File(IOStream *stream) :
183   TagLib::File(stream),
184   d(new FilePrivate())
185 {
186 }
187 
188 ////////////////////////////////////////////////////////////////////////////////
189 // private members
190 ////////////////////////////////////////////////////////////////////////////////
191 
readPages(unsigned int i)192 bool Ogg::File::readPages(unsigned int i)
193 {
194   while(true) {
195     unsigned int packetIndex;
196     long offset;
197 
198     if(d->pages.isEmpty()) {
199       packetIndex = 0;
200       offset = find("OggS");
201       if(offset < 0)
202         return false;
203     }
204     else {
205       const Page *page = d->pages.back();
206       packetIndex = nextPacketIndex(page);
207       offset = page->fileOffset() + page->size();
208     }
209 
210     // Enough pages have been fetched.
211 
212     if(packetIndex > i)
213       return true;
214 
215     // Read the next page and add it to the page list.
216 
217     Page *nextPage = new Page(this, offset);
218     if(!nextPage->header()->isValid()) {
219       delete nextPage;
220       return false;
221     }
222 
223     nextPage->setFirstPacketIndex(packetIndex);
224     d->pages.append(nextPage);
225 
226     if(nextPage->header()->lastPageOfStream())
227       return false;
228   }
229 }
230 
writePacket(unsigned int i,const ByteVector & packet)231 void Ogg::File::writePacket(unsigned int i, const ByteVector &packet)
232 {
233   if(!readPages(i)) {
234     debug("Ogg::File::writePacket() -- Could not find the requested packet.");
235     return;
236   }
237 
238   // Look for the pages where the requested packet should belong to.
239 
240   List<Page *>::ConstIterator it = d->pages.begin();
241   while((*it)->containsPacket(i) == Page::DoesNotContainPacket)
242     ++it;
243 
244   const Page *firstPage = *it;
245 
246   while(nextPacketIndex(*it) <= i)
247     ++it;
248 
249   const Page *lastPage = *it;
250 
251   // Replace the requested packet and create new pages to replace the located pages.
252 
253   ByteVectorList packets = firstPage->packets();
254   packets[i - firstPage->firstPacketIndex()] = packet;
255 
256   if(firstPage != lastPage && lastPage->packetCount() > 1) {
257     ByteVectorList lastPagePackets = lastPage->packets();
258     lastPagePackets.erase(lastPagePackets.begin());
259     packets.append(lastPagePackets);
260   }
261 
262   // TODO: This pagination method isn't accurate for what's being done here.
263   // This should account for real possibilities like non-aligned packets and such.
264 
265   List<Page *> pages = Page::paginate(packets,
266                                       Page::SinglePagePerGroup,
267                                       firstPage->header()->streamSerialNumber(),
268                                       firstPage->pageSequenceNumber(),
269                                       firstPage->header()->firstPacketContinued(),
270                                       lastPage->header()->lastPacketCompleted());
271   pages.setAutoDelete(true);
272 
273   // Write the pages.
274 
275   ByteVector data;
276   for(it = pages.begin(); it != pages.end(); ++it)
277     data.append((*it)->render());
278 
279   const unsigned long originalOffset = firstPage->fileOffset();
280   const unsigned long originalLength = lastPage->fileOffset() + lastPage->size() - originalOffset;
281 
282   insert(data, originalOffset, originalLength);
283 
284   // Renumber the following pages if the pages have been split or merged.
285 
286   const int numberOfNewPages
287     = pages.back()->pageSequenceNumber() - lastPage->pageSequenceNumber();
288 
289   if(numberOfNewPages != 0) {
290     long pageOffset = originalOffset + data.size();
291 
292     while(true) {
293       Page page(this, pageOffset);
294       if(!page.header()->isValid())
295         break;
296 
297       page.setPageSequenceNumber(page.pageSequenceNumber() + numberOfNewPages);
298       const ByteVector data = page.render();
299 
300       seek(pageOffset + 18);
301       writeBlock(data.mid(18, 8));
302 
303       if(page.header()->lastPageOfStream())
304         break;
305 
306       pageOffset += page.size();
307     }
308   }
309 
310   // Discard all the pages to keep them up-to-date by fetching them again.
311 
312   d->pages.clear();
313 }
314