1 /* borrowed from https://github.com/skhaz/qt-physfs-wrapper
2  * TODO: add copyright header, determine license
3  */
4 
5 #include "FileEngine.h"
6 #include "hwpacksmounter.h"
7 
8 
9 const QString FileEngineHandler::scheme = "physfs:/";
10 
FileEngine(const QString & filename)11 FileEngine::FileEngine(const QString& filename)
12     : m_handle(NULL)
13     , m_size(0)
14     , m_flags(0)
15     , m_bufferSet(false)
16     , m_readWrite(false)
17 {
18     setFileName(filename);
19 }
20 
~FileEngine()21 FileEngine::~FileEngine()
22 {
23     close();
24 }
25 
open(QIODevice::OpenMode openMode)26 bool FileEngine::open(QIODevice::OpenMode openMode)
27 {
28     close();
29 
30     if ((openMode & QIODevice::ReadWrite) == QIODevice::ReadWrite) {
31         m_handle = PHYSFS_openAppend(m_fileName.toUtf8().constData());
32         if(m_handle)
33         {
34             m_readWrite = true;
35             seek(0);
36         }
37     }
38 
39     else if (openMode & QIODevice::WriteOnly) {
40         m_handle = PHYSFS_openWrite(m_fileName.toUtf8().constData());
41         m_flags = QAbstractFileEngine::WriteOwnerPerm | QAbstractFileEngine::WriteUserPerm | QAbstractFileEngine::FileType;
42     }
43 
44     else if (openMode & QIODevice::ReadOnly) {
45         m_handle = PHYSFS_openRead(m_fileName.toUtf8().constData());
46     }
47 
48     else if (openMode & QIODevice::Append) {
49         m_handle = PHYSFS_openAppend(m_fileName.toUtf8().constData());
50     }
51 
52     else {
53         qWarning("[PHYSFS] Bad file open mode: %d", (int)openMode);
54     }
55 
56     if (!m_handle) {
57         qWarning("%s", QString("[PHYSFS] Failed to open %1, reason: %2").arg(m_fileName).arg(FileEngineHandler::errorStr()).toLocal8Bit().constData());
58         return false;
59     }
60 
61     return true;
62 }
63 
close()64 bool FileEngine::close()
65 {
66     if (isOpened()) {
67         int result = PHYSFS_close(m_handle);
68         m_handle = NULL;
69         return result != 0;
70     }
71 
72     return true;
73 }
74 
flush()75 bool FileEngine::flush()
76 {
77     return PHYSFS_flush(m_handle) != 0;
78 }
79 
size() const80 qint64 FileEngine::size() const
81 {
82     return m_size;
83 }
84 
pos() const85 qint64 FileEngine::pos() const
86 {
87     return PHYSFS_tell(m_handle);
88 }
89 
setSize(qint64 size)90 bool FileEngine::setSize(qint64 size)
91 {
92     if(size == 0)
93     {
94         m_size = 0;
95         return open(QIODevice::WriteOnly);
96     }
97     else
98         return false;
99 }
100 
seek(qint64 pos)101 bool FileEngine::seek(qint64 pos)
102 {
103     bool ok = PHYSFS_seek(m_handle, pos) != 0;
104 
105     return ok;
106 }
107 
isSequential() const108 bool FileEngine::isSequential() const
109 {
110     return false;
111 }
112 
remove()113 bool FileEngine::remove()
114 {
115     return PHYSFS_delete(m_fileName.toUtf8().constData()) != 0;
116 }
117 
mkdir(const QString & dirName,bool createParentDirectories) const118 bool FileEngine::mkdir(const QString &dirName, bool createParentDirectories) const
119 {
120     Q_UNUSED(createParentDirectories);
121 
122     return PHYSFS_mkdir(dirName.toUtf8().constData()) != 0;
123 }
124 
rmdir(const QString & dirName,bool recurseParentDirectories) const125 bool FileEngine::rmdir(const QString &dirName, bool recurseParentDirectories) const
126 {
127     Q_UNUSED(recurseParentDirectories);
128 
129     return PHYSFS_delete(dirName.toUtf8().constData()) != 0;
130 }
131 
caseSensitive() const132 bool FileEngine::caseSensitive() const
133 {
134     return true;
135 }
136 
isRelativePath() const137 bool FileEngine::isRelativePath() const
138 {
139     return false;
140 }
141 
beginEntryList(QDir::Filters filters,const QStringList & filterNames)142 QAbstractFileEngineIterator * FileEngine::beginEntryList(QDir::Filters filters, const QStringList &filterNames)
143 {
144     return new FileEngineIterator(filters, filterNames, entryList(filters, filterNames));
145 }
146 
entryList(QDir::Filters filters,const QStringList & filterNames) const147 QStringList FileEngine::entryList(QDir::Filters filters, const QStringList &filterNames) const
148 {
149     Q_UNUSED(filters);
150 
151     QString file;
152     QStringList result;
153     char **files = PHYSFS_enumerateFiles(m_fileName.toUtf8().constData());
154 
155     for (char **i = files; *i != NULL; i++) {
156         file = QString::fromUtf8(*i);
157 
158         if (filterNames.isEmpty() || QDir::match(filterNames, file)) {
159             result << file;
160         }
161     }
162 
163     PHYSFS_freeList(files);
164 
165     return result;
166 }
167 
fileFlags(FileFlags type) const168 QAbstractFileEngine::FileFlags FileEngine::fileFlags(FileFlags type) const
169 {
170     return type & m_flags;
171 }
172 
fileName(FileName file) const173 QString FileEngine::fileName(FileName file) const
174 {
175     switch(file)
176     {
177         case QAbstractFileEngine::AbsolutePathName:
178         {
179             QString s(PHYSFS_getWriteDir());
180             return s;
181         }
182         case QAbstractFileEngine::BaseName:
183         {
184             int l = m_fileName.lastIndexOf('/');
185             QString s = m_fileName.mid(l + 1);
186             return s;
187         }
188         case QAbstractFileEngine::DefaultName:
189         case QAbstractFileEngine::AbsoluteName:
190         default:
191         {
192             QString s = "physfs:/" + m_fileName;
193             return s;
194         }
195     }
196 }
197 
fileTime(FileTime time) const198 QDateTime FileEngine::fileTime(FileTime time) const
199 {
200     switch (time)
201     {
202         case QAbstractFileEngine::ModificationTime:
203         default:
204             return m_date;
205             break;
206     };
207 }
208 
setFileName(const QString & file)209 void FileEngine::setFileName(const QString &file)
210 {
211     if(file.startsWith(FileEngineHandler::scheme))
212         m_fileName = file.mid(FileEngineHandler::scheme.size());
213     else
214         m_fileName = file;
215     PHYSFS_Stat stat;
216     if (PHYSFS_stat(m_fileName.toUtf8().constData(), &stat) != 0) {
217         m_size = stat.filesize;
218         m_date = QDateTime::fromTime_t(stat.modtime);
219 //        m_flags |= QAbstractFileEngine::WriteOwnerPerm;
220         m_flags |= QAbstractFileEngine::ReadOwnerPerm;
221         m_flags |= QAbstractFileEngine::ReadUserPerm;
222         m_flags |= QAbstractFileEngine::ExistsFlag;
223         m_flags |= QAbstractFileEngine::LocalDiskFlag;
224 
225         switch (stat.filetype)
226         {
227             case PHYSFS_FILETYPE_REGULAR:
228                 m_flags |= QAbstractFileEngine::FileType;
229                 break;
230             case PHYSFS_FILETYPE_DIRECTORY:
231                 m_flags |= QAbstractFileEngine::DirectoryType;
232                 break;
233             case PHYSFS_FILETYPE_SYMLINK:
234                 m_flags |= QAbstractFileEngine::LinkType;
235                 break;
236             default: ;
237         }
238     }
239 }
240 
atEnd() const241 bool FileEngine::atEnd() const
242 {
243     return PHYSFS_eof(m_handle) != 0;
244 }
245 
read(char * data,qint64 maxlen)246 qint64 FileEngine::read(char *data, qint64 maxlen)
247 {
248     if(m_readWrite)
249     {
250         if(pos() == 0)
251             open(QIODevice::ReadOnly);
252         else
253             return -1;
254     }
255 
256     qint64 len = PHYSFS_readBytes(m_handle, data, maxlen);
257     return len;
258 }
259 
readLine(char * data,qint64 maxlen)260 qint64 FileEngine::readLine(char *data, qint64 maxlen)
261 {
262     if(!m_bufferSet)
263     {
264         PHYSFS_setBuffer(m_handle, 4096);
265         m_bufferSet = true;
266     }
267 
268     qint64 bytesRead = 0;
269     while(PHYSFS_readBytes(m_handle, data, 1)
270           && maxlen
271           && (*data == '\n'))
272     {
273         ++data;
274         --maxlen;
275         ++bytesRead;
276     }
277 
278     return bytesRead;
279 }
280 
write(const char * data,qint64 len)281 qint64 FileEngine::write(const char *data, qint64 len)
282 {
283     return PHYSFS_writeBytes(m_handle, data, len);
284 }
285 
isOpened() const286 bool FileEngine::isOpened() const
287 {
288     return m_handle != NULL;
289 }
290 
error() const291 QFile::FileError FileEngine::error() const
292 {
293     return QFile::UnspecifiedError;
294 }
295 
errorString() const296 QString FileEngine::errorString() const
297 {
298 #if PHYSFS_VER_MAJOR >= 3
299     return PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode());
300 #else
301     return PHYSFS_getLastError();
302 #endif
303 }
304 
supportsExtension(Extension extension) const305 bool FileEngine::supportsExtension(Extension extension) const
306 {
307     return
308             (extension == QAbstractFileEngine::AtEndExtension)
309             || (extension == QAbstractFileEngine::FastReadLineExtension)
310             ;
311 }
312 
313 
FileEngineHandler(char * argv0)314 FileEngineHandler::FileEngineHandler(char *argv0)
315 {
316     if (!PHYSFS_init(argv0))
317     {
318         qCritical("PHYSFS initialization failed");
319     }
320     qDebug("%s", QString("[PHYSFS] Init: %1").arg(errorStr()).toLocal8Bit().constData());
321 }
322 
~FileEngineHandler()323 FileEngineHandler::~FileEngineHandler()
324 {
325     PHYSFS_deinit();
326 }
327 
create(const QString & filename) const328 QAbstractFileEngine* FileEngineHandler::create(const QString &filename) const
329 {
330     if (filename.startsWith(scheme))
331         return new FileEngine(filename);
332     else
333         return NULL;
334 }
335 
mount(const QString & path)336 void FileEngineHandler::mount(const QString &path)
337 {
338     PHYSFS_mount(path.toUtf8().constData(), NULL, 0);
339     qDebug("%s", QString("[PHYSFS] Mounting '%1' to '/': %2").arg(path).arg(errorStr()).toLocal8Bit().constData());
340 }
341 
mount(const QString & path,const QString & mountPoint)342 void FileEngineHandler::mount(const QString & path, const QString & mountPoint)
343 {
344     PHYSFS_mount(path.toUtf8().constData(), mountPoint.toUtf8().constData(), 0);
345     qDebug("%s", QString("[PHYSFS] Mounting '%1' to '%2': %3").arg(path).arg(mountPoint).arg(errorStr()).toLocal8Bit().data());
346 }
347 
setWriteDir(const QString & path)348 void FileEngineHandler::setWriteDir(const QString &path)
349 {
350     PHYSFS_setWriteDir(path.toUtf8().constData());
351     qDebug("%s", QString("[PHYSFS] Setting write dir to '%1': %2").arg(path).arg(errorStr()).toLocal8Bit().data());
352 }
353 
mountPacks()354 void FileEngineHandler::mountPacks()
355 {
356     hedgewarsMountPackages();
357 }
358 
errorStr()359 QString FileEngineHandler::errorStr()
360 {
361     QString s;
362 #if PHYSFS_VER_MAJOR >= 3
363     s = QString::fromUtf8(PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
364 #else
365     s = QString::fromUtf8(PHYSFS_getLastError());
366 #endif
367     return s.isEmpty() ? "ok" : s;
368 }
369 
370 
FileEngineIterator(QDir::Filters filters,const QStringList & nameFilters,const QStringList & entries)371 FileEngineIterator::FileEngineIterator(QDir::Filters filters, const QStringList &nameFilters, const QStringList &entries)
372     : QAbstractFileEngineIterator(filters, nameFilters)
373 {
374     m_entries = entries;
375 
376     /* heck.. docs are unclear on this
377      * QDirIterator puts iterator before first entry
378      * but QAbstractFileEngineIterator example puts iterator on first entry
379      * though QDirIterator approach seems to be the right one
380      */
381 
382     m_index = -1;
383 }
384 
hasNext() const385 bool FileEngineIterator::hasNext() const
386 {
387     return m_index < m_entries.size() - 1;
388 }
389 
next()390 QString FileEngineIterator::next()
391 {
392    if (!hasNext())
393        return QString();
394 
395    ++m_index;
396    return currentFilePath();
397 }
398 
currentFileName() const399 QString FileEngineIterator::currentFileName() const
400 {
401     return m_entries.at(m_index);
402 }
403