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