MythTV master
mythiowrapper.cpp
Go to the documentation of this file.
1// Qt
2#include <QFile>
3#include <QMap>
4#include <QUrl>
5#include <QReadWriteLock>
6
7// MythTV
9#include "libmythbase/mythconfig.h"
13
14#include "io/mythmediabuffer.h"
15#include "mythiowrapper.h"
16
17// Std
18#ifdef _WIN32
19#include <windows.h>
20#else
21#include <dlfcn.h>
22#endif
23#include <cstdio>
24#include <dirent.h>
25#include <fcntl.h>
26#include <sys/stat.h>
27#include <sys/types.h>
28#include <unistd.h>
29
31{
32 public:
33 MythIOCallback(void* Object, callback_t Callback)
34 : m_object(Object),
35 m_callback(Callback)
36 {
37 }
38 bool operator==(MythIOCallback rhs) const
39 { return (m_object == rhs.m_object) && (m_callback == rhs.m_callback); };
40
41 void *m_object;
43};
44
45static const int s_maxID = 1024 * 1024;
46
47static QReadWriteLock s_fileWrapperLock;
48static QHash<int, MythMediaBuffer*> s_buffers;
49static QHash<int, RemoteFile*> s_remotefiles;
50static QHash<int, int> s_localfiles;
51static QHash<int, QString> s_filenames;
52
53static QReadWriteLock s_dirWrapperLock;
54static QHash<int, QStringList> s_remotedirs;
55static QHash<int, int> s_remotedirPositions;
56static QHash<int, QString> s_dirnames;
57static QHash<int, DIR*> s_localdirs;
58
59static QMutex s_callbackLock;
60static QMultiHash<QString, MythIOCallback> s_fileOpenCallbacks;
61
62#define LOC QString("MythIOWrap: ")
63
64extern "C" {
65
66static int GetNextFileID(void)
67{
68 int id = 100000;
69
70 for (; id < s_maxID; ++id)
71 if (!s_localfiles.contains(id) && !s_remotefiles.contains(id) && !s_buffers.contains(id))
72 break;
73
74 if (id == s_maxID)
75 LOG(VB_GENERAL, LOG_ERR, LOC + "Too many files are open.");
76
77 LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetNextFileID: '%1'").arg(id));
78 return id;
79}
80
81void MythFileOpenRegisterCallback(const char *Pathname, void* Object, callback_t Func)
82{
83 QMutexLocker locker(&s_callbackLock);
84 QString path(Pathname);
85 if (s_fileOpenCallbacks.contains(path))
86 {
87 // if we already have a callback registered for this path with this
88 // object then remove the callback and return (i.e. end callback)
89 for (auto it = s_fileOpenCallbacks.begin();
90 it != s_fileOpenCallbacks.end();
91 it++)
92 {
93 if (Object == it.value().m_object)
94 {
95 s_fileOpenCallbacks.remove(path, { Object, it.value().m_callback });
96 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Removing fileopen callback for %1").arg(path));
97 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("%1 callbacks remaining").arg(s_fileOpenCallbacks.size()));
98 return;
99 }
100 }
101 }
102
103 s_fileOpenCallbacks.insert(path, { Object, Func });
104 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Added fileopen callback for %1").arg(path));
105 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("%1 callbacks open").arg(s_fileOpenCallbacks.size()));
106}
107
108int MythFileCheck(int Id)
109{
110 LOG(VB_FILE, LOG_DEBUG, QString("MythFileCheck: '%1')").arg(Id));
111 QWriteLocker locker(&s_fileWrapperLock);
112 return (s_localfiles.contains(Id) || s_remotefiles.contains(Id) || s_buffers.contains(Id)) ? 1 : 0;
113}
114
115int MythFileOpen(const char *Pathname, int Flags)
116{
117 LOG(VB_FILE, LOG_DEBUG, QString("MythFileOpen('%1', %2)").arg(Pathname).arg(Flags));
118
119 struct stat fileinfo {};
120 if (MythFileStat(Pathname, &fileinfo))
121 return -1;
122
123 // libmythdvdnav tries to open() a dir
124 if (S_ISDIR(fileinfo.st_mode))
125 {
126 errno = EISDIR;
127 return -1;
128 }
129
130 int fileID = -1;
131 if (strncmp(Pathname, "myth://", 7) != 0)
132 {
133 int lfd = open(Pathname, Flags);
134 if (lfd < 0)
135 return -1;
136
137 s_fileWrapperLock.lockForWrite();
138 fileID = GetNextFileID();
139 s_localfiles[fileID] = lfd;
140 s_filenames[fileID] = Pathname;
141 s_fileWrapperLock.unlock();
142 }
143 else
144 {
145 MythMediaBuffer *buffer = nullptr;
146 RemoteFile *rf = nullptr;
147
148 if ((fileinfo.st_size < 512) && (fileinfo.st_mtime < (time(nullptr) - 300)))
149 {
150 if (Flags & O_WRONLY)
151 rf = new RemoteFile(Pathname, true, false); // Writeable
152 else
153 rf = new RemoteFile(Pathname, false, true); // Read-Only
154 if (!rf)
155 return -1;
156 }
157 else
158 {
159 if (Flags & O_WRONLY)
160 {
161 buffer = MythMediaBuffer::Create(Pathname, true, false,
162 MythMediaBuffer::kDefaultOpenTimeout, true); // Writeable
163 }
164 else
165 {
166 buffer = MythMediaBuffer::Create(Pathname, false, true,
167 MythMediaBuffer::kDefaultOpenTimeout, true); // Read-Only
168 }
169
170 if (!buffer)
171 return -1;
172
173 buffer->Start();
174 }
175
176 s_fileWrapperLock.lockForWrite();
177 fileID = GetNextFileID();
178
179 if (rf)
180 s_remotefiles[fileID] = rf;
181 else if (buffer)
182 s_buffers[fileID] = buffer;
183
184 s_filenames[fileID] = Pathname;
185 s_fileWrapperLock.unlock();
186 }
187
188 s_callbackLock.lock();
189 QString path(Pathname);
190 for (auto it = s_fileOpenCallbacks.begin();
191 it != s_fileOpenCallbacks.end();
192 it++)
193 {
194 if (path.startsWith(it.key())) {
195 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Calling callback %1 for %2")
196 .arg(it.key()).arg((qulonglong)it.value().m_callback));
197 it.value().m_callback(it.value().m_object);
198 }
199 }
200 s_callbackLock.unlock();
201
202 return fileID;
203}
204
205int MythfileClose(int FileID)
206{
207 LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythfileClose: '%1").arg(FileID));
208
209 // FIXME - surely this needs to hold write lock?
210 QReadLocker locker(&s_fileWrapperLock);
211 if (s_buffers.contains(FileID))
212 {
213 MythMediaBuffer *buffer = s_buffers[FileID];
214 s_buffers.remove(FileID);
215 delete buffer;
216 return 0;
217 }
218
219 if (s_remotefiles.contains(FileID))
220 {
221 RemoteFile *rf = s_remotefiles[FileID];
222 s_remotefiles.remove(FileID);
223 delete rf;
224 return 0;
225 }
226
227 if (s_localfiles.contains(FileID))
228 {
229 close(s_localfiles[FileID]);
230 s_localfiles.remove(FileID);
231 return 0;
232 }
233
234 return -1;
235}
236
237off_t MythFileSeek(int FileID, off_t Offset, int Whence)
238{
239 off_t result = -1;
240
241 LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythFileSeek(%1, %2, %3)")
242 .arg(FileID).arg(Offset).arg(Whence));
243
244 s_fileWrapperLock.lockForRead();
245 if (s_buffers.contains(FileID))
246 result = s_buffers[FileID]->Seek(Offset, Whence);
247 else if (s_remotefiles.contains(FileID))
248 result = s_remotefiles[FileID]->Seek(Offset, Whence);
249 else if (s_localfiles.contains(FileID))
250 result = lseek(s_localfiles[FileID], Offset, Whence);
251 s_fileWrapperLock.unlock();
252
253 return result;
254}
255
256off_t MythFileTell(int FileID)
257{
258 off_t result = -1;
259
260 LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythFileTell(%1)").arg(FileID));
261
262 s_fileWrapperLock.lockForRead();
263 if (s_buffers.contains(FileID))
264 result = s_buffers[FileID]->Seek(0, SEEK_CUR);
265 else if (s_remotefiles.contains(FileID))
266 result = s_remotefiles[FileID]->Seek(0, SEEK_CUR);
267 else if (s_localfiles.contains(FileID))
268 result = lseek(s_localfiles[FileID], 0, SEEK_CUR);
269 s_fileWrapperLock.unlock();
270
271 return result;
272}
273
274ssize_t MythFileRead(int FileID, void *Buffer, size_t Count)
275{
276 ssize_t result = -1;
277
278 LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythFileRead(%1, %2, %3)")
279 .arg(FileID).arg(reinterpret_cast<long long>(Buffer)).arg(Count));
280
281 s_fileWrapperLock.lockForRead();
282 if (s_buffers.contains(FileID))
283 result = s_buffers[FileID]->Read(Buffer, static_cast<int>(Count));
284 else if (s_remotefiles.contains(FileID))
285 result = s_remotefiles[FileID]->Read(Buffer, static_cast<int>(Count));
286 else if (s_localfiles.contains(FileID))
287 result = read(s_localfiles[FileID], Buffer, Count);
288 s_fileWrapperLock.unlock();
289
290 return result;
291}
292
293ssize_t MythFileWrite(int FileID, void *Buffer, size_t Count)
294{
295 ssize_t result = -1;
296
297 LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythFileWrite(%1, %2, %3)")
298 .arg(FileID).arg(reinterpret_cast<long long>(Buffer)).arg(Count));
299
300 s_fileWrapperLock.lockForRead();
301 if (s_buffers.contains(FileID))
302 result = s_buffers[FileID]->Write(Buffer, static_cast<uint>(Count));
303 else if (s_remotefiles.contains(FileID))
304 result = s_remotefiles[FileID]->Write(Buffer, static_cast<int>(Count));
305 else if (s_localfiles.contains(FileID))
306 result = write(s_localfiles[FileID], Buffer, Count);
307 s_fileWrapperLock.unlock();
308
309 return result;
310}
311
312int MythFileStat(const char *Path, struct stat *Buf)
313{
314 LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythfileStat('%1', %2)")
315 .arg(Path).arg(reinterpret_cast<long long>(Buf)));
316
317 if (strncmp(Path, "myth://", 7) == 0)
318 {
319 bool res = RemoteFile::Exists(Path, Buf);
320 if (res)
321 return 0;
322 }
323
324 return stat(Path, Buf);
325}
326
327int MythFileStatFD(int FileID, struct stat *Buf)
328{
329 LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythFileStatFD(%1, %2)")
330 .arg(FileID).arg(reinterpret_cast<long long>(Buf)));
331
332 s_fileWrapperLock.lockForRead();
333 if (!s_filenames.contains(FileID))
334 {
335 s_fileWrapperLock.unlock();
336 return -1;
337 }
338 QString filename = s_filenames[FileID];
339 s_fileWrapperLock.unlock();
340
341 return MythFileStat(filename.toLocal8Bit().constData(), Buf);
342}
343
344/*
345 * This function exists for the use of dvd_reader.c, thus the return
346 * value of int instead of bool. C doesn't have a bool type.
347 */
348int MythFileExists(const char *Path, const char *File)
349{
350 LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythFileExists('%1', '%2')")
351 .arg(Path, File));
352
353 bool ret = false;
354 if (strncmp(Path, "myth://", 7) == 0)
355 ret = RemoteFile::Exists(QString("%1/%2").arg(Path, File));
356 else
357 ret = QFile::exists(QString("%1/%2").arg(Path, File));
358 return static_cast<int>(ret);
359}
360
361static int GetNextDirID(void)
362{
363 int id = 100000;
364 for (; id < s_maxID; ++id)
365 if (!s_localdirs.contains(id) && !s_remotedirs.contains(id))
366 break;
367
368 if (id == s_maxID)
369 LOG(VB_GENERAL, LOG_ERR, LOC + "Too many directories are open.");
370 LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetNextDirID: '%1'").arg(id));
371
372 return id;
373}
374
375int MythDirCheck(int DirID)
376{
377 LOG(VB_FILE, LOG_DEBUG, QString("MythDirCheck: '%1'").arg(DirID));
378 s_dirWrapperLock.lockForWrite();
379 int result = ((s_localdirs.contains(DirID) || s_remotedirs.contains(DirID))) ? 1 : 0;
380 s_dirWrapperLock.unlock();
381 return result;
382}
383
384int MythDirOpen(const char *DirName)
385{
386 LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythDirOpen: '%1'").arg(DirName));
387
388 if (strncmp(DirName, "myth://", 7) != 0)
389 {
390 DIR *dir = opendir(DirName);
391 if (dir)
392 {
393 s_dirWrapperLock.lockForWrite();
394 int id = GetNextDirID();
395 s_localdirs[id] = dir;
396 s_dirnames[id] = DirName;
397 s_dirWrapperLock.unlock();
398 return id;
399 }
400 }
401 else
402 {
403 QStringList list;
404 QUrl qurl(DirName);
405 QString storageGroup = qurl.userName();
406
407 list.clear();
408 if (storageGroup.isEmpty())
409 storageGroup = "Default";
410
411 list << "QUERY_SG_GETFILELIST" << qurl.host() << storageGroup;
412
413 QString path = qurl.path();
414 if (!qurl.fragment().isEmpty())
415 path += "#" + qurl.fragment();
416
417 list << path << "1";
418
419 bool ok = gCoreContext->SendReceiveStringList(list);
420
421 if ((!ok) || ((list.size() == 1) && (list[0] == "EMPTY LIST")))
422 list.clear();
423
424 s_dirWrapperLock.lockForWrite();
425 int id = GetNextDirID();
426 s_remotedirs[id] = list;
427 s_remotedirPositions[id] = 0;
428 s_dirnames[id] = DirName;
429 s_dirWrapperLock.unlock();
430 return id;
431 }
432
433 return 0;
434}
435
436int MythDirClose(int DirID)
437{
438 LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythDirClose: '%1'").arg(DirID));
439
440 QReadLocker locker(&s_dirWrapperLock);
441 if (s_remotedirs.contains(DirID))
442 {
443 s_remotedirs.remove(DirID);
444 s_remotedirPositions.remove(DirID);
445 return 0;
446 }
447
448 if (s_localdirs.contains(DirID))
449 {
450 int result = closedir(s_localdirs[DirID]);
451 if (result == 0)
452 s_localdirs.remove(DirID);
453 return result;
454 }
455
456 return -1;
457}
458
459char *MythDirRead(int DirID)
460{
461 LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythDirRead: '%1'").arg(DirID));
462
463 QReadLocker locker(&s_dirWrapperLock);
464 if (s_remotedirs.contains(DirID))
465 {
466 int pos = s_remotedirPositions[DirID];
467 if (s_remotedirs[DirID].size() >= (pos + 1))
468 {
469 char* result = strdup(s_remotedirs[DirID][pos].toLocal8Bit().constData());
470 pos++;
471 s_remotedirPositions[DirID] = pos;
472 return result;
473 }
474 }
475 else if (s_localdirs.contains(DirID))
476 {
477 struct dirent *dir = readdir(s_localdirs[DirID]);
478 if (dir != nullptr)
479 return strdup(dir->d_name);
480 }
481
482 return nullptr;
483}
484
485} // extern "C"
486
bool SendReceiveStringList(QStringList &strlist, bool quickTimeout=false, bool block=true)
Send a message to the backend and wait for a response.
bool operator==(MythIOCallback rhs) const
callback_t m_callback
MythIOCallback(void *Object, callback_t Callback)
void Start(void)
Starts the read-ahead thread.
static MythMediaBuffer * Create(const QString &Filename, bool Write, bool UseReadAhead=true, std::chrono::milliseconds Timeout=kDefaultOpenTimeout, bool StreamOnly=false)
Creates a RingBuffer instance.
static constexpr std::chrono::milliseconds kDefaultOpenTimeout
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:461
unsigned int uint
Definition: compat.h:68
#define close
Definition: compat.h:30
IFSPoint * Buf
Definition: ifs.cpp:105
static void(* m_callback)(void *, QString &)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static QReadWriteLock s_fileWrapperLock
static QReadWriteLock s_dirWrapperLock
#define LOC
char * MythDirRead(int DirID)
static int GetNextDirID(void)
static QHash< int, QString > s_filenames
static QMutex s_callbackLock
static QHash< int, int > s_remotedirPositions
int MythDirClose(int DirID)
ssize_t MythFileRead(int FileID, void *Buffer, size_t Count)
static QHash< int, DIR * > s_localdirs
int MythFileExists(const char *Path, const char *File)
static int GetNextFileID(void)
int MythDirOpen(const char *DirName)
static QMultiHash< QString, MythIOCallback > s_fileOpenCallbacks
off_t MythFileSeek(int FileID, off_t Offset, int Whence)
int MythfileClose(int FileID)
static QHash< int, RemoteFile * > s_remotefiles
void MythFileOpenRegisterCallback(const char *Pathname, void *Object, callback_t Func)
int MythDirCheck(int DirID)
int MythFileOpen(const char *Pathname, int Flags)
int MythFileCheck(int Id)
int MythFileStat(const char *Path, struct stat *Buf)
int MythFileStatFD(int FileID, struct stat *Buf)
static const int s_maxID
static QHash< int, QString > s_dirnames
ssize_t MythFileWrite(int FileID, void *Buffer, size_t Count)
static QHash< int, MythMediaBuffer * > s_buffers
static QHash< int, QStringList > s_remotedirs
off_t MythFileTell(int FileID)
static QHash< int, int > s_localfiles
void(* callback_t)(void *)
Definition: mythiowrapper.h:20
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
def read(device=None, features=[])
Definition: disc.py:35
def write(text, progress=True)
Definition: mythburn.py:307
bool exists(str path)
Definition: xbmcvfs.py:51