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