MythTV  0.27pre
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
mythmedia.cpp
Go to the documentation of this file.
1 // C header
2 #include <fcntl.h>
3 #include <unistd.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <sys/param.h>
7 
8 // Qt Headers
9 #include <QDir>
10 #include <QFileInfo>
11 #include <QFileInfoList>
12 #include <QTextStream>
13 
14 // MythTV headers
15 #include "mythmedia.h"
16 #include "mythconfig.h"
17 #include "mythlogging.h"
18 #include "mythmiscutil.h"
19 #include "mythsystemlegacy.h"
20 #include "exitcodes.h"
21 
22 using namespace std;
23 
24 #ifdef USING_MINGW
25 # define O_NONBLOCK 0
26 #endif
27 
28 #define LOC QString("MythMediaDevice:")
29 
30 static const QString PATHTO_PMOUNT("/usr/bin/pmount");
31 static const QString PATHTO_PUMOUNT("/usr/bin/pumount");
32 #if CONFIG_DARWIN
33  static const QString PATHTO_MOUNT("/sbin/mount");
34 #else
35  static const QString PATHTO_MOUNT("/bin/mount");
36 #endif
37 static const QString PATHTO_UNMOUNT("/bin/umount");
38 static const QString PATHTO_MOUNTS("/proc/mounts");
39 
40 #if CONFIG_DARWIN
41 # define USE_MOUNT_COMMAND
42 #endif
43 
45 {
46  "MEDIASTAT_ERROR",
47  "MEDIASTAT_UNKNOWN",
48  "MEDIASTAT_UNPLUGGED",
49  "MEDIASTAT_OPEN",
50  "MEDIASTAT_NODISK",
51  "MEDIASTAT_UNFORMATTED",
52  "MEDIASTAT_USEABLE",
53  "MEDIASTAT_NOTMOUNTED",
54  "MEDIASTAT_MOUNTED"
55 };
56 
58 {
59  "MEDIAERR_OK",
60  "MEDIAERR_FAILED",
61  "MEDIAERR_UNSUPPORTED"
62 };
63 
64 QEvent::Type MythMediaEvent::kEventType =
65  (QEvent::Type) QEvent::registerEventType();
66 
67 MythMediaDevice::MythMediaDevice(QObject* par, const char* DevicePath,
68  bool SuperMount, bool AllowEject)
69  : QObject(par)
70 {
71  m_DevicePath = DevicePath;
72  m_AllowEject = AllowEject;
73  m_Locked = false;
74  m_DeviceHandle = -1;
75  m_SuperMount = SuperMount;
79 }
80 
82 {
83  // Sanity check
84  if (isDeviceOpen())
85  return true;
86 
87  QByteArray dev = m_DevicePath.toLocal8Bit();
88  m_DeviceHandle = open(dev.constData(), O_RDONLY | O_NONBLOCK);
89 
90  return isDeviceOpen();
91 }
92 
94 {
95  // Sanity check
96  if (!isDeviceOpen())
97  return true;
98 
99  int ret = close(m_DeviceHandle);
100  m_DeviceHandle = -1;
101 
102  return (ret != -1) ? true : false;
103 }
104 
106 {
107  return (m_DeviceHandle >= 0) ? true : false;
108 }
109 
111 {
112  if (DoMount && isMounted())
113  {
114 #ifdef Q_OS_MAC
115  // Not an error - DiskArbitration has already mounted the device.
116  // AddDevice calls mount() so onDeviceMounted() can get mediaType.
117  onDeviceMounted();
118 #else
119  LOG(VB_MEDIA, LOG_ERR, "MythMediaDevice::performMountCmd(true)"
120  " - Logic Error? Device already mounted.");
121  return true;
122 #endif
123  }
124 
125  if (isDeviceOpen())
126  closeDevice();
127 
128  if (!m_SuperMount)
129  {
130  QString MountCommand;
131 
132  // Build a command line for mount/unmount and execute it...
133  // Is there a better way to do this?
134  if (QFile(PATHTO_PMOUNT).exists() && QFile(PATHTO_PUMOUNT).exists())
135  MountCommand = QString("%1 %2")
136  .arg((DoMount) ? PATHTO_PMOUNT : PATHTO_PUMOUNT)
137  .arg(m_DevicePath);
138  else
139  MountCommand = QString("%1 %2")
140  .arg((DoMount) ? PATHTO_MOUNT : PATHTO_UNMOUNT)
141  .arg(m_DevicePath);
142 
143  LOG(VB_MEDIA, LOG_INFO, QString("Executing '%1'").arg(MountCommand));
144  if (myth_system(MountCommand, kMSDontBlockInputDevs) == GENERIC_EXIT_OK)
145  {
146  if (DoMount)
147  {
148  // we cannot tell beforehand what the pmount mount point is
149  // so verify the mount status of the device
150  if (!findMountPath())
151  {
152  LOG(VB_MEDIA, LOG_ERR, "performMountCmd() attempted to"
153  " find mounted media, but failed?");
154  return false;
155  }
157  onDeviceMounted();
158  LOG(VB_GENERAL, LOG_INFO,
159  QString("Detected MediaType ") + MediaTypeString());
160  }
161  else
163 
164  return true;
165  }
166  else
167  LOG(VB_GENERAL, LOG_ERR, QString("Failed to mount %1.")
168  .arg(m_DevicePath));
169  }
170  else
171  {
172  LOG(VB_MEDIA, LOG_INFO, "Disk inserted on a supermount device");
173  // If it's a super mount then the OS will handle mounting / unmounting.
174  // We just need to give derived classes a chance to perform their
175  // mount / unmount logic.
176  if (DoMount)
177  {
178  onDeviceMounted();
179  LOG(VB_GENERAL, LOG_INFO,
180  QString("Detected MediaType ") + MediaTypeString());
181  }
182  else
184 
185  return true;
186  }
187  return false;
188 }
189 
194 {
196  ext_cnt_t ext_cnt;
197 
198  if (!ScanMediaType(m_MountPath, ext_cnt))
199  {
200  LOG(VB_MEDIA, LOG_NOTICE,
201  QString("No files with extensions found in '%1'")
202  .arg(m_MountPath));
203  return mediatype;
204  }
205 
206  QMap<uint, uint> media_cnts, media_cnt;
207 
208  // convert raw counts to composite mediatype counts
209  ext_cnt_t::const_iterator it = ext_cnt.begin();
210  for (; it != ext_cnt.end(); ++it)
211  {
212  ext_to_media_t::const_iterator found = m_ext_to_media.find(it.key());
213  if (found != m_ext_to_media.end())
214  media_cnts[*found] += *it;
215  }
216 
217  // break composite mediatypes into constituent components
218  QMap<uint, uint>::const_iterator cit = media_cnts.begin();
219  for (; cit != media_cnts.end(); ++cit)
220  {
221  for (uint key = 0, j = 0; key != MEDIATYPE_END; j++)
222  {
223  if ((key = 1 << j) & cit.key())
224  media_cnt[key] += *cit;
225  }
226  }
227 
228  // decide on mediatype based on which one has a handler for > # of files
229  uint max_cnt = 0;
230  for (cit = media_cnt.begin(); cit != media_cnt.end(); ++cit)
231  {
232  if (*cit > max_cnt)
233  {
234  mediatype = (MythMediaType) cit.key();
235  max_cnt = *cit;
236  }
237  }
238 
239  return mediatype;
240 }
241 
246 bool MythMediaDevice::ScanMediaType(const QString &directory, ext_cnt_t &cnt)
247 {
248  QDir d(directory);
249  if (!d.exists())
250  return false;
251 
252  d.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
253  QFileInfoList list = d.entryInfoList();
254 
255  for( QFileInfoList::iterator it = list.begin();
256  it != list.end();
257  ++it )
258  {
259  QFileInfo &fi = *it;
260 
261  if (fi.isSymLink())
262  continue;
263 
264  if (fi.isDir())
265  {
266  ScanMediaType(fi.absoluteFilePath(), cnt);
267  continue;
268  }
269 
270  const QString ext = fi.suffix();
271  if (!ext.isEmpty())
272  cnt[ext.toLower()]++;
273  }
274 
275  return !cnt.empty();
276 }
277 
285  const QString &extensions)
286 {
287  const QStringList list = extensions.split(",");
288  for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it)
289  m_ext_to_media[*it] |= mediatype;
290 }
291 
293 {
294  (void) open_close;
295 
296 #if CONFIG_DARWIN
297  // Backgrounding this is a bit naughty, but it can take up to five
298  // seconds to execute, and freezing the frontend for that long is bad
299 
300  QString command = "disktool -e " + m_DevicePath + " &";
301 
302  if (myth_system(command, kMSRunBackground) != GENERIC_EXIT_OK)
303  return MEDIAERR_FAILED;
304 
305  return MEDIAERR_OK;
306 #endif
307 
308  return MEDIAERR_UNSUPPORTED;
309 }
310 
311 bool MythMediaDevice::isSameDevice(const QString &path)
312 {
313 #ifdef Q_OS_MAC
314  // The caller may be using a raw device instead of the BSD 'leaf' name
315  if (path == "/dev/r" + m_DevicePath)
316  return true;
317 #endif
318 
319  return (path == m_DevicePath);
320 }
321 
323 {
324  setDeviceSpeed(m_DevicePath.toLocal8Bit().constData(), speed);
325 }
326 
328 {
329  // We just open the device here, which may or may not do the trick,
330  // derived classes can do more...
331  if (openDevice())
332  {
333  m_Locked = true;
334  return MEDIAERR_OK;
335  }
336  m_Locked = false;
337  return MEDIAERR_FAILED;
338 }
339 
341 {
342  m_Locked = false;
343 
344  return MEDIAERR_OK;
345 }
346 
348 bool MythMediaDevice::isMounted(bool Verify)
349 {
350  if (Verify)
351  return findMountPath();
352  else
353  return (m_Status == MEDIASTAT_MOUNTED);
354 }
355 
358 {
359  if (m_DevicePath.isEmpty())
360  {
361  LOG(VB_MEDIA, LOG_ERR, "findMountPath() - logic error, no device path");
362  return false;
363  }
364 
365 #ifdef USE_MOUNT_COMMAND
366  // HACK. TODO: replace with something using popen()?
367  if (myth_system(PATHTO_MOUNT + " > /tmp/mounts") != GENERIC_EXIT_OK)
368  return false;
369  QFile mountFile("/tmp/mounts");
370 #else
371  QFile mountFile(PATHTO_MOUNTS);
372 #endif
373 
374  // Try to open the mounts file so we can search it for our device.
375  if (!mountFile.open(QIODevice::ReadOnly))
376  return false;
377 
378  QString debug;
379  QTextStream stream(&mountFile);
380 
381  for (;;)
382  {
383  QString mountPoint;
384  QString deviceName;
385 
386 
387 #ifdef USE_MOUNT_COMMAND
388  // Extract mount point and device name from something like:
389  // /dev/disk0s3 on / (hfs, local, journaled) - Mac OS X
390  // /dev/hdd on /tmp/AAA BBB type udf (ro) - Linux
391  stream >> deviceName;
392  mountPoint = stream.readLine();
393  mountPoint.remove(" on ");
394  mountPoint.remove(QRegExp(" type \\w.*")); // Linux
395  mountPoint.remove(QRegExp(" \\(\\w.*")); // Mac OS X
396 #else
397  // Extract the mount point and device name.
398  stream >> deviceName >> mountPoint;
399  stream.readLine(); // skip the rest of the line
400 #endif
401 
402  if (deviceName.isNull())
403  break;
404 
405  if (deviceName.isEmpty())
406  continue;
407 
408  if (!deviceName.startsWith("/dev/"))
409  continue;
410 
411  QStringList deviceNames;
412  getSymlinkTarget(deviceName, &deviceNames);
413 
414 #if CONFIG_DARWIN
415  // match short-style BSD node names:
416  if (m_DevicePath.startsWith("disk"))
417  deviceNames << deviceName.mid(5); // remove 5 chars - /dev/
418 #endif
419 
420  // Deal with escaped spaces
421  if (mountPoint.contains("\\040"))
422  mountPoint.replace("\\040", " ");
423 
424 
425  if (deviceNames.contains(m_DevicePath) ||
426  deviceNames.contains(m_RealDevice) )
427  {
428  m_MountPath = mountPoint;
429  mountFile.close();
430  return true;
431  }
432 
433  if (VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_DEBUG))
434  debug += QString(" %1 | %2\n")
435  .arg(deviceName, 16).arg(mountPoint);
436  }
437 
438  mountFile.close();
439 
440  if (VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_DEBUG))
441  {
442  debug = LOC + ":findMountPath() - mount of '"
443  + m_DevicePath + "' not found.\n"
444  + " Device name/type | Current mountpoint\n"
445  + " -----------------+-------------------\n"
446  + debug
447  + " =================+===================";
448  LOG(VB_MEDIA, LOG_DEBUG, debug);
449  }
450 
451  return false;
452 }
453 
455  bool CloseIt )
456 {
457  MythMediaStatus OldStatus = m_Status;
458 
459  m_Status = NewStatus;
460 
461  // If the status is changed we need to take some actions
462  // depending on the old and new status.
463  if (NewStatus != OldStatus)
464  {
465  switch (NewStatus)
466  {
467  // the disk is not / should not be mounted.
468  case MEDIASTAT_ERROR:
469  case MEDIASTAT_OPEN:
470  case MEDIASTAT_NODISK:
472  if (isMounted())
473  unmount();
474  break;
475  case MEDIASTAT_UNKNOWN:
476  case MEDIASTAT_USEABLE:
477  case MEDIASTAT_MOUNTED:
478  case MEDIASTAT_UNPLUGGED:
480  // get rid of the compiler warning...
481  break;
482  }
483 
484  // Don't fire off transitions to / from unknown states
485  if (m_Status != MEDIASTAT_UNKNOWN && OldStatus != MEDIASTAT_UNKNOWN)
486  emit statusChanged(OldStatus, this);
487  }
488 
489 
490  if (CloseIt)
491  closeDevice();
492 
493  return m_Status;
494 }
495 
497 {
498  m_VolumeID = QString::null;
499  m_KeyID = QString::null;
501 }
502 
504 {
506 }
507 
509 {
510  // MythMediaType is currently a bitmask. This code will only output the
511  // first matched type.
512 
513  if (type == MEDIATYPE_UNKNOWN)
514  return "MEDIATYPE_UNKNOWN";
515  if (type & MEDIATYPE_DATA)
516  return "MEDIATYPE_DATA";
517  if (type & MEDIATYPE_MIXED)
518  return "MEDIATYPE_MIXED";
519  if (type & MEDIATYPE_AUDIO)
520  return "MEDIATYPE_AUDIO";
521  if (type & MEDIATYPE_DVD)
522  return "MEDIATYPE_DVD";
523  if (type & MEDIATYPE_BD)
524  return "MEDIATYPE_BD";
525  if (type & MEDIATYPE_VCD)
526  return "MEDIATYPE_VCD";
527  if (type & MEDIATYPE_MMUSIC)
528  return "MEDIATYPE_MMUSIC";
529  if (type & MEDIATYPE_MVIDEO)
530  return "MEDIATYPE_MVIDEO";
531  if (type & MEDIATYPE_MGALLERY)
532  return "MEDIATYPE_MGALLERY";
533 
534  return "MEDIATYPE_UNKNOWN";
535 }
536