/* This file is part of the TDE libraries
   Copyright (C) 2012 Timothy Pearson <kb9vqf@pearsoncomputing.net>
             (C) 2013 Golubev Alexander <fatzer2@gmail.com>

   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.
*/

#include "tdestoragedevice.h"

#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/cdrom.h>

#include <tqregexp.h>
#include <tqpixmap.h>
#include <tqfile.h>

#include "tdelocale.h" 
#include "tdeglobal.h" 
#include "kiconloader.h" 
#include "tdetempfile.h" 
#include "kstandarddirs.h"

#include "tdehardwaredevices.h" 

#include "config.h"

// uDisks2 integration
#if defined(WITH_UDISKS) || defined(WITH_UDISKS2)
 	#include <tqdbusdata.h>
 	#include <tqdbusmessage.h>
 	#include <tqdbusproxy.h>
 	#include <tqdbusvariant.h>
 	#include <tqdbusconnection.h>
 	#include <tqdbuserror.h>
 	#include <tqdbusdatamap.h>
 	#include <tqdbusobjectpath.h>
#endif // defined(WITH_UDISKS) || defined(WITH_UDISKS2)
#if defined(WITH_UDISKS)
 	#include "tqdbusdatalist.h"
#endif // ddefined(WITH_UDISKS)

#if defined(WITH_UDISKS) || defined(WITH_UDISKS2)
	// Defined in tdehardwaredevices.cpp
	TQT_DBusData convertDBUSDataToVariantData(TQT_DBusData);
#endif // defined(WITH_UDISKS) || defined(WITH_UDISKS2)

TDEStorageDevice::TDEStorageDevice(TDEGenericDeviceType::TDEGenericDeviceType dt, TQString dn) : TDEGenericDevice(dt, dn), m_mediaInserted(true) {
	m_diskType = TDEDiskDeviceType::Null;
	m_diskStatus = TDEDiskDeviceStatus::Null;
}

TDEStorageDevice::~TDEStorageDevice() {
}

TDEDiskDeviceType::TDEDiskDeviceType TDEStorageDevice::diskType() {
	return m_diskType;
}

void TDEStorageDevice::internalSetDiskType(TDEDiskDeviceType::TDEDiskDeviceType dt) {
	m_diskType = dt;
}

bool TDEStorageDevice::isDiskOfType(TDEDiskDeviceType::TDEDiskDeviceType tf) {
	return ((m_diskType&tf)!=TDEDiskDeviceType::Null);
}

TDEDiskDeviceStatus::TDEDiskDeviceStatus TDEStorageDevice::diskStatus() {
	return m_diskStatus;
}

void TDEStorageDevice::internalSetDiskStatus(TDEDiskDeviceStatus::TDEDiskDeviceStatus st) {
	m_diskStatus = st;
}

bool TDEStorageDevice::checkDiskStatus(TDEDiskDeviceStatus::TDEDiskDeviceStatus sf) {
	return ((m_diskStatus&sf)!=(TDEDiskDeviceStatus::TDEDiskDeviceStatus)0);
}

bool TDEStorageDevice::lockDriveMedia(bool lock) {
	int fd = open(deviceNode().ascii(), O_RDWR | O_NONBLOCK);
	if (fd < 0) {
		return false;
	}
	if (ioctl(fd, CDROM_LOCKDOOR, (lock)?1:0) != 0) {
		close(fd);
		return false;
	}
	else {
		close(fd);
		return true;
	}
}

bool ejectDriveUDisks(TDEStorageDevice* sdevice) {
#ifdef WITH_UDISKS
	TQT_DBusConnection dbusConn = TQT_DBusConnection::addConnection(TQT_DBusConnection::SystemBus);
	if (dbusConn.isConnected()) {
		TQString blockDeviceString = sdevice->deviceNode();
		blockDeviceString.replace("/dev/", "");
		blockDeviceString.replace("-", "_2d");
		blockDeviceString = "/org/freedesktop/UDisks/devices/" + blockDeviceString;

		// Eject the drive!
		TQT_DBusError error;
		TQT_DBusProxy driveControl("org.freedesktop.UDisks", blockDeviceString, "org.freedesktop.UDisks.Device", dbusConn);
		if (driveControl.canSend()) {
			TQValueList<TQT_DBusData> params;
			TQT_DBusDataList options;
			params << TQT_DBusData::fromList(options);
			TQT_DBusMessage reply = driveControl.sendWithReply("DriveEject", params, &error);
			if (error.isValid()) {
				// Error!
				printf("[ERROR][tdehwlib] ejectDriveUDisks: %s\n", error.name().ascii()); fflush(stdout);
				return FALSE;
			}
			else {
				return TRUE;
			}
		}
	}
#endif // WITH_UDISKS
	return FALSE;
}

bool ejectDriveUDisks2(TDEStorageDevice* sdevice) {
#ifdef WITH_UDISKS2
	TQT_DBusConnection dbusConn = TQT_DBusConnection::addConnection(TQT_DBusConnection::SystemBus);
	if (dbusConn.isConnected()) {
		TQString blockDeviceString = sdevice->deviceNode();
		blockDeviceString.replace("/dev/", "");
		blockDeviceString.replace("-", "_2d");
		blockDeviceString = "/org/freedesktop/UDisks2/block_devices/" + blockDeviceString;
		TQT_DBusProxy hardwareControl("org.freedesktop.UDisks2", blockDeviceString, "org.freedesktop.DBus.Properties", dbusConn);
		if (hardwareControl.canSend()) {
			// get associated udisks2 drive path
			TQT_DBusError error;
			TQValueList<TQT_DBusData> params;
			params << TQT_DBusData::fromString("org.freedesktop.UDisks2.Block") << TQT_DBusData::fromString("Drive");
			TQT_DBusMessage reply = hardwareControl.sendWithReply("Get", params, &error);
			if (error.isValid()) {
				// Error!
				printf("[ERROR][tdehwlib] ejectDriveUDisks2: %s\n", error.name().ascii()); fflush(stdout);
				return FALSE;
			}
			else {
				if (reply.type() == TQT_DBusMessage::ReplyMessage && reply.count() == 1) {
					TQT_DBusObjectPath driveObjectPath = reply[0].toVariant().value.toObjectPath();
					if (!driveObjectPath.isValid()) {
						return FALSE;
					}

					error = TQT_DBusError();
					TQT_DBusProxy driveInformation("org.freedesktop.UDisks2", driveObjectPath, "org.freedesktop.DBus.Properties", dbusConn);
					// can eject?
					TQValueList<TQT_DBusData> params;
					params << TQT_DBusData::fromString("org.freedesktop.UDisks2.Drive") << TQT_DBusData::fromString("Ejectable");
					TQT_DBusMessage reply = driveInformation.sendWithReply("Get", params, &error);
					if (error.isValid()) {
						// Error!
						printf("[ERROR][tdehwlib] ejectDriveUDisks2: %s\n", error.name().ascii()); fflush(stdout);
						return FALSE;
					}
					if (reply.type() == TQT_DBusMessage::ReplyMessage && reply.count() == 1) {
						bool ejectable = reply[0].toVariant().value.toBool();
						if (!ejectable) {
							return FALSE;
						}

						// Eject the drive!
						TQT_DBusProxy driveControl("org.freedesktop.UDisks2", driveObjectPath, "org.freedesktop.UDisks2.Drive", dbusConn);
						TQValueList<TQT_DBusData> params;
						TQT_DBusDataMap<TQString> options(TQT_DBusData::Variant);
						params << TQT_DBusData::fromStringKeyMap(options);
						TQT_DBusMessage reply = driveControl.sendWithReply("Eject", params, &error);
						if (error.isValid()) {
							// Error!
							printf("[ERROR][tdehwlib] ejectDriveUDisks2: %s\n", error.name().ascii()); fflush(stdout);
							return FALSE;
						}
						else {
							return TRUE;
						}
					}
				}
			}
		}
	}
#endif // WITH_UDISKS2
	return FALSE;
}

int mountDriveUDisks(TQString deviceNode, TQString fileSystemType, TQStringList mountOptions, TQString* errStr = NULL) {
#ifdef WITH_UDISKS
	TQT_DBusConnection dbusConn = TQT_DBusConnection::addConnection(TQT_DBusConnection::SystemBus);
	if (dbusConn.isConnected()) {
		TQString blockDeviceString = deviceNode;
		blockDeviceString.replace("/dev/", "");
		blockDeviceString.replace("-", "_2d");
		blockDeviceString = "/org/freedesktop/UDisks/devices/" + blockDeviceString;

		// Mount the drive!
		TQT_DBusError error;
		TQT_DBusProxy driveControl("org.freedesktop.UDisks", blockDeviceString, "org.freedesktop.UDisks.Device", dbusConn);
		if (driveControl.canSend()) {
			TQValueList<TQT_DBusData> params;
			params << TQT_DBusData::fromString(fileSystemType);
			params << TQT_DBusData::fromList(TQT_DBusDataList(mountOptions));
			TQT_DBusMessage reply = driveControl.sendWithReply("FilesystemMount", params, &error);
			if (error.isValid()) {
				// Error!
				if (error.name() == "org.freedesktop.DBus.Error.ServiceUnknown") {
					// Service not installed or unavailable
					return -2;
				}
				if (errStr) {
					*errStr = error.name() + ": " + error.message();
				}
				else {
					printf("[ERROR][tdehwlib] mountDriveUDisks: %s\n", error.name().ascii()); fflush(stdout);
				}
				return -1;
			}
			else {
				return 0;
			}
		}
		else {
			return -2;
		}
	}
#endif // WITH_UDISKS
	return -2;
}

int mountDriveUDisks2(TQString deviceNode, TQString fileSystemType, TQString mountOptions, TQString* errStr = NULL) {
#ifdef WITH_UDISKS2
	TQT_DBusConnection dbusConn = TQT_DBusConnection::addConnection(TQT_DBusConnection::SystemBus);
	if (dbusConn.isConnected()) {
		TQString blockDeviceString = deviceNode;
		blockDeviceString.replace("/dev/", "");
		blockDeviceString.replace("-", "_2d");
		blockDeviceString = "/org/freedesktop/UDisks2/block_devices/" + blockDeviceString;

		// Mount the drive!
		TQT_DBusError error;
		TQT_DBusProxy driveControl("org.freedesktop.UDisks2", blockDeviceString, "org.freedesktop.UDisks2.Filesystem", dbusConn);
		if (driveControl.canSend()) {
			TQValueList<TQT_DBusData> params;
			TQMap<TQString, TQT_DBusData> optionsMap;
			if (fileSystemType != "") {
				optionsMap["fstype"] = convertDBUSDataToVariantData(TQT_DBusData::fromString(fileSystemType));
			}
			optionsMap["options"] = convertDBUSDataToVariantData(TQT_DBusData::fromString(mountOptions));
			params << TQT_DBusData::fromStringKeyMap(TQT_DBusDataMap<TQString>(optionsMap));
			TQT_DBusMessage reply = driveControl.sendWithReply("Mount", params, &error);
			if (error.isValid()) {
				// Error!
				if (error.name() == "org.freedesktop.DBus.Error.ServiceUnknown") {
					// Service not installed or unavailable
					return -2;
				}
				if (errStr) {
					*errStr = error.name() + ": " + error.message();
				}
				else {
					printf("[ERROR][tdehwlib] mountDriveUDisks2: %s\n", error.name().ascii()); fflush(stdout);
				}
				return -1;
			}
			else {
				return 0;
			}
		}
		else {
			return -2;
		}
	}
#endif // WITH_UDISKS2
	return -2;
}

int unMountDriveUDisks(TQString deviceNode, TQStringList unMountOptions, TQString* errStr = NULL) {
#ifdef WITH_UDISKS
	TQT_DBusConnection dbusConn = TQT_DBusConnection::addConnection(TQT_DBusConnection::SystemBus);
	if (dbusConn.isConnected()) {
		TQString blockDeviceString = deviceNode;
		blockDeviceString.replace("/dev/", "");
		blockDeviceString.replace("-", "_2d");
		blockDeviceString = "/org/freedesktop/UDisks/devices/" + blockDeviceString;

		// Mount the drive!
		TQT_DBusError error;
		TQT_DBusProxy driveControl("org.freedesktop.UDisks", blockDeviceString, "org.freedesktop.UDisks.Device", dbusConn);
		if (driveControl.canSend()) {
			TQValueList<TQT_DBusData> params;
			params << TQT_DBusData::fromList(TQT_DBusDataList(unMountOptions));
			TQT_DBusMessage reply = driveControl.sendWithReply("FilesystemUnmount", params, &error);
			if (error.isValid()) {
				// Error!
				if (error.name() == "org.freedesktop.DBus.Error.ServiceUnknown") {
					// Service not installed or unavailable
					return -2;
				}
				if (errStr) {
					*errStr = error.name() + ": " + error.message();
				}
				else {
					printf("[ERROR][tdehwlib] unMountDriveUDisks: %s\n", error.name().ascii()); fflush(stdout);
				}
				return -1;
			}
			else {
				return 0;
			}
		}
		else {
			return -2;
		}
	}
#endif // WITH_UDISKS
	return -2;
}

int unMountDriveUDisks2(TQString deviceNode, TQString unMountOptions, TQString* errStr = NULL) {
#ifdef WITH_UDISKS2
	TQT_DBusConnection dbusConn = TQT_DBusConnection::addConnection(TQT_DBusConnection::SystemBus);
	if (dbusConn.isConnected()) {
		TQString blockDeviceString = deviceNode;
		blockDeviceString.replace("/dev/", "");
		blockDeviceString.replace("-", "_2d");
		blockDeviceString = "/org/freedesktop/UDisks2/block_devices/" + blockDeviceString;

		// Mount the drive!
		TQT_DBusError error;
		TQT_DBusProxy driveControl("org.freedesktop.UDisks2", blockDeviceString, "org.freedesktop.UDisks2.Filesystem", dbusConn);
		if (driveControl.canSend()) {
			TQValueList<TQT_DBusData> params;
			TQMap<TQString, TQT_DBusData> optionsMap;
			optionsMap["options"] = convertDBUSDataToVariantData(TQT_DBusData::fromString(unMountOptions));
			params << TQT_DBusData::fromStringKeyMap(TQT_DBusDataMap<TQString>(optionsMap));
			TQT_DBusMessage reply = driveControl.sendWithReply("Unmount", params, &error);
			if (error.isValid()) {
				// Error!
				if (error.name() == "org.freedesktop.DBus.Error.ServiceUnknown") {
					// Service not installed or unavailable
					return -2;
				}
				if (errStr) {
					*errStr = error.name() + ": " + error.message();
				}
				else {
					printf("[ERROR][tdehwlib] unMountDriveUDisks2: %s\n", error.name().ascii()); fflush(stdout);
				}
				return -1;
			}
			else {
				return 0;
			}
		}
		else {
			return -2;
		}
	}
#endif // WITH_UDISKS2
	return -2;
}

bool TDEStorageDevice::ejectDrive() {
#ifdef WITH_UDISKS2
	if (!(TDEGlobal::dirs()->findExe("udisksctl").isEmpty())) {
		if (ejectDriveUDisks2(this)) {
			return TRUE;
		}
		else {
			printf("[tdehwlib] Failed to eject drive '%s' via udisks2, falling back to alternate mechanism\n", deviceNode().ascii());
			fflush(stdout);
		}
	}
#endif // WITH_UDISKS2

#ifdef WITH_UDISKS
	if (!(TDEGlobal::dirs()->findExe("udisks").isEmpty())) {
		if (ejectDriveUDisks(this)) {
			return TRUE;
		}
		else {
			printf("[tdehwlib] Failed to eject drive '%s' via udisks, falling back to alternate mechanism\n", deviceNode().ascii());
			fflush(stdout);
		}
	}
#endif // WITH_UDISKS

	if (!(TDEGlobal::dirs()->findExe("eject").isEmpty())) {
		TQString command = TQString("eject -v '%1' 2>&1").arg(deviceNode());

		FILE *exepipe = popen(command.ascii(), "r");
		if (exepipe) {
			TQString eject_output;
			TQTextStream ts(exepipe, IO_ReadOnly);
			eject_output = ts.read();
			int retcode = pclose(exepipe);
			if (retcode == 0) {
				return TRUE;
			}
		}
		printf("[tdehwlib] Failed to eject drive '%s' via 'eject' command\n", deviceNode().ascii());
		fflush(stdout);
	}

	return FALSE;
}

bool TDEStorageDevice::ejectDriveMedia() {
	int fd = open(deviceNode().ascii(), O_RDWR | O_NONBLOCK);
	if (fd < 0) {
		return false;
	}
	if (ioctl(fd, CDROMEJECT) != 0) {
		close(fd);
		return false;
	}
	else {
		close(fd);
		return true;
	}
}

TQString TDEStorageDevice::diskLabel() {
	return m_diskName;
}

void TDEStorageDevice::internalSetDiskLabel(TQString dn) {
	m_diskName = dn;
}

bool TDEStorageDevice::mediaInserted() {
	return m_mediaInserted;
}

void TDEStorageDevice::internalSetMediaInserted(bool inserted) {
	m_mediaInserted = inserted;
}

TQString TDEStorageDevice::fileSystemName() {
	return m_fileSystemName;
}

void TDEStorageDevice::internalSetFileSystemName(TQString fn) {
	m_fileSystemName = fn;
}

TQString TDEStorageDevice::fileSystemUsage() {
	return m_fileSystemUsage;
}

void TDEStorageDevice::internalSetFileSystemUsage(TQString fu) {
	m_fileSystemUsage = fu;
}

TQString TDEStorageDevice::diskUUID() {
	return m_diskUUID;
}

void TDEStorageDevice::internalSetDiskUUID(TQString id) {
	m_diskUUID = id;
}

TQStringList TDEStorageDevice::holdingDevices() {
	return m_holdingDevices;
}

void TDEStorageDevice::internalSetHoldingDevices(TQStringList hd) {
	m_holdingDevices = hd;
}

TQStringList TDEStorageDevice::slaveDevices() {
	return m_slaveDevices;
}

void TDEStorageDevice::internalSetSlaveDevices(TQStringList sd) {
	m_slaveDevices = sd;
}

TQString decodeHexEncoding(TQString str) {
	TQRegExp hexEncRegExp("\\\\x[0-9A-Fa-f]{1,2}");
	hexEncRegExp.setMinimal(false);
	hexEncRegExp.setCaseSensitive(true);
	int s = -1;

	while((s = hexEncRegExp.search(str, s+1))>=0){
		str.replace(s, hexEncRegExp.cap(0).length(), TQChar((char)strtol(hexEncRegExp.cap(0).mid(2).ascii(), NULL, 16)));
	}

	return str;
}

TQString TDEStorageDevice::friendlyName() {
	// Return the actual storage device name
	TQString devicevendorid = vendorEncoded();
	TQString devicemodelid = modelEncoded();

	devicevendorid = decodeHexEncoding(devicevendorid);
	devicemodelid = decodeHexEncoding(devicemodelid);

	devicevendorid = devicevendorid.stripWhiteSpace();
	devicemodelid = devicemodelid.stripWhiteSpace();
	devicevendorid = devicevendorid.simplifyWhiteSpace();
	devicemodelid = devicemodelid.simplifyWhiteSpace();

	TQString devicename = devicevendorid + " " + devicemodelid;

	devicename = devicename.stripWhiteSpace();
	devicename = devicename.simplifyWhiteSpace();

	if (devicename != "") {
		return devicename;
	}

	if (isDiskOfType(TDEDiskDeviceType::Camera)) {
		return TDEGenericDevice::friendlyName();
	}

	if (isDiskOfType(TDEDiskDeviceType::Floppy)) {
		return friendlyDeviceType();
	}

	TQString label = diskLabel();
	if (label.isNull()) {
		if (deviceSize() > 0) {
			if (checkDiskStatus(TDEDiskDeviceStatus::Hotpluggable)) {
				label = i18n("%1 Removable Device").arg(deviceFriendlySize());
			}
			else {
				label = i18n("%1 Fixed Storage Device").arg(deviceFriendlySize());
			}
		}
	}

	if (!label.isNull()) {
		return label;
	}

	return friendlyDeviceType();
}

TQString TDEStorageDevice::detailedFriendlyName() {
	return TQString("%1 [%2]").arg(friendlyName()).arg(deviceNode());
}

TQString TDEStorageDevice::friendlyDeviceType() {
	TQString ret = i18n("Hard Disk Drive");

	// Keep this in sync with TDEStorageDevice::icon(TDEIcon::StdSizes size) below
	if (isDiskOfType(TDEDiskDeviceType::Floppy)) {
		ret = i18n("Floppy Drive");
	}
	if (isDiskOfType(TDEDiskDeviceType::Optical)) {
		ret = i18n("Optical Drive");
	}
	if (isDiskOfType(TDEDiskDeviceType::CDROM)) {
		ret = i18n("CDROM Drive");
	}
	if (isDiskOfType(TDEDiskDeviceType::CDRW)) {
		ret = i18n("CDRW Drive");
	}
	if (isDiskOfType(TDEDiskDeviceType::DVDROM)) {
		ret = i18n("DVD Drive");
	}
	if (isDiskOfType(TDEDiskDeviceType::DVDRW)) {
		ret = i18n("DVDRW Drive");
	}
	if (isDiskOfType(TDEDiskDeviceType::DVDRAM)) {
		ret = i18n("DVDRAM Drive");
	}
	if (isDiskOfType(TDEDiskDeviceType::Zip)) {
		ret = i18n("Zip Drive");
	}
	if (isDiskOfType(TDEDiskDeviceType::Tape)) {
		ret = i18n("Tape Drive");
	}
	if (isDiskOfType(TDEDiskDeviceType::Camera)) {
		ret = i18n("Digital Camera");
	}

	if (isDiskOfType(TDEDiskDeviceType::HDD)) {
		ret = i18n("Hard Disk Drive");
		if (checkDiskStatus(TDEDiskDeviceStatus::Hotpluggable)) {
			ret = i18n("Removable Storage");
		}
		if (isDiskOfType(TDEDiskDeviceType::CompactFlash)) {
			ret = i18n("Compact Flash");
		}
		if (isDiskOfType(TDEDiskDeviceType::MemoryStick)) {
			ret = i18n("Memory Stick");
		}
		if (isDiskOfType(TDEDiskDeviceType::SmartMedia)) {
			ret = i18n("Smart Media");
		}
		if (isDiskOfType(TDEDiskDeviceType::SDMMC)) {
			ret = i18n("Secure Digital");
		}
	}

	if (isDiskOfType(TDEDiskDeviceType::RAM)) {
		ret = i18n("Random Access Memory");
	}
	if (isDiskOfType(TDEDiskDeviceType::Loop)) {
		ret = i18n("Loop Device");
	}

	return ret;
}

TQPixmap TDEStorageDevice::icon(TDEIcon::StdSizes size) {
	TQString mountString;
	if (mountPath() != TQString::null) {
		mountString = "-mounted";
	}

	TQPixmap ret = DesktopIcon("drive-harddisk" + mountString, size);

	if (isDiskOfType(TDEDiskDeviceType::Floppy)) {
		ret = DesktopIcon("media-floppy-3_5" + mountString, size);
	}
	if (isDiskOfType(TDEDiskDeviceType::Optical)) {
		ret = DesktopIcon("media-optical-cdrom" + mountString, size);
	}
	if (isDiskOfType(TDEDiskDeviceType::CDROM)) {
		ret = DesktopIcon("media-optical-cdrom" + mountString, size);
	}
	if (isDiskOfType(TDEDiskDeviceType::CDRW)) {
		ret = DesktopIcon("media-optical-cdwriter" + mountString, size);
	}
	if (isDiskOfType(TDEDiskDeviceType::DVDROM)) {
		ret = DesktopIcon("media-optical-dvd" + mountString, size);
	}
	if (isDiskOfType(TDEDiskDeviceType::DVDRW)) {
		ret = DesktopIcon("media-optical-dvd" + mountString, size);
	}
	if (isDiskOfType(TDEDiskDeviceType::DVDRAM)) {
		ret = DesktopIcon("media-optical-dvd" + mountString, size);
	}
	if (isDiskOfType(TDEDiskDeviceType::Zip)) {
		ret = DesktopIcon("media-floppy-zip" + mountString, size);
	}
	if (isDiskOfType(TDEDiskDeviceType::Tape)) {
		ret = DesktopIcon("media-tape" + mountString, size);
	}
	if (isDiskOfType(TDEDiskDeviceType::Camera)) {
		ret = DesktopIcon("camera" + TQString((mountPath() != TQString::null) ? "_mount" : "_umount"), size);
	}

	if (isDiskOfType(TDEDiskDeviceType::HDD)) {
		ret = DesktopIcon("drive-harddisk" + mountString, size);
		if (checkDiskStatus(TDEDiskDeviceStatus::Hotpluggable)) {
			ret = DesktopIcon("media-flash-usb" + mountString, size);
		}
		if (isDiskOfType(TDEDiskDeviceType::CompactFlash)) {
			ret = DesktopIcon("media-flash-compact_flash" + mountString, size);
		}
		if (isDiskOfType(TDEDiskDeviceType::MemoryStick)) {
			ret = DesktopIcon("media-flash-memory_stick" + mountString, size);
		}
		if (isDiskOfType(TDEDiskDeviceType::SmartMedia)) {
			ret = DesktopIcon("media-flash-smart_media" + mountString, size);
		}
		if (isDiskOfType(TDEDiskDeviceType::SDMMC)) {
			ret = DesktopIcon("media-flash-sd_mmc" + mountString, size);
		}
	}

	if (isDiskOfType(TDEDiskDeviceType::RAM)) {
		ret = DesktopIcon("memory" + mountString, size);
	}
	if (isDiskOfType(TDEDiskDeviceType::Loop)) {
		ret = DesktopIcon("blockdevice" + mountString, size);
	}

	return ret;
}

unsigned long long TDEStorageDevice::deviceSize() {
	TQString bsnodename = systemPath();
	// While at first glance it would seem that checking /queue/physical_block_size would be needed to get an accurate device size, in reality Linux
	// appears to only ever report the device size in 512 byte units.  This does not appear to be documented anywhere!
	TQString blocksize = "512";

	TQString dsnodename = systemPath();
	dsnodename.append("/size");
	TQFile dsfile( dsnodename );
	TQString devicesize;
	if ( dsfile.open( IO_ReadOnly ) ) {
		TQTextStream stream( &dsfile );
		devicesize = stream.readLine();
		dsfile.close();
	}

	return ((unsigned long long)blocksize.toULong()*(unsigned long long)devicesize.toULong());
}

TQString TDEStorageDevice::deviceFriendlySize() {
	return TDEHardwareDevices::bytesToFriendlySizeString(deviceSize());
}

TQString TDEStorageDevice::mountPath() {
	// See if this device node is mounted
	// This requires parsing /proc/mounts, looking for deviceNode()

	// The Device Mapper throws a monkey wrench into this
	// It likes to advertise mounts as /dev/mapper/<something>,
	// where <something> is listed in <system path>/dm/name

	// First, ensure that all device information (mainly holders/slaves) is accurate
	TDEGlobal::hardwareDevices()->rescanDeviceInformation(this);

	TQString dmnodename = systemPath();
	dmnodename.append("/dm/name");
	TQFile namefile( dmnodename );
	TQString dmaltname;
	if ( namefile.open( IO_ReadOnly ) ) {
		TQTextStream stream( &namefile );
		dmaltname = stream.readLine();
		namefile.close();
	}
	if (!dmaltname.isNull()) {
		dmaltname.prepend("/dev/mapper/");
	}

	TQStringList lines;
	TQFile file( "/proc/mounts" );
	if ( file.open( IO_ReadOnly ) ) {
		TQTextStream stream( &file );
		TQString line;
		while ( !stream.atEnd() ) {
			line = stream.readLine();
			TQStringList mountInfo = TQStringList::split(" ", line, true);
			TQString testNode = *mountInfo.at(0);
			// Check for match
			if ((testNode == deviceNode()) || (testNode == dmaltname) || (testNode == ("/dev/disk/by-uuid/" + diskUUID()))) {
				TQString ret = *mountInfo.at(1);
				ret.replace("\\040", " ");
				return ret;
			}
			lines += line;
		}
		file.close();
	}

	// While this device is not directly mounted, it could concievably be mounted via the Device Mapper
	// If so, try to retrieve the mount path...
	TQStringList slaveDeviceList = holdingDevices();
	for ( TQStringList::Iterator slavedevit = slaveDeviceList.begin(); slavedevit != slaveDeviceList.end(); ++slavedevit ) {
		// Try to locate this device path in the TDE device tree
		TDEHardwareDevices *hwdevices = TDEGlobal::hardwareDevices();
		TDEGenericDevice *hwdevice = hwdevices->findBySystemPath(*slavedevit);
		if ((hwdevice) && (hwdevice->type() == TDEGenericDeviceType::Disk)) {
			TDEStorageDevice* sdevice = static_cast<TDEStorageDevice*>(hwdevice);
			return sdevice->mountPath();
		}
	}

	return TQString::null;
}

TQString TDEStorageDevice::mountDevice(TQString mediaName, TDEStorageMountOptions mountOptions, TQString* errRet, int* retcode) {
	int internal_retcode;
	if (!retcode) {
		retcode = &internal_retcode;
	}

	TQString ret = mountPath();

	// Device is already mounted
	if (!ret.isNull()) {
		return ret;
	}

	TQString command;
	TQString devNode = deviceNode();
	devNode.replace("'", "'\\''");
	mediaName.replace("'", "'\\''");

#if defined(WITH_UDISKS2) || defined(WITH_UDISKS)
	// Prepare filesystem options for mount
	TQStringList udisksOptions;
	TQString optionString;

	if (mountOptions["ro"] == "true") {
		udisksOptions.append("ro");
	}

	if (mountOptions["atime"] != "true") {
		udisksOptions.append("noatime");
	}

	if (mountOptions["sync"] == "true") {
		udisksOptions.append("sync");
	}

	if(  (mountOptions["filesystem"] == "fat")
	  || (mountOptions["filesystem"] == "vfat")
	  || (mountOptions["filesystem"] == "msdos")
	  || (mountOptions["filesystem"] == "umsdos")
	) {
		if (mountOptions.contains("shortname")) {
			udisksOptions.append(TQString("shortname=%1").arg(mountOptions["shortname"]));
		}
	}

	if( (mountOptions["filesystem"] == "jfs")) {
		if (mountOptions["utf8"] == "true") {
			// udisks/udisks2 for now does not support option iocharset= for jfs
			// udisksOptions.append("iocharset=utf8");
		}
	}

	if( (mountOptions["filesystem"] == "ntfs-3g") ) {
		if (mountOptions.contains("locale")) {
			udisksOptions.append(TQString("locale=%1").arg(mountOptions["locale"]));
		}
	}

	if(  (mountOptions["filesystem"] == "ext3")
	  || (mountOptions["filesystem"] == "ext4")
	) {
		if (mountOptions.contains("journaling")) {
			// udisks/udisks2 for now does not support option data= for ext3/ext4
			// udisksOptions.append(TQString("data=%1").arg(mountOptions["journaling"]));
		}
	}

	for (TQStringList::Iterator it = udisksOptions.begin(); it != udisksOptions.end(); ++it) {
		optionString.append(",");
		optionString.append(*it);
	}

	if (!optionString.isEmpty()) {
		optionString.remove(0, 1);
	}
#endif // defined(WITH_UDISKS2) || defined(WITH_UDISKS)

#ifdef WITH_UDISKS2
	if(command.isEmpty()) {
		// Try to use UDISKS v2 via DBUS, if available
		TQString errorString;
		TQString fileSystemType;

		if (mountOptions.contains("filesystem") && !mountOptions["filesystem"].isEmpty()) {
			fileSystemType = mountOptions["filesystem"];
		}

		int uDisks2Ret = mountDriveUDisks2(devNode, fileSystemType, optionString, &errorString);
		if (uDisks2Ret == 0) {
			// Update internal mount data
			TDEGlobal::hardwareDevices()->processModifiedMounts();

			ret = mountPath();
			return ret;
		}
		else if (uDisks2Ret == -1) {
			if (errRet) {
				*errRet = errorString;
			}

			// Update internal mount data
			TDEGlobal::hardwareDevices()->processModifiedMounts();

			ret = mountPath();
			return ret;
		}
		else {
			// The UDISKS v2 DBUS service was either not available or was unusable; try another method...
			command = TQString::null;
		}
	}
#endif // WITH_UDISKS2

#ifdef WITH_UDISKS
	if(command.isEmpty()) {
		// Try to use UDISKS v1 via DBUS, if available
		TQString errorString;
		TQString fileSystemType;

		if (mountOptions.contains("filesystem") && !mountOptions["filesystem"].isEmpty()) {
			fileSystemType = mountOptions["filesystem"];
		}

		int uDisksRet = mountDriveUDisks(devNode, fileSystemType, udisksOptions, &errorString);
		if (uDisksRet == 0) {
			// Update internal mount data
			TDEGlobal::hardwareDevices()->processModifiedMounts();

			ret = mountPath();
			return ret;
		}
		else if (uDisksRet == -1) {
			if (errRet) {
				*errRet = errorString;
			}

			// Update internal mount data
			TDEGlobal::hardwareDevices()->processModifiedMounts();

			ret = mountPath();
			return ret;
		}
		else {
			// The UDISKS v1 DBUS service was either not available or was unusable; try another method...
			command = TQString::null;
		}
	}
#endif // WITH_UDISKS

	if(command.isEmpty()) {
		// Use 'pmount' command, if available
		TQString pmountProg = TDEGlobal::dirs()->findExe("pmount");
		if (!pmountProg.isEmpty()) {
			// Create dummy password file
			KTempFile passwordFile(TQString::null, "tmp", 0600);
			passwordFile.setAutoDelete(true);

			TQString optionString;
			if (mountOptions["ro"] == "true") {
				optionString.append(" -r");
			}

			if (mountOptions["atime"] != "true") {
				optionString.append(" -A");
			}

			if (mountOptions["utf8"] == "true") {
				optionString.append(" -c utf8");
			}

			if (mountOptions["sync"] == "true") {
				optionString.append(" -s");
			}

			if (mountOptions.contains("filesystem") && !mountOptions["filesystem"].isEmpty()) {
				optionString.append(TQString(" -t %1").arg(mountOptions["filesystem"]));
			}

			if (mountOptions.contains("locale")) {
				optionString.append(TQString(" -c %1").arg(mountOptions["locale"]));
			}

			TQString mountpoint;
			if (mountOptions.contains("mountpoint")
				&& !mountOptions["mountpoint"].isEmpty()
				&& (mountOptions["mountpoint"] != "/media/")) {
				mountpoint = mountOptions["mountpoint"];
				mountpoint.replace("'", "'\\''");
			}
			else {
				mountpoint = mediaName;
			}

			TQString passFileName = passwordFile.name();
			passFileName.replace("'", "'\\''");

			command = TQString("pmount -p '%1' %2 '%3' '%4' 2>&1").arg(passFileName).arg(optionString).arg(devNode).arg(mountpoint);
		}
	}

	if(command.isEmpty()) {
		if (errRet) {
			*errRet = i18n("No supported mounting methods were detected on your system");
		}
		return ret;
	}

	FILE *exepipe = popen(command.local8Bit(), "r");
	if (exepipe) {
		TQString mount_output;
		TQTextStream* ts = new TQTextStream(exepipe, IO_ReadOnly);
		mount_output = ts->read();
		delete ts;
		*retcode = pclose(exepipe);
		if (errRet) {
			*errRet = mount_output;
		}
	}

	// Update internal mount data
	TDEGlobal::hardwareDevices()->processModifiedMounts();

	ret = mountPath();

	return ret;
}

TQString TDEStorageDevice::mountEncryptedDevice(TQString passphrase, TQString mediaName, TDEStorageMountOptions mountOptions, TQString* errRet, int* retcode) {
	int internal_retcode;
	if (!retcode) {
		retcode = &internal_retcode;
	}

	TQString ret = mountPath();

	if (!ret.isNull()) {
		return ret;
	}

	// Create dummy password file
	KTempFile passwordFile(TQString::null, "tmp", 0600);
	passwordFile.setAutoDelete(true);
	TQFile* pwFile = passwordFile.file();
	if (!pwFile) {
		return TQString::null;
	}

	pwFile->writeBlock(passphrase.ascii(), passphrase.length());
	pwFile->flush();

	TQString optionString;
	if (mountOptions["ro"] == "true") {
		optionString.append(" -r");
	}
	
	if (mountOptions["atime"] != "true") {
		optionString.append(" -A");
	}
	
	if (mountOptions["utf8"] == "true") {
		optionString.append(" -c utf8");
	}
	
	if (mountOptions["sync"] == "true") {
		optionString.append(" -s");
	}

	if (mountOptions.contains("filesystem") && !mountOptions["filesystem"].isEmpty()) {
		optionString.append(TQString(" -t %1").arg(mountOptions["filesystem"]));
	}

	if (mountOptions.contains("locale")) {
		optionString.append(TQString(" -c %1").arg(mountOptions["locale"]));
	}

	TQString passFileName = passwordFile.name();
	TQString devNode = deviceNode();
	passFileName.replace("'", "'\\''");
	devNode.replace("'", "'\\''");
	mediaName.replace("'", "'\\''");
	TQString command = TQString("pmount -p '%1' %2 '%3' '%4' 2>&1").arg(passFileName).arg(optionString).arg(devNode).arg(mediaName);

	FILE *exepipe = popen(command.local8Bit(), "r");
	if (exepipe) {
		TQString mount_output;
		TQTextStream* ts = new TQTextStream(exepipe, IO_ReadOnly);
		mount_output = ts->read();
		delete ts;
		*retcode = pclose(exepipe);
		if (errRet) {
			*errRet = mount_output;
		}
	}

	// Update internal mount data
	TDEGlobal::hardwareDevices()->processModifiedMounts();

	ret = mountPath();

	return ret;
}

bool TDEStorageDevice::unmountDevice(TQString* errRet, int* retcode) {
	int internal_retcode;
	if (!retcode) {
		retcode = &internal_retcode;
	}

	TQString mountpoint = mountPath();
	TQString devNode = deviceNode();

	if (mountpoint.isNull()) {
		return true;
	}

	mountpoint.replace("'", "'\\''");

	TQString command;

#ifdef WITH_UDISKS2
	if(command.isEmpty()) {
		// Try to use UDISKS v2 via DBUS, if available
		TQString errorString;
		int unMountUDisks2Ret = unMountDriveUDisks2(devNode, TQString::null, &errorString);
		if (unMountUDisks2Ret == 0) {
			// Update internal mount data
			TDEGlobal::hardwareDevices()->processModifiedMounts();

			return true;
		}
		else if (unMountUDisks2Ret == -1) {
			if (errRet) {
				*errRet = errorString;
			}

			// Update internal mount data
			TDEGlobal::hardwareDevices()->processModifiedMounts();

			return false;
		}
		else {
			// The UDISKS v2 DBUS service was either not available or was unusable; try another method...
			command = TQString::null;
		}
	}
#endif // WITH_UDISKS2
#ifdef WITH_UDISKS
	if(command.isEmpty()) {
		// Try to use UDISKS v1 via DBUS, if available
		TQString errorString;
		int unMountUDisksRet = unMountDriveUDisks(devNode, TQStringList(), &errorString);
		if (unMountUDisksRet == 0) {
			// Update internal mount data
			TDEGlobal::hardwareDevices()->processModifiedMounts();

			return true;
		}
		else if (unMountUDisksRet == -1) {
			if (errRet) {
				*errRet = errorString;
			}

			// Update internal mount data
			TDEGlobal::hardwareDevices()->processModifiedMounts();

			return false;
		}
		else {
			// The UDISKS v1 DBUS service was either not available or was unusable; try another method...
			command = TQString::null;
		}
	}
#endif // WITH_UDISKS
	if(command.isEmpty() &&
	   !(TDEGlobal::dirs()->findExe("pumount").isEmpty())) {
		command = TQString("pumount '%1' 2>&1").arg(mountpoint);
	}

	if(command.isEmpty()) {
		if (errRet) {
			*errRet = i18n("No supported unmounting methods were detected on your system");
		}
		return true;
	}

	FILE *exepipe = popen(command.local8Bit(), "r");
	if (exepipe) {
		TQString umount_output;
		TQTextStream* ts = new TQTextStream(exepipe, IO_ReadOnly);
		umount_output = ts->read();
		delete ts;
		*retcode = pclose(exepipe);
		if (*retcode == 0) {
			// Update internal mount data
			TDEGlobal::hardwareDevices()->processModifiedMounts();

			return true;
		}
		else {
			if (errRet) {
				*errRet = umount_output;
			}
		}
	}

	// Update internal mount data
	TDEGlobal::hardwareDevices()->processModifiedMounts();

	return false;
}

TQString TDEStorageDevice::determineFileSystemType(TQString path) {
	TQStringList mountTable;
	TQString prevPath = path;
	dev_t prevDev = 0;
	int pos;
	struct stat directory_info;
	if (path.startsWith("/")) {
		stat(path.local8Bit(), &directory_info);
		prevDev = directory_info.st_dev;
		// Walk the directory tree up to the root, checking for any change in st_dev
		// If a change is found, the previous value of path is the mount point itself
		while (path != "/") {
			pos = path.findRev("/", -1, TRUE);
			if (pos < 0) {
				break;
			}
			path = path.mid(0, pos);
			if (path == "") {
				path = "/";
			}
			stat(path.local8Bit(), &directory_info);
			if (directory_info.st_dev != prevDev) {
				break;
			}
			prevPath = path;
			prevDev = directory_info.st_dev;
		}
	}

	// Read in mount table
	mountTable.clear();
	TQFile file( "/proc/mounts" );
	if ( file.open( IO_ReadOnly ) ) {
		TQTextStream stream( &file );
		while ( !stream.atEnd() ) {
			mountTable.append(stream.readLine());
		}
		file.close();
	}

	// Parse mount table
	TQStringList::Iterator it;
	for ( it = mountTable.begin(); it != mountTable.end(); ++it ) {
		TQStringList mountInfo = TQStringList::split(" ", (*it), true);
		if ((*mountInfo.at(1)) == prevPath) {
			return (*mountInfo.at(2));
		}
	}

	// Unknown file system type
	return TQString::null;
}

#include "tdestoragedevice.moc"
