You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdebase/kioslave/media/mediamanager/linuxcdpolling.cpp

586 lines
14 KiB

/* This file is part of the KDE Project
Copyright (c) 2003 Gav Wood <gav kde org>
Copyright (c) 2004 Kévin Ottens <ervin ipsquad net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
/* Some code of this file comes from kdeautorun */
#include "linuxcdpolling.h"
#include <tqthread.h>
#include <tqmutex.h>
#include <tqfile.h>
#include <kdebug.h>
#include "fstabbackend.h"
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
// Never ever include directly a kernel header!
// #include <linux/cdrom.h>
// Instead we redefine the necessary (copied from the header)
/* This struct is used by the CDROMREADTOCHDR ioctl */
struct cdrom_tochdr
{
unsigned char cdth_trk0; /* start track */
unsigned char cdth_trk1; /* end track */
};
#define CDROMREADTOCHDR 0x5305 /* Read TOC header
(struct cdrom_tochdr) */
#define CDROM_DRIVE_STATUS 0x5326 /* Get tray position, etc. */
#define CDROM_DISC_STATUS 0x5327 /* Get disc type, etc. */
/* drive status possibilities returned by CDROM_DRIVE_STATUS ioctl */
#define CDS_NO_INFO 0 /* if not implemented */
#define CDS_NO_DISC 1
#define CDS_TRAY_OPEN 2
#define CDS_DRIVE_NOT_READY 3
#define CDS_DISC_OK 4
/* return values for the CDROM_DISC_STATUS ioctl */
/* can also return CDS_NO_[INFO|DISC], from above */
#define CDS_AUDIO 100
#define CDS_DATA_1 101
#define CDS_DATA_2 102
#define CDS_XA_2_1 103
#define CDS_XA_2_2 104
#define CDS_MIXED 105
#define CDSL_CURRENT ((int) (~0U>>1))
// -------
DiscType::DiscType(Type type)
: m_type(type)
{
}
bool DiscType::isKnownDisc() const
{
return m_type != None
&& m_type != Unknown
&& m_type != UnknownType
&& m_type != Broken;
}
bool DiscType::isDisc() const
{
return m_type != None
&& m_type != Unknown
&& m_type != Broken;
}
bool DiscType::isNotDisc() const
{
return m_type == None;
}
bool DiscType::isData() const
{
return m_type == Data;
}
DiscType::operator int() const
{
return (int)m_type;
}
class PollingThread : public QThread
{
public:
PollingThread(const TQCString &devNode) : m_dev(devNode)
{
kdDebug(1219) << "PollingThread::PollingThread("
<< devNode << ")" << endl;
m_stop = false;
m_currentType = DiscType::None;
m_lastPollType = DiscType::None;
}
void stop()
{
TQMutexLocker locker(&m_mutex);
m_stop = true;
}
bool hasChanged()
{
TQMutexLocker locker(&m_mutex);
return m_currentType!=m_lastPollType;
}
DiscType type()
{
TQMutexLocker locker(&m_mutex);
m_currentType = m_lastPollType;
return m_currentType;
}
protected:
virtual void run()
{
kdDebug(1219) << "PollingThread(" << m_dev << ") start" << endl;
while (!m_stop && m_lastPollType!=DiscType::Broken)
{
m_mutex.lock();
DiscType type = m_lastPollType;
m_mutex.unlock();
type = LinuxCDPolling::identifyDiscType(m_dev, type);
m_mutex.lock();
m_lastPollType = type;
m_mutex.unlock();
msleep(500);
}
kdDebug(1219) << "PollingThread(" << m_dev << ") stop" << endl;
}
private:
TQMutex m_mutex;
bool m_stop;
const TQCString m_dev;
DiscType m_currentType;
DiscType m_lastPollType;
};
LinuxCDPolling::LinuxCDPolling(MediaList &list)
: TQObject(), BackendBase(list)
{
connect(&m_mediaList, TQT_SIGNAL(mediumAdded(const TQString &,
const TQString &, bool)),
this, TQT_SLOT(slotMediumAdded(const TQString &)) );
connect(&m_mediaList, TQT_SIGNAL(mediumRemoved(const TQString &,
const TQString &, bool)),
this, TQT_SLOT(slotMediumRemoved(const TQString &)) );
connect(&m_mediaList, TQT_SIGNAL(mediumStateChanged(const TQString &,
const TQString &, bool, bool)),
this, TQT_SLOT(slotMediumStateChanged(const TQString &)) );
connect(&m_timer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotTimeout()));
}
LinuxCDPolling::~LinuxCDPolling()
{
TQMap<TQString, PollingThread*>::iterator it = m_threads.begin();
TQMap<TQString, PollingThread*>::iterator end = m_threads.end();
for(; it!=end; ++it)
{
PollingThread *thread = it.data();
thread->stop();
thread->wait();
delete thread;
}
}
void LinuxCDPolling::slotMediumAdded(const TQString &id)
{
kdDebug(1219) << "LinuxCDPolling::slotMediumAdded(" << id << ")" << endl;
if (m_threads.contains(id)) return;
const Medium *medium = m_mediaList.findById(id);
TQString mime = medium->mimeType();
kdDebug(1219) << "mime == " << mime << endl;
if (mime.find("dvd")==-1 && mime.find("cd")==-1) return;
if (!medium->isMounted())
{
m_excludeNotification.append( id );
TQCString dev = TQFile::encodeName( medium->deviceNode() ).data();
PollingThread *thread = new PollingThread(dev);
m_threads[id] = thread;
thread->start();
m_timer.start(500);
}
}
void LinuxCDPolling::slotMediumRemoved(const TQString &id)
{
kdDebug(1219) << "LinuxCDPolling::slotMediumRemoved(" << id << ")" << endl;
if (!m_threads.contains(id)) return;
PollingThread *thread = m_threads[id];
m_threads.remove(id);
thread->stop();
thread->wait();
delete thread;
m_excludeNotification.remove(id);
}
void LinuxCDPolling::slotMediumStateChanged(const TQString &id)
{
kdDebug(1219) << "LinuxCDPolling::slotMediumStateChanged("
<< id << ")" << endl;
const Medium *medium = m_mediaList.findById(id);
TQString mime = medium->mimeType();
kdDebug(1219) << "mime == " << mime << endl;
if (mime.find("dvd")==-1 && mime.find("cd")==-1) return;
if (!m_threads.contains(id) && !medium->isMounted())
{
// It is just a mount state change, no need to notify
m_excludeNotification.append( id );
TQCString dev = TQFile::encodeName( medium->deviceNode() ).data();
PollingThread *thread = new PollingThread(dev);
m_threads[id] = thread;
thread->start();
m_timer.start(500);
}
else if (m_threads.contains(id) && medium->isMounted())
{
PollingThread *thread = m_threads[id];
m_threads.remove(id);
thread->stop();
thread->wait();
delete thread;
}
}
void LinuxCDPolling::slotTimeout()
{
//kdDebug(1219) << "LinuxCDPolling::slotTimeout()" << endl;
if (m_threads.isEmpty())
{
m_timer.stop();
return;
}
TQMap<TQString, PollingThread*>::iterator it = m_threads.begin();
TQMap<TQString, PollingThread*>::iterator end = m_threads.end();
for(; it!=end; ++it)
{
TQString id = it.key();
PollingThread *thread = it.data();
if (thread->hasChanged())
{
DiscType type = thread->type();
const Medium *medium = m_mediaList.findById(id);
applyType(type, medium);
}
}
}
static TQString baseType(const Medium *medium)
{
kdDebug(1219) << "baseType(" << medium->id() << ")" << endl;
TQString devNode = medium->deviceNode();
TQString mountPoint = medium->mountPoint();
TQString fsType = medium->fsType();
bool mounted = medium->isMounted();
TQString mimeType, iconName, label;
FstabBackend::guess(devNode, mountPoint, fsType, mounted,
mimeType, iconName, label);
if (devNode.find("dvd")!=-1)
{
kdDebug(1219) << "=> dvd" << endl;
return "dvd";
}
else
{
kdDebug(1219) << "=> cd" << endl;
return "cd";
}
}
static void restoreEmptyState(MediaList &list, const Medium *medium,
bool allowNotification)
{
kdDebug(1219) << "restoreEmptyState(" << medium->id() << ")" << endl;
TQString id = medium->id();
TQString devNode = medium->deviceNode();
TQString mountPoint = medium->mountPoint();
TQString fsType = medium->fsType();
bool mounted = medium->isMounted();
TQString mimeType, iconName, label;
FstabBackend::guess(devNode, mountPoint, fsType, mounted,
mimeType, iconName, label);
list.changeMediumState(id, devNode, mountPoint, fsType, mounted,
allowNotification, mimeType, iconName, label);
}
void LinuxCDPolling::applyType(DiscType type, const Medium *medium)
{
kdDebug(1219) << "LinuxCDPolling::applyType(" << type << ", "
<< medium->id() << ")" << endl;
TQString id = medium->id();
TQString dev = medium->deviceNode();
bool notify = !m_excludeNotification.contains(id);
m_excludeNotification.remove(id);
switch (type)
{
case DiscType::Data:
restoreEmptyState(m_mediaList, medium, notify);
break;
case DiscType::Audio:
case DiscType::Mixed:
m_mediaList.changeMediumState(id, "audiocd:/?device="+dev,
notify, "media/audiocd");
break;
case DiscType::VCD:
m_mediaList.changeMediumState(id, false, notify, "media/vcd");
break;
case DiscType::SVCD:
m_mediaList.changeMediumState(id, false, notify, "media/svcd");
break;
case DiscType::DVD:
m_mediaList.changeMediumState(id, false, notify, "media/dvdvideo");
break;
case DiscType::Blank:
if (baseType(medium)=="dvd")
{
m_mediaList.changeMediumState(id, false,
notify, "media/blankdvd");
}
else
{
m_mediaList.changeMediumState(id, false,
notify, "media/blankcd");
}
break;
case DiscType::None:
case DiscType::Unknown:
case DiscType::UnknownType:
restoreEmptyState(m_mediaList, medium, false);
break;
}
}
DiscType LinuxCDPolling::identifyDiscType(const TQCString &devNode,
const DiscType &current)
{
//kdDebug(1219) << "LinuxCDPolling::identifyDiscType("
// << devNode << ")" << endl;
int fd;
struct cdrom_tochdr th;
// open the device
fd = open(devNode, O_RDONLY | O_NONBLOCK);
if (fd < 0) return DiscType::Broken;
switch (ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT))
{
case CDS_DISC_OK:
{
if (current.isDisc())
{
close(fd);
return current;
}
// see if we can read the disc's table of contents (TOC).
if (ioctl(fd, CDROMREADTOCHDR, &th))
{
close(fd);
return DiscType::Blank;
}
// read disc status info
int status = ioctl(fd, CDROM_DISC_STATUS, CDSL_CURRENT);
// release the device
close(fd);
switch (status)
{
case CDS_AUDIO:
return DiscType::Audio;
case CDS_DATA_1:
case CDS_DATA_2:
if (hasDirectory(devNode, "video_ts"))
{
return DiscType::DVD;
}
else if (hasDirectory(devNode, "vcd"))
{
return DiscType::VCD;
}
else if (hasDirectory(devNode, "svcd"))
{
return DiscType::SVCD;
}
else
{
return DiscType::Data;
}
case CDS_MIXED:
return DiscType::Mixed;
default:
return DiscType::UnknownType;
}
}
case CDS_NO_INFO:
close(fd);
return DiscType::Unknown;
default:
close(fd);
return DiscType::None;
}
}
bool LinuxCDPolling::hasDirectory(const TQCString &devNode, const TQCString &dir)
{
bool ret = false; // return value
int fd = 0; // file descriptor for drive
unsigned short bs; // the discs block size
unsigned short ts; // the path table size
unsigned int tl; // the path table location (in blocks)
unsigned char len_di = 0; // length of the directory name in current path table entry
unsigned int parent = 0; // the number of the parent directory's path table entry
char dirname[256]; // filename for the current path table entry
int pos = 0; // our position into the path table
int curr_record = 1; // the path table record we're on
TQCString fixed_directory = dir.upper(); // the uppercase version of the "directory" parameter
// open the drive
fd = open(devNode, O_RDONLY | O_NONBLOCK);
if (fd == -1) return false;
// read the block size
lseek(fd, 0x8080, SEEK_CUR);
if (read(fd, &bs, 2) != 2)
{
close(fd);
return false;
}
if (Q_BYTE_ORDER != Q_LITTLE_ENDIAN)
bs = ((bs << 8) & 0xFF00) | ((bs >> 8) & 0xFF);
// read in size of path table
lseek(fd, 2, SEEK_CUR);
if (read(fd, &ts, 2) != 2)
{
close(fd);
return false;
}
if (Q_BYTE_ORDER != Q_LITTLE_ENDIAN)
ts = ((ts << 8) & 0xFF00) | ((ts >> 8) & 0xFF);
// read in which block path table is in
lseek(fd, 6, SEEK_CUR);
if (read(fd, &tl, 4) != 4)
{
close(fd);
return false;
}
if (Q_BYTE_ORDER != Q_LITTLE_ENDIAN)
tl = ((tl << 24) & 0xFF000000) | ((tl << 8) & 0xFF0000) |
((tl >> 8) & 0xFF00) | ((tl >> 24) & 0xFF);
// seek to the path table
lseek(fd, bs * tl, SEEK_SET);
// loop through the path table entries
while (pos < ts)
{
// get the length of the filename of the current entry
if (read(fd, &len_di, 1) != 1)
{
ret = false;
break;
}
// get the record number of this entry's parent
// i'm pretty sure that the 1st entry is always the top directory
lseek(fd, 5, SEEK_CUR);
if (read(fd, &parent, 2) != 2)
{
ret = false;
break;
}
if (Q_BYTE_ORDER != Q_LITTLE_ENDIAN)
parent = ((parent << 8) & 0xFF00) | ((parent >> 8) & 0xFF);
// read the name
if (read(fd, dirname, len_di) != len_di)
{
ret = false;
break;
}
dirname[len_di] = 0;
qstrcpy(dirname, TQCString(dirname).upper());
// if we found a folder that has the root as a parent, and the directory name matches
// then return success
if ((parent == 1) && (dirname == fixed_directory))
{
ret = true;
break;
}
// all path table entries are padded to be even, so if this is an odd-length table, seek a byte to fix it
if (len_di%2 == 1)
{
lseek(fd, 1, SEEK_CUR);
pos++;
}
// update our position
pos += 8 + len_di;
curr_record++;
}
close(fd);
return ret;
}
#include "linuxcdpolling.moc"