1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4     Sonic Visualiser
5     An audio file viewer and annotation editor.
6     Centre for Digital Music, Queen Mary, University of London.
7     This file copyright 2006 Chris Cannam.
8 
9     This program is free software; you can redistribute it and/or
10     modify it under the terms of the GNU General Public License as
11     published by the Free Software Foundation; either version 2 of the
12     License, or (at your option) any later version.  See the file
13     COPYING included with this distribution for more information.
14 */
15 
16 #include "BZipFileDevice.h"
17 
18 #include <bzlib.h>
19 
20 #include <iostream>
21 
22 #include "base/Debug.h"
23 
24 // for dup:
25 #ifdef _MSC_VER
26 #include <io.h>
27 #else
28 #include <unistd.h>
29 #endif
30 
BZipFileDevice(QString fileName)31 BZipFileDevice::BZipFileDevice(QString fileName) :
32     m_fileName(fileName),
33     m_qfile(fileName),
34     m_file(nullptr),
35     m_bzFile(nullptr),
36     m_atEnd(true),
37     m_ok(true)
38 {
39 }
40 
~BZipFileDevice()41 BZipFileDevice::~BZipFileDevice()
42 {
43 //    SVDEBUG << "BZipFileDevice::~BZipFileDevice(" << m_fileName << ")" << endl;
44     if (m_bzFile) close();
45 }
46 
47 bool
isOK() const48 BZipFileDevice::isOK() const
49 {
50     return m_ok;
51 }
52 
53 bool
open(OpenMode mode)54 BZipFileDevice::open(OpenMode mode)
55 {
56     setErrorString("");
57 
58     if (m_bzFile) {
59         setErrorString(tr("File is already open"));
60         return false;
61     }
62 
63     if (mode & Append) {
64         setErrorString(tr("Append mode not supported"));
65         m_ok = false;
66         return false;
67     }
68 
69     if ((mode & (ReadOnly | WriteOnly)) == 0) {
70         setErrorString(tr("File access mode not specified"));
71         m_ok = false;
72         return false;
73     }
74 
75     if ((mode & ReadOnly) && (mode & WriteOnly)) {
76         setErrorString(tr("Read and write modes both specified"));
77         m_ok = false;
78         return false;
79     }
80 
81     // This is all going to be a bit silly.
82     //
83     // We open the file with QFile so as not to have to worry about locale
84     // support ourselves (especially on Windows). Then we get a fd from
85     // QFile and "convert" it to a FILE* using fdopen because that is what
86     // the bz2 library needs for reading and writing an already-open file.
87     //
88     // fdopen takes over the fd it is given, and will close it when fclose
89     // is called. (We must call fclose, because it's needed to avoid
90     // leaking the file stream structure.)
91     //
92     // But QFile will also close its fd, either when we call QFile::close
93     // or on destruction -- there doesn't seem to be a way to avoid that
94     // for a file that QFile opened.
95     //
96     // So we have to add an extra dup() in to the fdopen to avoid a double
97     // close.
98     //
99     // Note that bz2 will *not* fclose the FILE* it was passed, so we
100     // don't have a problem with calling both bzWriteClose and fclose.
101 
102     if (mode & WriteOnly) {
103 
104         if (!m_qfile.open(QIODevice::WriteOnly)) {
105             setErrorString(tr("Failed to open file for writing"));
106             m_ok = false;
107             return false;
108         }
109 
110         m_file = fdopen(dup(m_qfile.handle()), "wb");
111         if (!m_file) {
112             setErrorString(tr("Failed to open file handle for writing"));
113             m_qfile.close();
114             m_ok = false;
115             return false;
116         }
117 
118         int bzError = BZ_OK;
119         m_bzFile = BZ2_bzWriteOpen(&bzError, m_file, 9, 0, 0);
120 
121         if (!m_bzFile) {
122             fclose(m_file);
123             m_file = nullptr;
124             m_qfile.close();
125             setErrorString(tr("Failed to open bzip2 stream for writing"));
126             m_ok = false;
127             return false;
128         }
129 
130 //        cerr << "BZipFileDevice: opened \"" << m_fileName << "\" for writing" << endl;
131 
132         setErrorString(QString());
133         setOpenMode(mode);
134         return true;
135     }
136 
137     if (mode & ReadOnly) {
138 
139         if (!m_qfile.open(QIODevice::ReadOnly)) {
140             setErrorString(tr("Failed to open file for reading"));
141             m_ok = false;
142             return false;
143         }
144 
145         m_file = fdopen(dup(m_qfile.handle()), "rb");
146         if (!m_file) {
147             setErrorString(tr("Failed to open file handle for reading"));
148             m_ok = false;
149             return false;
150         }
151 
152         int bzError = BZ_OK;
153         m_bzFile = BZ2_bzReadOpen(&bzError, m_file, 0, 0, nullptr, 0);
154 
155         if (!m_bzFile) {
156             fclose(m_file);
157             m_file = nullptr;
158             m_qfile.close();
159             setErrorString(tr("Failed to open bzip2 stream for reading"));
160             m_ok = false;
161             return false;
162         }
163 
164 //        cerr << "BZipFileDevice: opened \"" << m_fileName << "\" for reading" << endl;
165 
166         m_atEnd = false;
167 
168         setErrorString(QString());
169         setOpenMode(mode);
170         return true;
171     }
172 
173     setErrorString(tr("Internal error (open for neither read nor write)"));
174     m_ok = false;
175     return false;
176 }
177 
178 void
close()179 BZipFileDevice::close()
180 {
181     if (!m_bzFile) {
182         setErrorString(tr("File not open"));
183         m_ok = false;
184         return;
185     }
186 
187     int bzError = BZ_OK;
188 
189     if (openMode() & WriteOnly) {
190         unsigned int in = 0, out = 0;
191         BZ2_bzWriteClose(&bzError, m_bzFile, 0, &in, &out);
192 //        cerr << "Wrote bzip2 stream (in=" << in << ", out=" << out << ")" << endl;
193         if (bzError != BZ_OK) {
194             setErrorString(tr("bzip2 stream write close error"));
195         }
196         fclose(m_file);
197         m_qfile.close();
198         m_bzFile = nullptr;
199         m_file = nullptr;
200         m_ok = false;
201         return;
202     }
203 
204     if (openMode() & ReadOnly) {
205         BZ2_bzReadClose(&bzError, m_bzFile);
206         if (bzError != BZ_OK) {
207             setErrorString(tr("bzip2 stream read close error"));
208         }
209         fclose(m_file);
210         m_qfile.close();
211         m_bzFile = nullptr;
212         m_file = nullptr;
213         m_ok = false;
214         return;
215     }
216 
217     setErrorString(tr("Internal error (close for neither read nor write)"));
218     return;
219 }
220 
221 qint64
readData(char * data,qint64 maxSize)222 BZipFileDevice::readData(char *data, qint64 maxSize)
223 {
224     if (m_atEnd) return 0;
225 
226     int bzError = BZ_OK;
227     int read = BZ2_bzRead(&bzError, m_bzFile, data, int(maxSize));
228 
229 //    SVDEBUG << "BZipFileDevice::readData: requested " << maxSize << ", read " << read << endl;
230 
231     if (bzError != BZ_OK) {
232         if (bzError != BZ_STREAM_END) {
233             cerr << "BZipFileDevice::readData: error condition" << endl;
234             setErrorString(tr("bzip2 stream read error"));
235             m_ok = false;
236             return -1;
237         } else {
238 //            SVDEBUG << "BZipFileDevice::readData: reached end of file" << endl;
239             m_atEnd = true;
240         }
241     }
242 
243     return read;
244 }
245 
246 qint64
writeData(const char * data,qint64 maxSize)247 BZipFileDevice::writeData(const char *data, qint64 maxSize)
248 {
249     int bzError = BZ_OK;
250     BZ2_bzWrite(&bzError, m_bzFile, (void *)data, int(maxSize));
251 
252 //    SVDEBUG << "BZipFileDevice::writeData: " << maxSize << " to write" << endl;
253 
254     if (bzError != BZ_OK) {
255         cerr << "BZipFileDevice::writeData: error condition" << endl;
256         setErrorString("bzip2 stream write error");
257         m_ok = false;
258         return -1;
259     }
260 
261 //    SVDEBUG << "BZipFileDevice::writeData: wrote " << maxSize << endl;
262 
263     return maxSize;
264 }
265 
266