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 "libmythbase/compat.h"
9 #include "libmythbase/mythconfig.h"
12 #include "libmythbase/remotefile.h"
13 
14 #include "io/mythmediabuffer.h"
15 #include "mythiowrapper.h"
16 
17 // Std
18 #if defined(_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 
45 static const int s_maxID = 1024 * 1024;
46 
47 static QReadWriteLock s_fileWrapperLock;
48 static QHash<int, MythMediaBuffer*> s_buffers;
49 static QHash<int, RemoteFile*> s_remotefiles;
50 static QHash<int, int> s_localfiles;
51 static QHash<int, QString> s_filenames;
52 
53 static QReadWriteLock s_dirWrapperLock;
54 static QHash<int, QStringList> s_remotedirs;
55 static QHash<int, int> s_remotedirPositions;
56 static QHash<int, QString> s_dirnames;
57 static QHash<int, DIR*> s_localdirs;
58 
59 static QMutex s_callbackLock;
60 static QMultiHash<QString, MythIOCallback> s_fileOpenCallbacks;
61 
62 #define LOC QString("MythIOWrap: ")
63 
64 extern "C" {
65 
66 static 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 
81 void 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 
108 int 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 
115 int 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 
205 int 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 
237 #ifdef _WIN32
238 # undef lseek
239 # define lseek _lseeki64
240 # undef off_t
241 # define off_t off64_t
242 #endif
243 off_t MythFileSeek(int FileID, off_t Offset, int Whence)
244 {
245  off_t result = -1;
246 
247  LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythFileSeek(%1, %2, %3)")
248  .arg(FileID).arg(Offset).arg(Whence));
249 
250  s_fileWrapperLock.lockForRead();
251  if (s_buffers.contains(FileID))
252  result = s_buffers[FileID]->Seek(Offset, Whence);
253  else if (s_remotefiles.contains(FileID))
254  result = s_remotefiles[FileID]->Seek(Offset, Whence);
255  else if (s_localfiles.contains(FileID))
256  result = lseek(s_localfiles[FileID], Offset, Whence);
257  s_fileWrapperLock.unlock();
258 
259  return result;
260 }
261 
262 off_t MythFileTell(int FileID)
263 {
264  off_t result = -1;
265 
266  LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythFileTell(%1)").arg(FileID));
267 
268  s_fileWrapperLock.lockForRead();
269  if (s_buffers.contains(FileID))
270  result = s_buffers[FileID]->Seek(0, SEEK_CUR);
271  else if (s_remotefiles.contains(FileID))
272  result = s_remotefiles[FileID]->Seek(0, SEEK_CUR);
273  else if (s_localfiles.contains(FileID))
274  result = lseek(s_localfiles[FileID], 0, SEEK_CUR);
275  s_fileWrapperLock.unlock();
276 
277  return result;
278 }
279 
280 #ifdef _WIN32
281 # undef lseek
282 # undef off_t
283 #endif
284 
285 ssize_t MythFileRead(int FileID, void *Buffer, size_t Count)
286 {
287  ssize_t result = -1;
288 
289  LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythFileRead(%1, %2, %3)")
290  .arg(FileID).arg(reinterpret_cast<long long>(Buffer)).arg(Count));
291 
292  s_fileWrapperLock.lockForRead();
293  if (s_buffers.contains(FileID))
294  result = s_buffers[FileID]->Read(Buffer, static_cast<int>(Count));
295  else if (s_remotefiles.contains(FileID))
296  result = s_remotefiles[FileID]->Read(Buffer, static_cast<int>(Count));
297  else if (s_localfiles.contains(FileID))
298  result = read(s_localfiles[FileID], Buffer, Count);
299  s_fileWrapperLock.unlock();
300 
301  return result;
302 }
303 
304 ssize_t MythFileWrite(int FileID, void *Buffer, size_t Count)
305 {
306  ssize_t result = -1;
307 
308  LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythFileWrite(%1, %2, %3)")
309  .arg(FileID).arg(reinterpret_cast<long long>(Buffer)).arg(Count));
310 
311  s_fileWrapperLock.lockForRead();
312  if (s_buffers.contains(FileID))
313  result = s_buffers[FileID]->Write(Buffer, static_cast<uint>(Count));
314  else if (s_remotefiles.contains(FileID))
315  result = s_remotefiles[FileID]->Write(Buffer, static_cast<int>(Count));
316  else if (s_localfiles.contains(FileID))
317  result = write(s_localfiles[FileID], Buffer, Count);
318  s_fileWrapperLock.unlock();
319 
320  return result;
321 }
322 
323 int MythFileStat(const char *Path, struct stat *Buf)
324 {
325  LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythfileStat('%1', %2)")
326  .arg(Path).arg(reinterpret_cast<long long>(Buf)));
327 
328  if (strncmp(Path, "myth://", 7) == 0)
329  {
330  bool res = RemoteFile::Exists(Path, Buf);
331  if (res)
332  return 0;
333  }
334 
335  return stat(Path, Buf);
336 }
337 
338 int MythFileStatFD(int FileID, struct stat *Buf)
339 {
340  LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythFileStatFD(%1, %2)")
341  .arg(FileID).arg(reinterpret_cast<long long>(Buf)));
342 
343  s_fileWrapperLock.lockForRead();
344  if (!s_filenames.contains(FileID))
345  {
346  s_fileWrapperLock.unlock();
347  return -1;
348  }
349  QString filename = s_filenames[FileID];
350  s_fileWrapperLock.unlock();
351 
352  return MythFileStat(filename.toLocal8Bit().constData(), Buf);
353 }
354 
355 /*
356  * This function exists for the use of dvd_reader.c, thus the return
357  * value of int instead of bool. C doesn't have a bool type.
358  */
359 int MythFileExists(const char *Path, const char *File)
360 {
361  LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythFileExists('%1', '%2')")
362  .arg(Path, File));
363 
364  bool ret = false;
365  if (strncmp(Path, "myth://", 7) == 0)
366  ret = RemoteFile::Exists(QString("%1/%2").arg(Path, File));
367  else
368  ret = QFile::exists(QString("%1/%2").arg(Path, File));
369  return static_cast<int>(ret);
370 }
371 
372 static int GetNextDirID(void)
373 {
374  int id = 100000;
375  for (; id < s_maxID; ++id)
376  if (!s_localdirs.contains(id) && !s_remotedirs.contains(id))
377  break;
378 
379  if (id == s_maxID)
380  LOG(VB_GENERAL, LOG_ERR, LOC + "Too many directories are open.");
381  LOG(VB_FILE, LOG_DEBUG, LOC + QString("GetNextDirID: '%1'").arg(id));
382 
383  return id;
384 }
385 
386 int MythDirCheck(int DirID)
387 {
388  LOG(VB_FILE, LOG_DEBUG, QString("MythDirCheck: '%1'").arg(DirID));
389  s_dirWrapperLock.lockForWrite();
390  int result = ((s_localdirs.contains(DirID) || s_remotedirs.contains(DirID))) ? 1 : 0;
391  s_dirWrapperLock.unlock();
392  return result;
393 }
394 
395 int MythDirOpen(const char *DirName)
396 {
397  LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythDirOpen: '%1'").arg(DirName));
398 
399  if (strncmp(DirName, "myth://", 7) != 0)
400  {
401  DIR *dir = opendir(DirName);
402  if (dir)
403  {
404  s_dirWrapperLock.lockForWrite();
405  int id = GetNextDirID();
406  s_localdirs[id] = dir;
407  s_dirnames[id] = DirName;
408  s_dirWrapperLock.unlock();
409  return id;
410  }
411  }
412  else
413  {
414  QStringList list;
415  QUrl qurl(DirName);
416  QString storageGroup = qurl.userName();
417 
418  list.clear();
419  if (storageGroup.isEmpty())
420  storageGroup = "Default";
421 
422  list << "QUERY_SG_GETFILELIST" << qurl.host() << storageGroup;
423 
424  QString path = qurl.path();
425  if (!qurl.fragment().isEmpty())
426  path += "#" + qurl.fragment();
427 
428  list << path << "1";
429 
430  bool ok = gCoreContext->SendReceiveStringList(list);
431 
432  if ((!ok) || ((list.size() == 1) && (list[0] == "EMPTY LIST")))
433  list.clear();
434 
435  s_dirWrapperLock.lockForWrite();
436  int id = GetNextDirID();
437  s_remotedirs[id] = list;
438  s_remotedirPositions[id] = 0;
439  s_dirnames[id] = DirName;
440  s_dirWrapperLock.unlock();
441  return id;
442  }
443 
444  return 0;
445 }
446 
447 int MythDirClose(int DirID)
448 {
449  LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythDirClose: '%1'").arg(DirID));
450 
451  QReadLocker locker(&s_dirWrapperLock);
452  if (s_remotedirs.contains(DirID))
453  {
454  s_remotedirs.remove(DirID);
455  s_remotedirPositions.remove(DirID);
456  return 0;
457  }
458 
459  if (s_localdirs.contains(DirID))
460  {
461  int result = closedir(s_localdirs[DirID]);
462  if (result == 0)
463  s_localdirs.remove(DirID);
464  return result;
465  }
466 
467  return -1;
468 }
469 
470 char *MythDirRead(int DirID)
471 {
472  LOG(VB_FILE, LOG_DEBUG, LOC + QString("MythDirRead: '%1'").arg(DirID));
473 
474  QReadLocker locker(&s_dirWrapperLock);
475  if (s_remotedirs.contains(DirID))
476  {
477  int pos = s_remotedirPositions[DirID];
478  if (s_remotedirs[DirID].size() >= (pos + 1))
479  {
480  char* result = strdup(s_remotedirs[DirID][pos].toLocal8Bit().constData());
481  pos++;
482  s_remotedirPositions[DirID] = pos;
483  return result;
484  }
485  }
486  else if (s_localdirs.contains(DirID))
487  {
488  struct dirent *dir = nullptr;
489  // glibc deprecated readdir_r in version 2.24,
490  if ((dir = readdir(s_localdirs[DirID])) != nullptr)
491  return strdup(dir->d_name);
492  }
493 
494  return nullptr;
495 }
496 
497 } // extern "C"
498 
MythFileStatFD
int MythFileStatFD(int FileID, struct stat *Buf)
Definition: mythiowrapper.cpp:338
s_localdirs
static QHash< int, DIR * > s_localdirs
Definition: mythiowrapper.cpp:57
MythDirClose
int MythDirClose(int DirID)
Definition: mythiowrapper.cpp:447
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:1369
callback_t
void(* callback_t)(void *)
Definition: mythiowrapper.h:20
RemoteFile::Exists
static bool Exists(const QString &url, struct stat *fileinfo)
Definition: remotefile.cpp:454
discid.disc.read
def read(device=None, features=[])
Definition: disc.py:35
MythIOCallback
Definition: mythiowrapper.cpp:30
MythDirRead
char * MythDirRead(int DirID)
Definition: mythiowrapper.cpp:470
s_dirnames
static QHash< int, QString > s_dirnames
Definition: mythiowrapper.cpp:56
mythburn.write
def write(text, progress=True)
Definition: mythburn.py:308
MythfileClose
int MythfileClose(int FileID)
Definition: mythiowrapper.cpp:205
RemoteFile
Definition: remotefile.h:17
s_maxID
static const int s_maxID
Definition: mythiowrapper.cpp:45
MythMediaBuffer
Definition: mythmediabuffer.h:50
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythFileCheck
int MythFileCheck(int Id)
Definition: mythiowrapper.cpp:108
MythIOCallback::m_object
void * m_object
Definition: mythiowrapper.cpp:39
MythFileOpenRegisterCallback
void MythFileOpenRegisterCallback(const char *Pathname, void *Object, callback_t Func)
Definition: mythiowrapper.cpp:81
MythFileRead
ssize_t MythFileRead(int FileID, void *Buffer, size_t Count)
Definition: mythiowrapper.cpp:285
m_callback
static void(* m_callback)(void *, QString &)
Definition: mytharchive.cpp:244
s_fileOpenCallbacks
static QMultiHash< QString, MythIOCallback > s_fileOpenCallbacks
Definition: mythiowrapper.cpp:60
close
#define close
Definition: compat.h:43
s_remotedirs
static QHash< int, QStringList > s_remotedirs
Definition: mythiowrapper.cpp:54
lseek
#define lseek
Definition: mythiowrapper.cpp:239
mythlogging.h
s_buffers
static QHash< int, MythMediaBuffer * > s_buffers
Definition: mythiowrapper.cpp:48
remotefile.h
s_localfiles
static QHash< int, int > s_localfiles
Definition: mythiowrapper.cpp:50
LOC
#define LOC
Definition: mythiowrapper.cpp:62
compat.h
MythFileTell
off_t MythFileTell(int FileID)
Definition: mythiowrapper.cpp:262
s_dirWrapperLock
static QReadWriteLock s_dirWrapperLock
Definition: mythiowrapper.cpp:53
MythFileStat
int MythFileStat(const char *Path, struct stat *Buf)
Definition: mythiowrapper.cpp:323
s_remotedirPositions
static QHash< int, int > s_remotedirPositions
Definition: mythiowrapper.cpp:55
GetNextFileID
static int GetNextFileID(void)
Definition: mythiowrapper.cpp:66
uint
unsigned int uint
Definition: compat.h:79
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:54
MythIOCallback::operator==
bool operator==(MythIOCallback rhs) const
Definition: mythiowrapper.cpp:38
s_filenames
static QHash< int, QString > s_filenames
Definition: mythiowrapper.cpp:51
GetNextDirID
static int GetNextDirID(void)
Definition: mythiowrapper.cpp:372
mythmediabuffer.h
off_t
#define off_t
Definition: mythiowrapper.cpp:241
Buffer
Definition: MythExternControl.h:36
mythcorecontext.h
s_callbackLock
static QMutex s_callbackLock
Definition: mythiowrapper.cpp:59
MythMediaBuffer::Start
void Start(void)
Starts the read-ahead thread.
Definition: mythmediabuffer.cpp:611
MythFileOpen
int MythFileOpen(const char *Pathname, int Flags)
Definition: mythiowrapper.cpp:115
MythIOCallback::MythIOCallback
MythIOCallback(void *Object, callback_t Callback)
Definition: mythiowrapper.cpp:33
MythDirOpen
int MythDirOpen(const char *DirName)
Definition: mythiowrapper.cpp:395
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:98
Buf
IFSPoint * Buf
Definition: ifs.cpp:105
s_remotefiles
static QHash< int, RemoteFile * > s_remotefiles
Definition: mythiowrapper.cpp:49
MythIOCallback::m_callback
callback_t m_callback
Definition: mythiowrapper.cpp:42
s_fileWrapperLock
static QReadWriteLock s_fileWrapperLock
Definition: mythiowrapper.cpp:47
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:386
MythFileWrite
ssize_t MythFileWrite(int FileID, void *Buffer, size_t Count)
Definition: mythiowrapper.cpp:304
MythFileSeek
off_t MythFileSeek(int FileID, off_t Offset, int Whence)
Definition: mythiowrapper.cpp:243
mythiowrapper.h
MythFileExists
int MythFileExists(const char *Path, const char *File)
Definition: mythiowrapper.cpp:359